@lingjingai/lj-awb-cli-pre 0.3.18 → 0.4.5

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 (35) hide show
  1. package/README.md +57 -8
  2. package/build/_shared.mjs +54 -5
  3. package/build/prod.mjs +12 -3
  4. package/package.json +6 -2
  5. package/packages/awb-cli/package.json +2 -2
  6. package/packages/awb-core/package.json +6 -2
  7. package/packages/awb-core/src/api.js +22 -0
  8. package/packages/awb-core/src/commands.js +112 -39
  9. package/packages/awb-core/src/common.js +8 -0
  10. package/packages/awb-core/src/output.js +2030 -8
  11. package/packages/awb-core/src/services.js +1835 -205
  12. package/packages/awb-core/src/standalone.js +472 -136
  13. package/packages/awb-core/src/update.js +327 -0
  14. package/skills/lj-awb/SKILL.md +35 -12
  15. package/skills/lj-awb/VERSION +1 -1
  16. package/skills/lj-awb/compat.json +3 -3
  17. package/skills/lj-awb/modules/artifact/asset.md +1 -1
  18. package/skills/lj-awb/modules/artifact/clip.md +1 -1
  19. package/skills/lj-awb/modules/artifact/script.md +1 -1
  20. package/skills/lj-awb/modules/artifact/video.md +1 -1
  21. package/skills/lj-awb/modules/asset.md +10 -1
  22. package/skills/lj-awb/modules/auth.md +9 -1
  23. package/skills/lj-awb/modules/create-contract.md +5 -2
  24. package/skills/lj-awb/modules/create.md +4 -2
  25. package/skills/lj-awb/modules/driver.md +12 -6
  26. package/skills/lj-awb/modules/image.md +3 -1
  27. package/skills/lj-awb/modules/model.md +12 -9
  28. package/skills/lj-awb/modules/task.md +4 -1
  29. package/skills/lj-awb/modules/upload.md +1 -1
  30. package/skills/lj-awb/modules/video.md +11 -2
  31. package/skills/lj-awb/modules/workflows.md +3 -1
  32. package/skills/lj-awb/references/error-codes.md +24 -0
  33. package/skills/lj-awb/references/model-options-read.md +16 -10
  34. package/skills/lj-awb/references/output-fields.md +10 -7
  35. package/skills/lj-awb/scripts/resolve-lj-awb-cmd.sh +106 -4
@@ -3,7 +3,16 @@ import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { LingjingAwbCliError, createEnvelope, toBool, toInt } from './common.js';
5
5
  import { registerAwbCommands } from './commands.js';
6
- import { formatTextError, formatTextOutput, normalizeJsonData } from './output.js';
6
+ import { checkCliUpdate } from './update.js';
7
+ import {
8
+ formatCsvOutput,
9
+ formatPrettyError,
10
+ formatPrettyOutput,
11
+ formatTextError,
12
+ formatTextOutput,
13
+ formatYamlEnvelope,
14
+ normalizeJsonData,
15
+ } from './output.js';
7
16
 
8
17
  function normalizeKey(value) {
9
18
  return String(value || '').replace(/-([a-z])/g, (_, char) => char.toUpperCase());
@@ -96,6 +105,57 @@ function renderCliText(value) {
96
105
  return String(value ?? '').replace(/\blj-awb\b/g, commandPrefix());
97
106
  }
98
107
 
108
+ function shouldColorOutput() {
109
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === '0') return false;
110
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === 'false') return false;
111
+ if (String(process.env.FORCE_COLOR || '').trim() && process.env.FORCE_COLOR !== '0') return true;
112
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === '1') return true;
113
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === 'true') return true;
114
+ if (process.env.NO_COLOR) return false;
115
+ return Boolean(process.stdout.isTTY);
116
+ }
117
+
118
+ function colorText(value, code) {
119
+ const text = String(value ?? '');
120
+ return shouldColorOutput() && text ? `\u001b[${code}m${text}\u001b[0m` : text;
121
+ }
122
+
123
+ const HELP_COLOR = {
124
+ title: '1;38;5;39',
125
+ accent: '1;38;5;208',
126
+ heading: '1;38;5;75',
127
+ command: '38;5;82',
128
+ group: '38;5;215',
129
+ muted: '2;38;5;245',
130
+ option: '38;5;111',
131
+ };
132
+
133
+ function helpTitle(value) {
134
+ if (!shouldColorOutput()) return String(value ?? '');
135
+ return `${colorText('◆', HELP_COLOR.accent)} ${colorText(value, HELP_COLOR.title)}`;
136
+ }
137
+
138
+ function helpHeading(value) {
139
+ if (!shouldColorOutput()) return String(value ?? '');
140
+ return `${colorText('▸', HELP_COLOR.accent)} ${colorText(value, HELP_COLOR.heading)}`;
141
+ }
142
+
143
+ function helpCommand(value) {
144
+ return colorText(value, HELP_COLOR.command);
145
+ }
146
+
147
+ function helpGroup(value) {
148
+ return colorText(value, HELP_COLOR.group);
149
+ }
150
+
151
+ function helpMuted(value) {
152
+ return colorText(value, HELP_COLOR.muted);
153
+ }
154
+
155
+ function helpOption(value) {
156
+ return colorText(value, HELP_COLOR.option);
157
+ }
158
+
99
159
  function shellArg(value) {
100
160
  const text = String(value ?? '');
101
161
  return /^[A-Za-z0-9_./:=@,+-]+$/.test(text) ? text : JSON.stringify(text);
@@ -127,10 +187,14 @@ function commandInvocation(commandName, kwargs = {}, options = {}) {
127
187
  return parts.join(' ');
128
188
  }
129
189
 
130
- function buildOutputContext(command, kwargs = {}) {
190
+ function buildOutputContext(command, kwargs = {}, data = {}) {
131
191
  const optionKeys = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
132
192
  const canSuggestDryRunExecution = command.name !== 'auth login';
133
193
  const withoutDryRun = { omit: ['dryRun'] };
194
+ const confirmSet = { yes: true };
195
+ if (command.name === 'create asset' && data?.conversionPlan && !toBool(kwargs.autoConvert)) {
196
+ confirmSet.autoConvert = true;
197
+ }
134
198
  const isModelList = command.name === 'model image-models' || command.name === 'model video-models';
135
199
  const modelListAll = isModelList && toBool(kwargs.all);
136
200
  const modelListLimit = isModelList
@@ -141,14 +205,53 @@ function buildOutputContext(command, kwargs = {}) {
141
205
  : null;
142
206
  return {
143
207
  jsonCommand: `${commandInvocation(command.name, kwargs)} -f json`,
208
+ textCommand: `${commandInvocation(command.name, kwargs)} -f text`,
209
+ outputKind: buildCommandWorkflow(command).outputKind,
144
210
  executeCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) ? commandInvocation(command.name, kwargs, withoutDryRun) : null,
145
211
  confirmCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) && optionKeys.has('yes')
146
- ? commandInvocation(command.name, kwargs, { omit: ['dryRun'], set: { yes: true } })
212
+ ? commandInvocation(command.name, kwargs, { omit: ['dryRun'], set: confirmSet })
147
213
  : null,
148
214
  ...(isModelList ? { listLimit: modelListLimit, moreCommand: modelListMoreCommand } : {}),
149
215
  };
150
216
  }
151
217
 
218
+ function updateCheckDisabledByEnv() {
219
+ return ['1', 'true', 'yes', 'on'].includes(String(process.env.LINGJING_AWB_DISABLE_UPDATE_CHECK || '').trim().toLowerCase())
220
+ || ['1', 'true', 'yes', 'on'].includes(String(process.env.AWB_DISABLE_UPDATE_CHECK || '').trim().toLowerCase());
221
+ }
222
+
223
+ function shouldSkipUpdateCheck(argv = [], commandName = '') {
224
+ if (updateCheckDisabledByEnv()) return true;
225
+ return commandName === 'update';
226
+ }
227
+
228
+ async function resolveUpdateNotice(argv = [], commandName = '', version = 'unknown') {
229
+ if (shouldSkipUpdateCheck(argv, commandName)) return null;
230
+ return await checkCliUpdate({ currentVersion: version }).catch(() => null);
231
+ }
232
+
233
+ const OUTPUT_FORMAT_ALIASES = {
234
+ agent: 'text',
235
+ table: 'pretty',
236
+ human: 'pretty',
237
+ compact: 'text',
238
+ 'compact-text': 'text',
239
+ };
240
+
241
+ const SUPPORTED_OUTPUT_FORMATS = new Set(['auto', 'pretty', 'text', 'json', 'yaml', 'csv']);
242
+
243
+ function resolveOutputFormat(format) {
244
+ const raw = String(format || 'pretty').trim().toLowerCase();
245
+ const normalized = OUTPUT_FORMAT_ALIASES[raw] || raw;
246
+ if (normalized === 'auto') return process.stdout.isTTY ? 'pretty' : 'text';
247
+ if (SUPPORTED_OUTPUT_FORMATS.has(normalized)) return normalized;
248
+ throw new LingjingAwbCliError(`未知输出格式:${format}`, {
249
+ type: 'argument_error',
250
+ exitCode: 2,
251
+ hint: '可用格式:pretty、text、json、yaml、csv、auto。Agent 建议使用 -f text;严格脚本解析再用 -f json。',
252
+ });
253
+ }
254
+
152
255
  function assignKwarg(kwargs, key, value) {
153
256
  if (kwargs[key] === undefined) {
154
257
  kwargs[key] = value;
@@ -165,7 +268,7 @@ function parseArgv(argv) {
165
268
  const commandParts = [];
166
269
  const kwargs = {};
167
270
  const kwargRawFlags = {};
168
- let format = 'text';
271
+ let format = 'pretty';
169
272
 
170
273
  for (let index = 0; index < argv.length; index += 1) {
171
274
  const token = argv[index];
@@ -232,13 +335,15 @@ const VIRTUAL_COMMANDS = [
232
335
  '输出机器可读命令 schema,供 Agent / 脚本理解命令、参数和示例',
233
336
  '',
234
337
  'Examples:',
338
+ ' lj-awb schema --brief -f json',
235
339
  ' lj-awb schema -f json',
236
340
  ' lj-awb schema --domain create -f json',
237
341
  ' lj-awb schema --domain create --command image -f json',
238
342
  '',
239
- 'Hint: Agent 应优先读取 schema,而不是解析自然语言 help。',
343
+ 'Hint: Agent 先读 --brief 掌握能力地图;执行具体命令前再读精确 schema。',
240
344
  ].join('\n'),
241
345
  args: [
346
+ { name: 'brief', description: '输出面向 Agent 的轻量能力摘要,不展开每个参数' },
242
347
  { name: 'domain', valueName: 'name', description: '按命令域过滤,如 create / task / artifact / system' },
243
348
  { name: 'command', valueName: 'name', description: '按子命令过滤,如 image / video / wait / doctor' },
244
349
  ],
@@ -257,6 +362,9 @@ const RENAMED_COMMAND_HINTS = {
257
362
  'video create-batch': 'create video-batch',
258
363
  'video status': 'task video-status',
259
364
  'video subtitle-remove': 'create video-subtitle-removal',
365
+ 'video super-resolution': 'create video-super-resolution',
366
+ 'video super-resolution-fee': 'create video-super-resolution-fee',
367
+ 'video super-resolution-status': 'task video-super-resolution-status',
260
368
  'video subtitle-status': 'task video-subtitle-status',
261
369
  asset: 'create asset',
262
370
  'asset match-actor': 'create asset-match-actor',
@@ -395,14 +503,21 @@ const GROUP_EXAMPLES = {
395
503
  system: [
396
504
  'lj-awb doctor',
397
505
  'lj-awb doctor --verify',
506
+ 'lj-awb update --check',
507
+ 'lj-awb update',
508
+ 'lj-awb schema --brief -f json',
398
509
  'lj-awb schema -f json',
399
510
  'lj-awb schema --domain create -f json',
400
511
  ],
401
512
  auth: [
402
513
  'lj-awb auth status',
403
514
  'lj-awb auth verify',
515
+ 'lj-awb auth login',
516
+ 'lj-awb auth login --no-wait --json',
517
+ 'lj-awb auth login --flow-id <flowId>',
404
518
  'lj-awb auth login --access-key <access-key>',
405
519
  'LINGJING_AWB_ACCESS_KEY=<access-key> lj-awb auth login',
520
+ 'lj-awb auth logout',
406
521
  'lj-awb auth clear --dry-run',
407
522
  ],
408
523
  account: [
@@ -436,6 +551,7 @@ const GROUP_EXAMPLES = {
436
551
  'lj-awb create image-fee --model-group-code <code> --prompt "一只小狗"',
437
552
  'lj-awb create video --model-group-code <code> --prompt "镜头推进" --resource image:first_frame=./actor.png --dry-run',
438
553
  'lj-awb create video-fee --model-group-code <code> --prompt "雨夜奔跑" --duration 5',
554
+ 'lj-awb create video-super-resolution --object-name material/video-super/example.mp4 --dry-run',
439
555
  'lj-awb create subject --model-code tx --name 女主 --resource primary:./three-view.png --dry-run',
440
556
  'lj-awb create subject-wait --element-id <elementId> --wait-seconds 300',
441
557
  'lj-awb create asset --group-id <id> --platform JIMENG --file ./actor.png --name "女主正面" --dry-run',
@@ -446,6 +562,7 @@ const GROUP_EXAMPLES = {
446
562
  'lj-awb task image-status --task-id <imageTaskId>',
447
563
  'lj-awb task video-status --task-id <videoTaskId>',
448
564
  'lj-awb task video-subtitle-status --task-id <subtitleTaskId>',
565
+ 'lj-awb task video-super-resolution-status --task-id <superResolutionTaskId>',
449
566
  'lj-awb task wait --task-id <id> --task-type IMAGE_CREATE --wait-seconds 180',
450
567
  'lj-awb task records --task-record-file .awb/tasks.jsonl',
451
568
  ],
@@ -470,8 +587,11 @@ const COMMAND_REQUIRED_OPTIONS = {
470
587
  'create video-fee': ['modelGroupCode'],
471
588
  'create video': ['modelGroupCode'],
472
589
  'create video-batch': ['inputFile', 'modelGroupCode'],
590
+ 'create video-super-resolution-fee': ['objectName'],
591
+ 'create video-super-resolution': ['objectName'],
473
592
  'task video-status': ['taskId'],
474
593
  'task video-subtitle-status': ['taskId'],
594
+ 'task video-super-resolution-status': ['taskId'],
475
595
  'task wait': ['taskId', 'taskType'],
476
596
  'create asset-match-actor': ['description'],
477
597
  'create asset-groups': ['platform'],
@@ -521,6 +641,8 @@ const COMMAND_REQUIRED_ANY_OPTIONS = {
521
641
  'upload files': [['file', 'files', 'filesJson']],
522
642
  'create video-fee': [['prompt', 'resource', 'resourcesJson']],
523
643
  'create video': [['prompt', 'resource', 'resourcesJson']],
644
+ 'create video-super-resolution-fee': [['objectName']],
645
+ 'create video-super-resolution': [['objectName']],
524
646
  'create video-subtitle-removal': [['sourceTaskId']],
525
647
  'create asset-group-update': [['name', 'description', 'projectName']],
526
648
  'create asset': [['file', 'url', 'backendPath']],
@@ -586,6 +708,7 @@ const CONFIRMATION_COMMANDS = new Set([
586
708
  'create image-batch',
587
709
  'create video',
588
710
  'create video-batch',
711
+ 'create video-super-resolution',
589
712
  'create video-subtitle-removal',
590
713
  'create asset-group',
591
714
  'create asset-group-update',
@@ -601,6 +724,7 @@ const COST_COMMANDS = new Set([
601
724
  'create image-batch',
602
725
  'create video',
603
726
  'create video-batch',
727
+ 'create video-super-resolution',
604
728
  'create video-subtitle-removal',
605
729
  ]);
606
730
 
@@ -615,6 +739,7 @@ const REMOTE_WRITE_COMMANDS = new Set([
615
739
  'create image-batch',
616
740
  'create video',
617
741
  'create video-batch',
742
+ 'create video-super-resolution',
618
743
  'create video-subtitle-removal',
619
744
  'create asset-group',
620
745
  'create asset-group-update',
@@ -626,24 +751,28 @@ const REMOTE_WRITE_COMMANDS = new Set([
626
751
  ]);
627
752
 
628
753
  const LOCAL_STATE_WRITE_COMMANDS = new Set([
754
+ 'update',
629
755
  'auth login',
630
756
  'auth clear',
757
+ 'auth logout',
631
758
  'project use',
632
759
  'project create',
633
760
  'project ensure',
634
761
  ]);
635
762
 
636
763
  const DESTRUCTIVE_COMMANDS = new Set(['auth clear', ...ARTIFACT_DELETE_COMMANDS]);
637
- const LONG_RUNNING_COMMANDS = new Set(['task wait', 'task record-poll', 'create subject-wait', 'create subject-voice-wait']);
638
- const NETWORK_NONE_COMMANDS = new Set(['schema', 'auth status', 'auth clear', 'model input-guide', 'task records']);
764
+ const LONG_RUNNING_COMMANDS = new Set(['auth login', 'task wait', 'task record-poll', 'create subject-wait', 'create subject-voice-wait']);
765
+ const NETWORK_NONE_COMMANDS = new Set(['schema', 'auth status', 'auth clear', 'auth logout', 'model input-guide', 'task records']);
639
766
  const NETWORK_CONDITIONAL_COMMANDS = new Set(['doctor', 'auth login']);
640
767
 
641
768
  const OUTPUT_KIND_BY_COMMAND = {
642
769
  doctor: 'environment_report',
770
+ update: 'update_result',
643
771
  schema: 'command_schema',
644
772
  'auth status': 'auth_status',
645
773
  'auth verify': 'auth_status',
646
774
  'auth login': 'auth_result',
775
+ 'auth logout': 'auth_result',
647
776
  'account info': 'account_summary',
648
777
  'account teams': 'team_list',
649
778
  'project list': 'project_list',
@@ -665,9 +794,12 @@ const OUTPUT_KIND_BY_COMMAND = {
665
794
  'create video-fee': 'fee_estimate',
666
795
  'create video': 'task_submission',
667
796
  'create video-batch': 'batch_task_submission',
797
+ 'create video-super-resolution-fee': 'fee_estimate',
798
+ 'create video-super-resolution': 'task_submission',
668
799
  'task video-status': 'task_status',
669
800
  'create video-subtitle-removal': 'subtitle_task_submission',
670
801
  'task video-subtitle-status': 'subtitle_task_status',
802
+ 'task video-super-resolution-status': 'task_status',
671
803
  'task list': 'task_list',
672
804
  'task wait': 'task_status',
673
805
  'task records': 'local_task_records',
@@ -743,6 +875,8 @@ const PREFLIGHTS_BY_COMMAND = {
743
875
  'create image-batch': ['doctor --verify', 'model image-models', 'model options', 'model create-spec', 'prepare JSONL input', 'create image-batch --dry-run'],
744
876
  'create video': ['doctor --verify', 'model video-models', 'model options', 'model create-spec', 'confirm missing key params', 'create video-fee', 'create video --dry-run'],
745
877
  'create video-batch': ['doctor --verify', 'model video-models', 'model options', 'model create-spec', 'prepare JSONL input', 'create video-batch --dry-run'],
878
+ 'create video-super-resolution-fee': ['doctor --verify', 'create video-super-resolution-fee --object-name <objectName>'],
879
+ 'create video-super-resolution': ['doctor --verify', 'create video-super-resolution --object-name <objectName> --dry-run'],
746
880
  'create subject': ['doctor --verify', 'create subject --model-code tx|vidu --dry-run'],
747
881
  'create subject-batch': ['doctor --verify', 'prepare JSONL input with modelCode', 'create subject-batch --dry-run'],
748
882
  'create video-subtitle-removal': ['doctor --verify', 'create video-subtitle-removal --source-task-id <videoTaskId> --dry-run'],
@@ -805,9 +939,10 @@ function buildCommandSafety(command, optionKeys) {
805
939
  const costsPoints = COST_COMMANDS.has(name);
806
940
  const destructive = DESTRUCTIVE_COMMANDS.has(name);
807
941
  const supportsDryRun = optionKeys.has('dryRun');
942
+ const updateInstall = name === 'update';
808
943
  return {
809
- safeToAutoRun: !(remoteWrite || localStateWrite || requiresConfirmation || costsPoints || destructive),
810
- network: commandNetworkMode(name),
944
+ safeToAutoRun: !(remoteWrite || localStateWrite || requiresConfirmation || costsPoints || destructive || updateInstall),
945
+ network: updateInstall ? 'conditional' : commandNetworkMode(name),
811
946
  remoteWrite,
812
947
  localStateWrite,
813
948
  costsPoints,
@@ -829,6 +964,9 @@ function buildCommandWorkflow(command) {
829
964
  if (command.name === 'create video') {
830
965
  next.push('读取 data.taskId 和 data.nextCommand', '立即运行响应里的 nextCommand,或 task wait --task-type VIDEO_GROUP 等待结果');
831
966
  }
967
+ if (command.name === 'create video-super-resolution') {
968
+ next.push('读取 data.taskId 和 data.nextCommand', '运行响应里的 task video-super-resolution-status 命令查询结果');
969
+ }
832
970
  if (command.name === 'create video-subtitle-removal') {
833
971
  next.push('读取 data.taskId 和 data.nextCommand', '运行 task video-subtitle-status 或响应里的 nextCommand 等待结果');
834
972
  }
@@ -839,12 +977,12 @@ function buildCommandWorkflow(command) {
839
977
  next.push('读取 data.voiceRecordId / data.externalId', 'externalId 为空时立刻运行 create subject-voice-wait 获取 nextVoiceArg');
840
978
  }
841
979
  if (command.name === 'create asset') {
842
- next.push('读取 data.assetPath', '若后端返回审核 taskId,再运行 task wait --task-type ASSET_REGISTER');
980
+ next.push('读取 data.assetType / data.assetPath', 'data.conversionRequired=true 时先确认是否转码;正式执行可交互确认或追加 --auto-convert', '若后端返回审核 taskId,再运行 task wait --task-type ASSET_REGISTER');
843
981
  }
844
982
  if (command.name === 'model image-models' || command.name === 'model video-models') {
845
983
  next.push('读取 data.models[] 得到全部候选;用户给了模型口语名时保留同族全部候选,不只取第一个');
846
984
  next.push('对每个候选运行 model options --model-group-code <modelGroupCode> 查看真实参数和素材约束');
847
- next.push('先向用户展示候选模型 + 参数取值 + 资源能力,再推荐模型或进入 fee/dry-run');
985
+ next.push('向用户展示候选模型 + 参数取值 + 资源能力后 STOP;等待用户选择或明确授权默认,未确认不得进入 create-spec/fee/dry-run/create');
848
986
  }
849
987
  if (command.name === 'model asset-review-models') {
850
988
  next.push('读取 data.models[].platform', '用 create asset-groups --platform <platform> 查重或创建素材组');
@@ -868,11 +1006,167 @@ function buildCommandWorkflow(command) {
868
1006
  };
869
1007
  }
870
1008
 
1009
+ function buildDomainSummaries(commands) {
1010
+ const domains = new Map();
1011
+ for (const command of commands) {
1012
+ const group = commandGroup(command);
1013
+ domains.set(group, {
1014
+ name: group,
1015
+ description: GROUP_DESCRIPTIONS[group] || '',
1016
+ commandCount: (domains.get(group)?.commandCount || 0) + 1,
1017
+ });
1018
+ }
1019
+ return Array.from(domains.values()).sort((a, b) => a.name.localeCompare(b.name));
1020
+ }
1021
+
1022
+ function requiredOptionsForCommand(command) {
1023
+ const requiredOptions = new Set();
1024
+ if (commandGroup(command) === 'artifact') requiredOptions.add('projectId');
1025
+ for (const key of COMMAND_REQUIRED_OPTIONS[command.name] || []) requiredOptions.add(key);
1026
+ return [...requiredOptions];
1027
+ }
1028
+
1029
+ function requiredAnyOptionsForCommand(command) {
1030
+ return COMMAND_REQUIRED_ANY_OPTIONS[command.name] || [];
1031
+ }
1032
+
1033
+ function buildCommandSchemaEntry(command) {
1034
+ const requiredOptions = requiredOptionsForCommand(command);
1035
+ const requiredAnyOptions = requiredAnyOptionsForCommand(command);
1036
+ const optionKeys = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
1037
+ return {
1038
+ name: command.name,
1039
+ domain: commandGroup(command),
1040
+ command: commandSubcommand(command),
1041
+ invocation: `${commandPrefix()} ${command.name}`,
1042
+ helpCommand: `${commandPrefix()} ${command.name} -h`,
1043
+ summary: summaryOf(command),
1044
+ description: renderCliText(summaryOf(command)),
1045
+ examples: extractExamples(command).map(renderCliText),
1046
+ jsonExamples: extractExamples(command).map(asJsonExample),
1047
+ requiredOptions,
1048
+ requiredAnyOptions,
1049
+ safety: buildCommandSafety(command, optionKeys),
1050
+ workflow: buildCommandWorkflow(command),
1051
+ options: (command.args || []).map((arg) => {
1052
+ const key = normalizeKey(arg.name);
1053
+ return {
1054
+ name: arg.name,
1055
+ key,
1056
+ flag: formatOptionName(arg.name),
1057
+ valueName: arg.valueName || null,
1058
+ required: requiredOptions.includes(key),
1059
+ requiredAnyGroup: requiredAnyOptions.find((group) => group.includes(key)) || null,
1060
+ description: arg.description || '',
1061
+ };
1062
+ }),
1063
+ };
1064
+ }
1065
+
1066
+ function namesWhere(entries, predicate) {
1067
+ return entries.filter(predicate).map((entry) => entry.name);
1068
+ }
1069
+
1070
+ function buildAgentBrief(commands, version = 'unknown') {
1071
+ const contract = buildAgentContract();
1072
+ const entries = commands.map(buildCommandSchemaEntry);
1073
+ const domains = buildDomainSummaries(commands).map((domain) => ({
1074
+ ...domain,
1075
+ commands: entries
1076
+ .filter((entry) => entry.domain === domain.name)
1077
+ .map((entry) => entry.name),
1078
+ }));
1079
+ return {
1080
+ schemaVersion: 1,
1081
+ kind: 'agent_brief',
1082
+ cli: {
1083
+ name: 'lj-awb',
1084
+ version,
1085
+ commandPrefix: commandPrefix(),
1086
+ },
1087
+ commandCount: entries.length,
1088
+ domains,
1089
+ outputPolicy: {
1090
+ agentRecommendedOutputFormat: contract.agentRecommendedOutputFormat,
1091
+ scriptRecommendedOutputFormat: contract.scriptRecommendedOutputFormat,
1092
+ textFlag: contract.textFlag,
1093
+ jsonFlag: contract.jsonFlag,
1094
+ policy: contract.outputPolicy,
1095
+ },
1096
+ statePolicy: contract.statePolicy,
1097
+ workflowPolicy: contract.workflowPolicy,
1098
+ safetyPolicy: {
1099
+ confirmationPolicy: contract.confirmationPolicy,
1100
+ dryRunPolicy: contract.dryRunPolicy,
1101
+ remoteWriteCount: namesWhere(entries, (entry) => entry.safety.remoteWrite).length,
1102
+ costsPointsCount: namesWhere(entries, (entry) => entry.safety.costsPoints).length,
1103
+ localStateWriteCount: namesWhere(entries, (entry) => entry.safety.localStateWrite).length,
1104
+ destructiveCount: namesWhere(entries, (entry) => entry.safety.destructive).length,
1105
+ longRunningCount: namesWhere(entries, (entry) => entry.safety.longRunning).length,
1106
+ },
1107
+ commandClasses: {
1108
+ costsPoints: namesWhere(entries, (entry) => entry.safety.costsPoints),
1109
+ remoteWrite: namesWhere(entries, (entry) => entry.safety.remoteWrite),
1110
+ localStateWrite: namesWhere(entries, (entry) => entry.safety.localStateWrite),
1111
+ destructive: namesWhere(entries, (entry) => entry.safety.destructive),
1112
+ longRunning: namesWhere(entries, (entry) => entry.safety.longRunning),
1113
+ safeRead: namesWhere(entries, (entry) => entry.safety.safeToAutoRun && entry.safety.network !== 'none'),
1114
+ offlineRead: namesWhere(entries, (entry) => entry.safety.safeToAutoRun && entry.safety.network === 'none'),
1115
+ },
1116
+ routing: [
1117
+ {
1118
+ intent: '环境、认证、账号、团队、项目组、积分',
1119
+ start: ['doctor --verify', 'account info', 'project current', 'credits balance'],
1120
+ rule: '只读查询可自动运行;切换团队、切换/创建/更新项目组和清空认证按 safety dry-run/confirm 处理。',
1121
+ },
1122
+ {
1123
+ intent: '模型发现和创作任务',
1124
+ start: ['model image-models', 'model video-models', 'model options', 'model create-spec'],
1125
+ rule: '先展示候选模型、真实参数和资源能力并 STOP;用户选择或明确授权默认后,关键参数确认,再只跑一次 fee、dry-run 和最终 --yes。',
1126
+ },
1127
+ {
1128
+ intent: '上传、主体、音色、素材加白',
1129
+ start: ['upload files', 'create subject-list', 'create subject-voice-list', 'model asset-review-models'],
1130
+ rule: '复用已上传 backendPath、已有主体/音色/素材组;素材加白平台来自 model asset-review-models 或用户明确选择。',
1131
+ },
1132
+ {
1133
+ intent: '异步任务和批量恢复',
1134
+ start: ['task wait', 'task record-poll', 'task records'],
1135
+ rule: '优先复用 create 返回的 nextCommand;批量任务使用 task-record-file,失败项单独重试。',
1136
+ },
1137
+ {
1138
+ intent: '最终产物 artifact',
1139
+ start: ['artifact script', 'artifact asset', 'artifact video', 'artifact clip'],
1140
+ rule: 'artifact 使用 project-id,不是 project-group-no;写入/删除/导入先 dry-run,再一次性确认写入面。',
1141
+ },
1142
+ ],
1143
+ lookup: {
1144
+ preciseSchema: `${commandPrefix()} schema --domain <domain> --command <command> -f json`,
1145
+ fullSchema: `${commandPrefix()} schema -f json`,
1146
+ commandHelp: `${commandPrefix()} <domain> <command> -h`,
1147
+ modelInputGuide: contract.modelInputGuide.textCommand,
1148
+ schemaPolicy: contract.schemaPolicy,
1149
+ },
1150
+ canonicalFields: contract.canonicalFields,
1151
+ resourceShortcut: contract.resourceShortcut,
1152
+ resourcesJson: contract.resourcesJson,
1153
+ modelCandidatePresentation: contract.modelCandidatePresentation,
1154
+ taskTypes: contract.taskTypes,
1155
+ exitCodes: contract.exitCodes,
1156
+ };
1157
+ }
1158
+
871
1159
  function buildAgentContract() {
872
1160
  return {
873
- defaultOutputFormat: 'compact-text',
1161
+ defaultOutputFormat: 'pretty',
1162
+ humanDefaultOutputFormat: 'pretty',
1163
+ agentRecommendedOutputFormat: 'text',
1164
+ agentCompactOutputFormat: 'text',
1165
+ scriptRecommendedOutputFormat: 'json',
874
1166
  jsonFlag: '-f json',
875
- outputPolicy: '默认输出精简 key=value 文本,只保留决策字段;需要稳定 JSON、嵌套结构或脚本严格解析时显式传 -f json 或 --json。JSON envelope 字段已做归一化,见 canonicalFields。',
1167
+ textFlag: '-f text',
1168
+ outputFormats: ['pretty', 'text', 'json', 'yaml', 'csv', 'auto'],
1169
+ outputPolicy: '默认输出 pretty 人类友好视图,包含标题、摘要表格、列表表格和下一步建议;Agent 推荐显式传 -f text 读取稳定的分区 key=value 输出;脚本严格解析或需要完整嵌套结构时再传 -f json 或 --json。JSON envelope 字段已做归一化,见 canonicalFields。',
876
1170
  statePolicy: {
877
1171
  purpose: 'Agent 应在同一轮 / 同一项目中维护 AWB 上下文账本,减少重复查询和重复提交。',
878
1172
  cacheKeys: [
@@ -890,12 +1184,13 @@ function buildAgentContract() {
890
1184
  invalidation: '用户切换账号、团队、项目组、模型、素材、prompt 或关键参数后,只刷新受影响的缓存;不要重复跑未变化的 model options / create-spec / fee。',
891
1185
  },
892
1186
  workflowPolicy: [
893
- '模型口语名命中后必须先展示候选模型、真实参数取值和资源能力;没有完成用户可见候选清单前,不得代选默认模型 / 参数,也不得进入 fee 或 dry-run。',
1187
+ '模型口语名命中后必须先展示候选模型、真实参数取值和资源能力,并在清单后 STOP 等用户选择或明确授权默认;没有完成用户可见候选清单和确认前,不得代选默认模型 / 参数,也不得进入 create-spec、fee、dry-runcreate。',
894
1188
  '模型探索阶段只读 model list / options / create-spec;fee 只在用户确认关键参数后跑一次。',
1189
+ 'model options.params[] 中带 cliArg/genericModelParam 的新增模型配置参数可通过 --model-param key=value、--model-params-json 或同名下划线长参数传入;defaultValue 只能展示为候选默认,不能静默代选。',
895
1190
  'supportsDryRun=true 的写入 / 扣费命令先 dry-run,确认后 yes;不要把 dry-run 当参数探索工具反复跑。',
896
1191
  '用户给出多条同模型同参数任务时优先 batch + task-record-file,不要单条循环 create。',
897
1192
  '命令返回 nextCommand / nextRefSubject / nextVoiceArg 时优先复用返回值,不手拼等价命令。',
898
- '素材加白平台先通过 model asset-review-models 或用户明确输入确定;create asset-* 必须显式传 --platform,不要依赖默认平台。',
1193
+ '素材加白平台先通过 model asset-review-models 或用户明确输入确定;create asset-* 必须显式传 --platform,不要依赖默认平台。create asset 会自动判断 assetType=Image/Video/Audio,本地文件会用 ffprobe 校验图片/视频尺寸、视频时长/FPS/像素数和音频时长;不合法素材正式执行时会询问是否转码,非交互场景可追加 --auto-convert;macOS + Homebrew 环境缺失时 CLI 会自动安装 ffmpeg。',
899
1194
  '旧根域 image / video / asset / subject 已移除;只使用 create / task / artifact 等 schema 暴露的 domain。',
900
1195
  ],
901
1196
  canonicalFields: {
@@ -909,7 +1204,7 @@ function buildAgentContract() {
909
1204
  { canonical: 'pointCost', aliases: ['points', 'point', 'costPoint', 'estimatePoint'], meaning: '本次任务预计 / 实际消耗积分' },
910
1205
  ],
911
1206
  },
912
- schemaPolicy: 'schema 是机器契约,只给摘要、参数 key、安全规则和示例;查看长帮助用 schema.commands[].helpCommand 或 lj-awb <domain> <command> -h。',
1207
+ schemaPolicy: 'Agent 先读 schema --brief 建立能力地图;执行具体命令前再读精确 schema --domain <domain> --command <command>。完整 schema -f json 只用于覆盖校验、脚本生成或调试 schema 本身;查看长帮助用 schema.commands[].helpCommand 或 lj-awb <domain> <command> -h。',
913
1208
  confirmationPolicy: 'schema.commands[].safety.requiresConfirmation=true 的命令,Agent 必须先向用户确认,再追加 --yes。',
914
1209
  dryRunPolicy: 'schema.commands[].safety.supportsDryRun=true 的写入 / 扣费命令,正式执行前优先跑 --dry-run。',
915
1210
  resourceShortcut: {
@@ -936,32 +1231,39 @@ function buildAgentContract() {
936
1231
  modelCandidatePresentation: {
937
1232
  purpose: '把 model image-models / video-models 与每个候选的 model options 转成用户可见清单,避免 Agent 内部看完 options 后直接代选。',
938
1233
  trigger: '用户问有哪些模型、给出口语名、或模型列表返回候选时都触发;即使只有一个候选也要展示真实可选项。',
1234
+ gateType: 'hard_stop',
1235
+ stopAfterPresentation: true,
1236
+ resumeCondition: '用户选择模型和关键参数,或明确说“按默认 / 你来选”。',
939
1237
  requiredVisibleFields: ['displayName', 'modelDesc', 'taskQueueNum', 'quality values/default', 'ratio values/default', 'duration or generateNum values/default', 'resource modes/media/usages', 'channel or fast/pro differences'],
940
- blockedBeforePresentation: ['create image-fee', 'create video-fee', 'create image --dry-run', 'create video --dry-run', 'default model recommendation', 'default quality/ratio/duration choice'],
1238
+ blockedBeforePresentation: ['model create-spec', 'create image-fee', 'create video-fee', 'create image-batch --dry-run', 'create video-batch --dry-run', 'create image --dry-run', 'create video --dry-run', 'create image', 'create video', 'default model recommendation', 'default quality/ratio/duration choice'],
941
1239
  },
942
1240
  modelOptions: {
943
1241
  command: `${commandPrefix()} model options --model-group-code <code>`,
1242
+ textCommand: `${commandPrefix()} model options --model-group-code <code> -f text`,
944
1243
  jsonCommand: `${commandPrefix()} model options --model-group-code <code> -f json`,
945
- purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。',
1244
+ purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
946
1245
  useBefore: ['model create-spec', 'create image-fee', 'create image', 'create video-fee', 'create video'],
947
- keyFields: ['params[].values', 'params[].defaultValue', 'resources[].mediaType', 'resources[].usage', 'resources[].fileTypes', 'resources[].maxFiles', 'resources[].supportLastFrameOnly', 'resources[].minDurationMs', 'resources[].maxDurationMs', 'constraints[].target', 'constraints[].conditions', 'constraints[].effect'],
1246
+ keyFields: ['params[].key', 'params[].cliArg', 'params[].genericModelParam', 'params[].values', 'params[].defaultValue', 'resources[].mediaType', 'resources[].usage', 'resources[].fileTypes', 'resources[].maxFiles', 'resources[].supportLastFrameOnly', 'resources[].minDurationMs', 'resources[].maxDurationMs', 'constraints[].target', 'constraints[].conditions', 'constraints[].effect'],
948
1247
  },
949
1248
  modelCreateSpec: {
950
1249
  command: `${commandPrefix()} model create-spec --model-group-code <code>`,
1250
+ textCommand: `${commandPrefix()} model create-spec --model-group-code <code> -f text`,
951
1251
  jsonCommand: `${commandPrefix()} model create-spec --model-group-code <code> -f json`,
952
- purpose: '查看指定模型如何组织 create 命令:输入模式、素材绑定规则、示例和创建前检查项。',
1252
+ purpose: '查看指定模型如何组织 create 命令:输入模式、素材绑定规则、示例和创建前检查项。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
953
1253
  useBefore: ['create image-fee', 'create image', 'create video-fee', 'create video'],
954
1254
  keyFields: ['inputRequirement', 'supportedIntents', 'validationRules', 'agentGuidance', 'preflight', 'examples'],
955
1255
  },
956
1256
  modelInputGuide: {
957
1257
  command: `${commandPrefix()} model input-guide`,
1258
+ textCommand: `${commandPrefix()} model input-guide -f text`,
958
1259
  jsonCommand: `${commandPrefix()} model input-guide -f json`,
959
- purpose: '查看跨模型统一创建参数、resources 字段和素材绑定规则。',
1260
+ purpose: '查看跨模型统一创建参数、resources 字段和素材绑定规则。Agent 默认读 textCommand;脚本严格解析时读 jsonCommand。',
960
1261
  useBefore: ['model options', 'model create-spec'],
961
1262
  },
962
1263
  taskTypes: {
963
1264
  image: 'IMAGE_CREATE',
964
1265
  video: 'VIDEO_GROUP',
1266
+ videoSuperResolution: 'VIDEO_SUPER_RESOLUTION',
965
1267
  subtitleRemoval: 'VIDEO_SUBTITLE_REMOVAL',
966
1268
  assetRegister: 'ASSET_REGISTER',
967
1269
  },
@@ -982,17 +1284,10 @@ function buildAgentContract() {
982
1284
  }
983
1285
 
984
1286
  function buildCommandSchema(commands, kwargs = {}, version = 'unknown') {
1287
+ if (toBool(kwargs.brief)) return buildAgentBrief(commands, version);
985
1288
  const domainFilter = String(kwargs.domain ?? '').trim();
986
1289
  const commandFilter = String(kwargs.command ?? '').trim();
987
- const domains = new Map();
988
- for (const command of commands) {
989
- const group = commandGroup(command);
990
- domains.set(group, {
991
- name: group,
992
- description: GROUP_DESCRIPTIONS[group] || '',
993
- commandCount: (domains.get(group)?.commandCount || 0) + 1,
994
- });
995
- }
1290
+ const domains = buildDomainSummaries(commands);
996
1291
  const filteredCommands = commands
997
1292
  .filter((command) => !domainFilter || commandGroup(command) === domainFilter)
998
1293
  .filter((command) => !commandFilter || command.name === commandFilter || commandSubcommand(command) === commandFilter);
@@ -1016,49 +1311,16 @@ function buildCommandSchema(commands, kwargs = {}, version = 'unknown') {
1016
1311
  `${commandPrefix()} <domain> <command> -h`,
1017
1312
  ],
1018
1313
  globalOptions: [
1019
- { flag: '-f, --format json', key: 'format', description: '输出 JSON;默认不传时输出精简文本' },
1314
+ { flag: '-f, --format <format>', key: 'format', description: '输出格式:pretty(默认)、text、json、yaml、csv、auto' },
1020
1315
  { flag: '--json', key: 'format', value: 'json', description: '等同于 -f json' },
1021
1316
  { flag: '-h, --help', key: 'help', description: '查看帮助' },
1022
1317
  { flag: '-v, --version', key: 'version', description: '查看版本' },
1023
1318
  ],
1024
1319
  agentContract: buildAgentContract(),
1025
- domains: Array.from(domains.values())
1320
+ domains: domains
1026
1321
  .filter((domain) => !shouldFilterDomains || matchedDomains.has(domain.name))
1027
1322
  .sort((a, b) => a.name.localeCompare(b.name)),
1028
- commands: filteredCommands.map((command) => {
1029
- const requiredOptions = new Set();
1030
- if (commandGroup(command) === 'artifact') requiredOptions.add('projectId');
1031
- for (const key of COMMAND_REQUIRED_OPTIONS[command.name] || []) requiredOptions.add(key);
1032
- const requiredAnyOptions = COMMAND_REQUIRED_ANY_OPTIONS[command.name] || [];
1033
- const optionKeys = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
1034
- return {
1035
- name: command.name,
1036
- domain: commandGroup(command),
1037
- command: commandSubcommand(command),
1038
- invocation: `${commandPrefix()} ${command.name}`,
1039
- helpCommand: `${commandPrefix()} ${command.name} -h`,
1040
- summary: summaryOf(command),
1041
- description: renderCliText(summaryOf(command)),
1042
- examples: extractExamples(command).map(renderCliText),
1043
- jsonExamples: extractExamples(command).map(asJsonExample),
1044
- requiredOptions: [...requiredOptions],
1045
- requiredAnyOptions,
1046
- safety: buildCommandSafety(command, optionKeys),
1047
- workflow: buildCommandWorkflow(command),
1048
- options: (command.args || []).map((arg) => {
1049
- const key = normalizeKey(arg.name);
1050
- return {
1051
- name: arg.name,
1052
- key,
1053
- flag: formatOptionName(arg.name),
1054
- valueName: arg.valueName || null,
1055
- required: requiredOptions.has(key),
1056
- requiredAnyGroup: requiredAnyOptions.find((group) => group.includes(key)) || null,
1057
- description: arg.description || '',
1058
- };
1059
- }),
1060
- };
1061
- }),
1323
+ commands: filteredCommands.map(buildCommandSchemaEntry),
1062
1324
  };
1063
1325
  }
1064
1326
 
@@ -1071,43 +1333,44 @@ function printRootHelp(commands, version) {
1071
1333
  }
1072
1334
 
1073
1335
  const lines = [
1074
- `lj-awb v${version}`,
1336
+ helpTitle(`lj-awb v${version}`),
1075
1337
  '',
1076
- '灵境 AWB 命令行工具。默认输出精简文本;需要 JSON 时传 -f json。',
1338
+ '灵境 AWB 命令行工具。默认输出人类友好的 pretty 视图;Agent 建议显式传 -f text,脚本严格解析用 -f json。',
1077
1339
  '',
1078
- 'Usage:',
1079
- ` ${commandPrefix()} <domain> <command> [options]`,
1080
- ` ${commandPrefix()} <command> [options]`,
1081
- ` ${commandPrefix()} <domain> -h`,
1082
- ` ${commandPrefix()} <domain> <command> -h`,
1340
+ helpHeading('Usage:'),
1341
+ helpCommand(` ${commandPrefix()} <domain> <command> [options]`),
1342
+ helpCommand(` ${commandPrefix()} <command> [options]`),
1343
+ helpCommand(` ${commandPrefix()} <domain> -h`),
1344
+ helpCommand(` ${commandPrefix()} <domain> <command> -h`),
1083
1345
  '',
1084
- 'Quick start:',
1085
- ` ${commandPrefix()} auth status`,
1086
- ` ${commandPrefix()} auth login --access-key <access-key>`,
1087
- ` ${commandPrefix()} model image-models --model Banana`,
1088
- ` ${commandPrefix()} model input-guide`,
1089
- ` ${commandPrefix()} model options --model-group-code <code>`,
1090
- ` ${commandPrefix()} model create-spec --model-group-code <code>`,
1091
- ` ${commandPrefix()} create image --model-group-code <code> --prompt "一只小狗" --dry-run`,
1346
+ helpHeading('Quick start:'),
1347
+ helpCommand(` ${commandPrefix()} auth status`),
1348
+ helpCommand(` ${commandPrefix()} auth login --access-key <access-key>`),
1349
+ helpCommand(` ${commandPrefix()} model image-models --model Banana`),
1350
+ helpCommand(` ${commandPrefix()} model input-guide`),
1351
+ helpCommand(` ${commandPrefix()} model options --model-group-code <code>`),
1352
+ helpCommand(` ${commandPrefix()} model create-spec --model-group-code <code>`),
1353
+ helpCommand(` ${commandPrefix()} create image --model-group-code <code> --prompt "一只小狗" --dry-run`),
1092
1354
  '',
1093
- 'Command groups:',
1355
+ helpHeading('Command groups:'),
1094
1356
  ];
1095
1357
  for (const [group] of groups.entries()) {
1096
- lines.push(` ${group.padEnd(12)} ${GROUP_DESCRIPTIONS[group] || ''}`);
1358
+ lines.push(` ${helpGroup(group.padEnd(12))} ${GROUP_DESCRIPTIONS[group] || ''}`);
1097
1359
  }
1098
1360
  lines.push(
1099
1361
  '',
1100
- 'More help:',
1101
- ` ${commandPrefix()} <domain> -h`,
1102
- ` ${commandPrefix()} <domain> <command> -h`,
1103
- ` ${commandPrefix()} schema -f json`,
1104
- ` ${commandPrefix()} doctor -h`,
1362
+ helpHeading('More help:'),
1363
+ helpCommand(` ${commandPrefix()} <domain> -h`),
1364
+ helpCommand(` ${commandPrefix()} <domain> <command> -h`),
1365
+ helpCommand(` ${commandPrefix()} schema --brief -f json`),
1366
+ helpCommand(` ${commandPrefix()} schema -f json`),
1367
+ helpCommand(` ${commandPrefix()} doctor -h`),
1105
1368
  '',
1106
- 'Global options:',
1107
- ' -f, --format json 输出 JSON;默认不传时输出精简文本',
1108
- ' --json 等同于 -f json',
1109
- ' -h, --help 查看帮助',
1110
- ' -v, --version 查看版本',
1369
+ helpHeading('Global options:'),
1370
+ ` ${helpOption('-f, --format <format>')} 输出格式:pretty(默认)、text、json、yaml、csv、auto`,
1371
+ ` ${helpOption('--json'.padEnd(22))} 等同于 -f json`,
1372
+ ` ${helpOption('-h, --help'.padEnd(22))} 查看帮助`,
1373
+ ` ${helpOption('-v, --version'.padEnd(22))} 查看版本`,
1111
1374
  );
1112
1375
  process.stdout.write(`${lines.join('\n')}\n`);
1113
1376
  }
@@ -1119,27 +1382,27 @@ function printGroupHelp(group, commands, version) {
1119
1382
  const directItems = hasSubgroups ? items.filter((command) => !commandSubgroupTokens(command)) : [];
1120
1383
 
1121
1384
  const lines = [
1122
- `lj-awb v${version}`,
1385
+ helpTitle(`lj-awb v${version}`),
1123
1386
  '',
1124
1387
  GROUP_DESCRIPTIONS[group] || `${group} 命令组`,
1125
1388
  '',
1126
- 'Usage:',
1389
+ helpHeading('Usage:'),
1127
1390
  ];
1128
1391
  if (group === 'system') {
1129
- lines.push(` ${commandPrefix()} <command> [options]`);
1392
+ lines.push(helpCommand(` ${commandPrefix()} <command> [options]`));
1130
1393
  } else if (hasSubgroups) {
1131
- if (directItems.length) lines.push(` ${commandPrefix()} ${group} <command> [options]`);
1132
- lines.push(` ${commandPrefix()} ${group} <subdomain> <command> [options]`);
1394
+ if (directItems.length) lines.push(helpCommand(` ${commandPrefix()} ${group} <command> [options]`));
1395
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> <command> [options]`));
1133
1396
  } else {
1134
- lines.push(` ${commandPrefix()} ${group} <command> [options]`);
1397
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <command> [options]`));
1135
1398
  }
1136
1399
  lines.push('');
1137
1400
  if (group === 'system') {
1138
- lines.push(`提示:system 是命令组;下面的命令可直接运行,例如 ${commandPrefix()} doctor。`);
1401
+ lines.push(helpMuted(`提示:system 是命令组;下面的命令可直接运行,例如 ${commandPrefix()} doctor。`));
1139
1402
  } else if (hasSubgroups) {
1140
- lines.push(`提示:${group} 下按子领域分组;先选子领域再看具体命令。`);
1403
+ lines.push(helpMuted(`提示:${group} 下按子领域分组;先选子领域再看具体命令。`));
1141
1404
  } else {
1142
- lines.push(`提示:${group} 是命令组;请选择下面的子命令运行。`);
1405
+ lines.push(helpMuted(`提示:${group} 是命令组;请选择下面的子命令运行。`));
1143
1406
  }
1144
1407
  lines.push('');
1145
1408
 
@@ -1147,43 +1410,43 @@ function printGroupHelp(group, commands, version) {
1147
1410
  const presentSubgroups = new Set(
1148
1411
  items
1149
1412
  .map((command) => commandSubgroupTokens(command))
1150
- .filter(Boolean)
1151
- .map(([, subgroup]) => subgroup),
1413
+ .filter(Boolean)
1414
+ .map(([, subgroup]) => subgroup),
1152
1415
  );
1153
- lines.push('Subdomains:');
1416
+ lines.push(helpHeading('Subdomains:'));
1154
1417
  for (const [subgroup, description] of Object.entries(subgroupMap)) {
1155
1418
  if (!presentSubgroups.has(subgroup)) continue;
1156
- lines.push(` ${subgroup.padEnd(10)} ${description}`);
1419
+ lines.push(` ${helpGroup(subgroup.padEnd(10))} ${description}`);
1157
1420
  }
1158
1421
  if (directItems.length) {
1159
- lines.push('', 'Commands:');
1422
+ lines.push('', helpHeading('Commands:'));
1160
1423
  for (const command of directItems) {
1161
1424
  const subcommand = commandSubcommand(command);
1162
- lines.push(` ${subcommand.padEnd(28)} ${summaryOf(command)}`);
1425
+ lines.push(` ${helpCommand(subcommand.padEnd(28))} ${summaryOf(command)}`);
1163
1426
  }
1164
1427
  }
1165
1428
  } else {
1166
- lines.push('Commands:');
1429
+ lines.push(helpHeading('Commands:'));
1167
1430
  for (const command of items) {
1168
1431
  const subcommand = commandSubcommand(command);
1169
- lines.push(` ${subcommand.padEnd(28)} ${summaryOf(command)}`);
1432
+ lines.push(` ${helpCommand(subcommand.padEnd(28))} ${summaryOf(command)}`);
1170
1433
  }
1171
1434
  }
1172
1435
 
1173
1436
  if (GROUP_EXAMPLES[group]?.length) {
1174
- lines.push('', 'Examples:');
1437
+ lines.push('', helpHeading('Examples:'));
1175
1438
  for (const example of GROUP_EXAMPLES[group]) {
1176
- lines.push(` ${renderCliText(example)}`);
1439
+ lines.push(helpCommand(` ${renderCliText(example)}`));
1177
1440
  }
1178
1441
  }
1179
- lines.push('', 'More help:');
1442
+ lines.push('', helpHeading('More help:'));
1180
1443
  if (group === 'system') {
1181
- for (const command of items) lines.push(` ${commandPrefix()} ${command.name} -h`);
1444
+ for (const command of items) lines.push(helpCommand(` ${commandPrefix()} ${command.name} -h`));
1182
1445
  } else if (hasSubgroups) {
1183
- lines.push(` ${commandPrefix()} ${group} <subdomain> -h`);
1184
- lines.push(` ${commandPrefix()} ${group} <subdomain> <command> -h`);
1446
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> -h`));
1447
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> <command> -h`));
1185
1448
  } else {
1186
- lines.push(` ${commandPrefix()} ${group} <command> -h`);
1449
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <command> -h`));
1187
1450
  }
1188
1451
  process.stdout.write(`${lines.join('\n')}\n`);
1189
1452
  }
@@ -1195,50 +1458,58 @@ function printSubgroupHelp(group, subgroup, commands, version) {
1195
1458
  });
1196
1459
  const description = SUBGROUP_DESCRIPTIONS[group]?.[subgroup] || `${group} ${subgroup} 子领域`;
1197
1460
  const lines = [
1198
- `lj-awb v${version}`,
1461
+ helpTitle(`lj-awb v${version}`),
1199
1462
  '',
1200
1463
  description,
1201
1464
  '',
1202
- 'Usage:',
1203
- ` ${commandPrefix()} ${group} ${subgroup} <command> [options]`,
1465
+ helpHeading('Usage:'),
1466
+ helpCommand(` ${commandPrefix()} ${group} ${subgroup} <command> [options]`),
1204
1467
  '',
1205
- 'Commands:',
1468
+ helpHeading('Commands:'),
1206
1469
  ];
1207
1470
  for (const command of items) {
1208
1471
  const parts = String(command.name || '').split(' ').filter(Boolean);
1209
1472
  const tail = parts.slice(2).join(' ');
1210
- lines.push(` ${tail.padEnd(28)} ${summaryOf(command)}`);
1473
+ lines.push(` ${helpCommand(tail.padEnd(28))} ${summaryOf(command)}`);
1211
1474
  }
1212
1475
  const examples = SUBGROUP_EXAMPLES[`${group} ${subgroup}`];
1213
1476
  if (examples?.length) {
1214
- lines.push('', 'Examples:');
1477
+ lines.push('', helpHeading('Examples:'));
1215
1478
  for (const example of examples) {
1216
- lines.push(` ${renderCliText(example)}`);
1479
+ lines.push(helpCommand(` ${renderCliText(example)}`));
1217
1480
  }
1218
1481
  }
1219
- lines.push('', 'More help:');
1220
- lines.push(` ${commandPrefix()} ${group} ${subgroup} <command> -h`);
1482
+ lines.push('', helpHeading('More help:'));
1483
+ lines.push(helpCommand(` ${commandPrefix()} ${group} ${subgroup} <command> -h`));
1221
1484
  process.stdout.write(`${lines.join('\n')}\n`);
1222
1485
  }
1223
1486
 
1224
1487
  function printCommandHelp(command) {
1225
- const lines = [`Usage: ${commandPrefix()} ${command.name} [options]`, '', renderCliText(command.description || '')];
1488
+ const lines = [helpHeading(`Usage: ${commandPrefix()} ${command.name} [options]`), '', renderCliText(command.description || '')];
1226
1489
  if (command.args?.length) {
1227
- lines.push('', 'Options:');
1490
+ lines.push('', helpHeading('Options:'));
1228
1491
  for (const arg of command.args) {
1229
1492
  const value = arg.valueName ? ` <${arg.valueName}>` : '';
1230
1493
  const description = arg.description ? ` ${arg.description}` : '';
1231
- lines.push(` --${arg.name}${value}${description}`);
1494
+ lines.push(` ${helpOption(`--${arg.name}${value}`)}${description}`);
1232
1495
  }
1233
1496
  }
1234
- lines.push('', 'Global options:', ' -f, --format json 输出 JSON;默认不传时输出精简文本', ' --json 等同于 -f json');
1497
+ lines.push(
1498
+ '',
1499
+ helpHeading('Global options:'),
1500
+ ` ${helpOption('-f, --format <format>')} 输出格式:pretty(默认)、text、json、yaml、csv、auto`,
1501
+ ` ${helpOption('--json'.padEnd(22))} 等同于 -f json`,
1502
+ );
1235
1503
  process.stdout.write(`${lines.join('\n')}\n`);
1236
1504
  }
1237
1505
 
1238
1506
  function validateCommandOptions(command, kwargs, kwargRawFlags = {}) {
1239
1507
  const allowedKeys = (command.args || []).map((arg) => normalizeKey(arg.name));
1240
1508
  const allowed = new Set(allowedKeys);
1241
- const unknown = Object.keys(kwargs).filter((key) => !allowed.has(key));
1509
+ const unknown = Object.keys(kwargs).filter((key) => (
1510
+ !allowed.has(key)
1511
+ && !(command.allowDynamicModelParams && isAllowedDynamicModelParamFlag(key))
1512
+ ));
1242
1513
  if (!unknown.length) return;
1243
1514
  const displayFlag = (key) => kwargRawFlags[key] || formatOptionName(key);
1244
1515
  const suggestions = {};
@@ -1271,23 +1542,78 @@ function validateCommandOptions(command, kwargs, kwargRawFlags = {}) {
1271
1542
  });
1272
1543
  }
1273
1544
 
1545
+ function normalizeDynamicModelParamFlags(command, kwargs = {}, kwargRawFlags = {}) {
1546
+ if (!command.allowDynamicModelParams) return;
1547
+ const allowed = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
1548
+ for (const key of Object.keys(kwargs)) {
1549
+ if (allowed.has(key) || key.includes('_')) continue;
1550
+ const flag = kwargRawFlags[key] || formatOptionName(key);
1551
+ if (!flag.startsWith('--') || !flag.includes('-')) continue;
1552
+ const normalized = flag.slice(2).replaceAll('-', '_');
1553
+ if (!normalized || normalized === key || allowed.has(normalized)) continue;
1554
+ if (kwargs[normalized] === undefined) kwargs[normalized] = kwargs[key];
1555
+ delete kwargs[key];
1556
+ if (kwargRawFlags[key] && !kwargRawFlags[normalized]) kwargRawFlags[normalized] = kwargRawFlags[key];
1557
+ }
1558
+ }
1559
+
1560
+ function isAllowedDynamicModelParamFlag(key) {
1561
+ return /^(generation|prompt|negative|seed|style|mode|cfg|guidance|aspect|image|video|audio|subject|output|response|media|safety|watermark|camera|motion|strength|steps)_/i.test(String(key || ''));
1562
+ }
1563
+
1274
1564
  function printData(data, format, meta = {}, context = {}) {
1275
- if (format === 'json') {
1565
+ const outputFormat = resolveOutputFormat(format);
1566
+ if (outputFormat === 'json') {
1276
1567
  process.stdout.write(`${JSON.stringify(createEnvelope({ status: 'success', data: normalizeJsonData(meta.command, data), meta }), null, 2)}\n`);
1277
1568
  return;
1278
1569
  }
1279
- process.stdout.write(formatTextOutput(meta.command, data, context));
1570
+ if (outputFormat === 'yaml') {
1571
+ process.stdout.write(formatYamlEnvelope(createEnvelope({ status: 'success', data: normalizeJsonData(meta.command, data), meta })));
1572
+ return;
1573
+ }
1574
+ if (outputFormat === 'csv') {
1575
+ process.stdout.write(formatCsvOutput(meta.command, data, context));
1576
+ return;
1577
+ }
1578
+ if (outputFormat === 'text') {
1579
+ process.stdout.write(formatTextOutput(meta.command, data, context));
1580
+ return;
1581
+ }
1582
+ process.stdout.write(formatPrettyOutput(meta.command, data, context));
1583
+ }
1584
+
1585
+ function printUpdateNotice(updateNotice) {
1586
+ if (!updateNotice?.message) return;
1587
+ process.stderr.write(`${updateNotice.message}\n`);
1588
+ }
1589
+
1590
+ function isStructuredOutputFormat(format) {
1591
+ try {
1592
+ return ['json', 'yaml'].includes(resolveOutputFormat(format));
1593
+ } catch {
1594
+ return false;
1595
+ }
1280
1596
  }
1281
1597
 
1282
1598
  function printError(error, format, meta = {}) {
1599
+ let outputFormat = 'text';
1600
+ try {
1601
+ outputFormat = resolveOutputFormat(format);
1602
+ } catch {
1603
+ outputFormat = 'text';
1604
+ }
1283
1605
  const payload = {
1284
1606
  type: error.type || 'error',
1285
1607
  message: error.message || String(error),
1286
1608
  ...(error.hint ? { hint: error.hint } : {}),
1287
1609
  ...(error.details ? { details: error.details } : {}),
1288
1610
  };
1289
- if (format === 'json') {
1611
+ if (outputFormat === 'json') {
1290
1612
  process.stderr.write(`${JSON.stringify(createEnvelope({ status: 'error', error: payload, meta }), null, 2)}\n`);
1613
+ } else if (outputFormat === 'yaml') {
1614
+ process.stderr.write(formatYamlEnvelope(createEnvelope({ status: 'error', error: payload, meta })));
1615
+ } else if (outputFormat === 'pretty') {
1616
+ process.stderr.write(formatPrettyError(payload));
1291
1617
  } else {
1292
1618
  process.stderr.write(formatTextError(payload));
1293
1619
  }
@@ -1330,6 +1656,7 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
1330
1656
  const { commandName: rawCommandName, kwargs, kwargRawFlags, format } = parseArgv(argv);
1331
1657
  const commandName = resolveCommandName(rawCommandName, commands);
1332
1658
  const command = commands.find((item) => item.name === commandName);
1659
+ const updateNotice = await resolveUpdateNotice(argv, commandName || rawCommandName, version);
1333
1660
  if (!command) {
1334
1661
  if (isCommandSubgroupName(rawCommandName, commands)) {
1335
1662
  const [group, subgroup] = rawCommandName.split(' ');
@@ -1344,18 +1671,27 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
1344
1671
  type: 'unknown_command',
1345
1672
  exitCode: 2,
1346
1673
  hint: unknownCommandHint(rawCommandName),
1347
- }), format, { command: rawCommandName });
1674
+ }), format, {
1675
+ command: rawCommandName,
1676
+ ...(updateNotice ? { _notice: { update: updateNotice } } : {}),
1677
+ });
1348
1678
  process.exitCode = 2;
1679
+ if (!isStructuredOutputFormat(format)) printUpdateNotice(updateNotice);
1349
1680
  return;
1350
1681
  }
1351
1682
 
1352
1683
  const startedAt = Date.now();
1353
1684
  try {
1685
+ const outputFormat = resolveOutputFormat(format);
1686
+ normalizeDynamicModelParamFlags(command, kwargs, kwargRawFlags);
1354
1687
  validateCommandOptions(command, kwargs, kwargRawFlags);
1355
1688
  const data = command.virtual === 'schema'
1356
1689
  ? buildCommandSchema(commands, kwargs, version)
1357
1690
  : await command.func({ command }, kwargs);
1358
- printData(data, format, { command: command.name, elapsedMs: Date.now() - startedAt }, buildOutputContext(command, kwargs));
1691
+ const meta = { command: command.name, elapsedMs: Date.now() - startedAt };
1692
+ if (updateNotice) meta._notice = { update: updateNotice };
1693
+ printData(data, outputFormat, meta, buildOutputContext(command, kwargs, data));
1694
+ if (outputFormat !== 'json' && outputFormat !== 'yaml') printUpdateNotice(updateNotice);
1359
1695
  } catch (error) {
1360
1696
  const cliError = error instanceof LingjingAwbCliError
1361
1697
  ? error