yuanflow-cli 0.1.7 → 0.1.9

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
@@ -36,7 +36,7 @@ yuanflow-cli works download --platform douyin --target "https://v.douyin.com/xxx
36
36
  yuanflow-cli search content --platform xiaohongshu --keyword "美妆" --format agent-json
37
37
  yuanflow-cli search users --platform instagram --keyword "nasa" --format agent-json
38
38
  yuanflow-cli knowledge docs --format agent-json
39
- yuanflow-cli knowledge entry --task-intent generate_script --content-goal "写一个创业者短视频选题" --format agent-json
39
+ yuanflow-cli knowledge entry --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
40
40
  yuanflow-cli oss signed-url --key path/to/file.png --ttl-seconds 1800 --format agent-json
41
41
  yuanflow-cli list douyin
42
42
  ```
@@ -96,12 +96,12 @@ Agent 不确定参数时先执行 `commands list`,再用 `schema works.douyin.
96
96
 
97
97
  ```bash
98
98
  yuanflow-cli knowledge docs --format agent-json
99
- yuanflow-cli knowledge entry --task-intent generate_script --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --format agent-json
100
- yuanflow-cli knowledge rules --pack-code topic_pain_point_pack --task-intent generate_script --content-goal "写一个创业者短视频选题" --format agent-json
101
- yuanflow-cli knowledge rule-detail --rule-code some_rule_code --task-intent generate_script --content-goal "写一个创业者短视频选题" --format agent-json
99
+ yuanflow-cli knowledge entry --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
100
+ yuanflow-cli knowledge rules --pack-code topic_pain_point_pack --output-format short_video_script --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
101
+ yuanflow-cli knowledge rule-detail --rule-code some_rule_code --output-format short_video_script --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
102
102
  ```
103
103
 
104
- Agent 应先查看 `knowledge docs`,再把用户需求整理成 `task_frame`,按接口返回的 `next_actions` 渐进查询。
104
+ Agent 应先查看 `knowledge docs`,再把用户需求整理成 `task_frame`,按接口返回的 `next_actions` 渐进查询。v2 接口不要求 `task_intent`;旧字段仅保留兼容。`packs`、`rules` 的 `data` 可能直接是数组,Agent 不应假设固定对象结构,应优先读取 `next_actions` 继续下钻。
105
105
 
106
106
  ### OSS 原子能力
107
107
 
@@ -115,6 +115,14 @@ yuanflow-cli oss copy --source-key temp/cover.png --target-key final/cover.png -
115
115
 
116
116
  上传本地文件前必须确认用户授权,不能上传密钥、cookie、账号凭据或隐私文件。
117
117
 
118
+ ### 生图技能
119
+
120
+ `生图技能` 随 npm 包安装到 Skill bundle。YuanFlow 主程序内优先使用受控工具 `yuanflow_image_request`,自动注入当前用户 token 并缓存返回图片。
121
+
122
+ - 生成图片:`/v1/images/generations`,主模型 `gpt-image-2-c`,备用模型 `gpt-image-2`,请求体使用 `prompt`。
123
+ - 编辑图片:`/v1/images/edits`,必须使用 multipart/form-data 上传本地图片文件。
124
+ - `gpt-image-2-c` 通常返回 URL,`gpt-image-2` 通常返回 `b64_json`;两种返回都应缓存后再展示。
125
+
118
126
  ## Skill 安装器
119
127
 
120
128
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yuanflow-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "YuanFlow API CLI and skill installer for supported AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,12 +20,13 @@ description: Use when the user asks about social-media API workflows, platform d
20
20
  - `综合用户搜索工具/`
21
21
  - `自媒体知识库/`
22
22
  - `OSS文件中转工具/`
23
+ - `生图技能/`
23
24
 
24
25
  ## 环境判断
25
26
 
26
27
  执行本 Skill 前,先判断当前运行环境:
27
28
 
28
- 1. 如果当前 Agent 可用 YuanFlow 内置工具,例如 `yuanflow_cli_call` 或 `yuanflow_gateway_request`,优先使用 YuanFlow 内置工具。不要要求用户安装 npm,也不要要求用户配置 token。
29
+ 1. 如果当前 Agent 可用 YuanFlow 内置工具,例如 `yuanflow_cli_call`、`yuanflow_gateway_request` 或 `yuanflow_image_request`,优先使用 YuanFlow 内置工具。不要要求用户安装 npm,也不要要求用户配置 token。
29
30
  2. 如果当前不在 YuanFlow 内,且本地可执行 `yuanflow-cli --help`,使用本地 CLI。
30
31
  3. 如果当前不在 YuanFlow 内,且本地没有 `yuanflow-cli`,再提示用户安装 `npm install -g yuanflow-cli`。
31
32
 
@@ -139,6 +140,18 @@ description: Use when the user asks about social-media API workflows, platform d
139
140
 
140
141
  - `OSS文件中转工具`
141
142
 
143
+ ### 9. 走 `生图技能`
144
+
145
+ 遇到下面这些需求,优先进入这个子 Skill:
146
+
147
+ - 生成图片、画图、出图、做海报、生成视觉素材。
148
+ - 编辑图片、修改本地图片、基于已有图片做变化。
149
+ - 调用 YuanFlow 内置图片生成或图片编辑接口。
150
+
151
+ 子 Skill 名称:
152
+
153
+ - `生图技能`
154
+
142
155
  ## 多需求时怎么处理
143
156
 
144
157
  如果用户一次提了多段流程,不要强行塞进一个子 Skill,按阶段拆开:
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: 生图技能
3
+ description: Use when the user asks to generate images, create pictures, make posters or visual assets, edit images, modify local pictures, or call YuanFlow image generation/edit APIs.
4
+ ---
5
+
6
+ # 生图技能
7
+
8
+ 当用户提到生成图片、画图、出图、做海报、生成视觉素材、编辑图片或修改本地图片时,优先使用本 Skill。
9
+
10
+ ## YuanFlow 内置环境
11
+
12
+ 如果当前 Agent 可用 `yuanflow_image_request`,优先使用该受控工具。它会自动注入当前用户 token,并把接口返回的 URL 或 base64 图片缓存为可预览资源。
13
+
14
+ 生成图片:
15
+
16
+ ```json
17
+ {
18
+ "mode": "generate",
19
+ "model": "gpt-image-2-c",
20
+ "prompt": "一张写实产品图:白色马克杯放在木桌上,自然光",
21
+ "size": "1024x1024"
22
+ }
23
+ ```
24
+
25
+ 编辑图片:
26
+
27
+ ```json
28
+ {
29
+ "mode": "edit",
30
+ "model": "gpt-image-2-c",
31
+ "prompt": "把杯子改成红色,保持背景不变",
32
+ "image_path": "D:\\path\\to\\source.png"
33
+ }
34
+ ```
35
+
36
+ ## 接口顺序
37
+
38
+ 生成图片只走 `/v1/images/generations`,请求体使用 `prompt`:
39
+
40
+ 1. 主模型:`gpt-image-2-c`
41
+ 2. 备用模型:`gpt-image-2`
42
+
43
+ 只有主模型请求失败或返回失败时,才切换到备用模型。不要使用 `gpt-image`、`doubao-生图` 或 chat completions/responses 端点作为本 Skill 的生图路径。
44
+
45
+ 编辑图片只走 `/v1/images/edits`。编辑模式必须使用 `multipart/form-data` 上传本地图片文件,不能用 JSON 直接传图片路径。
46
+
47
+ 已确认返回差异:
48
+
49
+ - `gpt-image-2-c` 通常返回图片 URL。
50
+ - `gpt-image-2` 通常返回 `b64_json` 图片数据。
51
+ - 两种返回都需要缓存成本地资源,并在最终回复中给出可显示的图片链接。
52
+
53
+ ## 外部 Agent 或手工调用
54
+
55
+ 生成图片请求示例:
56
+
57
+ ```bash
58
+ curl https://open.yuanchuangai.com/v1/images/generations \
59
+ -H "Authorization: Bearer $YUAN_API_KEY" \
60
+ -H "Content-Type: application/json" \
61
+ -d '{
62
+ "model": "gpt-image-2-c",
63
+ "prompt": "一张写实产品图:白色马克杯放在木桌上,自然光"
64
+ }'
65
+ ```
66
+
67
+ 如果主模型失败,把 `model` 改为 `gpt-image-2` 后重试。
68
+
69
+ 编辑图片请求示例:
70
+
71
+ ```bash
72
+ curl https://open.yuanchuangai.com/v1/images/edits \
73
+ -H "Authorization: Bearer $YUAN_API_KEY" \
74
+ -F "model=gpt-image-2-c" \
75
+ -F "prompt=把杯子改成红色,保持背景不变" \
76
+ -F "image=@D:/path/to/source.png"
77
+ ```
78
+
79
+ ## 输出要求
80
+
81
+ - 返回图片 URL 时,先缓存图片,再回复缓存后的可预览 URL。
82
+ - 返回 `b64_json` 时,先解码保存为图片文件,再回复缓存后的可预览 URL。
83
+ - 用户未指定保存目录时,使用 YuanFlow 程序资源缓存目录。
84
+ - 回复中不要暴露 token、Authorization header 或完整鉴权配置。
@@ -50,10 +50,10 @@ yuanflow-cli knowledge docs --format agent-json
50
50
 
51
51
  ```bash
52
52
  yuanflow-cli knowledge entry \
53
- --task-intent generate_script \
54
53
  --output-format short_video_script \
55
54
  --domain 自媒体运营 \
56
55
  --content-goal "写一个面向创业者的短视频选题" \
56
+ --target-audience 创业者 \
57
57
  --tone 清晰直接 \
58
58
  --communication-mode 口播 \
59
59
  --format agent-json
@@ -64,8 +64,9 @@ yuanflow-cli knowledge entry \
64
64
  ```bash
65
65
  yuanflow-cli knowledge rules \
66
66
  --pack-code topic_pain_point_pack \
67
- --task-intent generate_script \
67
+ --output-format short_video_script \
68
68
  --content-goal "写一个面向创业者的短视频选题" \
69
+ --target-audience 创业者 \
69
70
  --format agent-json
70
71
  ```
71
72
 
@@ -80,14 +81,14 @@ yuanflow-cli knowledge rules \
80
81
  "args": [
81
82
  "knowledge",
82
83
  "entry",
83
- "--task-intent",
84
- "generate_script",
85
84
  "--output-format",
86
85
  "short_video_script",
87
86
  "--domain",
88
87
  "自媒体运营",
89
88
  "--content-goal",
90
89
  "写一个面向创业者的短视频选题",
90
+ "--target-audience",
91
+ "创业者",
91
92
  "--format",
92
93
  "agent-json"
93
94
  ]
@@ -100,4 +101,6 @@ yuanflow-cli knowledge rules \
100
101
  - 不要使用 `/atomic/aliyun-db/query` 查询知识库内部表。
101
102
  - 不要把知识库核心方法论静态写入回复,只按接口返回内容使用。
102
103
  - 如果接口返回 `next_actions`,优先按它推荐的动作继续查询。
104
+ - v2 接口不要求 `task_intent`;旧字段只作为兼容字段,不要在新调用里默认填写。
105
+ - `packs`、`rules` 的 `data` 可能直接是数组,不要假设固定 `{ packs: [] }` 或 `{ rules: [] }` 结构。
103
106
  - 如果用户只是在问“有什么知识库方向”,先用 `knowledge docs`。
@@ -29,12 +29,27 @@ const ERROR_MAP = [
29
29
  retryable: false,
30
30
  test: (message) => message.includes('HTTP 401') || message.includes('HTTP 403'),
31
31
  },
32
+ {
33
+ code: 'UPSTREAM_CONFIG_ERROR',
34
+ exitCode: 4,
35
+ retryable: false,
36
+ test: (message) => message.includes('原子能力主站令牌未配置'),
37
+ },
32
38
  {
33
39
  code: 'UPSTREAM_ERROR',
34
40
  exitCode: 4,
35
41
  retryable: true,
36
42
  test: (message) => /HTTP 5\d\d/.test(message),
37
43
  },
44
+ {
45
+ code: 'UPSTREAM_ROUTE_MISSING',
46
+ exitCode: 4,
47
+ retryable: false,
48
+ test: (message) =>
49
+ message.includes('Invalid URL') ||
50
+ message.includes('接口返回了前端 HTML 页面') ||
51
+ message.includes('接口返回了非 JSON 内容'),
52
+ },
38
53
  {
39
54
  code: 'NETWORK_ERROR',
40
55
  exitCode: 5,
@@ -45,11 +45,22 @@ export async function callAtomic(path, options = {}) {
45
45
  });
46
46
 
47
47
  const text = await response.text();
48
+ const contentType = response.headers.get('content-type') || '';
48
49
  const payload = parseMaybeJson(text);
49
50
  if (!response.ok) {
50
51
  const message = typeof payload === 'object' ? JSON.stringify(payload) : text;
51
52
  throw new Error(`请求失败:HTTP ${response.status} ${message}`);
52
53
  }
54
+ if (options.expectJson !== false) {
55
+ assertJsonResponse({
56
+ contentType,
57
+ method,
58
+ path: normalizedPath,
59
+ status: response.status,
60
+ payload,
61
+ text,
62
+ });
63
+ }
53
64
  return payload;
54
65
  }
55
66
 
@@ -95,6 +106,34 @@ function parseMaybeJson(text) {
95
106
  }
96
107
  }
97
108
 
109
+ function assertJsonResponse({ contentType, method, path, status, payload, text }) {
110
+ if (payload !== null && typeof payload === 'object') {
111
+ return;
112
+ }
113
+ const preview = compactPreview(text);
114
+ const isHtml = looksLikeHtml(text) || contentType.toLowerCase().includes('text/html');
115
+ if (isHtml) {
116
+ throw new Error(
117
+ `接口返回了前端 HTML 页面,不是结构化 JSON。请检查云端接口是否已部署或路径是否正确:HTTP ${status} ${method} ${path}。返回预览:${preview}`,
118
+ );
119
+ }
120
+ throw new Error(
121
+ `接口返回了非 JSON 内容。请检查云端接口是否已部署或路径是否正确:HTTP ${status} ${method} ${path}。返回预览:${preview}`,
122
+ );
123
+ }
124
+
125
+ function looksLikeHtml(text) {
126
+ const normalized = String(text || '').trim().toLowerCase();
127
+ return normalized.startsWith('<!doctype') || normalized.startsWith('<html') || normalized.includes('<html');
128
+ }
129
+
130
+ function compactPreview(text) {
131
+ return String(text || '')
132
+ .replace(/\s+/g, ' ')
133
+ .trim()
134
+ .slice(0, 160);
135
+ }
136
+
98
137
  function maskToken(token) {
99
138
  if (!token) {
100
139
  return '';
package/src/cli.js CHANGED
@@ -539,7 +539,7 @@ function printHelp() {
539
539
  yuanflow-cli search content --platform xiaohongshu --keyword "美妆" --dry-run
540
540
  yuanflow-cli search users --platform instagram --keyword "nasa" --dry-run
541
541
  yuanflow-cli knowledge docs --dry-run
542
- yuanflow-cli knowledge entry --task-intent generate_script --content-goal "写一个创业者短视频选题" --format agent-json
542
+ yuanflow-cli knowledge entry --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
543
543
  yuanflow-cli oss signed-url --key path/to/file.png --ttl-seconds 1800 --format agent-json
544
544
  yuanflow-cli list douyin
545
545
 
@@ -92,10 +92,11 @@ function knowledgeCommand(action, description, extraOptions = []) {
92
92
 
93
93
  function taskFrameOptions() {
94
94
  return [
95
- { flag: '--task-intent', name: 'taskIntent', required: false, label: '任务意图,例如 generate_script、generate_copy、evaluate_content。' },
95
+ { flag: '--task-intent', name: 'taskIntent', required: false, label: '兼容旧字段;v2 接口不要求填写。' },
96
96
  { flag: '--output-format', name: 'outputFormat', required: false, label: '输出格式,例如 short_video_script、social_post、any。' },
97
97
  { flag: '--domain', name: 'domain', required: false, label: '内容领域。' },
98
98
  { flag: '--content-goal', name: 'contentGoal', required: false, label: '具体创作或评估目标。' },
99
+ { flag: '--target-audience', name: 'targetAudience', required: false, label: '目标受众,例如 创业者、宝妈、普通用户。' },
99
100
  { flag: '--tone', name: 'tone', required: false, label: '语气风格。' },
100
101
  { flag: '--communication-mode', name: 'communicationMode', required: false, label: '表达方式,例如 口播、图文、文章。' },
101
102
  { flag: '--task-frame-file', name: 'taskFrameFile', required: false, label: '读取 task_frame JSON 文件。' },
@@ -116,10 +117,11 @@ async function buildTaskFrame(options) {
116
117
  : {};
117
118
  return removeUndefined({
118
119
  ...fromFile,
119
- task_intent: pick(options, 'task-intent', fromFile.task_intent || 'general'),
120
+ task_intent: pick(options, 'task-intent', fromFile.task_intent),
120
121
  output_format: pick(options, 'output-format', fromFile.output_format || 'any'),
121
122
  domain: pick(options, 'domain', fromFile.domain || '自媒体运营'),
122
123
  content_goal: pick(options, 'content-goal', fromFile.content_goal || ''),
124
+ target_audience: pick(options, 'target-audience', fromFile.target_audience || ''),
123
125
  tone: pick(options, 'tone', fromFile.tone || ''),
124
126
  communication_mode: pick(options, 'communication-mode', fromFile.communication_mode || ''),
125
127
  });