@lingjingai/lj-awb-cli-pre 0.3.15

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.
Files changed (49) hide show
  1. package/README.md +335 -0
  2. package/build/_shared.mjs +130 -0
  3. package/build/build.mjs +50 -0
  4. package/build/pre-publish.mjs +57 -0
  5. package/build/pre.mjs +42 -0
  6. package/build/prod.mjs +52 -0
  7. package/install.mjs +53 -0
  8. package/package.json +44 -0
  9. package/packages/awb-cli/README.md +19 -0
  10. package/packages/awb-cli/bin/lj-awb +19 -0
  11. package/packages/awb-cli/bin/lj-awb.js +11 -0
  12. package/packages/awb-cli/package.json +18 -0
  13. package/packages/awb-core/README.md +12 -0
  14. package/packages/awb-core/package.json +21 -0
  15. package/packages/awb-core/src/api.js +349 -0
  16. package/packages/awb-core/src/artifact.js +936 -0
  17. package/packages/awb-core/src/auth.js +80 -0
  18. package/packages/awb-core/src/commands.js +1321 -0
  19. package/packages/awb-core/src/common.js +508 -0
  20. package/packages/awb-core/src/output.js +1189 -0
  21. package/packages/awb-core/src/services.js +3811 -0
  22. package/packages/awb-core/src/standalone.js +1213 -0
  23. package/skills/lj-awb/SKILL.md +160 -0
  24. package/skills/lj-awb/VERSION +1 -0
  25. package/skills/lj-awb/compat.json +6 -0
  26. package/skills/lj-awb/modules/account.md +30 -0
  27. package/skills/lj-awb/modules/artifact/asset.md +64 -0
  28. package/skills/lj-awb/modules/artifact/clip.md +65 -0
  29. package/skills/lj-awb/modules/artifact/script.md +37 -0
  30. package/skills/lj-awb/modules/artifact/video.md +65 -0
  31. package/skills/lj-awb/modules/artifact.md +65 -0
  32. package/skills/lj-awb/modules/asset.md +53 -0
  33. package/skills/lj-awb/modules/auth.md +30 -0
  34. package/skills/lj-awb/modules/create-contract.md +118 -0
  35. package/skills/lj-awb/modules/credits.md +28 -0
  36. package/skills/lj-awb/modules/evals.md +186 -0
  37. package/skills/lj-awb/modules/image.md +75 -0
  38. package/skills/lj-awb/modules/model.md +110 -0
  39. package/skills/lj-awb/modules/project.md +30 -0
  40. package/skills/lj-awb/modules/subject.md +32 -0
  41. package/skills/lj-awb/modules/task-manual.md +185 -0
  42. package/skills/lj-awb/modules/task.md +62 -0
  43. package/skills/lj-awb/modules/upload.md +33 -0
  44. package/skills/lj-awb/modules/video.md +102 -0
  45. package/skills/lj-awb/modules/workflows.md +482 -0
  46. package/skills/lj-awb/references/error-codes.md +102 -0
  47. package/skills/lj-awb/references/model-options-read.md +49 -0
  48. package/skills/lj-awb/references/output-fields.md +113 -0
  49. package/skills/lj-awb/scripts/resolve-lj-awb-cmd.sh +10 -0
@@ -0,0 +1,1321 @@
1
+ import { fetchProjectGroupUsers, fetchTeams, fetchUserInfo } from './api.js';
2
+ import * as artifact from './artifact.js';
3
+ import { clearAuth, loadAuth, saveAccessKey, summarizeAuth } from './auth.js';
4
+ import { LingjingAwbCliError, maskSecret, toBool, trimSecret } from './common.js';
5
+ import {
6
+ assetGroupCreate,
7
+ assetGroupGet,
8
+ assetGroupList,
9
+ assetGroupUpdate,
10
+ assetMatchActor,
11
+ assetRegister,
12
+ creditsBalance,
13
+ creditsUsageSummary,
14
+ createProjectGroupCommand,
15
+ accountInfo,
16
+ doctor,
17
+ ensureConfirmed,
18
+ ensureProjectGroupCommand,
19
+ fetchProjectGroupSummary,
20
+ imageCreate,
21
+ imageCreateBatch,
22
+ imageFee,
23
+ listModels,
24
+ modelOptions,
25
+ modelCreateSpec,
26
+ modelInputGuide,
27
+ listProjectGroups,
28
+ normalizeRows,
29
+ normalizeUserInfo,
30
+ selectProjectGroupCommand,
31
+ selectTeam,
32
+ subjectList,
33
+ subjectPublish,
34
+ subjectPublishBatch,
35
+ subjectWait,
36
+ subtitleRemove,
37
+ subtitleStatus,
38
+ taskList,
39
+ taskRecordPoll,
40
+ taskRecords,
41
+ taskStatus,
42
+ uploadFilesCommand,
43
+ updateProjectGroupCommand,
44
+ videoCreate,
45
+ videoCreateBatch,
46
+ videoFee,
47
+ waitTask,
48
+ } from './services.js';
49
+
50
+ function commandHelp(summary, options = {}) {
51
+ const lines = [summary];
52
+ if (options.quickStart?.length) {
53
+ lines.push('', 'Suggested flow:');
54
+ for (const item of options.quickStart) lines.push(` ${item}`);
55
+ }
56
+ if (options.examples?.length) {
57
+ lines.push('', 'Examples:');
58
+ for (const example of options.examples) lines.push(` ${example}`);
59
+ }
60
+ if (options.hint) {
61
+ lines.push('', `Hint: ${options.hint}`);
62
+ }
63
+ return lines.join('\n');
64
+ }
65
+
66
+ const YES_ARG = { name: 'yes', description: '确认执行写入 / 扣费 / 切换动作' };
67
+ const DRY_RUN_ARG = { name: 'dry-run', description: '只预览请求,不执行写入 / 扣费动作' };
68
+ const FEE_DRY_RUN_ARG = { name: 'dry-run', description: '只预览估价请求,不调用平台接口' };
69
+ const INCLUDE_RAW_ARG = { name: 'include-raw', description: '在 JSON 中附带平台原始返回' };
70
+ const PROJECT_ID_ARG = { name: 'project-id', valueName: 'projectId', description: 'AWB 项目 ID(不同于 --project-group-no)' };
71
+ const PROJECT_GROUP_ARG = { name: 'project-group-no', valueName: 'no', description: '项目组编号;不传时读取当前项目组 / 环境变量 / 本地状态' };
72
+ const CUSTOM_BIZ_ARG = { name: 'custom-biz-id', valueName: 'id', description: '外部追踪 ID;不传则请求不携带 customBizId' };
73
+ const MODEL_GROUP_ARG = { name: 'model-group-code', valueName: 'code', description: '模型组编码;创建和参数查询只传这个字段' };
74
+ const MODEL_LIST_ARGS = [
75
+ { name: 'model', valueName: 'keyword', description: '按模型名、编码、供应商或输入方式过滤;不传时查询全部' },
76
+ { name: 'limit', valueName: 'n', description: '默认文本最多展示 N 条,默认 8;JSON 输出始终返回全部匹配' },
77
+ { name: 'all', description: '默认文本展示全部匹配模型(覆盖 --limit)' },
78
+ INCLUDE_RAW_ARG,
79
+ ];
80
+ const TASK_RECORD_ARG = { name: 'task-record-file', valueName: 'path', description: '任务台账 JSONL 文件;也可用 LINGJING_AWB_TASK_RECORD_FILE' };
81
+ const RESOURCE_ARG = { name: 'resource', valueName: 'spec', description: '统一素材,格式 type:usage[:key]=path|url|asset,可重复传;音频参考用 audio:reference=./music.mp3' };
82
+ const RESOURCES_JSON_ARG = { name: 'resources-json', valueName: 'json|path', description: 'resources 数组 JSON 或 JSON 文件路径' };
83
+ const CREATE_WAIT_ARGS = [
84
+ { name: 'wait-seconds', valueName: 'seconds', description: '提交后顺带等待结果的秒数;默认 0 不等待' },
85
+ { name: 'poll-interval-ms', valueName: 'ms', description: '任务轮询间隔毫秒数,默认 5000' },
86
+ ];
87
+ const TASK_WAIT_ARGS = [
88
+ { name: 'wait-seconds', valueName: 'seconds', description: '最长等待秒数;默认 300,传 0 只查询一次' },
89
+ { name: 'poll-interval-ms', valueName: 'ms', description: '任务轮询间隔毫秒数,默认 5000' },
90
+ ];
91
+ const RECORD_POLL_ARGS = [
92
+ { name: 'wait-seconds', valueName: 'seconds', description: '最长轮询秒数;默认 0 只轮询一次本地台账' },
93
+ { name: 'poll-interval-ms', valueName: 'ms', description: '任务轮询间隔毫秒数,默认 5000' },
94
+ ];
95
+ const SUBJECT_WAIT_ARGS = [
96
+ { name: 'wait-seconds', valueName: 'seconds', description: '最长等待 externalId 的秒数;默认 0 只查询一次' },
97
+ { name: 'poll-interval-ms', valueName: 'ms', description: '轮询间隔毫秒数,默认 2000' },
98
+ ];
99
+ const BODY_INPUT_ARGS = [
100
+ { name: 'body-json', valueName: 'json', description: '直接传接口 body JSON' },
101
+ { name: 'input-file', valueName: 'path', description: '从本地 JSON 文件读取接口 body' },
102
+ ];
103
+ const ARTIFACT_WRITE_ARGS = [DRY_RUN_ARG, YES_ARG];
104
+
105
+ export function registerAwbCommands(cli) {
106
+ cli({
107
+ name: 'doctor',
108
+ description: commandHelp('检查 CLI 运行环境、认证、API 地址、项目组和 skill 安装状态', {
109
+ examples: [
110
+ 'lj-awb doctor',
111
+ 'lj-awb doctor --verify',
112
+ 'lj-awb doctor --verify --project-group-no <no>',
113
+ ],
114
+ hint: '默认只做本地检查;追加 --verify 才联网校验 access key、当前用户和项目组。',
115
+ }),
116
+ args: [
117
+ { name: 'verify', description: '联网校验 access key、当前用户和项目组' },
118
+ PROJECT_GROUP_ARG,
119
+ ],
120
+ func: async (_ctx, kwargs) => doctor(kwargs),
121
+ });
122
+
123
+ cli({
124
+ name: 'auth status',
125
+ description: commandHelp('查看本地 access key 是否已配置;不联网', {
126
+ examples: ['lj-awb auth status'],
127
+ hint: '只说明本地是否配置,不代表远端可用;需要联网验证运行 auth verify 或 doctor --verify。',
128
+ }),
129
+ args: [],
130
+ func: async () => {
131
+ const auth = await loadAuth();
132
+ return {
133
+ ...summarizeAuth(auth),
134
+ verification: 'local_only',
135
+ verifyCommand: 'lj-awb auth verify',
136
+ };
137
+ },
138
+ });
139
+
140
+ cli({
141
+ name: 'auth verify',
142
+ description: commandHelp('联网校验 access key 是否远端有效', {
143
+ examples: ['lj-awb auth verify'],
144
+ hint: '会调用 userInfo;失败表示当前 access key 缺失、无效、过期或不适用于当前 API 地址。',
145
+ }),
146
+ args: [],
147
+ func: async () => {
148
+ const auth = await loadAuth();
149
+ const user = normalizeUserInfo(await fetchUserInfo());
150
+ return {
151
+ ...summarizeAuth(auth),
152
+ verified: true,
153
+ verification: 'remote',
154
+ user,
155
+ };
156
+ },
157
+ });
158
+
159
+ cli({
160
+ name: 'auth login',
161
+ description: commandHelp('登录:使用 access key 校验身份,并保存到本地认证文件', {
162
+ examples: [
163
+ 'lj-awb auth login --access-key <key>',
164
+ 'LINGJING_AWB_ACCESS_KEY=<key> lj-awb auth login',
165
+ 'lj-awb auth login --access-key <key> --skip-verify',
166
+ ],
167
+ hint: '不要写成 lj-awb auth <key>。不传 --access-key 时自动读取 LINGJING_AWB_ACCESS_KEY / AWB_ACCESS_KEY / AWB_CODE 环境变量。',
168
+ }),
169
+ args: [
170
+ { name: 'access-key', valueName: 'key', description: '平台 access key;不传时读取环境变量' },
171
+ { name: 'skip-verify', description: '只保存 key,不调用 userInfo 校验;仅用于本地调试' },
172
+ DRY_RUN_ARG,
173
+ ],
174
+ func: async (_ctx, kwargs) => {
175
+ const skipVerify = toBool(kwargs.skipVerify);
176
+ const accessKey = trimSecret(kwargs.accessKey || process.env.LINGJING_AWB_ACCESS_KEY || process.env.AWB_ACCESS_KEY || process.env.AWB_CODE || process.env.ANIME_ACCESS_KEY || '');
177
+ if (!accessKey) {
178
+ throw new LingjingAwbCliError('缺少 access key', {
179
+ type: 'argument_error',
180
+ exitCode: 2,
181
+ hint: '传 --access-key <key>,或设置 LINGJING_AWB_ACCESS_KEY 环境变量。',
182
+ });
183
+ }
184
+ if (toBool(kwargs.dryRun)) {
185
+ return {
186
+ dryRun: true,
187
+ saved: false,
188
+ verified: false,
189
+ accessKey: maskSecret(accessKey),
190
+ };
191
+ }
192
+ let user = null;
193
+ if (!skipVerify) {
194
+ process.env.LINGJING_AWB_ACCESS_KEY = accessKey;
195
+ user = normalizeUserInfo(await fetchUserInfo());
196
+ }
197
+ const { auth } = await saveAccessKey(accessKey, { verified: !skipVerify });
198
+ return {
199
+ auth: summarizeAuth(auth, { accessKey, source: 'saved', sourceName: 'auth' }),
200
+ accessKey: maskSecret(accessKey),
201
+ verified: !skipVerify,
202
+ user,
203
+ };
204
+ },
205
+ });
206
+
207
+ cli({
208
+ name: 'auth clear',
209
+ description: commandHelp('清空本地认证文件', {
210
+ examples: ['lj-awb auth clear --dry-run', 'lj-awb auth clear --yes'],
211
+ hint: '只清本地文件,不清当前 shell 中的环境变量。',
212
+ }),
213
+ args: [DRY_RUN_ARG, YES_ARG],
214
+ func: async (_ctx, kwargs) => {
215
+ if (toBool(kwargs.dryRun)) return { cleared: false, dryRun: true };
216
+ ensureConfirmed(kwargs, '清空本地认证文件需要确认', { action: 'auth clear' });
217
+ await clearAuth();
218
+ return { cleared: true };
219
+ },
220
+ });
221
+
222
+ cli({
223
+ name: 'account info',
224
+ description: commandHelp('查看当前 AWB 用户、团队、项目组和积分摘要', {
225
+ examples: ['lj-awb account info'],
226
+ }),
227
+ args: [],
228
+ func: async () => accountInfo(),
229
+ });
230
+
231
+ cli({
232
+ name: 'account teams',
233
+ description: commandHelp('列出当前账号可用团队', {
234
+ examples: ['lj-awb account teams'],
235
+ }),
236
+ args: [],
237
+ func: async () => ({ teams: normalizeRows(await fetchTeams()) }),
238
+ });
239
+
240
+ cli({
241
+ name: 'account switch-team',
242
+ description: commandHelp('切换当前团队;会影响后续项目组和积分上下文', {
243
+ examples: ['lj-awb account switch-team --group-id <groupId> --dry-run', 'lj-awb account switch-team --group-id <groupId> --yes'],
244
+ }),
245
+ args: [
246
+ { name: 'group-id', valueName: 'id', description: '目标团队 groupId' },
247
+ DRY_RUN_ARG,
248
+ YES_ARG,
249
+ ],
250
+ func: async (_ctx, kwargs) => selectTeam(kwargs),
251
+ });
252
+
253
+ cli({
254
+ name: 'project list',
255
+ description: commandHelp('列出项目组;可按名称关键词过滤', {
256
+ examples: ['lj-awb project list', 'lj-awb project list --name "测试"'],
257
+ }),
258
+ args: [
259
+ { name: 'name', valueName: 'keyword', description: '按项目组名称或编号过滤' },
260
+ ],
261
+ func: async (_ctx, kwargs) => listProjectGroups(kwargs),
262
+ });
263
+
264
+ cli({
265
+ name: 'project current',
266
+ description: commandHelp('查看云端当前项目组和项目组预算', {
267
+ examples: ['lj-awb project current'],
268
+ }),
269
+ args: [PROJECT_GROUP_ARG],
270
+ func: async (_ctx, kwargs) => fetchProjectGroupSummary(kwargs.projectGroupNo),
271
+ });
272
+
273
+ cli({
274
+ name: 'project use',
275
+ description: commandHelp('切换当前项目组;后续生成任务会归属到该项目组', {
276
+ examples: ['lj-awb project use --project-group-no <no> --dry-run', 'lj-awb project use --project-group-no <no> --yes'],
277
+ }),
278
+ args: [
279
+ PROJECT_GROUP_ARG,
280
+ DRY_RUN_ARG,
281
+ YES_ARG,
282
+ ],
283
+ func: async (_ctx, kwargs) => selectProjectGroupCommand(kwargs),
284
+ });
285
+
286
+ cli({
287
+ name: 'project users',
288
+ description: commandHelp('列出创建项目组时可选成员', {
289
+ examples: ['lj-awb project users'],
290
+ }),
291
+ args: [],
292
+ func: async () => ({ users: normalizeRows(await fetchProjectGroupUsers()) }),
293
+ });
294
+
295
+ cli({
296
+ name: 'project create',
297
+ description: commandHelp('创建并切换到新项目组', {
298
+ examples: [
299
+ 'lj-awb project create --name "CLI 测试项目" --point 1000 --dry-run',
300
+ 'lj-awb project create --name "CLI 测试项目" --point 1000 --yes',
301
+ ],
302
+ }),
303
+ args: [
304
+ { name: 'name', valueName: 'name', description: '项目组名称' },
305
+ { name: 'point', valueName: 'num', description: '项目组积分上限,默认 0' },
306
+ { name: 'user-ids', valueName: 'ids', description: '额外成员 userId,逗号分隔' },
307
+ { name: 'members-json', valueName: 'json', description: '直接传 groupUser JSON 数组' },
308
+ DRY_RUN_ARG,
309
+ YES_ARG,
310
+ ],
311
+ func: async (_ctx, kwargs) => createProjectGroupCommand(kwargs),
312
+ });
313
+
314
+ cli({
315
+ name: 'project update',
316
+ description: commandHelp('修改项目组名称或积分上限', {
317
+ examples: [
318
+ 'lj-awb project update --project-group-no <no> --point 1000 --dry-run',
319
+ 'lj-awb project update --project-group-no <no> --name "新名称" --yes',
320
+ ],
321
+ }),
322
+ args: [
323
+ PROJECT_GROUP_ARG,
324
+ { name: 'name', valueName: 'name', description: '新的项目组名称' },
325
+ { name: 'point', valueName: 'num', description: '新的项目组积分上限' },
326
+ DRY_RUN_ARG,
327
+ YES_ARG,
328
+ ],
329
+ func: async (_ctx, kwargs) => updateProjectGroupCommand(kwargs),
330
+ });
331
+
332
+ cli({
333
+ name: 'project ensure',
334
+ description: commandHelp('确保项目组存在:先查找同名项目组,找不到再创建', {
335
+ examples: [
336
+ 'lj-awb project ensure --name "短剧项目A" --point 1000 --dry-run',
337
+ 'lj-awb project ensure --name "短剧项目A" --point 1000 --yes',
338
+ ],
339
+ }),
340
+ args: [
341
+ { name: 'name', valueName: 'name', description: '目标项目组名称' },
342
+ { name: 'point', valueName: 'num', description: '不存在时创建的积分上限' },
343
+ { name: 'user-ids', valueName: 'ids', description: '额外成员 userId,逗号分隔' },
344
+ { name: 'members-json', valueName: 'json', description: '直接传 groupUser JSON 数组' },
345
+ DRY_RUN_ARG,
346
+ YES_ARG,
347
+ ],
348
+ func: async (_ctx, kwargs) => ensureProjectGroupCommand(kwargs),
349
+ });
350
+
351
+ cli({
352
+ name: 'credits balance',
353
+ description: commandHelp('查看可扣积分余额和项目组预算', {
354
+ examples: ['lj-awb credits balance', 'lj-awb credits balance --project-group-no <no>'],
355
+ }),
356
+ args: [PROJECT_GROUP_ARG],
357
+ func: async (_ctx, kwargs) => creditsBalance(kwargs),
358
+ });
359
+
360
+ cli({
361
+ name: 'credits usage',
362
+ description: commandHelp('按项目组汇总图片 / 视频任务数与积分消耗', {
363
+ examples: [
364
+ 'lj-awb credits usage --project-group-no <no> --last-hours 24',
365
+ 'lj-awb credits usage --project-group-no <no> --task-types IMAGE_CREATE,VIDEO_GROUP --include-tasks',
366
+ ],
367
+ }),
368
+ args: [
369
+ PROJECT_GROUP_ARG,
370
+ { name: 'task-types', valueName: 'types', description: '任务类型,逗号分隔;默认 IMAGE_CREATE,IMAGE_EDIT,VIDEO_GROUP' },
371
+ { name: 'since', valueName: 'time', description: '开始时间,支持 ISO 或 2026-05-07 00:00:00' },
372
+ { name: 'since-ms', valueName: 'ms', description: '开始时间戳' },
373
+ { name: 'until', valueName: 'time', description: '结束时间' },
374
+ { name: 'last-hours', valueName: 'hours', description: '最近 N 小时' },
375
+ { name: 'page-size', valueName: 'n', description: '每次拉取任务数,最大 200' },
376
+ { name: 'include-tasks', description: 'JSON 中附带扫描到的任务明细' },
377
+ ],
378
+ func: async (_ctx, kwargs) => creditsUsageSummary(kwargs),
379
+ });
380
+
381
+ cli({
382
+ name: 'model image-models',
383
+ description: commandHelp('查询可用生图模型', {
384
+ examples: ['lj-awb model image-models', 'lj-awb model image-models --model Banana', 'lj-awb model image-models --all'],
385
+ }),
386
+ args: MODEL_LIST_ARGS,
387
+ func: async (_ctx, kwargs) => listModels('image', kwargs),
388
+ });
389
+
390
+ cli({
391
+ name: 'model video-models',
392
+ description: commandHelp('查询可用生视频模型', {
393
+ examples: ['lj-awb model video-models', 'lj-awb model video-models --model Seedance', 'lj-awb model video-models --all'],
394
+ }),
395
+ args: MODEL_LIST_ARGS,
396
+ func: async (_ctx, kwargs) => listModels('video', kwargs),
397
+ });
398
+
399
+ cli({
400
+ name: 'model options',
401
+ description: commandHelp('查询指定模型支持的 CLI 参数、枚举值、素材约束和条件约束', {
402
+ examples: [
403
+ 'lj-awb model options --model-group-code <code>',
404
+ 'lj-awb model options --model-group-code <code> --selected-configs-json \'{"quality":"720"}\'',
405
+ ],
406
+ hint: '用 options 判断 values、fileTypes、maxFiles、duration、constraints 等约束;如何组装 create 命令看 model create-spec。',
407
+ }),
408
+ args: [
409
+ MODEL_GROUP_ARG,
410
+ { name: 'selected-configs-json', valueName: 'json', description: '已选参数 JSON,用于联动查询模型选项' },
411
+ INCLUDE_RAW_ARG,
412
+ ],
413
+ func: async (_ctx, kwargs) => modelOptions(kwargs),
414
+ });
415
+
416
+ cli({
417
+ name: 'model create-spec',
418
+ description: commandHelp('查看指定模型如何创建任务:输入模式、素材绑定规则、示例和前置步骤', {
419
+ examples: [
420
+ 'lj-awb model create-spec --model-group-code <code>',
421
+ ],
422
+ hint: '创建前先用 model input-guide 理解统一参数,再用 model options 查具体枚举和素材限制,最后按 create-spec 组装 image create / video create。',
423
+ }),
424
+ args: [
425
+ MODEL_GROUP_ARG,
426
+ INCLUDE_RAW_ARG,
427
+ ],
428
+ func: async (_ctx, kwargs) => modelCreateSpec(kwargs),
429
+ });
430
+
431
+ cli({
432
+ name: 'model input-guide',
433
+ description: commandHelp('查看统一创建参数和 resources 素材绑定的通用说明', {
434
+ examples: [
435
+ 'lj-awb model input-guide',
436
+ ],
437
+ hint: '这是跨模型的统一参数说明;具体枚举值、文件类型、数量和条件约束仍以 model options 为准。',
438
+ }),
439
+ args: [],
440
+ func: async () => modelInputGuide(),
441
+ });
442
+
443
+ cli({
444
+ name: 'upload files',
445
+ description: commandHelp('上传本地图片 / 视频 / 音频到平台 COS,并返回 backendPath / URL', {
446
+ examples: [
447
+ 'lj-awb upload files --files ./a.png,./b.mp4 --dry-run',
448
+ 'lj-awb upload files --file ./ref.png --scene-type material-video-create',
449
+ ],
450
+ hint: '不会暴露 COS 临时密钥;skill 只使用返回的 backendPath / url。',
451
+ }),
452
+ args: [
453
+ { name: 'file', valueName: 'path', description: '单个文件路径' },
454
+ { name: 'files', valueName: 'paths', description: '多个文件路径,逗号分隔' },
455
+ { name: 'files-json', valueName: 'path|json', description: '文件数组 JSON 或 JSON 文件路径' },
456
+ { name: 'scene-type', valueName: 'scene', description: '上传场景,默认 material-image-edit' },
457
+ { name: 'project-no', valueName: 'no', description: '可选项目组编号;不传时读取当前项目组 / 环境变量 / 本地状态' },
458
+ DRY_RUN_ARG,
459
+ ],
460
+ func: async (_ctx, kwargs) => uploadFilesCommand(kwargs),
461
+ });
462
+
463
+ cli({
464
+ name: 'image fee',
465
+ description: commandHelp('计算生图预计积分(必须有 --prompt;参考图通过 --resource 传入)', {
466
+ examples: ['lj-awb image fee --model-group-code <code> --prompt "一只小狗" --ratio 9:16 --quality 2K'],
467
+ hint: '估价前先确认 quality、ratio、generate-num 等会影响积分的关键参数。',
468
+ }),
469
+ args: [
470
+ MODEL_GROUP_ARG,
471
+ PROJECT_GROUP_ARG,
472
+ { name: 'prompt', valueName: 'text', description: '生图提示词;默认原样透传,不润色' },
473
+ { name: 'ratio', valueName: 'ratio', description: '画幅比例,如 9:16' },
474
+ { name: 'quality', valueName: 'quality', description: '清晰度,如 2K' },
475
+ { name: 'generate-num', valueName: 'n', description: '生成张数' },
476
+ RESOURCE_ARG,
477
+ RESOURCES_JSON_ARG,
478
+ FEE_DRY_RUN_ARG,
479
+ ],
480
+ func: async (_ctx, kwargs) => imageFee(kwargs),
481
+ });
482
+
483
+ cli({
484
+ name: 'image create',
485
+ description: commandHelp('提交生图任务;正式执行需要 --yes', {
486
+ examples: [
487
+ 'lj-awb image create --model-group-code <code> --prompt "一只小狗" --dry-run',
488
+ 'lj-awb image create --model-group-code <code> --prompt "参考图里的角色奔跑" --resource image:reference=./ref.png --project-group-no <no> --yes',
489
+ 'lj-awb image create --model-group-code <code> --prompt "产品海报" --resources-json \'[{"type":"image","usage":"reference","source":{"kind":"url","value":"./product.jpg"}}]\' --dry-run',
490
+ ],
491
+ }),
492
+ args: [
493
+ MODEL_GROUP_ARG,
494
+ PROJECT_GROUP_ARG,
495
+ CUSTOM_BIZ_ARG,
496
+ { name: 'prompt', valueName: 'text', description: '生图提示词;默认原样透传,不润色' },
497
+ { name: 'ratio', valueName: 'ratio', description: '画幅比例' },
498
+ { name: 'quality', valueName: 'quality', description: '清晰度' },
499
+ { name: 'generate-num', valueName: 'n', description: '生成张数' },
500
+ RESOURCE_ARG,
501
+ RESOURCES_JSON_ARG,
502
+ TASK_RECORD_ARG,
503
+ ...CREATE_WAIT_ARGS,
504
+ DRY_RUN_ARG,
505
+ YES_ARG,
506
+ ],
507
+ func: async (_ctx, kwargs) => imageCreate(kwargs),
508
+ });
509
+
510
+ cli({
511
+ name: 'image create-batch',
512
+ description: commandHelp('批量提交生图任务;输入支持 JSON / JSONL / 纯文本 prompt(每行一条)', {
513
+ examples: [
514
+ 'lj-awb image create-batch --input-file ./prompts.txt --model-group-code <code> --dry-run',
515
+ 'lj-awb image create-batch --input-file ./image-input.jsonl --model-group-code <code> --concurrency 3 --yes',
516
+ ],
517
+ hint: 'JSON/JSONL 每项只写任务差异字段:prompt、ratio、quality、generate_num、resources、resource、customBizId。',
518
+ }),
519
+ args: [
520
+ { name: 'input-file', valueName: 'path', description: '批量输入文件' },
521
+ MODEL_GROUP_ARG,
522
+ PROJECT_GROUP_ARG,
523
+ { name: 'concurrency', valueName: 'n', description: '并发数,默认 1' },
524
+ TASK_RECORD_ARG,
525
+ DRY_RUN_ARG,
526
+ YES_ARG,
527
+ ],
528
+ func: async (_ctx, kwargs) => imageCreateBatch(kwargs),
529
+ });
530
+
531
+ cli({
532
+ name: 'image status',
533
+ description: commandHelp('查询生图任务状态', {
534
+ examples: ['lj-awb image status --task-id <id>'],
535
+ }),
536
+ args: [
537
+ { name: 'task-id', valueName: 'id', description: '任务 ID' },
538
+ ],
539
+ func: async (_ctx, kwargs) => taskStatus({ ...kwargs, taskType: 'IMAGE_CREATE' }),
540
+ });
541
+
542
+ cli({
543
+ name: 'video fee',
544
+ description: commandHelp('计算生视频预计积分(--prompt / --resource / --resources-json 至少传一个)', {
545
+ examples: ['lj-awb video fee --model-group-code <code> --prompt "雨夜奔跑" --duration 5 --quality 720 --ratio 9:16'],
546
+ hint: '估价前先确认 quality、duration、ratio 等会影响积分的关键参数。',
547
+ }),
548
+ args: [
549
+ MODEL_GROUP_ARG,
550
+ PROJECT_GROUP_ARG,
551
+ { name: 'prompt', valueName: 'text', description: '视频提示词;默认原样透传,不润色(和 --resource / --resources-json 至少传一个)' },
552
+ { name: 'ratio', valueName: 'ratio', description: '视频比例' },
553
+ { name: 'quality', valueName: 'quality', description: '清晰度,如 720' },
554
+ { name: 'duration', valueName: 'seconds', description: '生成时长秒数' },
555
+ { name: 'need-audio', valueName: 'bool', description: '输出音效开关:是否让模型在视频中生成音效(true/false)。不接收音频文件;参考音频请用 --resource audio:reference=...' },
556
+ RESOURCE_ARG,
557
+ RESOURCES_JSON_ARG,
558
+ FEE_DRY_RUN_ARG,
559
+ ],
560
+ func: async (_ctx, kwargs) => videoFee(kwargs),
561
+ });
562
+
563
+ cli({
564
+ name: 'video create',
565
+ description: commandHelp('提交生视频任务;正式执行需要 --yes', {
566
+ examples: [
567
+ 'lj-awb video create --model-group-code <code> --prompt "雨夜奔跑" --dry-run',
568
+ 'lj-awb video create --model-group-code <code> --prompt "镜头推进" --resource image:first_frame=./actor.png --duration 5 --yes',
569
+ 'lj-awb video create --model-group-code <code> --prompt "首尾帧过渡" --duration 5 --resources-json \'[{"type":"image","usage":"first_frame","source":{"kind":"url","value":"./first.png"}},{"type":"image","usage":"last_frame","source":{"kind":"url","value":"./last.png"}}]\' --dry-run',
570
+ 'lj-awb video create --model-group-code <code> --prompt "让 <<<hero>>> 跟随音乐节奏转身" --resource image:reference:hero=./actor.png --resource audio:reference=./music.mp3 --duration 5 --dry-run',
571
+ ],
572
+ hint: '--prompt / --resource / --resources-json 至少传一个;fee / --dry-run 之前先确认 quality、duration、ratio 等关键参数。',
573
+ }),
574
+ args: [
575
+ MODEL_GROUP_ARG,
576
+ PROJECT_GROUP_ARG,
577
+ CUSTOM_BIZ_ARG,
578
+ { name: 'prompt', valueName: 'text', description: '视频提示词;默认原样透传,不润色(和 --resource / --resources-json 至少传一个)' },
579
+ { name: 'ratio', valueName: 'ratio', description: '视频比例' },
580
+ { name: 'quality', valueName: 'quality', description: '清晰度' },
581
+ { name: 'duration', valueName: 'seconds', description: '生成时长秒数' },
582
+ { name: 'need-audio', valueName: 'bool', description: '输出音效开关:是否让模型在视频中生成音效(true/false)。不接收音频文件;参考音频请用 --resource audio:reference=...' },
583
+ RESOURCE_ARG,
584
+ RESOURCES_JSON_ARG,
585
+ TASK_RECORD_ARG,
586
+ ...CREATE_WAIT_ARGS,
587
+ DRY_RUN_ARG,
588
+ YES_ARG,
589
+ ],
590
+ func: async (_ctx, kwargs) => videoCreate(kwargs),
591
+ });
592
+
593
+ cli({
594
+ name: 'video create-batch',
595
+ description: commandHelp('批量提交生视频任务;输入支持 JSON / JSONL / 纯文本 prompt(每行一条)', {
596
+ examples: [
597
+ 'lj-awb video create-batch --input-file ./prompts.txt --model-group-code <code> --dry-run',
598
+ 'lj-awb video create-batch --input-file ./video-input.jsonl --model-group-code <code> --concurrency 2 --yes',
599
+ ],
600
+ hint: 'JSON/JSONL 每项只写任务差异字段:prompt、ratio、quality、duration、need_audio、resources、resource、customBizId。',
601
+ }),
602
+ args: [
603
+ { name: 'input-file', valueName: 'path', description: '批量输入文件' },
604
+ MODEL_GROUP_ARG,
605
+ PROJECT_GROUP_ARG,
606
+ { name: 'concurrency', valueName: 'n', description: '并发数,默认 1' },
607
+ TASK_RECORD_ARG,
608
+ DRY_RUN_ARG,
609
+ YES_ARG,
610
+ ],
611
+ func: async (_ctx, kwargs) => videoCreateBatch(kwargs),
612
+ });
613
+
614
+ cli({
615
+ name: 'video status',
616
+ description: commandHelp('查询生视频任务状态', {
617
+ examples: ['lj-awb video status --task-id <id>'],
618
+ }),
619
+ args: [{ name: 'task-id', valueName: 'id', description: '任务 ID' }],
620
+ func: async (_ctx, kwargs) => taskStatus({ ...kwargs, taskType: 'VIDEO_GROUP' }),
621
+ });
622
+
623
+ cli({
624
+ name: 'video subtitle-remove',
625
+ description: commandHelp('提交视频去字幕 / 去水印任务', {
626
+ examples: ['lj-awb video subtitle-remove --video-url "https://..." --dry-run'],
627
+ hint: '该能力走 asset-edit.lingjingai.cn,适合 Seedance 原始结果后处理。',
628
+ }),
629
+ args: [
630
+ { name: 'video-url', valueName: 'url', description: '待处理视频 URL' },
631
+ { name: 'name', valueName: 'name', description: '可选任务名' },
632
+ { name: 'callback-url', valueName: 'url', description: '可选回调 URL' },
633
+ DRY_RUN_ARG,
634
+ YES_ARG,
635
+ ],
636
+ func: async (_ctx, kwargs) => subtitleRemove(kwargs),
637
+ });
638
+
639
+ cli({
640
+ name: 'video subtitle-status',
641
+ description: commandHelp('查询去字幕 / 去水印任务状态', {
642
+ examples: ['lj-awb video subtitle-status --remote-task-id <id>', 'lj-awb video subtitle-status --public-id <id>'],
643
+ }),
644
+ args: [
645
+ { name: 'remote-task-id', valueName: 'id', description: '远端任务 ID' },
646
+ { name: 'public-id', valueName: 'id', description: '公开任务 ID' },
647
+ ],
648
+ func: async (_ctx, kwargs) => subtitleStatus(kwargs),
649
+ });
650
+
651
+ cli({
652
+ name: 'task list',
653
+ description: commandHelp('查询项目组近期任务流', {
654
+ examples: ['lj-awb task list --task-type IMAGE_CREATE --project-group-no <no>'],
655
+ }),
656
+ args: [
657
+ { name: 'task-type', valueName: 'type', description: 'IMAGE_CREATE / IMAGE_EDIT / VIDEO_GROUP' },
658
+ PROJECT_GROUP_ARG,
659
+ { name: 'page-size', valueName: 'n', description: '每页任务数' },
660
+ { name: 'min-time', valueName: 'ms', description: '任务流 minTime,默认当前时间' },
661
+ ],
662
+ func: async (_ctx, kwargs) => taskList(kwargs),
663
+ });
664
+
665
+ cli({
666
+ name: 'task wait',
667
+ description: commandHelp('等待图片或视频任务进入终态', {
668
+ examples: ['lj-awb task wait --task-id <id> --task-type IMAGE_CREATE --wait-seconds 180'],
669
+ hint: '超时返回 task_still_running / exit code 20,不代表远端失败。',
670
+ }),
671
+ args: [
672
+ { name: 'task-id', valueName: 'id', description: '任务 ID' },
673
+ { name: 'task-type', valueName: 'type', description: 'IMAGE_CREATE / VIDEO_GROUP' },
674
+ PROJECT_GROUP_ARG,
675
+ ...TASK_WAIT_ARGS,
676
+ ],
677
+ func: async (_ctx, kwargs) => waitTask(kwargs),
678
+ });
679
+
680
+ cli({
681
+ name: 'task records',
682
+ description: commandHelp('读取本地任务台账 JSONL', {
683
+ examples: ['lj-awb task records --task-record-file .awb/tasks.jsonl'],
684
+ }),
685
+ args: [
686
+ TASK_RECORD_ARG,
687
+ { name: 'task-type', valueName: 'type', description: '按任务类型过滤' },
688
+ { name: 'pending-only', description: '只看未终态记录' },
689
+ { name: 'limit', valueName: 'n', description: '最多返回条数' },
690
+ ],
691
+ func: async (_ctx, kwargs) => taskRecords(kwargs),
692
+ });
693
+
694
+ cli({
695
+ name: 'task record-poll',
696
+ description: commandHelp('根据本地任务台账轮询任务状态', {
697
+ examples: ['lj-awb task record-poll --task-record-file .awb/tasks.jsonl --wait-seconds 180'],
698
+ }),
699
+ args: [
700
+ TASK_RECORD_ARG,
701
+ { name: 'task-type', valueName: 'type', description: '按任务类型过滤' },
702
+ { name: 'pending-only', description: '只轮询未终态记录,默认 true' },
703
+ ...RECORD_POLL_ARGS,
704
+ ],
705
+ func: async (_ctx, kwargs) => taskRecordPoll(kwargs),
706
+ });
707
+
708
+ cli({
709
+ name: 'asset match-actor',
710
+ description: commandHelp('按描述和标签匹配平台素材库角色候选', {
711
+ examples: ['lj-awb asset match-actor --description "十八岁少女,古风,清冷" --tags-json \'{"gender":"女"}\''],
712
+ }),
713
+ args: [
714
+ { name: 'description', valueName: 'text', description: '角色 / 状态描述' },
715
+ { name: 'tags-json', valueName: 'json', description: '标签 JSON' },
716
+ { name: 'tag-weight', valueName: 'num', description: '标签权重,默认 0.5' },
717
+ { name: 'desc-weight', valueName: 'num', description: '描述权重,默认 0.5' },
718
+ INCLUDE_RAW_ARG,
719
+ ],
720
+ func: async (_ctx, kwargs) => assetMatchActor(kwargs),
721
+ });
722
+
723
+ cli({
724
+ name: 'asset groups',
725
+ description: commandHelp('查询素材组', {
726
+ examples: ['lj-awb asset groups --name "女主"'],
727
+ }),
728
+ args: [
729
+ { name: 'name', valueName: 'keyword', description: '素材组名称关键词' },
730
+ { name: 'group-ids', valueName: 'ids', description: '素材组 ID,逗号分隔' },
731
+ { name: 'page-number', valueName: 'n', description: '页码' },
732
+ { name: 'page-size', valueName: 'n', description: '每页数量' },
733
+ INCLUDE_RAW_ARG,
734
+ ],
735
+ func: async (_ctx, kwargs) => assetGroupList(kwargs),
736
+ });
737
+
738
+ cli({
739
+ name: 'asset group',
740
+ description: commandHelp('查看素材组详情', {
741
+ examples: ['lj-awb asset group --group-id <id>'],
742
+ }),
743
+ args: [{ name: 'group-id', valueName: 'id', description: '素材组 ID' }],
744
+ func: async (_ctx, kwargs) => assetGroupGet(kwargs),
745
+ });
746
+
747
+ cli({
748
+ name: 'asset group-create',
749
+ description: commandHelp('创建素材组(用于素材注册时作为命名空间)', {
750
+ examples: [
751
+ 'lj-awb asset group-create --name "女主素材组" --dry-run',
752
+ 'lj-awb asset group-create --name "女主素材组" --description "用于第二集女主相关素材" --yes',
753
+ ],
754
+ }),
755
+ args: [
756
+ { name: 'name', valueName: 'name', description: '素材组名称' },
757
+ { name: 'description', valueName: 'text', description: '素材组描述' },
758
+ { name: 'project-name', valueName: 'name', description: '项目名,默认 default' },
759
+ DRY_RUN_ARG,
760
+ YES_ARG,
761
+ ],
762
+ func: async (_ctx, kwargs) => assetGroupCreate(kwargs),
763
+ });
764
+
765
+ cli({
766
+ name: 'asset group-update',
767
+ description: commandHelp('更新素材组名称 / 描述', {
768
+ examples: ['lj-awb asset group-update --group-id <id> --name "新名称" --dry-run'],
769
+ }),
770
+ args: [
771
+ { name: 'group-id', valueName: 'id', description: '素材组 ID' },
772
+ { name: 'name', valueName: 'name', description: '新名称' },
773
+ { name: 'description', valueName: 'text', description: '新描述' },
774
+ { name: 'project-name', valueName: 'name', description: '项目名' },
775
+ DRY_RUN_ARG,
776
+ YES_ARG,
777
+ ],
778
+ func: async (_ctx, kwargs) => assetGroupUpdate(kwargs),
779
+ });
780
+
781
+ cli({
782
+ name: 'asset register',
783
+ description: commandHelp('把 COS 路径 / URL / 本地文件注册成平台素材', {
784
+ examples: ['lj-awb asset register --group-id <id> --url "material/upload/a.png" --name "女主正面" --dry-run'],
785
+ }),
786
+ args: [
787
+ { name: 'group-id', valueName: 'id', description: '素材组 ID' },
788
+ { name: 'name', valueName: 'name', description: '素材名称' },
789
+ { name: 'file', valueName: 'path', description: '本地素材文件;会先上传' },
790
+ { name: 'url', valueName: 'url', description: '素材 URL 或 COS 路径' },
791
+ { name: 'backend-path', valueName: 'path', description: 'upload files 返回的 backendPath' },
792
+ { name: 'platform', valueName: 'platform', description: '可选平台字段' },
793
+ DRY_RUN_ARG,
794
+ YES_ARG,
795
+ ],
796
+ func: async (_ctx, kwargs) => assetRegister(kwargs),
797
+ });
798
+
799
+ const ARTIFACT_ROW_KIND_ARG = { name: 'row-kind', valueName: 'kind', description: 'document/asset/state/speaker/episode/scene/action' };
800
+ const ARTIFACT_ENTITY_KEY_ARG = { name: 'entity-key', valueName: 'key', description: '业务唯一键;可包含 / 和 #' };
801
+ const ARTIFACT_EPISODE_ARG = { name: 'episode-id', valueName: 'id', description: '集 ID,例如 ep_001' };
802
+ const ARTIFACT_SCENE_ARG = { name: 'scene-id', valueName: 'id', description: '场 ID,例如 scn_001' };
803
+ const ARTIFACT_CLIP_ARG = { name: 'clip-id', valueName: 'id', description: 'clip ID,例如 clip_001' };
804
+ const ARTIFACT_VIDEO_EPISODE_ID_ARG = { name: 'video-episode-id', valueName: 'id', description: '视频集产物的数字主键,由 artifact video upsert-episode / artifact video episode 返回的 id 字段提供(不是 ep_001 这种业务 ID)' };
805
+ const ARTIFACT_INCLUDE_STATES_ARG = { name: 'include-states', valueName: 'bool', description: '是否带 states,默认 true' };
806
+
807
+ for (const spec of [
808
+ {
809
+ name: 'artifact script get',
810
+ summary: '查询项目完整剧本最终产物',
811
+ examples: ['lj-awb artifact script get --project-id <projectId>'],
812
+ args: [PROJECT_ID_ARG],
813
+ func: artifact.artifactScriptGet,
814
+ },
815
+ {
816
+ name: 'artifact script document',
817
+ summary: '查询剧本 document 根记录',
818
+ examples: ['lj-awb artifact script document --project-id <projectId>'],
819
+ args: [PROJECT_ID_ARG],
820
+ func: artifact.artifactScriptDocument,
821
+ },
822
+ {
823
+ name: 'artifact script rows',
824
+ summary: '查询剧本最终产物行列表',
825
+ examples: [
826
+ 'lj-awb artifact script rows --project-id <projectId> --row-kind scene',
827
+ 'lj-awb artifact script rows --project-id <projectId> --row-kind action --parent-key scn_001',
828
+ ],
829
+ args: [
830
+ PROJECT_ID_ARG,
831
+ ARTIFACT_ROW_KIND_ARG,
832
+ { name: 'parent-key', valueName: 'key', description: '按父节点业务键过滤' },
833
+ { name: 'revision-after', valueName: 'n', description: '只查 revisionNo 大于该值的行' },
834
+ { name: 'include-deleted', valueName: 'bool', description: '是否包含逻辑删除行,默认 false' },
835
+ ],
836
+ func: artifact.artifactScriptRows,
837
+ },
838
+ {
839
+ name: 'artifact script row',
840
+ summary: '查询单条剧本最终产物行',
841
+ examples: ['lj-awb artifact script row --project-id <projectId> --row-kind action --entity-key "ep_001/scn_001#0"'],
842
+ args: [PROJECT_ID_ARG, ARTIFACT_ROW_KIND_ARG, ARTIFACT_ENTITY_KEY_ARG],
843
+ func: artifact.artifactScriptRow,
844
+ },
845
+ {
846
+ name: 'artifact script children',
847
+ summary: '按 parentKey 查询剧本子节点',
848
+ examples: ['lj-awb artifact script children --project-id <projectId> --parent-key scn_001 --row-kind action'],
849
+ args: [
850
+ PROJECT_ID_ARG,
851
+ { name: 'parent-key', valueName: 'key', description: '父节点业务键' },
852
+ ARTIFACT_ROW_KIND_ARG,
853
+ ],
854
+ func: artifact.artifactScriptChildren,
855
+ },
856
+ {
857
+ name: 'artifact script upsert-row',
858
+ summary: '新增或更新单条剧本最终产物行',
859
+ examples: ['lj-awb artifact script upsert-row --project-id <projectId> --input-file ./row.json --dry-run', 'lj-awb artifact script upsert-row --project-id <projectId> --input-file ./row.json --yes'],
860
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
861
+ func: artifact.artifactScriptUpsertRow,
862
+ },
863
+ {
864
+ name: 'artifact script delete-row',
865
+ summary: '逻辑删除单条剧本最终产物行',
866
+ examples: ['lj-awb artifact script delete-row --project-id <projectId> --row-kind action --entity-key "ep_001/scn_001#0" --dry-run', 'lj-awb artifact script delete-row --project-id <projectId> --row-kind action --entity-key "ep_001/scn_001#0" --yes'],
867
+ args: [PROJECT_ID_ARG, ARTIFACT_ROW_KIND_ARG, ARTIFACT_ENTITY_KEY_ARG, ...ARTIFACT_WRITE_ARGS],
868
+ func: artifact.artifactScriptDeleteRow,
869
+ },
870
+ {
871
+ name: 'artifact script import',
872
+ summary: '从本地 script.json 拆分并批量导入剧本最终产物',
873
+ examples: [
874
+ 'lj-awb artifact script import --project-id <projectId> --input-file 1_script/output/script.json --dry-run',
875
+ 'lj-awb artifact script import --project-id <projectId> --input-file output/script.json --yes',
876
+ ],
877
+ args: [
878
+ PROJECT_ID_ARG,
879
+ { name: 'input-file', valueName: 'path', description: 'script.json 路径,默认 output/script.json' },
880
+ ...ARTIFACT_WRITE_ARGS,
881
+ ],
882
+ func: artifact.artifactScriptImport,
883
+ hint: '不传 --input-file 时默认读取当前目录的 output/script.json。',
884
+ },
885
+ ]) {
886
+ cli({
887
+ name: spec.name,
888
+ description: commandHelp(spec.summary, { examples: spec.examples, hint: spec.hint }),
889
+ args: spec.args,
890
+ func: async (_ctx, kwargs) => spec.func(kwargs),
891
+ });
892
+ }
893
+
894
+ for (const spec of [
895
+ {
896
+ name: 'artifact asset get',
897
+ summary: '查询项目完整资产最终产物',
898
+ examples: ['lj-awb artifact asset get --project-id <projectId>'],
899
+ args: [PROJECT_ID_ARG],
900
+ func: artifact.artifactAssetGet,
901
+ },
902
+ {
903
+ name: 'artifact asset actors',
904
+ summary: '查询角色最终产物列表',
905
+ examples: ['lj-awb artifact asset actors --project-id <projectId> --include-states true'],
906
+ args: [PROJECT_ID_ARG, ARTIFACT_INCLUDE_STATES_ARG],
907
+ func: artifact.artifactAssetActors,
908
+ },
909
+ {
910
+ name: 'artifact asset actor',
911
+ summary: '查询单个角色最终产物',
912
+ examples: ['lj-awb artifact asset actor --project-id <projectId> --actor-key act_001'],
913
+ args: [PROJECT_ID_ARG, { name: 'actor-key', valueName: 'key', description: '角色业务键' }, ARTIFACT_INCLUDE_STATES_ARG],
914
+ func: artifact.artifactAssetActor,
915
+ },
916
+ {
917
+ name: 'artifact asset props',
918
+ summary: '查询道具最终产物列表',
919
+ examples: ['lj-awb artifact asset props --project-id <projectId>'],
920
+ args: [PROJECT_ID_ARG, ARTIFACT_INCLUDE_STATES_ARG],
921
+ func: artifact.artifactAssetProps,
922
+ },
923
+ {
924
+ name: 'artifact asset prop',
925
+ summary: '查询单个道具最终产物',
926
+ examples: ['lj-awb artifact asset prop --project-id <projectId> --prop-key prp_001'],
927
+ args: [PROJECT_ID_ARG, { name: 'prop-key', valueName: 'key', description: '道具业务键' }, ARTIFACT_INCLUDE_STATES_ARG],
928
+ func: artifact.artifactAssetProp,
929
+ },
930
+ {
931
+ name: 'artifact asset locations',
932
+ summary: '查询场景最终产物列表',
933
+ examples: ['lj-awb artifact asset locations --project-id <projectId>'],
934
+ args: [PROJECT_ID_ARG, ARTIFACT_INCLUDE_STATES_ARG],
935
+ func: artifact.artifactAssetLocations,
936
+ },
937
+ {
938
+ name: 'artifact asset location',
939
+ summary: '查询单个场景最终产物',
940
+ examples: ['lj-awb artifact asset location --project-id <projectId> --location-key loc_001'],
941
+ args: [PROJECT_ID_ARG, { name: 'location-key', valueName: 'key', description: '场景业务键' }, ARTIFACT_INCLUDE_STATES_ARG],
942
+ func: artifact.artifactAssetLocation,
943
+ },
944
+ {
945
+ name: 'artifact asset upsert-actor',
946
+ summary: '新增或更新角色最终产物',
947
+ examples: ['lj-awb artifact asset upsert-actor --project-id <projectId> --input-file ./actor.json --dry-run', 'lj-awb artifact asset upsert-actor --project-id <projectId> --input-file ./actor.json --yes'],
948
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
949
+ func: artifact.artifactAssetUpsertActor,
950
+ },
951
+ {
952
+ name: 'artifact asset upsert-prop',
953
+ summary: '新增或更新道具最终产物',
954
+ examples: ['lj-awb artifact asset upsert-prop --project-id <projectId> --input-file ./prop.json --dry-run', 'lj-awb artifact asset upsert-prop --project-id <projectId> --input-file ./prop.json --yes'],
955
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
956
+ func: artifact.artifactAssetUpsertProp,
957
+ },
958
+ {
959
+ name: 'artifact asset upsert-location',
960
+ summary: '新增或更新场景最终产物',
961
+ examples: ['lj-awb artifact asset upsert-location --project-id <projectId> --input-file ./location.json --dry-run', 'lj-awb artifact asset upsert-location --project-id <projectId> --input-file ./location.json --yes'],
962
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
963
+ func: artifact.artifactAssetUpsertLocation,
964
+ },
965
+ {
966
+ name: 'artifact asset upsert-actor-state',
967
+ summary: '新增或更新角色状态最终产物',
968
+ examples: ['lj-awb artifact asset upsert-actor-state --project-id <projectId> --actor-key act_001 --input-file ./state.json --dry-run', 'lj-awb artifact asset upsert-actor-state --project-id <projectId> --actor-key act_001 --input-file ./state.json --yes'],
969
+ args: [PROJECT_ID_ARG, { name: 'actor-key', valueName: 'key', description: '角色业务键' }, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
970
+ func: artifact.artifactAssetUpsertActorState,
971
+ },
972
+ {
973
+ name: 'artifact asset upsert-prop-state',
974
+ summary: '新增或更新道具状态最终产物',
975
+ examples: ['lj-awb artifact asset upsert-prop-state --project-id <projectId> --prop-key prp_001 --input-file ./state.json --dry-run', 'lj-awb artifact asset upsert-prop-state --project-id <projectId> --prop-key prp_001 --input-file ./state.json --yes'],
976
+ args: [PROJECT_ID_ARG, { name: 'prop-key', valueName: 'key', description: '道具业务键' }, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
977
+ func: artifact.artifactAssetUpsertPropState,
978
+ },
979
+ {
980
+ name: 'artifact asset upsert-location-state',
981
+ summary: '新增或更新场景状态最终产物',
982
+ examples: ['lj-awb artifact asset upsert-location-state --project-id <projectId> --location-key loc_001 --input-file ./state.json --dry-run', 'lj-awb artifact asset upsert-location-state --project-id <projectId> --location-key loc_001 --input-file ./state.json --yes'],
983
+ args: [PROJECT_ID_ARG, { name: 'location-key', valueName: 'key', description: '场景业务键' }, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
984
+ func: artifact.artifactAssetUpsertLocationState,
985
+ },
986
+ {
987
+ name: 'artifact asset delete-actor',
988
+ summary: '逻辑删除角色及其状态最终产物',
989
+ examples: ['lj-awb artifact asset delete-actor --project-id <projectId> --actor-key act_001 --dry-run', 'lj-awb artifact asset delete-actor --project-id <projectId> --actor-key act_001 --yes'],
990
+ args: [PROJECT_ID_ARG, { name: 'actor-key', valueName: 'key', description: '角色业务键' }, ...ARTIFACT_WRITE_ARGS],
991
+ func: artifact.artifactAssetDeleteActor,
992
+ },
993
+ {
994
+ name: 'artifact asset delete-prop',
995
+ summary: '逻辑删除道具及其状态最终产物',
996
+ examples: ['lj-awb artifact asset delete-prop --project-id <projectId> --prop-key prp_001 --dry-run', 'lj-awb artifact asset delete-prop --project-id <projectId> --prop-key prp_001 --yes'],
997
+ args: [PROJECT_ID_ARG, { name: 'prop-key', valueName: 'key', description: '道具业务键' }, ...ARTIFACT_WRITE_ARGS],
998
+ func: artifact.artifactAssetDeleteProp,
999
+ },
1000
+ {
1001
+ name: 'artifact asset delete-location',
1002
+ summary: '逻辑删除场景及其状态最终产物',
1003
+ examples: ['lj-awb artifact asset delete-location --project-id <projectId> --location-key loc_001 --dry-run', 'lj-awb artifact asset delete-location --project-id <projectId> --location-key loc_001 --yes'],
1004
+ args: [PROJECT_ID_ARG, { name: 'location-key', valueName: 'key', description: '场景业务键' }, ...ARTIFACT_WRITE_ARGS],
1005
+ func: artifact.artifactAssetDeleteLocation,
1006
+ },
1007
+ {
1008
+ name: 'artifact asset delete-actor-state',
1009
+ summary: '逻辑删除角色状态最终产物',
1010
+ examples: ['lj-awb artifact asset delete-actor-state --project-id <projectId> --actor-key act_001 --state-key default --dry-run', 'lj-awb artifact asset delete-actor-state --project-id <projectId> --actor-key act_001 --state-key default --yes'],
1011
+ args: [PROJECT_ID_ARG, { name: 'actor-key', valueName: 'key', description: '角色业务键' }, { name: 'state-key', valueName: 'key', description: '状态业务键' }, ...ARTIFACT_WRITE_ARGS],
1012
+ func: artifact.artifactAssetDeleteActorState,
1013
+ },
1014
+ {
1015
+ name: 'artifact asset delete-prop-state',
1016
+ summary: '逻辑删除道具状态最终产物',
1017
+ examples: ['lj-awb artifact asset delete-prop-state --project-id <projectId> --prop-key prp_001 --state-key default --dry-run', 'lj-awb artifact asset delete-prop-state --project-id <projectId> --prop-key prp_001 --state-key default --yes'],
1018
+ args: [PROJECT_ID_ARG, { name: 'prop-key', valueName: 'key', description: '道具业务键' }, { name: 'state-key', valueName: 'key', description: '状态业务键' }, ...ARTIFACT_WRITE_ARGS],
1019
+ func: artifact.artifactAssetDeletePropState,
1020
+ },
1021
+ {
1022
+ name: 'artifact asset delete-location-state',
1023
+ summary: '逻辑删除场景状态最终产物',
1024
+ examples: ['lj-awb artifact asset delete-location-state --project-id <projectId> --location-key loc_001 --state-key default --dry-run', 'lj-awb artifact asset delete-location-state --project-id <projectId> --location-key loc_001 --state-key default --yes'],
1025
+ args: [PROJECT_ID_ARG, { name: 'location-key', valueName: 'key', description: '场景业务键' }, { name: 'state-key', valueName: 'key', description: '状态业务键' }, ...ARTIFACT_WRITE_ARGS],
1026
+ func: artifact.artifactAssetDeleteLocationState,
1027
+ },
1028
+ {
1029
+ name: 'artifact asset import',
1030
+ summary: '从本地 actors/props/locations JSON 批量导入资产最终产物',
1031
+ examples: [
1032
+ 'lj-awb artifact asset import --project-id <projectId> --input-dir 2_asset/output --dry-run',
1033
+ 'lj-awb artifact asset import --project-id <projectId> --actors-file output/actors/actors.json --props-file output/props/props.json --locations-file output/locations/locations.json --yes',
1034
+ ],
1035
+ args: [
1036
+ PROJECT_ID_ARG,
1037
+ { name: 'input-dir', valueName: 'path', description: '资产 output 目录,默认 output' },
1038
+ { name: 'actors-file', valueName: 'path', description: '可选 actors.json 路径' },
1039
+ { name: 'props-file', valueName: 'path', description: '可选 props.json 路径' },
1040
+ { name: 'locations-file', valueName: 'path', description: '可选 locations.json 路径' },
1041
+ ...ARTIFACT_WRITE_ARGS,
1042
+ ],
1043
+ func: artifact.artifactAssetImport,
1044
+ hint: '默认在 --input-dir 下查找 actors/actors.json、props/props.json、locations/locations.json;也兼容 actors.json 等扁平文件名。',
1045
+ },
1046
+ ]) {
1047
+ cli({
1048
+ name: spec.name,
1049
+ description: commandHelp(spec.summary, { examples: spec.examples, hint: spec.hint }),
1050
+ args: spec.args,
1051
+ func: async (_ctx, kwargs) => spec.func(kwargs),
1052
+ });
1053
+ }
1054
+
1055
+ for (const spec of [
1056
+ {
1057
+ name: 'artifact video get',
1058
+ summary: '查询项目完整视频最终产物',
1059
+ examples: ['lj-awb artifact video get --project-id <projectId>'],
1060
+ args: [PROJECT_ID_ARG],
1061
+ func: artifact.artifactVideoGet,
1062
+ },
1063
+ {
1064
+ name: 'artifact video episodes',
1065
+ summary: '查询视频集列表',
1066
+ examples: ['lj-awb artifact video episodes --project-id <projectId>'],
1067
+ args: [PROJECT_ID_ARG],
1068
+ func: artifact.artifactVideoEpisodes,
1069
+ },
1070
+ {
1071
+ name: 'artifact video episode',
1072
+ summary: '查询单集视频最终产物',
1073
+ examples: ['lj-awb artifact video episode --project-id <projectId> --episode-id ep_001'],
1074
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG],
1075
+ func: artifact.artifactVideoEpisode,
1076
+ },
1077
+ {
1078
+ name: 'artifact video scenes',
1079
+ summary: '查询某集下的场列表',
1080
+ examples: ['lj-awb artifact video scenes --project-id <projectId> --episode-id ep_001'],
1081
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG],
1082
+ func: artifact.artifactVideoScenes,
1083
+ },
1084
+ {
1085
+ name: 'artifact video scene',
1086
+ summary: '查询单场视频最终产物',
1087
+ examples: ['lj-awb artifact video scene --project-id <projectId> --episode-id ep_001 --scene-id scn_001'],
1088
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ARTIFACT_SCENE_ARG],
1089
+ func: artifact.artifactVideoScene,
1090
+ },
1091
+ {
1092
+ name: 'artifact video clips',
1093
+ summary: '查询某场下的 clip 列表',
1094
+ examples: ['lj-awb artifact video clips --project-id <projectId> --episode-id ep_001 --scene-id scn_001'],
1095
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ARTIFACT_SCENE_ARG],
1096
+ func: artifact.artifactVideoClips,
1097
+ },
1098
+ {
1099
+ name: 'artifact video clip',
1100
+ summary: '查询单个 clip 视频最终产物',
1101
+ examples: ['lj-awb artifact video clip --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --clip-id clip_001'],
1102
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ARTIFACT_SCENE_ARG, ARTIFACT_CLIP_ARG],
1103
+ func: artifact.artifactVideoClip,
1104
+ },
1105
+ {
1106
+ name: 'artifact video upsert-episode',
1107
+ summary: '新增或更新视频 episode 最终产物',
1108
+ examples: ['lj-awb artifact video upsert-episode --project-id <projectId> --input-file ./episode.json --dry-run', 'lj-awb artifact video upsert-episode --project-id <projectId> --input-file ./episode.json --yes'],
1109
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
1110
+ func: artifact.artifactVideoUpsertEpisode,
1111
+ },
1112
+ {
1113
+ name: 'artifact video upsert-scene',
1114
+ summary: '新增或更新视频 scene 最终产物',
1115
+ examples: ['lj-awb artifact video upsert-scene --project-id <projectId> --episode-id ep_001 --input-file ./scene.json --dry-run', 'lj-awb artifact video upsert-scene --project-id <projectId> --episode-id ep_001 --input-file ./scene.json --yes'],
1116
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
1117
+ func: artifact.artifactVideoUpsertScene,
1118
+ },
1119
+ {
1120
+ name: 'artifact video upsert-clip',
1121
+ summary: '新增或更新视频 clip 最终产物',
1122
+ examples: ['lj-awb artifact video upsert-clip --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --input-file ./clip.json --dry-run', 'lj-awb artifact video upsert-clip --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --input-file ./clip.json --yes'],
1123
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ARTIFACT_SCENE_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
1124
+ func: artifact.artifactVideoUpsertClip,
1125
+ },
1126
+ {
1127
+ name: 'artifact video update-clip-urls',
1128
+ summary: '只回写 clip.videoUrls,不覆盖 payload / prompt / duration',
1129
+ examples: [
1130
+ 'lj-awb artifact video update-clip-urls --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --clip-id clip_001 --video-urls-json \'["https://example.com/a.mp4"]\' --dry-run',
1131
+ 'lj-awb artifact video update-clip-urls --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --clip-id clip_001 --video-urls-json \'["https://example.com/a.mp4"]\' --yes',
1132
+ ],
1133
+ args: [
1134
+ PROJECT_ID_ARG,
1135
+ ARTIFACT_EPISODE_ARG,
1136
+ ARTIFACT_SCENE_ARG,
1137
+ ARTIFACT_CLIP_ARG,
1138
+ { name: 'video-urls-json', valueName: 'json', description: '视频 URL 数组 JSON' },
1139
+ ...ARTIFACT_WRITE_ARGS,
1140
+ ],
1141
+ func: artifact.artifactVideoUpdateClipVideoUrls,
1142
+ },
1143
+ {
1144
+ name: 'artifact video delete-episode',
1145
+ summary: '逻辑删除视频 episode,并级联删除该集 scenes/clips',
1146
+ examples: ['lj-awb artifact video delete-episode --project-id <projectId> --episode-id ep_001 --dry-run', 'lj-awb artifact video delete-episode --project-id <projectId> --episode-id ep_001 --yes'],
1147
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ...ARTIFACT_WRITE_ARGS],
1148
+ func: artifact.artifactVideoDeleteEpisode,
1149
+ },
1150
+ {
1151
+ name: 'artifact video delete-scene',
1152
+ summary: '逻辑删除视频 scene,并级联删除该场 clips',
1153
+ examples: ['lj-awb artifact video delete-scene --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --dry-run', 'lj-awb artifact video delete-scene --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --yes'],
1154
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ARTIFACT_SCENE_ARG, ...ARTIFACT_WRITE_ARGS],
1155
+ func: artifact.artifactVideoDeleteScene,
1156
+ },
1157
+ {
1158
+ name: 'artifact video delete-clip',
1159
+ summary: '逻辑删除视频 clip',
1160
+ examples: ['lj-awb artifact video delete-clip --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --clip-id clip_001 --dry-run', 'lj-awb artifact video delete-clip --project-id <projectId> --episode-id ep_001 --scene-id scn_001 --clip-id clip_001 --yes'],
1161
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG, ARTIFACT_SCENE_ARG, ARTIFACT_CLIP_ARG, ...ARTIFACT_WRITE_ARGS],
1162
+ func: artifact.artifactVideoDeleteClip,
1163
+ },
1164
+ {
1165
+ name: 'artifact video import-storyboard',
1166
+ summary: '从 epXXX_storyboard.json 拆分 episode/scenes/clips 并导入视频最终产物',
1167
+ examples: [
1168
+ 'lj-awb artifact video import-storyboard --project-id <projectId> --input-file 3_footage/output/ep001/ep001_storyboard.json --dry-run',
1169
+ 'lj-awb artifact video import-storyboard --project-id <projectId> --input-file output/ep001/ep001_storyboard.json --yes',
1170
+ ],
1171
+ args: [PROJECT_ID_ARG, { name: 'input-file', valueName: 'path', description: 'epXXX_storyboard.json 路径' }, ...ARTIFACT_WRITE_ARGS],
1172
+ func: artifact.artifactVideoImportStoryboard,
1173
+ },
1174
+ ]) {
1175
+ cli({
1176
+ name: spec.name,
1177
+ description: commandHelp(spec.summary, { examples: spec.examples }),
1178
+ args: spec.args,
1179
+ func: async (_ctx, kwargs) => spec.func(kwargs),
1180
+ });
1181
+ }
1182
+
1183
+ for (const spec of [
1184
+ {
1185
+ name: 'artifact clip get',
1186
+ summary: '查询项目完整剪辑最终产物',
1187
+ examples: ['lj-awb artifact clip get --project-id <projectId>'],
1188
+ args: [PROJECT_ID_ARG],
1189
+ func: artifact.artifactClipGet,
1190
+ },
1191
+ {
1192
+ name: 'artifact clip episodes',
1193
+ summary: '查询项目剪辑产物列表',
1194
+ examples: ['lj-awb artifact clip episodes --project-id <projectId>'],
1195
+ args: [PROJECT_ID_ARG],
1196
+ func: artifact.artifactClipEpisodes,
1197
+ },
1198
+ {
1199
+ name: 'artifact clip episode',
1200
+ summary: '按 videoEpisodeId 查询单集剪辑产物',
1201
+ examples: ['lj-awb artifact clip episode --project-id <projectId> --video-episode-id 101'],
1202
+ args: [PROJECT_ID_ARG, ARTIFACT_VIDEO_EPISODE_ID_ARG],
1203
+ func: artifact.artifactClipEpisode,
1204
+ },
1205
+ {
1206
+ name: 'artifact clip episode-by-id',
1207
+ summary: '按业务 episodeId 查询单集剪辑产物',
1208
+ examples: ['lj-awb artifact clip episode-by-id --project-id <projectId> --episode-id ep_001'],
1209
+ args: [PROJECT_ID_ARG, ARTIFACT_EPISODE_ARG],
1210
+ func: artifact.artifactClipEpisodeById,
1211
+ },
1212
+ {
1213
+ name: 'artifact clip upsert-episode',
1214
+ summary: '新增或更新单集剪辑最终产物',
1215
+ examples: ['lj-awb artifact clip upsert-episode --project-id <projectId> --input-file ./clip-output.json --dry-run', 'lj-awb artifact clip upsert-episode --project-id <projectId> --input-file ./clip-output.json --yes'],
1216
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
1217
+ func: artifact.artifactClipUpsertEpisode,
1218
+ },
1219
+ {
1220
+ name: 'artifact clip upsert-batch',
1221
+ summary: '批量新增或更新剪辑最终产物',
1222
+ examples: ['lj-awb artifact clip upsert-batch --project-id <projectId> --input-file ./clip-output-list.json --dry-run', 'lj-awb artifact clip upsert-batch --project-id <projectId> --input-file ./clip-output-list.json --yes'],
1223
+ args: [PROJECT_ID_ARG, ...BODY_INPUT_ARGS, ...ARTIFACT_WRITE_ARGS],
1224
+ func: artifact.artifactClipUpsertBatch,
1225
+ },
1226
+ {
1227
+ name: 'artifact clip update-status',
1228
+ summary: '只更新剪辑产物 status;messages 非空时覆盖',
1229
+ examples: ['lj-awb artifact clip update-status --project-id <projectId> --video-episode-id 101 --status music_success --messages-json \'[{"level":"info","step":"music","message":"配乐完成"}]\' --dry-run'],
1230
+ args: [
1231
+ PROJECT_ID_ARG,
1232
+ ARTIFACT_VIDEO_EPISODE_ID_ARG,
1233
+ { name: 'status', valueName: 'status', description: '剪辑阶段状态,例如 concat_success / music_success / burn_success' },
1234
+ { name: 'messages-json', valueName: 'json', description: '可选 messages 数组 JSON;传入时覆盖原 messages' },
1235
+ ...ARTIFACT_WRITE_ARGS,
1236
+ ],
1237
+ func: artifact.artifactClipUpdateStatus,
1238
+ },
1239
+ {
1240
+ name: 'artifact clip delete-episode',
1241
+ summary: '逻辑删除单集剪辑最终产物',
1242
+ examples: ['lj-awb artifact clip delete-episode --project-id <projectId> --video-episode-id 101 --dry-run', 'lj-awb artifact clip delete-episode --project-id <projectId> --video-episode-id 101 --yes'],
1243
+ args: [PROJECT_ID_ARG, ARTIFACT_VIDEO_EPISODE_ID_ARG, ...ARTIFACT_WRITE_ARGS],
1244
+ func: artifact.artifactClipDeleteEpisode,
1245
+ },
1246
+ ]) {
1247
+ cli({
1248
+ name: spec.name,
1249
+ description: commandHelp(spec.summary, { examples: spec.examples }),
1250
+ args: spec.args,
1251
+ func: async (_ctx, kwargs) => spec.func(kwargs),
1252
+ });
1253
+ }
1254
+
1255
+ cli({
1256
+ name: 'subject list',
1257
+ description: commandHelp('查询可用于视频主体参考的元素 / 主体', {
1258
+ examples: ['lj-awb subject list --name "女主"'],
1259
+ }),
1260
+ args: [
1261
+ { name: 'name', valueName: 'keyword', description: '主体名称关键词' },
1262
+ { name: 'page-number', valueName: 'n', description: '页码' },
1263
+ { name: 'page-size', valueName: 'n', description: '每页数量' },
1264
+ INCLUDE_RAW_ARG,
1265
+ ],
1266
+ func: async (_ctx, kwargs) => subjectList(kwargs),
1267
+ });
1268
+
1269
+ cli({
1270
+ name: 'subject publish',
1271
+ description: commandHelp('创建平台主体 element,用于 KeLing / 可灵 视频主体参考', {
1272
+ examples: [
1273
+ 'lj-awb subject publish --name 女主 --resource primary:./three-view.png --dry-run',
1274
+ 'lj-awb subject publish --name 女主 --resource primary:material/assets/a.png --resource face:./face.png --yes',
1275
+ ],
1276
+ hint: '参考图 slot:primary | three-view | face | side | back。无 primary 时 three-view 自动升为主图;本地路径(./ 或绝对路径)会自动上传,含 :// 或 material/ 前缀的视为已上传 URL。',
1277
+ }),
1278
+ args: [
1279
+ { name: 'name', valueName: 'name', description: '主体名称' },
1280
+ { name: 'description', valueName: 'text', description: '主体描述' },
1281
+ { name: 'model-code', valueName: 'code', description: '主体创建模型编码,可选' },
1282
+ { name: 'reference-type', valueName: 'type', description: '参考类型,默认 image_refer' },
1283
+ { name: 'voice-id', valueName: 'id', description: '参考音色 ID,可选' },
1284
+ { name: 'tags-json', valueName: 'json', description: '标签数组,例如 [{"tagId":"o_102"}]' },
1285
+ { name: 'resource', valueName: 'spec', description: '参考图 <slot>:<file|url>,可重复;slot ∈ primary | three-view | face | side | back' },
1286
+ DRY_RUN_ARG,
1287
+ YES_ARG,
1288
+ ],
1289
+ func: async (_ctx, kwargs) => subjectPublish(kwargs),
1290
+ });
1291
+
1292
+ cli({
1293
+ name: 'subject wait',
1294
+ description: commandHelp('等待主体 externalId 回填,用于后续视频主体参考', {
1295
+ examples: ['lj-awb subject wait --element-id <elementId> --wait-seconds 300'],
1296
+ }),
1297
+ args: [
1298
+ { name: 'element-id', valueName: 'id', description: 'subject publish 返回的 elementId' },
1299
+ ...SUBJECT_WAIT_ARGS,
1300
+ ],
1301
+ func: async (_ctx, kwargs) => subjectWait(kwargs),
1302
+ });
1303
+
1304
+ cli({
1305
+ name: 'subject publish-batch',
1306
+ description: commandHelp('批量发布主体资产;输入支持 JSON 数组文件或 JSONL(每行一条)', {
1307
+ examples: [
1308
+ 'lj-awb subject publish-batch --input-file ./subjects.jsonl --dry-run',
1309
+ 'lj-awb subject publish-batch --input-file ./subjects.json --concurrency 2 --yes',
1310
+ ],
1311
+ }),
1312
+ args: [
1313
+ { name: 'input-file', valueName: 'path', description: '批量输入文件' },
1314
+ { name: 'project-name', valueName: 'name', description: '默认项目名' },
1315
+ { name: 'concurrency', valueName: 'n', description: '并发数,默认 1' },
1316
+ DRY_RUN_ARG,
1317
+ YES_ARG,
1318
+ ],
1319
+ func: async (_ctx, kwargs) => subjectPublishBatch(kwargs),
1320
+ });
1321
+ }