@lingjingai/lj-awb-cli-pre 0.4.0 → 0.4.6

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.
@@ -3,6 +3,7 @@ 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 { checkCliUpdate } from './update.js';
6
7
  import {
7
8
  formatCsvOutput,
8
9
  formatPrettyError,
@@ -186,10 +187,14 @@ function commandInvocation(commandName, kwargs = {}, options = {}) {
186
187
  return parts.join(' ');
187
188
  }
188
189
 
189
- function buildOutputContext(command, kwargs = {}) {
190
+ function buildOutputContext(command, kwargs = {}, data = {}) {
190
191
  const optionKeys = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
191
192
  const canSuggestDryRunExecution = command.name !== 'auth login';
192
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
+ }
193
198
  const isModelList = command.name === 'model image-models' || command.name === 'model video-models';
194
199
  const modelListAll = isModelList && toBool(kwargs.all);
195
200
  const modelListLimit = isModelList
@@ -204,12 +209,27 @@ function buildOutputContext(command, kwargs = {}) {
204
209
  outputKind: buildCommandWorkflow(command).outputKind,
205
210
  executeCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) ? commandInvocation(command.name, kwargs, withoutDryRun) : null,
206
211
  confirmCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) && optionKeys.has('yes')
207
- ? commandInvocation(command.name, kwargs, { omit: ['dryRun'], set: { yes: true } })
212
+ ? commandInvocation(command.name, kwargs, { omit: ['dryRun'], set: confirmSet })
208
213
  : null,
209
214
  ...(isModelList ? { listLimit: modelListLimit, moreCommand: modelListMoreCommand } : {}),
210
215
  };
211
216
  }
212
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
+
213
233
  const OUTPUT_FORMAT_ALIASES = {
214
234
  agent: 'text',
215
235
  table: 'pretty',
@@ -342,6 +362,9 @@ const RENAMED_COMMAND_HINTS = {
342
362
  'video create-batch': 'create video-batch',
343
363
  'video status': 'task video-status',
344
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',
345
368
  'video subtitle-status': 'task video-subtitle-status',
346
369
  asset: 'create asset',
347
370
  'asset match-actor': 'create asset-match-actor',
@@ -480,6 +503,8 @@ const GROUP_EXAMPLES = {
480
503
  system: [
481
504
  'lj-awb doctor',
482
505
  'lj-awb doctor --verify',
506
+ 'lj-awb update --check',
507
+ 'lj-awb update',
483
508
  'lj-awb schema --brief -f json',
484
509
  'lj-awb schema -f json',
485
510
  'lj-awb schema --domain create -f json',
@@ -487,8 +512,12 @@ const GROUP_EXAMPLES = {
487
512
  auth: [
488
513
  'lj-awb auth status',
489
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>',
490
518
  'lj-awb auth login --access-key <access-key>',
491
519
  'LINGJING_AWB_ACCESS_KEY=<access-key> lj-awb auth login',
520
+ 'lj-awb auth logout',
492
521
  'lj-awb auth clear --dry-run',
493
522
  ],
494
523
  account: [
@@ -505,6 +534,9 @@ const GROUP_EXAMPLES = {
505
534
  credits: [
506
535
  'lj-awb credits balance',
507
536
  'lj-awb credits usage --project-group-no <no> --last-hours 24',
537
+ 'lj-awb credits buy',
538
+ 'lj-awb credits redeem',
539
+ 'lj-awb credits redeem --code <code>',
508
540
  ],
509
541
  model: [
510
542
  'lj-awb model image-models --model Banana',
@@ -522,6 +554,7 @@ const GROUP_EXAMPLES = {
522
554
  'lj-awb create image-fee --model-group-code <code> --prompt "一只小狗"',
523
555
  'lj-awb create video --model-group-code <code> --prompt "镜头推进" --resource image:first_frame=./actor.png --dry-run',
524
556
  'lj-awb create video-fee --model-group-code <code> --prompt "雨夜奔跑" --duration 5',
557
+ 'lj-awb create video-super-resolution --object-name material/video-super/example.mp4 --dry-run',
525
558
  'lj-awb create subject --model-code tx --name 女主 --resource primary:./three-view.png --dry-run',
526
559
  'lj-awb create subject-wait --element-id <elementId> --wait-seconds 300',
527
560
  'lj-awb create asset --group-id <id> --platform JIMENG --file ./actor.png --name "女主正面" --dry-run',
@@ -532,6 +565,7 @@ const GROUP_EXAMPLES = {
532
565
  'lj-awb task image-status --task-id <imageTaskId>',
533
566
  'lj-awb task video-status --task-id <videoTaskId>',
534
567
  'lj-awb task video-subtitle-status --task-id <subtitleTaskId>',
568
+ 'lj-awb task video-super-resolution-status --task-id <superResolutionTaskId>',
535
569
  'lj-awb task wait --task-id <id> --task-type IMAGE_CREATE --wait-seconds 180',
536
570
  'lj-awb task records --task-record-file .awb/tasks.jsonl',
537
571
  ],
@@ -556,8 +590,11 @@ const COMMAND_REQUIRED_OPTIONS = {
556
590
  'create video-fee': ['modelGroupCode'],
557
591
  'create video': ['modelGroupCode'],
558
592
  'create video-batch': ['inputFile', 'modelGroupCode'],
593
+ 'create video-super-resolution-fee': ['objectName'],
594
+ 'create video-super-resolution': ['objectName'],
559
595
  'task video-status': ['taskId'],
560
596
  'task video-subtitle-status': ['taskId'],
597
+ 'task video-super-resolution-status': ['taskId'],
561
598
  'task wait': ['taskId', 'taskType'],
562
599
  'create asset-match-actor': ['description'],
563
600
  'create asset-groups': ['platform'],
@@ -607,6 +644,8 @@ const COMMAND_REQUIRED_ANY_OPTIONS = {
607
644
  'upload files': [['file', 'files', 'filesJson']],
608
645
  'create video-fee': [['prompt', 'resource', 'resourcesJson']],
609
646
  'create video': [['prompt', 'resource', 'resourcesJson']],
647
+ 'create video-super-resolution-fee': [['objectName']],
648
+ 'create video-super-resolution': [['objectName']],
610
649
  'create video-subtitle-removal': [['sourceTaskId']],
611
650
  'create asset-group-update': [['name', 'description', 'projectName']],
612
651
  'create asset': [['file', 'url', 'backendPath']],
@@ -672,6 +711,7 @@ const CONFIRMATION_COMMANDS = new Set([
672
711
  'create image-batch',
673
712
  'create video',
674
713
  'create video-batch',
714
+ 'create video-super-resolution',
675
715
  'create video-subtitle-removal',
676
716
  'create asset-group',
677
717
  'create asset-group-update',
@@ -687,6 +727,7 @@ const COST_COMMANDS = new Set([
687
727
  'create image-batch',
688
728
  'create video',
689
729
  'create video-batch',
730
+ 'create video-super-resolution',
690
731
  'create video-subtitle-removal',
691
732
  ]);
692
733
 
@@ -696,11 +737,13 @@ const REMOTE_WRITE_COMMANDS = new Set([
696
737
  'project create',
697
738
  'project update',
698
739
  'project ensure',
740
+ 'credits redeem',
699
741
  'upload files',
700
742
  'create image',
701
743
  'create image-batch',
702
744
  'create video',
703
745
  'create video-batch',
746
+ 'create video-super-resolution',
704
747
  'create video-subtitle-removal',
705
748
  'create asset-group',
706
749
  'create asset-group-update',
@@ -712,24 +755,28 @@ const REMOTE_WRITE_COMMANDS = new Set([
712
755
  ]);
713
756
 
714
757
  const LOCAL_STATE_WRITE_COMMANDS = new Set([
758
+ 'update',
715
759
  'auth login',
716
760
  'auth clear',
761
+ 'auth logout',
717
762
  'project use',
718
763
  'project create',
719
764
  'project ensure',
720
765
  ]);
721
766
 
722
767
  const DESTRUCTIVE_COMMANDS = new Set(['auth clear', ...ARTIFACT_DELETE_COMMANDS]);
723
- const LONG_RUNNING_COMMANDS = new Set(['task wait', 'task record-poll', 'create subject-wait', 'create subject-voice-wait']);
724
- const NETWORK_NONE_COMMANDS = new Set(['schema', 'auth status', 'auth clear', 'model input-guide', 'task records']);
768
+ const LONG_RUNNING_COMMANDS = new Set(['auth login', 'credits redeem', 'task wait', 'task record-poll', 'create subject-wait', 'create subject-voice-wait']);
769
+ const NETWORK_NONE_COMMANDS = new Set(['schema', 'auth status', 'auth clear', 'auth logout', 'credits buy', 'model input-guide', 'task records']);
725
770
  const NETWORK_CONDITIONAL_COMMANDS = new Set(['doctor', 'auth login']);
726
771
 
727
772
  const OUTPUT_KIND_BY_COMMAND = {
728
773
  doctor: 'environment_report',
774
+ update: 'update_result',
729
775
  schema: 'command_schema',
730
776
  'auth status': 'auth_status',
731
777
  'auth verify': 'auth_status',
732
778
  'auth login': 'auth_result',
779
+ 'auth logout': 'auth_result',
733
780
  'account info': 'account_summary',
734
781
  'account teams': 'team_list',
735
782
  'project list': 'project_list',
@@ -737,6 +784,8 @@ const OUTPUT_KIND_BY_COMMAND = {
737
784
  'project users': 'project_user_list',
738
785
  'credits balance': 'credits_balance',
739
786
  'credits usage': 'credits_usage',
787
+ 'credits buy': 'purchase_guide',
788
+ 'credits redeem': 'redeem_result',
740
789
  'model image-models': 'model_list',
741
790
  'model video-models': 'model_list',
742
791
  'model asset-review-models': 'asset_review_model_list',
@@ -751,9 +800,12 @@ const OUTPUT_KIND_BY_COMMAND = {
751
800
  'create video-fee': 'fee_estimate',
752
801
  'create video': 'task_submission',
753
802
  'create video-batch': 'batch_task_submission',
803
+ 'create video-super-resolution-fee': 'fee_estimate',
804
+ 'create video-super-resolution': 'task_submission',
754
805
  'task video-status': 'task_status',
755
806
  'create video-subtitle-removal': 'subtitle_task_submission',
756
807
  'task video-subtitle-status': 'subtitle_task_status',
808
+ 'task video-super-resolution-status': 'task_status',
757
809
  'task list': 'task_list',
758
810
  'task wait': 'task_status',
759
811
  'task records': 'local_task_records',
@@ -829,6 +881,8 @@ const PREFLIGHTS_BY_COMMAND = {
829
881
  'create image-batch': ['doctor --verify', 'model image-models', 'model options', 'model create-spec', 'prepare JSONL input', 'create image-batch --dry-run'],
830
882
  'create video': ['doctor --verify', 'model video-models', 'model options', 'model create-spec', 'confirm missing key params', 'create video-fee', 'create video --dry-run'],
831
883
  'create video-batch': ['doctor --verify', 'model video-models', 'model options', 'model create-spec', 'prepare JSONL input', 'create video-batch --dry-run'],
884
+ 'create video-super-resolution-fee': ['doctor --verify', 'create video-super-resolution-fee --object-name <objectName>'],
885
+ 'create video-super-resolution': ['doctor --verify', 'create video-super-resolution --object-name <objectName> --dry-run'],
832
886
  'create subject': ['doctor --verify', 'create subject --model-code tx|vidu --dry-run'],
833
887
  'create subject-batch': ['doctor --verify', 'prepare JSONL input with modelCode', 'create subject-batch --dry-run'],
834
888
  'create video-subtitle-removal': ['doctor --verify', 'create video-subtitle-removal --source-task-id <videoTaskId> --dry-run'],
@@ -891,9 +945,10 @@ function buildCommandSafety(command, optionKeys) {
891
945
  const costsPoints = COST_COMMANDS.has(name);
892
946
  const destructive = DESTRUCTIVE_COMMANDS.has(name);
893
947
  const supportsDryRun = optionKeys.has('dryRun');
948
+ const updateInstall = name === 'update';
894
949
  return {
895
- safeToAutoRun: !(remoteWrite || localStateWrite || requiresConfirmation || costsPoints || destructive),
896
- network: commandNetworkMode(name),
950
+ safeToAutoRun: !(remoteWrite || localStateWrite || requiresConfirmation || costsPoints || destructive || updateInstall),
951
+ network: updateInstall ? 'conditional' : commandNetworkMode(name),
897
952
  remoteWrite,
898
953
  localStateWrite,
899
954
  costsPoints,
@@ -915,6 +970,9 @@ function buildCommandWorkflow(command) {
915
970
  if (command.name === 'create video') {
916
971
  next.push('读取 data.taskId 和 data.nextCommand', '立即运行响应里的 nextCommand,或 task wait --task-type VIDEO_GROUP 等待结果');
917
972
  }
973
+ if (command.name === 'create video-super-resolution') {
974
+ next.push('读取 data.taskId 和 data.nextCommand', '运行响应里的 task video-super-resolution-status 命令查询结果');
975
+ }
918
976
  if (command.name === 'create video-subtitle-removal') {
919
977
  next.push('读取 data.taskId 和 data.nextCommand', '运行 task video-subtitle-status 或响应里的 nextCommand 等待结果');
920
978
  }
@@ -925,7 +983,7 @@ function buildCommandWorkflow(command) {
925
983
  next.push('读取 data.voiceRecordId / data.externalId', 'externalId 为空时立刻运行 create subject-voice-wait 获取 nextVoiceArg');
926
984
  }
927
985
  if (command.name === 'create asset') {
928
- next.push('读取 data.assetPath', '若后端返回审核 taskId,再运行 task wait --task-type ASSET_REGISTER');
986
+ next.push('读取 data.assetType / data.assetPath', 'data.conversionRequired=true 时先确认是否转码;正式执行可交互确认或追加 --auto-convert', '若后端返回审核 taskId,再运行 task wait --task-type ASSET_REGISTER');
929
987
  }
930
988
  if (command.name === 'model image-models' || command.name === 'model video-models') {
931
989
  next.push('读取 data.models[] 得到全部候选;用户给了模型口语名时保留同族全部候选,不只取第一个');
@@ -1134,10 +1192,11 @@ function buildAgentContract() {
1134
1192
  workflowPolicy: [
1135
1193
  '模型口语名命中后必须先展示候选模型、真实参数取值和资源能力,并在清单后 STOP 等用户选择或明确授权默认;没有完成用户可见候选清单和确认前,不得代选默认模型 / 参数,也不得进入 create-spec、fee、dry-run 或 create。',
1136
1194
  '模型探索阶段只读 model list / options / create-spec;fee 只在用户确认关键参数后跑一次。',
1195
+ 'model options.params[] 中带 cliArg/genericModelParam 的新增模型配置参数可通过 --model-param key=value、--model-params-json 或同名下划线长参数传入;defaultValue 只能展示为候选默认,不能静默代选。',
1137
1196
  'supportsDryRun=true 的写入 / 扣费命令先 dry-run,确认后 yes;不要把 dry-run 当参数探索工具反复跑。',
1138
1197
  '用户给出多条同模型同参数任务时优先 batch + task-record-file,不要单条循环 create。',
1139
1198
  '命令返回 nextCommand / nextRefSubject / nextVoiceArg 时优先复用返回值,不手拼等价命令。',
1140
- '素材加白平台先通过 model asset-review-models 或用户明确输入确定;create asset-* 必须显式传 --platform,不要依赖默认平台。',
1199
+ '素材加白平台先通过 model asset-review-models 或用户明确输入确定;create asset-* 必须显式传 --platform,不要依赖默认平台。create asset 会自动判断 assetType=Image/Video/Audio,本地文件会用 ffprobe 校验图片/视频尺寸、视频时长/FPS/像素数和音频时长;不合法素材正式执行时会询问是否转码,非交互场景可追加 --auto-convert;macOS + Homebrew 环境缺失时 CLI 会自动安装 ffmpeg。',
1141
1200
  '旧根域 image / video / asset / subject 已移除;只使用 create / task / artifact 等 schema 暴露的 domain。',
1142
1201
  ],
1143
1202
  canonicalFields: {
@@ -1190,7 +1249,7 @@ function buildAgentContract() {
1190
1249
  jsonCommand: `${commandPrefix()} model options --model-group-code <code> -f json`,
1191
1250
  purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
1192
1251
  useBefore: ['model create-spec', 'create image-fee', 'create image', 'create video-fee', 'create video'],
1193
- 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'],
1252
+ 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'],
1194
1253
  },
1195
1254
  modelCreateSpec: {
1196
1255
  command: `${commandPrefix()} model create-spec --model-group-code <code>`,
@@ -1210,6 +1269,7 @@ function buildAgentContract() {
1210
1269
  taskTypes: {
1211
1270
  image: 'IMAGE_CREATE',
1212
1271
  video: 'VIDEO_GROUP',
1272
+ videoSuperResolution: 'VIDEO_SUPER_RESOLUTION',
1213
1273
  subtitleRemoval: 'VIDEO_SUBTITLE_REMOVAL',
1214
1274
  assetRegister: 'ASSET_REGISTER',
1215
1275
  },
@@ -1452,7 +1512,10 @@ function printCommandHelp(command) {
1452
1512
  function validateCommandOptions(command, kwargs, kwargRawFlags = {}) {
1453
1513
  const allowedKeys = (command.args || []).map((arg) => normalizeKey(arg.name));
1454
1514
  const allowed = new Set(allowedKeys);
1455
- const unknown = Object.keys(kwargs).filter((key) => !allowed.has(key));
1515
+ const unknown = Object.keys(kwargs).filter((key) => (
1516
+ !allowed.has(key)
1517
+ && !(command.allowDynamicModelParams && isAllowedDynamicModelParamFlag(key))
1518
+ ));
1456
1519
  if (!unknown.length) return;
1457
1520
  const displayFlag = (key) => kwargRawFlags[key] || formatOptionName(key);
1458
1521
  const suggestions = {};
@@ -1485,6 +1548,25 @@ function validateCommandOptions(command, kwargs, kwargRawFlags = {}) {
1485
1548
  });
1486
1549
  }
1487
1550
 
1551
+ function normalizeDynamicModelParamFlags(command, kwargs = {}, kwargRawFlags = {}) {
1552
+ if (!command.allowDynamicModelParams) return;
1553
+ const allowed = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
1554
+ for (const key of Object.keys(kwargs)) {
1555
+ if (allowed.has(key) || key.includes('_')) continue;
1556
+ const flag = kwargRawFlags[key] || formatOptionName(key);
1557
+ if (!flag.startsWith('--') || !flag.includes('-')) continue;
1558
+ const normalized = flag.slice(2).replaceAll('-', '_');
1559
+ if (!normalized || normalized === key || allowed.has(normalized)) continue;
1560
+ if (kwargs[normalized] === undefined) kwargs[normalized] = kwargs[key];
1561
+ delete kwargs[key];
1562
+ if (kwargRawFlags[key] && !kwargRawFlags[normalized]) kwargRawFlags[normalized] = kwargRawFlags[key];
1563
+ }
1564
+ }
1565
+
1566
+ function isAllowedDynamicModelParamFlag(key) {
1567
+ 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 || ''));
1568
+ }
1569
+
1488
1570
  function printData(data, format, meta = {}, context = {}) {
1489
1571
  const outputFormat = resolveOutputFormat(format);
1490
1572
  if (outputFormat === 'json') {
@@ -1506,6 +1588,19 @@ function printData(data, format, meta = {}, context = {}) {
1506
1588
  process.stdout.write(formatPrettyOutput(meta.command, data, context));
1507
1589
  }
1508
1590
 
1591
+ function printUpdateNotice(updateNotice) {
1592
+ if (!updateNotice?.message) return;
1593
+ process.stderr.write(`${updateNotice.message}\n`);
1594
+ }
1595
+
1596
+ function isStructuredOutputFormat(format) {
1597
+ try {
1598
+ return ['json', 'yaml'].includes(resolveOutputFormat(format));
1599
+ } catch {
1600
+ return false;
1601
+ }
1602
+ }
1603
+
1509
1604
  function printError(error, format, meta = {}) {
1510
1605
  let outputFormat = 'text';
1511
1606
  try {
@@ -1567,6 +1662,7 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
1567
1662
  const { commandName: rawCommandName, kwargs, kwargRawFlags, format } = parseArgv(argv);
1568
1663
  const commandName = resolveCommandName(rawCommandName, commands);
1569
1664
  const command = commands.find((item) => item.name === commandName);
1665
+ const updateNotice = await resolveUpdateNotice(argv, commandName || rawCommandName, version);
1570
1666
  if (!command) {
1571
1667
  if (isCommandSubgroupName(rawCommandName, commands)) {
1572
1668
  const [group, subgroup] = rawCommandName.split(' ');
@@ -1581,19 +1677,27 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
1581
1677
  type: 'unknown_command',
1582
1678
  exitCode: 2,
1583
1679
  hint: unknownCommandHint(rawCommandName),
1584
- }), format, { command: rawCommandName });
1680
+ }), format, {
1681
+ command: rawCommandName,
1682
+ ...(updateNotice ? { _notice: { update: updateNotice } } : {}),
1683
+ });
1585
1684
  process.exitCode = 2;
1685
+ if (!isStructuredOutputFormat(format)) printUpdateNotice(updateNotice);
1586
1686
  return;
1587
1687
  }
1588
1688
 
1589
1689
  const startedAt = Date.now();
1590
1690
  try {
1591
1691
  const outputFormat = resolveOutputFormat(format);
1692
+ normalizeDynamicModelParamFlags(command, kwargs, kwargRawFlags);
1592
1693
  validateCommandOptions(command, kwargs, kwargRawFlags);
1593
1694
  const data = command.virtual === 'schema'
1594
1695
  ? buildCommandSchema(commands, kwargs, version)
1595
1696
  : await command.func({ command }, kwargs);
1596
- printData(data, outputFormat, { command: command.name, elapsedMs: Date.now() - startedAt }, buildOutputContext(command, kwargs));
1697
+ const meta = { command: command.name, elapsedMs: Date.now() - startedAt };
1698
+ if (updateNotice) meta._notice = { update: updateNotice };
1699
+ printData(data, outputFormat, meta, buildOutputContext(command, kwargs, data));
1700
+ if (outputFormat !== 'json' && outputFormat !== 'yaml') printUpdateNotice(updateNotice);
1597
1701
  } catch (error) {
1598
1702
  const cliError = error instanceof LingjingAwbCliError
1599
1703
  ? error