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,175 @@
1
+ import { callAtomic, readJsonFile } from './atomic-request.js';
2
+
3
+ const KNOWLEDGE_NAVIGATE_PATH = '/atomic/agent-rules/navigate';
4
+ const KNOWLEDGE_DOCS_PATH = '/api/knowledge-base/docs';
5
+
6
+ export function listKnowledgeCommands() {
7
+ return [
8
+ {
9
+ key: 'knowledge.docs',
10
+ command: 'knowledge docs',
11
+ kind: 'knowledge-base',
12
+ description: '读取自媒体知识库公开目录、一级/二级方向和流程说明。',
13
+ method: 'GET',
14
+ apiPath: KNOWLEDGE_DOCS_PATH,
15
+ positionals: [],
16
+ options: commonOptions({ tokenRequired: false }),
17
+ requestBody: null,
18
+ returns: '返回知识库公开目录、能力方向和可用说明。',
19
+ },
20
+ knowledgeCommand('entry', '进入知识库,根据用户任务获取领域上下文、推荐能力和方法包。'),
21
+ knowledgeCommand('packs', '查看某类能力下的方法包。', [
22
+ { flag: '--capability-code', name: 'capabilityCode', required: false, label: '一级能力 code,例如 topic_generation。' },
23
+ ]),
24
+ knowledgeCommand('rules', '查看方法包下的规则方向。', [
25
+ { flag: '--pack-code', name: 'packCode', required: true, label: '方法包 code。' },
26
+ ]),
27
+ knowledgeCommand('rule-detail', '查看单条规则的公开摘要。', [
28
+ { flag: '--rule-code', name: 'ruleCode', required: true, label: '规则 code。' },
29
+ ]),
30
+ knowledgeCommand('evaluate', '获取内容评分维度和优化方向。'),
31
+ ];
32
+ }
33
+
34
+ export async function getKnowledgeDocs({ options }) {
35
+ const response = await callAtomic(KNOWLEDGE_DOCS_PATH, {
36
+ ...options,
37
+ method: 'GET',
38
+ requiresToken: false,
39
+ });
40
+ return {
41
+ ok: true,
42
+ action: 'docs',
43
+ endpoint: { method: 'GET', path: KNOWLEDGE_DOCS_PATH, kind: 'knowledge-base' },
44
+ response,
45
+ };
46
+ }
47
+
48
+ export async function navigateKnowledge({ action, options }) {
49
+ const taskFrame = await buildTaskFrame(options);
50
+ const params = buildParams(action, options);
51
+ const body = {
52
+ action: normalizeAction(action),
53
+ task_frame: taskFrame,
54
+ params,
55
+ };
56
+ const response = await callAtomic(KNOWLEDGE_NAVIGATE_PATH, {
57
+ ...options,
58
+ method: 'POST',
59
+ body,
60
+ });
61
+ return {
62
+ ok: true,
63
+ action: normalizeAction(action),
64
+ endpoint: { method: 'POST', path: KNOWLEDGE_NAVIGATE_PATH, kind: 'knowledge-base' },
65
+ request: { body },
66
+ response,
67
+ };
68
+ }
69
+
70
+ function knowledgeCommand(action, description, extraOptions = []) {
71
+ return {
72
+ key: `knowledge.${action}`,
73
+ command: `knowledge ${action}`,
74
+ kind: 'knowledge-base',
75
+ description,
76
+ method: 'POST',
77
+ apiPath: KNOWLEDGE_NAVIGATE_PATH,
78
+ positionals: [],
79
+ options: [
80
+ ...taskFrameOptions(),
81
+ ...extraOptions,
82
+ ...commonOptions(),
83
+ ],
84
+ requestBody: {
85
+ action: normalizeAction(action),
86
+ task_frame: '<由 task-frame 参数组装>',
87
+ params: '<由命令参数组装>',
88
+ },
89
+ returns: '返回当前知识库层级数据和 next_actions,Agent 应按 next_actions 继续查询。',
90
+ };
91
+ }
92
+
93
+ function taskFrameOptions() {
94
+ return [
95
+ { flag: '--task-intent', name: 'taskIntent', required: false, label: '任务意图,例如 generate_script、generate_copy、evaluate_content。' },
96
+ { flag: '--output-format', name: 'outputFormat', required: false, label: '输出格式,例如 short_video_script、social_post、any。' },
97
+ { flag: '--domain', name: 'domain', required: false, label: '内容领域。' },
98
+ { flag: '--content-goal', name: 'contentGoal', required: false, label: '具体创作或评估目标。' },
99
+ { flag: '--tone', name: 'tone', required: false, label: '语气风格。' },
100
+ { flag: '--communication-mode', name: 'communicationMode', required: false, label: '表达方式,例如 口播、图文、文章。' },
101
+ { flag: '--task-frame-file', name: 'taskFrameFile', required: false, label: '读取 task_frame JSON 文件。' },
102
+ ];
103
+ }
104
+
105
+ function commonOptions({ tokenRequired = true } = {}) {
106
+ return [
107
+ { flag: '--extra', name: 'extra', required: false, label: 'JSON 字符串,会合并进 params。' },
108
+ { flag: '--format', name: 'format', required: false, label: 'Agent 调用时建议使用 agent-json。' },
109
+ { flag: '--dry-run', name: 'dryRun', required: false, label: `仅预览请求映射,不发起真实请求${tokenRequired ? ',也不要求 token' : ''}。` },
110
+ ];
111
+ }
112
+
113
+ async function buildTaskFrame(options) {
114
+ const fromFile = options.named?.['task-frame-file']
115
+ ? await readJsonFile(options.named['task-frame-file'])
116
+ : {};
117
+ return removeUndefined({
118
+ ...fromFile,
119
+ task_intent: pick(options, 'task-intent', fromFile.task_intent || 'general'),
120
+ output_format: pick(options, 'output-format', fromFile.output_format || 'any'),
121
+ domain: pick(options, 'domain', fromFile.domain || '自媒体运营'),
122
+ content_goal: pick(options, 'content-goal', fromFile.content_goal || ''),
123
+ tone: pick(options, 'tone', fromFile.tone || ''),
124
+ communication_mode: pick(options, 'communication-mode', fromFile.communication_mode || ''),
125
+ });
126
+ }
127
+
128
+ function buildParams(action, options) {
129
+ const params = parseExtra(options.named?.extra);
130
+ const capabilityCode = cleanOptional(options.named?.['capability-code']);
131
+ const packCode = cleanOptional(options.named?.['pack-code']);
132
+ const ruleCode = cleanOptional(options.named?.['rule-code']);
133
+ if (capabilityCode) params.capability_code = capabilityCode;
134
+ if (packCode) params.pack_code = packCode;
135
+ if (ruleCode) params.rule_code = ruleCode;
136
+ if (action === 'rules' && !params.pack_code) {
137
+ throw new Error('缺少 --pack-code。');
138
+ }
139
+ if (action === 'rule-detail' && !params.rule_code) {
140
+ throw new Error('缺少 --rule-code。');
141
+ }
142
+ return params;
143
+ }
144
+
145
+ function normalizeAction(action) {
146
+ return action === 'rule-detail' ? 'rule_detail' : action;
147
+ }
148
+
149
+ function pick(options, key, fallback) {
150
+ return cleanOptional(options.named?.[key]) ?? cleanOptional(fallback);
151
+ }
152
+
153
+ function cleanOptional(value) {
154
+ if (value === undefined || value === null) return undefined;
155
+ if (typeof value === 'string') {
156
+ const trimmed = value.trim();
157
+ return trimmed ? trimmed : undefined;
158
+ }
159
+ return value;
160
+ }
161
+
162
+ function removeUndefined(payload) {
163
+ return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined));
164
+ }
165
+
166
+ function parseExtra(value) {
167
+ if (!value) return {};
168
+ if (typeof value === 'object') return value;
169
+ try {
170
+ const parsed = JSON.parse(String(value));
171
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
172
+ } catch {
173
+ return {};
174
+ }
175
+ }
@@ -0,0 +1,181 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { callAtomic } from './atomic-request.js';
4
+
5
+ const OSS_TEMP_UPLOAD_PATH = '/atomic/oss/temp-upload';
6
+ const OSS_SIGNED_URL_PATH = '/atomic/oss/signed-url';
7
+ const OSS_COPY_PATH = '/atomic/oss/copy';
8
+
9
+ export function listOssCommands() {
10
+ return [
11
+ {
12
+ key: 'oss.temp-upload',
13
+ command: 'oss temp-upload',
14
+ kind: 'oss-atomic',
15
+ description: '上传本地文件到 Yuan API 临时 OSS bucket,并返回对象 key 和临时访问信息。',
16
+ method: 'POST',
17
+ apiPath: OSS_TEMP_UPLOAD_PATH,
18
+ positionals: [],
19
+ options: [
20
+ { flag: '--file', name: 'file', required: true, label: '本地文件路径。' },
21
+ { flag: '--filename', name: 'filename', required: false, label: '上传时使用的文件名,默认取本地文件名。' },
22
+ { flag: '--key', name: 'key', required: false, label: '可选 OSS 对象 key。' },
23
+ { flag: '--content-type', name: 'contentType', required: false, label: '文件 MIME 类型,默认 application/octet-stream。' },
24
+ ...commonOptions(),
25
+ ],
26
+ requestBody: {
27
+ filename: '<filename>',
28
+ key: '<optional key>',
29
+ content_base64: '<base64>',
30
+ content_type: '<content-type>',
31
+ },
32
+ returns: '返回 bucket、key、url、signed_url、expires_at 等字段,字段以后端实际响应为准。',
33
+ },
34
+ {
35
+ key: 'oss.signed-url',
36
+ command: 'oss signed-url',
37
+ kind: 'oss-atomic',
38
+ description: '为已有 OSS 对象生成临时签名访问链接。',
39
+ method: 'POST',
40
+ apiPath: OSS_SIGNED_URL_PATH,
41
+ positionals: [],
42
+ options: [
43
+ { flag: '--key', name: 'key', required: true, label: 'OSS 对象 key。' },
44
+ { flag: '--bucket', name: 'bucket', required: false, label: '可选 bucket,不传则使用后端默认配置。' },
45
+ { flag: '--ttl-seconds', name: 'ttlSeconds', required: false, label: '签名有效期,最大 86400 秒。' },
46
+ { flag: '--method', name: 'method', required: false, label: '签名方法:GET、PUT、HEAD。' },
47
+ ...commonOptions(),
48
+ ],
49
+ requestBody: {
50
+ key: '<key>',
51
+ bucket: '<optional bucket>',
52
+ ttl_seconds: 1800,
53
+ method: 'GET',
54
+ },
55
+ returns: '返回签名 URL、过期时间和对象信息,字段以后端实际响应为准。',
56
+ },
57
+ {
58
+ key: 'oss.copy',
59
+ command: 'oss copy',
60
+ kind: 'oss-atomic',
61
+ description: '复制 OSS 对象到目标 key。',
62
+ method: 'POST',
63
+ apiPath: OSS_COPY_PATH,
64
+ positionals: [],
65
+ options: [
66
+ { flag: '--source-key', name: 'sourceKey', required: true, label: '源 OSS 对象 key。' },
67
+ { flag: '--target-key', name: 'targetKey', required: true, label: '目标 OSS 对象 key。' },
68
+ { flag: '--source-bucket', name: 'sourceBucket', required: false, label: '可选源 bucket。' },
69
+ { flag: '--target-bucket', name: 'targetBucket', required: false, label: '可选目标 bucket。' },
70
+ ...commonOptions(),
71
+ ],
72
+ requestBody: {
73
+ source_key: '<source-key>',
74
+ target_key: '<target-key>',
75
+ source_bucket: '<optional source bucket>',
76
+ target_bucket: '<optional target bucket>',
77
+ },
78
+ returns: '返回复制后的对象信息,字段以后端实际响应为准。',
79
+ },
80
+ ];
81
+ }
82
+
83
+ export async function tempUpload({ options }) {
84
+ const filePath = cleanOptional(options.named?.file || options.file);
85
+ if (!filePath) {
86
+ throw new Error('缺少 --file。');
87
+ }
88
+ const filename = cleanOptional(options.named?.filename) || path.basename(filePath);
89
+ const contentType = cleanOptional(options.named?.['content-type']) || 'application/octet-stream';
90
+ const body = {
91
+ filename,
92
+ content_base64: options.dryRun ? '<base64 omitted in dry-run>' : (await readFile(filePath)).toString('base64'),
93
+ content_type: contentType,
94
+ ...optionalField('key', options.named?.key),
95
+ };
96
+ const response = await callAtomic(OSS_TEMP_UPLOAD_PATH, {
97
+ ...options,
98
+ method: 'POST',
99
+ body,
100
+ });
101
+ return result('temp-upload', OSS_TEMP_UPLOAD_PATH, body, response);
102
+ }
103
+
104
+ export async function signedUrl({ options }) {
105
+ const key = cleanOptional(options.named?.key);
106
+ if (!key) {
107
+ throw new Error('缺少 --key。');
108
+ }
109
+ const body = {
110
+ key,
111
+ ...optionalField('bucket', options.named?.bucket),
112
+ ...optionalField('ttl_seconds', numberOrString(options.named?.['ttl-seconds'])),
113
+ ...optionalField('method', cleanOptional(options.named?.method) || 'GET'),
114
+ };
115
+ const response = await callAtomic(OSS_SIGNED_URL_PATH, {
116
+ ...options,
117
+ method: 'POST',
118
+ body,
119
+ });
120
+ return result('signed-url', OSS_SIGNED_URL_PATH, body, response);
121
+ }
122
+
123
+ export async function copyObject({ options }) {
124
+ const sourceKey = cleanOptional(options.named?.['source-key']);
125
+ const targetKey = cleanOptional(options.named?.['target-key']);
126
+ if (!sourceKey) {
127
+ throw new Error('缺少 --source-key。');
128
+ }
129
+ if (!targetKey) {
130
+ throw new Error('缺少 --target-key。');
131
+ }
132
+ const body = {
133
+ source_key: sourceKey,
134
+ target_key: targetKey,
135
+ ...optionalField('source_bucket', options.named?.['source-bucket']),
136
+ ...optionalField('target_bucket', options.named?.['target-bucket']),
137
+ };
138
+ const response = await callAtomic(OSS_COPY_PATH, {
139
+ ...options,
140
+ method: 'POST',
141
+ body,
142
+ });
143
+ return result('copy', OSS_COPY_PATH, body, response);
144
+ }
145
+
146
+ function commonOptions() {
147
+ return [
148
+ { flag: '--format', name: 'format', required: false, label: 'Agent 调用时建议使用 agent-json。' },
149
+ { flag: '--dry-run', name: 'dryRun', required: false, label: '仅预览请求映射,不发起真实请求,也不要求 token。' },
150
+ ];
151
+ }
152
+
153
+ function result(action, endpointPath, body, response) {
154
+ return {
155
+ ok: true,
156
+ action,
157
+ endpoint: { method: 'POST', path: endpointPath, kind: 'oss-atomic' },
158
+ request: { body },
159
+ response,
160
+ };
161
+ }
162
+
163
+ function optionalField(name, value) {
164
+ const cleaned = cleanOptional(value);
165
+ return cleaned === undefined ? {} : { [name]: cleaned };
166
+ }
167
+
168
+ function cleanOptional(value) {
169
+ if (value === undefined || value === null) return undefined;
170
+ if (typeof value === 'string') {
171
+ const trimmed = value.trim();
172
+ return trimmed ? trimmed : undefined;
173
+ }
174
+ return value;
175
+ }
176
+
177
+ function numberOrString(value) {
178
+ const cleaned = cleanOptional(value);
179
+ if (cleaned === undefined) return undefined;
180
+ return /^-?\d+$/.test(String(cleaned)) ? Number(cleaned) : cleaned;
181
+ }