aidrama-cli 1.0.1 → 1.0.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/aidrama.js +155 -29
- package/package.json +1 -1
package/aidrama.js
CHANGED
|
@@ -161,35 +161,161 @@ function parseArgs(argv) {
|
|
|
161
161
|
return { positionals, flags };
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
const
|
|
164
|
+
const PKG_VERSION = '1.0.2';
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
// 全局选项 / 环境变量 / 退出码(帮助文本与 JSON 规格共用)
|
|
167
|
+
const GLOBALS = [
|
|
168
|
+
['--base-url <url>', '后端基地址;默认 http://localhost:38000(或环境变量 AI_DRAMA_BASE_URL)'],
|
|
169
|
+
['--json', '以紧凑 JSON 输出结果,便于程序/agent 解析'],
|
|
170
|
+
['--help', '查看帮助;附在命令后看该命令详情,加 --json 输出机器可读规格'],
|
|
171
|
+
];
|
|
172
|
+
const ENVS = [
|
|
173
|
+
['AI_DRAMA_BASE_URL', '后端基地址(优先级低于 --base-url)'],
|
|
174
|
+
['AI_DRAMA_TOKEN', '鉴权 token(优先级高于配置文件;适合 CI/agent 无交互鉴权)'],
|
|
175
|
+
['AI_DRAMA_CLI_CONFIG', '配置文件路径,默认 ~/.ai-drama-cli.json(登录后 token 存于此)'],
|
|
176
|
+
];
|
|
177
|
+
const EXIT_CODES = '0=成功 1=运行错误(网络/HTTP/参数/未登录) 2=未知命令';
|
|
167
178
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
auth
|
|
172
|
-
|
|
173
|
-
auth
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
179
|
+
// 命令规格:单一数据源,既渲染人读帮助也输出 JSON 规格。
|
|
180
|
+
// 字段: d=简介, u=用法, opts=[[标志,说明]], ret=返回说明, ex=[示例], auth=是否需登录
|
|
181
|
+
const SPEC = {
|
|
182
|
+
'auth methods': { d: '查询后端启用的登录方式', u: 'auth methods', auth: false,
|
|
183
|
+
ret: '{ password: bool, feishu: bool }', ex: ['aidrama auth methods'] },
|
|
184
|
+
'auth login': { d: '用户名/密码登录(兼容),成功后保存 token', u: 'auth login -u <user> -p <pass>', auth: false,
|
|
185
|
+
opts: [['-u, --username <user>', '用户名'], ['-p, --password <pass>', '密码']],
|
|
186
|
+
ret: '{ ok: true, token_prefix };token 写入配置文件', ex: ['aidrama auth login -u admin -p ******'] },
|
|
187
|
+
'auth feishu': { d: '飞书登录(OAuth):无 code 打印授权链接;有 code 换取并保存 token', u: 'auth feishu [--code <c> --state <s> | --redirect-url <url>]', auth: false,
|
|
188
|
+
opts: [['--code <c>', '飞书回调返回的授权 code'], ['--state <s>', '授权时的 state'], ['--redirect-url <url>', '直接粘贴整个回调 URL,自动解析 code/state']],
|
|
189
|
+
ret: '无 code: { authorize_url, state };有 code: { ok, token_prefix }',
|
|
190
|
+
ex: ['aidrama auth feishu', "aidrama auth feishu --redirect-url 'http://host/cb?code=XXX&state=YYY'"] },
|
|
191
|
+
'auth me': { d: '查看当前登录用户', u: 'auth me', auth: true, ret: '{ id, username, roles, permissions }' },
|
|
192
|
+
'auth logout': { d: '清除本地保存的 token', u: 'auth logout', auth: false },
|
|
193
|
+
'config show': { d: '查看当前配置(路径/base_url/token 前缀)', u: 'config show', auth: false },
|
|
194
|
+
'config set-base-url': { d: '把默认后端地址写入配置文件', u: 'config set-base-url <url>', auth: false,
|
|
195
|
+
ex: ['aidrama config set-base-url http://10.0.0.5:38000'] },
|
|
196
|
+
'projects list': { d: '列出项目', u: 'projects list', auth: true, ret: '项目数组' },
|
|
197
|
+
'projects create': { d: '创建项目', u: 'projects create <name>', auth: true, ex: ['aidrama projects create 测试项目'] },
|
|
198
|
+
'providers list': { d: '列出可用 AI 渠道', u: 'providers list [--capability <text|image|video>]', auth: true,
|
|
199
|
+
opts: [['--capability <c>', '按能力筛选:text/image/video']],
|
|
200
|
+
ret: '渠道数组 { id, display_name, provider_type, capability }', ex: ['aidrama providers list --capability video'] },
|
|
201
|
+
'content get': { d: '获取项目原文/内容', u: 'content get <project_id>', auth: true },
|
|
202
|
+
'content set': { d: '设置项目原文(文本或文件)', u: 'content set <project_id> [--text <t> | --file <path>] [--content-type <type>]', auth: true,
|
|
203
|
+
opts: [['--text <t>', '直接给原文文本'], ['--file <path>', '从本地文件读取原文'], ['--content-type <type>', '内容类型,默认 novel']],
|
|
204
|
+
ex: ['aidrama content set 2 --file ./novel.txt'] },
|
|
205
|
+
'elements run': { d: '元素推理(角色/场景/道具)', u: 'elements run <project_id> [--provider-id N] [--wait]', auth: true,
|
|
206
|
+
opts: [['--provider-id N', '文本渠道 id(默认后端选)'], ['--wait', '轮询到完成再返回']],
|
|
207
|
+
ret: '{ session_id, status };--wait 返回完成的会话' },
|
|
208
|
+
'elements list': { d: '列出已推理的元素', u: 'elements list <project_id>', auth: true },
|
|
209
|
+
'images run': { d: '生成元素图片', u: 'images run <project_id> [--element-id N] [--state-id N] [--provider-id N] [--wait]', auth: true,
|
|
210
|
+
opts: [['--element-id N', '仅生成指定元素'], ['--state-id N', '指定状态'], ['--provider-id N', '图片渠道 id'], ['--wait', '轮询到完成']] },
|
|
211
|
+
'episodes run': { d: '剧集拆解', u: 'episodes run <project_id> [--provider-id N] [--wait]', auth: true,
|
|
212
|
+
opts: [['--provider-id N', '文本渠道 id'], ['--wait', '轮询到完成']] },
|
|
213
|
+
'episodes list': { d: '列出剧集', u: 'episodes list <project_id>', auth: true },
|
|
214
|
+
'storyboards run': { d: '分镜策划', u: 'storyboards run <project_id> [--episode-id N] [--provider-id N] [--wait]', auth: true,
|
|
215
|
+
opts: [['--episode-id N', '指定剧集'], ['--provider-id N', '文本渠道 id'], ['--wait', '轮询到完成']] },
|
|
216
|
+
'storyboards list': { d: '列出分镜', u: 'storyboards list <project_id> [--episode-id N]', auth: true,
|
|
217
|
+
opts: [['--episode-id N', '按剧集筛选']] },
|
|
218
|
+
'videos run': { d: '按分镜生成视频', u: 'videos run <project_id> --storyboard-ids <A B C> --provider-id N [--gen-mode <m>] [--resolution R] [--wait]', auth: true,
|
|
219
|
+
opts: [['--storyboard-ids <A B C>', '分镜 id 列表(空格分隔)'], ['--provider-id N', '视频渠道 id'], ['--gen-mode <m>', 'multi_ref(默认)/first_last_frame'], ['--resolution R', '分辨率'], ['--wait', '轮询到完成']] },
|
|
220
|
+
'videos list': { d: '列出已生成视频', u: 'videos list <project_id>', auth: true },
|
|
221
|
+
'videos raw': { d: '直连视频渠道(默认 seedance 2.0)生视频:纯提示词→视频,不依赖分镜,支持多模态参考', u: 'videos raw --prompt <text> [options]', auth: true,
|
|
222
|
+
opts: [
|
|
223
|
+
['--prompt <text>', '【必填】文本提示词'],
|
|
224
|
+
['--provider-id N', '视频渠道 id;默认首个启用的视频渠道(如 seedance 2.0)'],
|
|
225
|
+
['--duration N', '时长(秒),默认 5'],
|
|
226
|
+
['--aspect-ratio R', '画幅:9:16 / 16:9 / 1:1 / 4:3(建议显式传,否则可能跟随参考图自适应)'],
|
|
227
|
+
['--resolution R', '分辨率档:480p / 720p / 1080p'],
|
|
228
|
+
['--gen-mode M', 'multi_ref(默认) 或 first_last_frame(首张=首帧、末张=尾帧)'],
|
|
229
|
+
['--ref-images URL...', '参考图,可多张(空格分隔)'],
|
|
230
|
+
['--ref-videos URL...', '参考视频,可多个'],
|
|
231
|
+
['--ref-audios URL...', '参考音色,可多个;须与图/视频同传,不能单独'],
|
|
232
|
+
['--negative-prompt <t>', '负面约束(并入提示词文本)'],
|
|
233
|
+
['--idempotency-key K', '幂等键→渠道头 Idempotency-Key;同 key 不重复生成'],
|
|
234
|
+
['--wait', '轮询到完成再返回'],
|
|
235
|
+
['--out FILE', '隐含 --wait,完成后把成片下载到本地文件'],
|
|
236
|
+
],
|
|
237
|
+
ret: '触发: { session_id, status };--wait: 完成会话(validation_report.video_url 即视频地址);--out: 另附 saved_to(本地路径)',
|
|
238
|
+
ex: [
|
|
239
|
+
'aidrama videos raw --prompt "夜雨霓虹的赛博街道" --aspect-ratio 16:9 --wait',
|
|
240
|
+
'aidrama videos raw --prompt "她转身微笑" --ref-images https://x/a.png https://x/b.png --out ./out.mp4',
|
|
241
|
+
'aidrama videos raw --prompt "镜头推进" --ref-videos https://x/ref.mp4 --idempotency-key job-001 --wait',
|
|
242
|
+
] },
|
|
243
|
+
'session status': { d: '查询会话状态(一次)', u: 'session status <session_id>', auth: true,
|
|
244
|
+
ret: '{ session_id, status, validation_report, items }' },
|
|
245
|
+
'session wait': { d: '轮询会话直到完成/失败', u: 'session wait <session_id> [--interval 3]', auth: true,
|
|
246
|
+
opts: [['--interval N', '轮询间隔秒,默认 3']] },
|
|
247
|
+
'api': { d: '通用透传:直接调用任意后端接口', u: 'api <GET|POST|PUT|DELETE> <path> [--data <json>] [--query k=v ...]', auth: true,
|
|
248
|
+
opts: [['--data <json>', '请求体 JSON 字符串'], ['--query k=v', '查询参数,可多个']],
|
|
249
|
+
ret: '后端原始响应',
|
|
250
|
+
ex: ['aidrama api GET /api/providers --query capability=video', 'aidrama api POST /api/projects --data \'{"name":"x"}\''] },
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
function pad(s, n) { return s + ' '.repeat(Math.max(1, n - s.length)); }
|
|
254
|
+
|
|
255
|
+
// 总览帮助(人读)
|
|
256
|
+
function overview() {
|
|
257
|
+
const L = [];
|
|
258
|
+
L.push(`aidrama v${PKG_VERSION} —— AI 短剧 Agent 流程命令行(零依赖, 飞书/密码登录, 适合脚本与 agent 调用)`);
|
|
259
|
+
L.push('');
|
|
260
|
+
L.push('用法: aidrama [全局选项] <分组> <命令> [参数...]');
|
|
261
|
+
L.push('命令详情: aidrama <分组> <命令> --help 机器可读规格: aidrama --help --json');
|
|
262
|
+
L.push('');
|
|
263
|
+
L.push('全局选项:');
|
|
264
|
+
for (const [f, d] of GLOBALS) L.push(' ' + pad(f, 16) + d);
|
|
265
|
+
L.push('');
|
|
266
|
+
L.push('环境变量:');
|
|
267
|
+
for (const [f, d] of ENVS) L.push(' ' + pad(f, 22) + d);
|
|
268
|
+
L.push('');
|
|
269
|
+
L.push('命令(用 <命令> --help 看详情):');
|
|
270
|
+
let grp = '';
|
|
271
|
+
for (const id of Object.keys(SPEC)) {
|
|
272
|
+
const g = id.split(' ')[0];
|
|
273
|
+
if (g !== grp) { grp = g; L.push(' ' + g + ':'); }
|
|
274
|
+
L.push(' ' + pad(id, 22) + SPEC[id].d);
|
|
275
|
+
}
|
|
276
|
+
L.push('');
|
|
277
|
+
L.push('退出码: ' + EXIT_CODES);
|
|
278
|
+
return L.join('\n');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 单命令详情(人读)
|
|
282
|
+
function cmdHelp(id) {
|
|
283
|
+
const s = SPEC[id];
|
|
284
|
+
const L = [`aidrama ${id} —— ${s.d}`, '', '用法:', ' aidrama ' + s.u];
|
|
285
|
+
if (s.opts && s.opts.length) { L.push('', '选项:'); for (const [f, d] of s.opts) L.push(' ' + pad(f, 26) + d); }
|
|
286
|
+
if (s.auth) L.push('', '需登录(先 auth login / auth feishu,或设环境变量 AI_DRAMA_TOKEN)');
|
|
287
|
+
if (s.ret) L.push('', '返回: ' + s.ret);
|
|
288
|
+
if (s.ex && s.ex.length) { L.push('', '示例:'); for (const e of s.ex) L.push(' ' + e); }
|
|
289
|
+
return L.join('\n');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 帮助分发:支持总览 / 分组 / 单命令;--json 输出机器可读规格
|
|
293
|
+
function showHelp(positionals, flags) {
|
|
294
|
+
const g = positionals[0], c = positionals[1];
|
|
295
|
+
const exact = (g && c && SPEC[`${g} ${c}`]) ? `${g} ${c}` : (g && SPEC[g] ? g : null);
|
|
296
|
+
if (flags.json) {
|
|
297
|
+
let payload;
|
|
298
|
+
if (exact) payload = { command: exact, ...SPEC[exact] };
|
|
299
|
+
else {
|
|
300
|
+
const sub = g ? Object.keys(SPEC).filter((k) => k === g || k.startsWith(g + ' ')) : null;
|
|
301
|
+
if (sub && sub.length) payload = { group: g, commands: Object.fromEntries(sub.map((k) => [k, SPEC[k]])) };
|
|
302
|
+
else payload = { name: 'aidrama', version: PKG_VERSION, globals: GLOBALS, env: ENVS, exit_codes: EXIT_CODES, commands: SPEC };
|
|
303
|
+
}
|
|
304
|
+
console.log(JSON.stringify(payload, null, 0));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (exact) { console.log(cmdHelp(exact)); return; }
|
|
308
|
+
if (g) {
|
|
309
|
+
const sub = Object.keys(SPEC).filter((k) => k === g || k.startsWith(g + ' '));
|
|
310
|
+
if (sub.length) {
|
|
311
|
+
const L = [`aidrama ${g} —— 命令(用 <命令> --help 看详情):`, ''];
|
|
312
|
+
for (const k of sub) L.push(' ' + pad(k, 22) + SPEC[k].d);
|
|
313
|
+
console.log(L.join('\n'));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
console.log(overview());
|
|
318
|
+
}
|
|
193
319
|
|
|
194
320
|
// ---------------- 命令实现 ----------------
|
|
195
321
|
async function dispatch({ positionals, flags }) {
|
|
@@ -351,7 +477,7 @@ async function dispatch({ positionals, flags }) {
|
|
|
351
477
|
}
|
|
352
478
|
// 未匹配
|
|
353
479
|
eprint(`未知命令: ${group || ''} ${cmd || ''}`.trim());
|
|
354
|
-
eprint('\n' +
|
|
480
|
+
eprint('\n' + overview());
|
|
355
481
|
process.exit(2);
|
|
356
482
|
}
|
|
357
483
|
|
|
@@ -361,7 +487,7 @@ function reqStr(v) { if (v === undefined) fail('缺少 session_id 参数'); retu
|
|
|
361
487
|
async function main() {
|
|
362
488
|
const argv = process.argv.slice(2);
|
|
363
489
|
const { positionals, flags } = parseArgs(argv);
|
|
364
|
-
if (flags.help || positionals.length === 0) {
|
|
490
|
+
if (flags.help || positionals.length === 0) { showHelp(positionals, flags); return 0; }
|
|
365
491
|
await dispatch({ positionals, flags });
|
|
366
492
|
return 0;
|
|
367
493
|
}
|