yuanflow-cli 0.1.0 → 0.1.2
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/LICENSE +1 -1
- package/README.md +34 -50
- package/bin/yuanflow-cli.js +26 -0
- package/bin/yuanflow-skill.cjs +334 -0
- package/generated/registry.json +24641 -0
- package/lib/skill-installer/agents.cjs +203 -0
- package/lib/skill-installer/discover-skills.cjs +79 -0
- package/lib/skill-installer/installer.cjs +300 -0
- package/lib/skill-installer/publish.cjs +53 -0
- package/lib/skill-installer/repo-source.cjs +162 -0
- package/package.json +57 -6
- package/scripts/generate-registry.js +174 -0
- package/skills/yuanflow-skill/SKILL.md +60 -0
- package/skills/yuanflow-skill/yuanflow-cli/SKILL.md +207 -0
- package/src/agent-protocol.js +169 -0
- package/src/cli.js +382 -0
- package/src/config.js +31 -0
- package/src/registry.js +45 -0
- package/src/request.js +97 -0
- package/src/shortcuts.js +346 -0
- package/cli.js +0 -3
- package/src/ycloud/cli.js +0 -29
- package/src/ycloud/commands/analysis.js +0 -82
- package/src/ycloud/commands/auth.js +0 -191
- package/src/ycloud/commands/commands.js +0 -262
- package/src/ycloud/commands/compliance.js +0 -146
- package/src/ycloud/commands/config.js +0 -103
- package/src/ycloud/commands/health.js +0 -35
- package/src/ycloud/commands/index.js +0 -381
- package/src/ycloud/commands/kb.js +0 -82
- package/src/ycloud/commands/schema.js +0 -229
- package/src/ycloud/commands/shared.js +0 -30
- package/src/ycloud/commands/tool-registry.js +0 -209
- package/src/ycloud/commands/tool-runner.js +0 -226
- package/src/ycloud/commands/tool.js +0 -178
- package/src/ycloud/commands/version.js +0 -84
- package/src/ycloud/core/config.js +0 -78
- package/src/ycloud/core/http.js +0 -133
- package/src/ycloud/core/token.js +0 -30
- package/src/ycloud/resources/.gitkeep +0 -1
- package/src/ycloud/resources/tool_catalog_full.json +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { listEndpoints } from './registry.js';
|
|
2
|
+
import { listShortcuts } from './shortcuts.js';
|
|
3
|
+
|
|
4
|
+
const ERROR_MAP = [
|
|
5
|
+
{
|
|
6
|
+
code: 'TOKEN_MISSING',
|
|
7
|
+
exitCode: 3,
|
|
8
|
+
retryable: false,
|
|
9
|
+
test: (message) => message.includes('缺少 token'),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
code: 'BAD_ARGUMENT',
|
|
13
|
+
exitCode: 2,
|
|
14
|
+
retryable: false,
|
|
15
|
+
test: (message) =>
|
|
16
|
+
message.includes('缺少') ||
|
|
17
|
+
message.includes('未知平台') ||
|
|
18
|
+
message.includes('未找到命令') ||
|
|
19
|
+
message.includes('未知命令') ||
|
|
20
|
+
message.includes('不支持'),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
code: 'AUTH_INVALID',
|
|
24
|
+
exitCode: 3,
|
|
25
|
+
retryable: false,
|
|
26
|
+
test: (message) => message.includes('HTTP 401') || message.includes('HTTP 403'),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: 'UPSTREAM_ERROR',
|
|
30
|
+
exitCode: 4,
|
|
31
|
+
retryable: true,
|
|
32
|
+
test: (message) => /HTTP 5\d\d/.test(message),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: 'NETWORK_ERROR',
|
|
36
|
+
exitCode: 5,
|
|
37
|
+
retryable: true,
|
|
38
|
+
test: (message) => message.includes('fetch failed') || message.includes('ECONN'),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export function isAgentJsonFormat(options = {}) {
|
|
43
|
+
return options.format === 'agent-json';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createAgentSuccess(command, data, meta = {}) {
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
command,
|
|
50
|
+
data,
|
|
51
|
+
warnings: [],
|
|
52
|
+
error: null,
|
|
53
|
+
meta,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createAgentError(command, error) {
|
|
58
|
+
const message = error?.message || String(error);
|
|
59
|
+
const mapped = mapError(message);
|
|
60
|
+
return {
|
|
61
|
+
payload: {
|
|
62
|
+
success: false,
|
|
63
|
+
command: command || 'unknown',
|
|
64
|
+
data: null,
|
|
65
|
+
warnings: [],
|
|
66
|
+
error: {
|
|
67
|
+
code: mapped.code,
|
|
68
|
+
message,
|
|
69
|
+
retryable: mapped.retryable,
|
|
70
|
+
},
|
|
71
|
+
meta: {},
|
|
72
|
+
},
|
|
73
|
+
exitCode: mapped.exitCode,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getCommandName(platform, command) {
|
|
78
|
+
return [platform, command].filter(Boolean).join(' ') || 'unknown';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildCommandRegistry() {
|
|
82
|
+
const shortcuts = listShortcuts().map((shortcut) => shortcutToCommand(shortcut));
|
|
83
|
+
const endpoints = listEndpoints().map((endpoint) => endpointToCommand(endpoint));
|
|
84
|
+
return [...shortcuts, ...endpoints].sort((left, right) =>
|
|
85
|
+
left.key.localeCompare(right.key),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function findCommandByKey(key) {
|
|
90
|
+
return buildCommandRegistry().find((item) => item.key === key);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function commandToSchema(command) {
|
|
94
|
+
if (!command) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
key: command.key,
|
|
100
|
+
command: command.command,
|
|
101
|
+
kind: command.kind,
|
|
102
|
+
description: command.description,
|
|
103
|
+
cli: {
|
|
104
|
+
positionals: command.positionals || [],
|
|
105
|
+
options: command.options || [],
|
|
106
|
+
},
|
|
107
|
+
api: {
|
|
108
|
+
method: command.method,
|
|
109
|
+
socialPath: command.socialPath,
|
|
110
|
+
url: `/social${command.socialPath}`,
|
|
111
|
+
queryParams: command.queryParams || [],
|
|
112
|
+
requestBody: command.requestBody || null,
|
|
113
|
+
},
|
|
114
|
+
returns: command.returns || '返回字段以上游接口实际响应为准。',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function shortcutToCommand(shortcut) {
|
|
119
|
+
return {
|
|
120
|
+
key: `${shortcut.platform}.${shortcut.command}`,
|
|
121
|
+
command: `${shortcut.platform} ${shortcut.command}`,
|
|
122
|
+
kind: 'shortcut',
|
|
123
|
+
description: shortcut.description,
|
|
124
|
+
method: shortcut.method,
|
|
125
|
+
socialPath: shortcut.socialPath,
|
|
126
|
+
positionals: shortcut.positionals,
|
|
127
|
+
options: shortcut.options.map((item) => ({
|
|
128
|
+
...item,
|
|
129
|
+
flag: `--${item.flag}`,
|
|
130
|
+
required: false,
|
|
131
|
+
})),
|
|
132
|
+
returns: shortcut.returns,
|
|
133
|
+
alternatives: shortcut.alternatives,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function endpointToCommand(endpoint) {
|
|
138
|
+
const command = `${endpoint.platform} ${endpoint.command}`;
|
|
139
|
+
return {
|
|
140
|
+
key: command.replace(/\s+/g, '.'),
|
|
141
|
+
command,
|
|
142
|
+
kind: 'endpoint',
|
|
143
|
+
description: endpoint.summary,
|
|
144
|
+
method: endpoint.method,
|
|
145
|
+
socialPath: endpoint.socialPath,
|
|
146
|
+
positionals: [],
|
|
147
|
+
options: (endpoint.queryParams || []).map((item) => ({
|
|
148
|
+
flag: `--${item.name.replace(/_/g, '-')}`,
|
|
149
|
+
name: item.name,
|
|
150
|
+
required: item.required,
|
|
151
|
+
label: item.description || item.name,
|
|
152
|
+
})),
|
|
153
|
+
queryParams: endpoint.queryParams || [],
|
|
154
|
+
requestBody: endpoint.bodyExample,
|
|
155
|
+
returns: '返回字段以上游接口实际响应为准。',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function mapError(message) {
|
|
160
|
+
const mapped = ERROR_MAP.find((item) => item.test(message));
|
|
161
|
+
if (mapped) {
|
|
162
|
+
return mapped;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
code: 'INTERNAL_ERROR',
|
|
166
|
+
exitCode: 1,
|
|
167
|
+
retryable: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { getConfigFilePath, readConfig, writeConfig } from './config.js';
|
|
3
|
+
import {
|
|
4
|
+
findEndpointByAlias,
|
|
5
|
+
findEndpointByCommand,
|
|
6
|
+
getPlatforms,
|
|
7
|
+
listEndpoints,
|
|
8
|
+
} from './registry.js';
|
|
9
|
+
import { callEndpoint } from './request.js';
|
|
10
|
+
import {
|
|
11
|
+
buildCommandRegistry,
|
|
12
|
+
commandToSchema,
|
|
13
|
+
createAgentSuccess,
|
|
14
|
+
findCommandByKey,
|
|
15
|
+
getCommandName,
|
|
16
|
+
isAgentJsonFormat,
|
|
17
|
+
} from './agent-protocol.js';
|
|
18
|
+
import { findShortcut, listShortcuts } from './shortcuts.js';
|
|
19
|
+
|
|
20
|
+
export async function main(argv) {
|
|
21
|
+
const args = argv.slice(2);
|
|
22
|
+
const [command, ...rest] = args;
|
|
23
|
+
|
|
24
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
25
|
+
printHelp();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (command === 'config') {
|
|
30
|
+
await handleConfig(rest);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (command === 'list') {
|
|
35
|
+
handleList(rest);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (command === 'shortcuts') {
|
|
40
|
+
handleShortcuts(rest);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (command === 'commands') {
|
|
45
|
+
await handleCommands(rest);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (command === 'schema') {
|
|
50
|
+
await handleSchema(rest);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (command === 'call') {
|
|
55
|
+
await handleCall(rest);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await handleGeneratedCommand(command, rest);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function handleCommands(args) {
|
|
63
|
+
const { positionals, options } = parseOptions(args);
|
|
64
|
+
const [action, key] = positionals;
|
|
65
|
+
|
|
66
|
+
if (!action || action === 'list') {
|
|
67
|
+
const commands = buildCommandRegistry().map((item) => ({
|
|
68
|
+
key: item.key,
|
|
69
|
+
command: item.command,
|
|
70
|
+
kind: item.kind,
|
|
71
|
+
description: item.description,
|
|
72
|
+
method: item.method,
|
|
73
|
+
socialPath: item.socialPath,
|
|
74
|
+
}));
|
|
75
|
+
await outputResult(
|
|
76
|
+
createAgentSuccess('commands list', { commands }),
|
|
77
|
+
{ ...options, forceJson: true },
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (action === 'describe') {
|
|
83
|
+
if (!key) {
|
|
84
|
+
throw new Error('缺少命令标识,例如 douyin.video-detail');
|
|
85
|
+
}
|
|
86
|
+
const command = findCommandByKey(key);
|
|
87
|
+
if (!command) {
|
|
88
|
+
throw new Error(`未找到命令: ${key}`);
|
|
89
|
+
}
|
|
90
|
+
await outputResult(createAgentSuccess('commands describe', command), {
|
|
91
|
+
...options,
|
|
92
|
+
forceJson: true,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
throw new Error('未知 commands 命令。用法:yuanflow-cli commands list');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function handleSchema(args) {
|
|
101
|
+
const { positionals, options } = parseOptions(args);
|
|
102
|
+
const [key] = positionals;
|
|
103
|
+
|
|
104
|
+
if (!key || key === 'list') {
|
|
105
|
+
const commands = buildCommandRegistry().map((item) => ({
|
|
106
|
+
key: item.key,
|
|
107
|
+
command: item.command,
|
|
108
|
+
kind: item.kind,
|
|
109
|
+
method: item.method,
|
|
110
|
+
socialPath: item.socialPath,
|
|
111
|
+
}));
|
|
112
|
+
await outputResult(createAgentSuccess('schema list', { commands }), {
|
|
113
|
+
...options,
|
|
114
|
+
forceJson: true,
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const command = findCommandByKey(key);
|
|
120
|
+
if (!command) {
|
|
121
|
+
throw new Error(`不支持的 schema 命令: ${key}`);
|
|
122
|
+
}
|
|
123
|
+
await outputResult(
|
|
124
|
+
createAgentSuccess(`schema ${key}`, commandToSchema(command)),
|
|
125
|
+
{ ...options, forceJson: true },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function handleConfig(args) {
|
|
130
|
+
const [action, key, ...valueParts] = args;
|
|
131
|
+
const value = valueParts.join(' ');
|
|
132
|
+
|
|
133
|
+
if (!action || action === 'show') {
|
|
134
|
+
const config = await readConfig();
|
|
135
|
+
console.log(JSON.stringify({ ...config, token: maskToken(config.token) }, null, 2));
|
|
136
|
+
console.log(`配置文件:${getConfigFilePath()}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (action === 'set-token') {
|
|
141
|
+
await writeConfig({ token: key || value });
|
|
142
|
+
console.log('token 已保存');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (action === 'set-base-url') {
|
|
147
|
+
await writeConfig({ baseUrl: key || value });
|
|
148
|
+
console.log('baseUrl 已保存');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new Error('未知 config 命令。用法:yuanflow-cli config set-token <你的令牌>');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function handleList(args) {
|
|
156
|
+
const [platform] = args;
|
|
157
|
+
const endpoints = listEndpoints().filter((endpoint) => {
|
|
158
|
+
return platform ? endpoint.platform === platform : true;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
for (const endpoint of endpoints) {
|
|
162
|
+
const aliases = endpoint.aliases.length > 0 ? ` aliases: ${endpoint.aliases.join(', ')}` : '';
|
|
163
|
+
console.log(`${endpoint.platform} ${endpoint.command} -> ${endpoint.method} /social${endpoint.socialPath}${aliases}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleShortcuts(args) {
|
|
168
|
+
const [platform] = args;
|
|
169
|
+
const items = listShortcuts(platform);
|
|
170
|
+
if (platform && items.length === 0) {
|
|
171
|
+
throw new Error(`未找到平台 ${platform} 的快捷命令。`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const shortcut of items) {
|
|
175
|
+
const positionalUsage = shortcut.positionals.map((item) => `<${item.name}>`).join(' ');
|
|
176
|
+
const optionUsage = shortcut.options.map((item) => `[--${item.flag}]`).join(' ');
|
|
177
|
+
const usage = [shortcut.platform, shortcut.command, positionalUsage, optionUsage]
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.join(' ');
|
|
180
|
+
console.log(`${usage}`);
|
|
181
|
+
console.log(` ${shortcut.description}`);
|
|
182
|
+
console.log(` 接口:${shortcut.method} /social${shortcut.socialPath}`);
|
|
183
|
+
if (shortcut.positionals.length > 0 || shortcut.options.length > 0) {
|
|
184
|
+
console.log(' 参数:');
|
|
185
|
+
for (const item of shortcut.positionals) {
|
|
186
|
+
console.log(` <${item.name}> ${item.label}`);
|
|
187
|
+
}
|
|
188
|
+
for (const item of shortcut.options) {
|
|
189
|
+
console.log(` --${item.flag} ${item.label}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
console.log(` 返回:${shortcut.returns}`);
|
|
193
|
+
if (shortcut.alternatives.length > 0) {
|
|
194
|
+
console.log(` 备用接口:${shortcut.alternatives.join(';')}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function handleCall(args) {
|
|
200
|
+
const { positionals, options } = parseOptions(args);
|
|
201
|
+
const [socialPath] = positionals;
|
|
202
|
+
if (!socialPath) {
|
|
203
|
+
throw new Error('缺少接口路径。示例:yuanflow-cli call /douyin/web/fetch_multi_video_v2 --json "{\"url\":\"...\"}"');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const result = await callEndpoint(socialPath, options);
|
|
207
|
+
await outputResult(result, options, {
|
|
208
|
+
command: `call ${socialPath}`,
|
|
209
|
+
meta: { endpoint: socialPath },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function handleGeneratedCommand(platform, args) {
|
|
214
|
+
if (!getPlatforms().includes(platform)) {
|
|
215
|
+
throw new Error(`未知平台:${platform}。可用平台:${getPlatforms().join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const { positionals, options } = parseOptions(args);
|
|
219
|
+
const [commandOrAlias, ...rest] = positionals;
|
|
220
|
+
const shortcut = findShortcut(platform, commandOrAlias);
|
|
221
|
+
|
|
222
|
+
if (shortcut) {
|
|
223
|
+
const body = buildShortcutBody(shortcut, rest, options);
|
|
224
|
+
const result = await callEndpoint(shortcut.socialPath, {
|
|
225
|
+
...options,
|
|
226
|
+
method: shortcut.method,
|
|
227
|
+
body,
|
|
228
|
+
});
|
|
229
|
+
await outputResult(result, options, {
|
|
230
|
+
command: getCommandName(platform, shortcut.command),
|
|
231
|
+
meta: { endpoint: shortcut.socialPath, kind: 'shortcut' },
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const aliasEndpoint = findEndpointByAlias(platform, commandOrAlias);
|
|
237
|
+
const endpoint = aliasEndpoint || findEndpointByCommand(platform, [commandOrAlias, ...rest]);
|
|
238
|
+
const commandArgs = aliasEndpoint ? rest : [];
|
|
239
|
+
|
|
240
|
+
if (!endpoint) {
|
|
241
|
+
throw new Error(`未找到命令:${platform} ${positionals.join(' ')}。可执行 yuanflow-cli list ${platform} 查看命令。`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const body = buildBodyFromPositionals(endpoint, commandArgs, options);
|
|
245
|
+
const result = await callEndpoint(endpoint.socialPath, {
|
|
246
|
+
...options,
|
|
247
|
+
method: endpoint.method,
|
|
248
|
+
body,
|
|
249
|
+
});
|
|
250
|
+
await outputResult(result, options, {
|
|
251
|
+
command: getCommandName(platform, endpoint.command),
|
|
252
|
+
meta: { endpoint: endpoint.socialPath, kind: 'endpoint' },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function buildShortcutBody(shortcut, rest, options) {
|
|
257
|
+
if (options.json || options.file) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const body = { ...(shortcut.defaults || {}) };
|
|
262
|
+
for (const [index, item] of shortcut.positionals.entries()) {
|
|
263
|
+
const value = rest[index];
|
|
264
|
+
if (value === undefined) {
|
|
265
|
+
throw new Error(`缺少参数 <${item.name}>。可执行 yuanflow-cli shortcuts ${shortcut.platform} 查看用法。`);
|
|
266
|
+
}
|
|
267
|
+
body[item.name] = value;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const item of shortcut.options) {
|
|
271
|
+
const value = options.named?.[item.flag];
|
|
272
|
+
if (value !== undefined) {
|
|
273
|
+
body[item.name] = value;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return body;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function buildBodyFromPositionals(endpoint, rest, options) {
|
|
281
|
+
if (options.json || options.file) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (endpoint.aliases.includes('video-detail') && rest.length > 0) {
|
|
286
|
+
return { url: rest[0] };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function parseOptions(args) {
|
|
293
|
+
const options = { named: {} };
|
|
294
|
+
const positionals = [];
|
|
295
|
+
|
|
296
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
297
|
+
const arg = args[index];
|
|
298
|
+
if (arg === '--json') {
|
|
299
|
+
options.json = args[++index];
|
|
300
|
+
} else if (arg === '--file') {
|
|
301
|
+
options.file = args[++index];
|
|
302
|
+
} else if (arg === '--output' || arg === '-o') {
|
|
303
|
+
options.output = args[++index];
|
|
304
|
+
} else if (arg === '--base-url') {
|
|
305
|
+
options.baseUrl = args[++index];
|
|
306
|
+
} else if (arg === '--token') {
|
|
307
|
+
options.token = args[++index];
|
|
308
|
+
} else if (arg === '--method') {
|
|
309
|
+
options.method = args[++index];
|
|
310
|
+
} else if (arg === '--format') {
|
|
311
|
+
options.format = args[++index];
|
|
312
|
+
} else if (arg === '--dry-run') {
|
|
313
|
+
options.dryRun = true;
|
|
314
|
+
} else if (arg.startsWith('--')) {
|
|
315
|
+
const name = arg.slice(2);
|
|
316
|
+
const next = args[index + 1];
|
|
317
|
+
if (next === undefined || next.startsWith('--')) {
|
|
318
|
+
options.named[name] = true;
|
|
319
|
+
} else {
|
|
320
|
+
options.named[name] = args[++index];
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
positionals.push(arg);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return { positionals, options };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function outputResult(result, options, agentContext = {}) {
|
|
331
|
+
if (isAgentJsonFormat(options) && !options.forceJson) {
|
|
332
|
+
const payload = createAgentSuccess(
|
|
333
|
+
agentContext.command || 'unknown',
|
|
334
|
+
result,
|
|
335
|
+
agentContext.meta || {},
|
|
336
|
+
);
|
|
337
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const text = typeof result === 'string' ? result : `${JSON.stringify(result, null, 2)}\n`;
|
|
342
|
+
if (options.output) {
|
|
343
|
+
await writeFile(options.output, text, 'utf8');
|
|
344
|
+
console.log(`已保存:${options.output}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
process.stdout.write(text);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function printHelp() {
|
|
351
|
+
console.log(`yuanflow-cli
|
|
352
|
+
|
|
353
|
+
用法:
|
|
354
|
+
yuanflow-cli config set-token <你的令牌>
|
|
355
|
+
yuanflow-cli config set-base-url https://open.yuanchuangai.com
|
|
356
|
+
yuanflow-cli call /douyin/app/v3/fetch_multi_video_v2 --json '{"url":"https://v.douyin.com/xxx/"}'
|
|
357
|
+
yuanflow-cli douyin video-detail "https://v.douyin.com/xxx/"
|
|
358
|
+
yuanflow-cli douyin video-detail "https://v.douyin.com/xxx/" --dry-run
|
|
359
|
+
yuanflow-cli douyin video-download "https://v.douyin.com/xxx/" --region CN
|
|
360
|
+
yuanflow-cli xiaohongshu note-detail note_id --xsec-token xxx
|
|
361
|
+
yuanflow-cli bilibili user-posts uid --page 1
|
|
362
|
+
yuanflow-cli wechat channels-search "关键词"
|
|
363
|
+
yuanflow-cli shortcuts douyin
|
|
364
|
+
yuanflow-cli commands list
|
|
365
|
+
yuanflow-cli schema douyin.video-detail
|
|
366
|
+
yuanflow-cli list douyin
|
|
367
|
+
|
|
368
|
+
说明:
|
|
369
|
+
所有请求都会调用 Yuan API 的 /social/*path,并使用 Authorization: Bearer <token>。
|
|
370
|
+
AI Agent 推荐使用 --format agent-json 获取稳定 JSON 外壳。
|
|
371
|
+
`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function maskToken(token) {
|
|
375
|
+
if (!token) {
|
|
376
|
+
return '';
|
|
377
|
+
}
|
|
378
|
+
if (token.length <= 10) {
|
|
379
|
+
return '***';
|
|
380
|
+
}
|
|
381
|
+
return `${token.slice(0, 6)}...${token.slice(-4)}`;
|
|
382
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(homedir(), '.yuanflow-cli');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
baseUrl: 'https://open.yuanchuangai.com',
|
|
10
|
+
token: '',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function readConfig() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await readFile(CONFIG_FILE, 'utf8');
|
|
16
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
17
|
+
} catch {
|
|
18
|
+
return { ...DEFAULT_CONFIG };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function writeConfig(nextConfig) {
|
|
23
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
24
|
+
const config = { ...(await readConfig()), ...nextConfig };
|
|
25
|
+
await writeFile(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getConfigFilePath() {
|
|
30
|
+
return CONFIG_FILE;
|
|
31
|
+
}
|
package/src/registry.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import registry from '../generated/registry.json' with { type: 'json' };
|
|
2
|
+
|
|
3
|
+
export function listEndpoints() {
|
|
4
|
+
return registry.endpoints;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function findEndpointByPath(socialPath) {
|
|
8
|
+
const normalized = normalizeSocialPath(socialPath);
|
|
9
|
+
return registry.endpoints.find((endpoint) => endpoint.socialPath === normalized);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function findEndpointByCommand(platform, commandParts) {
|
|
13
|
+
const command = commandParts.join(' ');
|
|
14
|
+
return registry.endpoints.find(
|
|
15
|
+
(endpoint) => endpoint.platform === platform && endpoint.command === command,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function findEndpointByAlias(platform, alias) {
|
|
20
|
+
return registry.endpoints.find(
|
|
21
|
+
(endpoint) => endpoint.platform === platform && endpoint.aliases.includes(alias),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getPlatforms() {
|
|
26
|
+
return registry.platforms;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function normalizeSocialPath(value) {
|
|
30
|
+
if (!value || typeof value !== 'string') {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
if (trimmed.startsWith('/social/')) {
|
|
36
|
+
return trimmed.slice('/social'.length);
|
|
37
|
+
}
|
|
38
|
+
if (trimmed.startsWith('/api/v1/')) {
|
|
39
|
+
return trimmed.slice('/api/v1'.length);
|
|
40
|
+
}
|
|
41
|
+
if (!trimmed.startsWith('/')) {
|
|
42
|
+
return `/${trimmed}`;
|
|
43
|
+
}
|
|
44
|
+
return trimmed;
|
|
45
|
+
}
|