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

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,7 +3,15 @@ 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 {
7
+ formatCsvOutput,
8
+ formatPrettyError,
9
+ formatPrettyOutput,
10
+ formatTextError,
11
+ formatTextOutput,
12
+ formatYamlEnvelope,
13
+ normalizeJsonData,
14
+ } from './output.js';
7
15
 
8
16
  function normalizeKey(value) {
9
17
  return String(value || '').replace(/-([a-z])/g, (_, char) => char.toUpperCase());
@@ -96,6 +104,57 @@ function renderCliText(value) {
96
104
  return String(value ?? '').replace(/\blj-awb\b/g, commandPrefix());
97
105
  }
98
106
 
107
+ function shouldColorOutput() {
108
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === '0') return false;
109
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === 'false') return false;
110
+ if (String(process.env.FORCE_COLOR || '').trim() && process.env.FORCE_COLOR !== '0') return true;
111
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === '1') return true;
112
+ if (String(process.env.LINGJING_AWB_COLOR || '').toLowerCase() === 'true') return true;
113
+ if (process.env.NO_COLOR) return false;
114
+ return Boolean(process.stdout.isTTY);
115
+ }
116
+
117
+ function colorText(value, code) {
118
+ const text = String(value ?? '');
119
+ return shouldColorOutput() && text ? `\u001b[${code}m${text}\u001b[0m` : text;
120
+ }
121
+
122
+ const HELP_COLOR = {
123
+ title: '1;38;5;39',
124
+ accent: '1;38;5;208',
125
+ heading: '1;38;5;75',
126
+ command: '38;5;82',
127
+ group: '38;5;215',
128
+ muted: '2;38;5;245',
129
+ option: '38;5;111',
130
+ };
131
+
132
+ function helpTitle(value) {
133
+ if (!shouldColorOutput()) return String(value ?? '');
134
+ return `${colorText('◆', HELP_COLOR.accent)} ${colorText(value, HELP_COLOR.title)}`;
135
+ }
136
+
137
+ function helpHeading(value) {
138
+ if (!shouldColorOutput()) return String(value ?? '');
139
+ return `${colorText('▸', HELP_COLOR.accent)} ${colorText(value, HELP_COLOR.heading)}`;
140
+ }
141
+
142
+ function helpCommand(value) {
143
+ return colorText(value, HELP_COLOR.command);
144
+ }
145
+
146
+ function helpGroup(value) {
147
+ return colorText(value, HELP_COLOR.group);
148
+ }
149
+
150
+ function helpMuted(value) {
151
+ return colorText(value, HELP_COLOR.muted);
152
+ }
153
+
154
+ function helpOption(value) {
155
+ return colorText(value, HELP_COLOR.option);
156
+ }
157
+
99
158
  function shellArg(value) {
100
159
  const text = String(value ?? '');
101
160
  return /^[A-Za-z0-9_./:=@,+-]+$/.test(text) ? text : JSON.stringify(text);
@@ -141,6 +200,8 @@ function buildOutputContext(command, kwargs = {}) {
141
200
  : null;
142
201
  return {
143
202
  jsonCommand: `${commandInvocation(command.name, kwargs)} -f json`,
203
+ textCommand: `${commandInvocation(command.name, kwargs)} -f text`,
204
+ outputKind: buildCommandWorkflow(command).outputKind,
144
205
  executeCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) ? commandInvocation(command.name, kwargs, withoutDryRun) : null,
145
206
  confirmCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) && optionKeys.has('yes')
146
207
  ? commandInvocation(command.name, kwargs, { omit: ['dryRun'], set: { yes: true } })
@@ -149,6 +210,28 @@ function buildOutputContext(command, kwargs = {}) {
149
210
  };
150
211
  }
151
212
 
213
+ const OUTPUT_FORMAT_ALIASES = {
214
+ agent: 'text',
215
+ table: 'pretty',
216
+ human: 'pretty',
217
+ compact: 'text',
218
+ 'compact-text': 'text',
219
+ };
220
+
221
+ const SUPPORTED_OUTPUT_FORMATS = new Set(['auto', 'pretty', 'text', 'json', 'yaml', 'csv']);
222
+
223
+ function resolveOutputFormat(format) {
224
+ const raw = String(format || 'pretty').trim().toLowerCase();
225
+ const normalized = OUTPUT_FORMAT_ALIASES[raw] || raw;
226
+ if (normalized === 'auto') return process.stdout.isTTY ? 'pretty' : 'text';
227
+ if (SUPPORTED_OUTPUT_FORMATS.has(normalized)) return normalized;
228
+ throw new LingjingAwbCliError(`未知输出格式:${format}`, {
229
+ type: 'argument_error',
230
+ exitCode: 2,
231
+ hint: '可用格式:pretty、text、json、yaml、csv、auto。Agent 建议使用 -f text;严格脚本解析再用 -f json。',
232
+ });
233
+ }
234
+
152
235
  function assignKwarg(kwargs, key, value) {
153
236
  if (kwargs[key] === undefined) {
154
237
  kwargs[key] = value;
@@ -165,7 +248,7 @@ function parseArgv(argv) {
165
248
  const commandParts = [];
166
249
  const kwargs = {};
167
250
  const kwargRawFlags = {};
168
- let format = 'text';
251
+ let format = 'pretty';
169
252
 
170
253
  for (let index = 0; index < argv.length; index += 1) {
171
254
  const token = argv[index];
@@ -232,13 +315,15 @@ const VIRTUAL_COMMANDS = [
232
315
  '输出机器可读命令 schema,供 Agent / 脚本理解命令、参数和示例',
233
316
  '',
234
317
  'Examples:',
318
+ ' lj-awb schema --brief -f json',
235
319
  ' lj-awb schema -f json',
236
320
  ' lj-awb schema --domain create -f json',
237
321
  ' lj-awb schema --domain create --command image -f json',
238
322
  '',
239
- 'Hint: Agent 应优先读取 schema,而不是解析自然语言 help。',
323
+ 'Hint: Agent 先读 --brief 掌握能力地图;执行具体命令前再读精确 schema。',
240
324
  ].join('\n'),
241
325
  args: [
326
+ { name: 'brief', description: '输出面向 Agent 的轻量能力摘要,不展开每个参数' },
242
327
  { name: 'domain', valueName: 'name', description: '按命令域过滤,如 create / task / artifact / system' },
243
328
  { name: 'command', valueName: 'name', description: '按子命令过滤,如 image / video / wait / doctor' },
244
329
  ],
@@ -395,6 +480,7 @@ const GROUP_EXAMPLES = {
395
480
  system: [
396
481
  'lj-awb doctor',
397
482
  'lj-awb doctor --verify',
483
+ 'lj-awb schema --brief -f json',
398
484
  'lj-awb schema -f json',
399
485
  'lj-awb schema --domain create -f json',
400
486
  ],
@@ -844,7 +930,7 @@ function buildCommandWorkflow(command) {
844
930
  if (command.name === 'model image-models' || command.name === 'model video-models') {
845
931
  next.push('读取 data.models[] 得到全部候选;用户给了模型口语名时保留同族全部候选,不只取第一个');
846
932
  next.push('对每个候选运行 model options --model-group-code <modelGroupCode> 查看真实参数和素材约束');
847
- next.push('先向用户展示候选模型 + 参数取值 + 资源能力,再推荐模型或进入 fee/dry-run');
933
+ next.push('向用户展示候选模型 + 参数取值 + 资源能力后 STOP;等待用户选择或明确授权默认,未确认不得进入 create-spec/fee/dry-run/create');
848
934
  }
849
935
  if (command.name === 'model asset-review-models') {
850
936
  next.push('读取 data.models[].platform', '用 create asset-groups --platform <platform> 查重或创建素材组');
@@ -868,11 +954,167 @@ function buildCommandWorkflow(command) {
868
954
  };
869
955
  }
870
956
 
957
+ function buildDomainSummaries(commands) {
958
+ const domains = new Map();
959
+ for (const command of commands) {
960
+ const group = commandGroup(command);
961
+ domains.set(group, {
962
+ name: group,
963
+ description: GROUP_DESCRIPTIONS[group] || '',
964
+ commandCount: (domains.get(group)?.commandCount || 0) + 1,
965
+ });
966
+ }
967
+ return Array.from(domains.values()).sort((a, b) => a.name.localeCompare(b.name));
968
+ }
969
+
970
+ function requiredOptionsForCommand(command) {
971
+ const requiredOptions = new Set();
972
+ if (commandGroup(command) === 'artifact') requiredOptions.add('projectId');
973
+ for (const key of COMMAND_REQUIRED_OPTIONS[command.name] || []) requiredOptions.add(key);
974
+ return [...requiredOptions];
975
+ }
976
+
977
+ function requiredAnyOptionsForCommand(command) {
978
+ return COMMAND_REQUIRED_ANY_OPTIONS[command.name] || [];
979
+ }
980
+
981
+ function buildCommandSchemaEntry(command) {
982
+ const requiredOptions = requiredOptionsForCommand(command);
983
+ const requiredAnyOptions = requiredAnyOptionsForCommand(command);
984
+ const optionKeys = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
985
+ return {
986
+ name: command.name,
987
+ domain: commandGroup(command),
988
+ command: commandSubcommand(command),
989
+ invocation: `${commandPrefix()} ${command.name}`,
990
+ helpCommand: `${commandPrefix()} ${command.name} -h`,
991
+ summary: summaryOf(command),
992
+ description: renderCliText(summaryOf(command)),
993
+ examples: extractExamples(command).map(renderCliText),
994
+ jsonExamples: extractExamples(command).map(asJsonExample),
995
+ requiredOptions,
996
+ requiredAnyOptions,
997
+ safety: buildCommandSafety(command, optionKeys),
998
+ workflow: buildCommandWorkflow(command),
999
+ options: (command.args || []).map((arg) => {
1000
+ const key = normalizeKey(arg.name);
1001
+ return {
1002
+ name: arg.name,
1003
+ key,
1004
+ flag: formatOptionName(arg.name),
1005
+ valueName: arg.valueName || null,
1006
+ required: requiredOptions.includes(key),
1007
+ requiredAnyGroup: requiredAnyOptions.find((group) => group.includes(key)) || null,
1008
+ description: arg.description || '',
1009
+ };
1010
+ }),
1011
+ };
1012
+ }
1013
+
1014
+ function namesWhere(entries, predicate) {
1015
+ return entries.filter(predicate).map((entry) => entry.name);
1016
+ }
1017
+
1018
+ function buildAgentBrief(commands, version = 'unknown') {
1019
+ const contract = buildAgentContract();
1020
+ const entries = commands.map(buildCommandSchemaEntry);
1021
+ const domains = buildDomainSummaries(commands).map((domain) => ({
1022
+ ...domain,
1023
+ commands: entries
1024
+ .filter((entry) => entry.domain === domain.name)
1025
+ .map((entry) => entry.name),
1026
+ }));
1027
+ return {
1028
+ schemaVersion: 1,
1029
+ kind: 'agent_brief',
1030
+ cli: {
1031
+ name: 'lj-awb',
1032
+ version,
1033
+ commandPrefix: commandPrefix(),
1034
+ },
1035
+ commandCount: entries.length,
1036
+ domains,
1037
+ outputPolicy: {
1038
+ agentRecommendedOutputFormat: contract.agentRecommendedOutputFormat,
1039
+ scriptRecommendedOutputFormat: contract.scriptRecommendedOutputFormat,
1040
+ textFlag: contract.textFlag,
1041
+ jsonFlag: contract.jsonFlag,
1042
+ policy: contract.outputPolicy,
1043
+ },
1044
+ statePolicy: contract.statePolicy,
1045
+ workflowPolicy: contract.workflowPolicy,
1046
+ safetyPolicy: {
1047
+ confirmationPolicy: contract.confirmationPolicy,
1048
+ dryRunPolicy: contract.dryRunPolicy,
1049
+ remoteWriteCount: namesWhere(entries, (entry) => entry.safety.remoteWrite).length,
1050
+ costsPointsCount: namesWhere(entries, (entry) => entry.safety.costsPoints).length,
1051
+ localStateWriteCount: namesWhere(entries, (entry) => entry.safety.localStateWrite).length,
1052
+ destructiveCount: namesWhere(entries, (entry) => entry.safety.destructive).length,
1053
+ longRunningCount: namesWhere(entries, (entry) => entry.safety.longRunning).length,
1054
+ },
1055
+ commandClasses: {
1056
+ costsPoints: namesWhere(entries, (entry) => entry.safety.costsPoints),
1057
+ remoteWrite: namesWhere(entries, (entry) => entry.safety.remoteWrite),
1058
+ localStateWrite: namesWhere(entries, (entry) => entry.safety.localStateWrite),
1059
+ destructive: namesWhere(entries, (entry) => entry.safety.destructive),
1060
+ longRunning: namesWhere(entries, (entry) => entry.safety.longRunning),
1061
+ safeRead: namesWhere(entries, (entry) => entry.safety.safeToAutoRun && entry.safety.network !== 'none'),
1062
+ offlineRead: namesWhere(entries, (entry) => entry.safety.safeToAutoRun && entry.safety.network === 'none'),
1063
+ },
1064
+ routing: [
1065
+ {
1066
+ intent: '环境、认证、账号、团队、项目组、积分',
1067
+ start: ['doctor --verify', 'account info', 'project current', 'credits balance'],
1068
+ rule: '只读查询可自动运行;切换团队、切换/创建/更新项目组和清空认证按 safety dry-run/confirm 处理。',
1069
+ },
1070
+ {
1071
+ intent: '模型发现和创作任务',
1072
+ start: ['model image-models', 'model video-models', 'model options', 'model create-spec'],
1073
+ rule: '先展示候选模型、真实参数和资源能力并 STOP;用户选择或明确授权默认后,关键参数确认,再只跑一次 fee、dry-run 和最终 --yes。',
1074
+ },
1075
+ {
1076
+ intent: '上传、主体、音色、素材加白',
1077
+ start: ['upload files', 'create subject-list', 'create subject-voice-list', 'model asset-review-models'],
1078
+ rule: '复用已上传 backendPath、已有主体/音色/素材组;素材加白平台来自 model asset-review-models 或用户明确选择。',
1079
+ },
1080
+ {
1081
+ intent: '异步任务和批量恢复',
1082
+ start: ['task wait', 'task record-poll', 'task records'],
1083
+ rule: '优先复用 create 返回的 nextCommand;批量任务使用 task-record-file,失败项单独重试。',
1084
+ },
1085
+ {
1086
+ intent: '最终产物 artifact',
1087
+ start: ['artifact script', 'artifact asset', 'artifact video', 'artifact clip'],
1088
+ rule: 'artifact 使用 project-id,不是 project-group-no;写入/删除/导入先 dry-run,再一次性确认写入面。',
1089
+ },
1090
+ ],
1091
+ lookup: {
1092
+ preciseSchema: `${commandPrefix()} schema --domain <domain> --command <command> -f json`,
1093
+ fullSchema: `${commandPrefix()} schema -f json`,
1094
+ commandHelp: `${commandPrefix()} <domain> <command> -h`,
1095
+ modelInputGuide: contract.modelInputGuide.textCommand,
1096
+ schemaPolicy: contract.schemaPolicy,
1097
+ },
1098
+ canonicalFields: contract.canonicalFields,
1099
+ resourceShortcut: contract.resourceShortcut,
1100
+ resourcesJson: contract.resourcesJson,
1101
+ modelCandidatePresentation: contract.modelCandidatePresentation,
1102
+ taskTypes: contract.taskTypes,
1103
+ exitCodes: contract.exitCodes,
1104
+ };
1105
+ }
1106
+
871
1107
  function buildAgentContract() {
872
1108
  return {
873
- defaultOutputFormat: 'compact-text',
1109
+ defaultOutputFormat: 'pretty',
1110
+ humanDefaultOutputFormat: 'pretty',
1111
+ agentRecommendedOutputFormat: 'text',
1112
+ agentCompactOutputFormat: 'text',
1113
+ scriptRecommendedOutputFormat: 'json',
874
1114
  jsonFlag: '-f json',
875
- outputPolicy: '默认输出精简 key=value 文本,只保留决策字段;需要稳定 JSON、嵌套结构或脚本严格解析时显式传 -f json 或 --json。JSON envelope 字段已做归一化,见 canonicalFields。',
1115
+ textFlag: '-f text',
1116
+ outputFormats: ['pretty', 'text', 'json', 'yaml', 'csv', 'auto'],
1117
+ outputPolicy: '默认输出 pretty 人类友好视图,包含标题、摘要表格、列表表格和下一步建议;Agent 推荐显式传 -f text 读取稳定的分区 key=value 输出;脚本严格解析或需要完整嵌套结构时再传 -f json 或 --json。JSON envelope 字段已做归一化,见 canonicalFields。',
876
1118
  statePolicy: {
877
1119
  purpose: 'Agent 应在同一轮 / 同一项目中维护 AWB 上下文账本,减少重复查询和重复提交。',
878
1120
  cacheKeys: [
@@ -890,7 +1132,7 @@ function buildAgentContract() {
890
1132
  invalidation: '用户切换账号、团队、项目组、模型、素材、prompt 或关键参数后,只刷新受影响的缓存;不要重复跑未变化的 model options / create-spec / fee。',
891
1133
  },
892
1134
  workflowPolicy: [
893
- '模型口语名命中后必须先展示候选模型、真实参数取值和资源能力;没有完成用户可见候选清单前,不得代选默认模型 / 参数,也不得进入 fee 或 dry-run。',
1135
+ '模型口语名命中后必须先展示候选模型、真实参数取值和资源能力,并在清单后 STOP 等用户选择或明确授权默认;没有完成用户可见候选清单和确认前,不得代选默认模型 / 参数,也不得进入 create-spec、fee、dry-runcreate。',
894
1136
  '模型探索阶段只读 model list / options / create-spec;fee 只在用户确认关键参数后跑一次。',
895
1137
  'supportsDryRun=true 的写入 / 扣费命令先 dry-run,确认后 yes;不要把 dry-run 当参数探索工具反复跑。',
896
1138
  '用户给出多条同模型同参数任务时优先 batch + task-record-file,不要单条循环 create。',
@@ -909,7 +1151,7 @@ function buildAgentContract() {
909
1151
  { canonical: 'pointCost', aliases: ['points', 'point', 'costPoint', 'estimatePoint'], meaning: '本次任务预计 / 实际消耗积分' },
910
1152
  ],
911
1153
  },
912
- schemaPolicy: 'schema 是机器契约,只给摘要、参数 key、安全规则和示例;查看长帮助用 schema.commands[].helpCommand 或 lj-awb <domain> <command> -h。',
1154
+ schemaPolicy: 'Agent 先读 schema --brief 建立能力地图;执行具体命令前再读精确 schema --domain <domain> --command <command>。完整 schema -f json 只用于覆盖校验、脚本生成或调试 schema 本身;查看长帮助用 schema.commands[].helpCommand 或 lj-awb <domain> <command> -h。',
913
1155
  confirmationPolicy: 'schema.commands[].safety.requiresConfirmation=true 的命令,Agent 必须先向用户确认,再追加 --yes。',
914
1156
  dryRunPolicy: 'schema.commands[].safety.supportsDryRun=true 的写入 / 扣费命令,正式执行前优先跑 --dry-run。',
915
1157
  resourceShortcut: {
@@ -936,27 +1178,33 @@ function buildAgentContract() {
936
1178
  modelCandidatePresentation: {
937
1179
  purpose: '把 model image-models / video-models 与每个候选的 model options 转成用户可见清单,避免 Agent 内部看完 options 后直接代选。',
938
1180
  trigger: '用户问有哪些模型、给出口语名、或模型列表返回候选时都触发;即使只有一个候选也要展示真实可选项。',
1181
+ gateType: 'hard_stop',
1182
+ stopAfterPresentation: true,
1183
+ resumeCondition: '用户选择模型和关键参数,或明确说“按默认 / 你来选”。',
939
1184
  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'],
1185
+ 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
1186
  },
942
1187
  modelOptions: {
943
1188
  command: `${commandPrefix()} model options --model-group-code <code>`,
1189
+ textCommand: `${commandPrefix()} model options --model-group-code <code> -f text`,
944
1190
  jsonCommand: `${commandPrefix()} model options --model-group-code <code> -f json`,
945
- purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。',
1191
+ purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
946
1192
  useBefore: ['model create-spec', 'create image-fee', 'create image', 'create video-fee', 'create video'],
947
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'],
948
1194
  },
949
1195
  modelCreateSpec: {
950
1196
  command: `${commandPrefix()} model create-spec --model-group-code <code>`,
1197
+ textCommand: `${commandPrefix()} model create-spec --model-group-code <code> -f text`,
951
1198
  jsonCommand: `${commandPrefix()} model create-spec --model-group-code <code> -f json`,
952
- purpose: '查看指定模型如何组织 create 命令:输入模式、素材绑定规则、示例和创建前检查项。',
1199
+ purpose: '查看指定模型如何组织 create 命令:输入模式、素材绑定规则、示例和创建前检查项。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
953
1200
  useBefore: ['create image-fee', 'create image', 'create video-fee', 'create video'],
954
1201
  keyFields: ['inputRequirement', 'supportedIntents', 'validationRules', 'agentGuidance', 'preflight', 'examples'],
955
1202
  },
956
1203
  modelInputGuide: {
957
1204
  command: `${commandPrefix()} model input-guide`,
1205
+ textCommand: `${commandPrefix()} model input-guide -f text`,
958
1206
  jsonCommand: `${commandPrefix()} model input-guide -f json`,
959
- purpose: '查看跨模型统一创建参数、resources 字段和素材绑定规则。',
1207
+ purpose: '查看跨模型统一创建参数、resources 字段和素材绑定规则。Agent 默认读 textCommand;脚本严格解析时读 jsonCommand。',
960
1208
  useBefore: ['model options', 'model create-spec'],
961
1209
  },
962
1210
  taskTypes: {
@@ -982,17 +1230,10 @@ function buildAgentContract() {
982
1230
  }
983
1231
 
984
1232
  function buildCommandSchema(commands, kwargs = {}, version = 'unknown') {
1233
+ if (toBool(kwargs.brief)) return buildAgentBrief(commands, version);
985
1234
  const domainFilter = String(kwargs.domain ?? '').trim();
986
1235
  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
- }
1236
+ const domains = buildDomainSummaries(commands);
996
1237
  const filteredCommands = commands
997
1238
  .filter((command) => !domainFilter || commandGroup(command) === domainFilter)
998
1239
  .filter((command) => !commandFilter || command.name === commandFilter || commandSubcommand(command) === commandFilter);
@@ -1016,49 +1257,16 @@ function buildCommandSchema(commands, kwargs = {}, version = 'unknown') {
1016
1257
  `${commandPrefix()} <domain> <command> -h`,
1017
1258
  ],
1018
1259
  globalOptions: [
1019
- { flag: '-f, --format json', key: 'format', description: '输出 JSON;默认不传时输出精简文本' },
1260
+ { flag: '-f, --format <format>', key: 'format', description: '输出格式:pretty(默认)、text、json、yaml、csv、auto' },
1020
1261
  { flag: '--json', key: 'format', value: 'json', description: '等同于 -f json' },
1021
1262
  { flag: '-h, --help', key: 'help', description: '查看帮助' },
1022
1263
  { flag: '-v, --version', key: 'version', description: '查看版本' },
1023
1264
  ],
1024
1265
  agentContract: buildAgentContract(),
1025
- domains: Array.from(domains.values())
1266
+ domains: domains
1026
1267
  .filter((domain) => !shouldFilterDomains || matchedDomains.has(domain.name))
1027
1268
  .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
- }),
1269
+ commands: filteredCommands.map(buildCommandSchemaEntry),
1062
1270
  };
1063
1271
  }
1064
1272
 
@@ -1071,43 +1279,44 @@ function printRootHelp(commands, version) {
1071
1279
  }
1072
1280
 
1073
1281
  const lines = [
1074
- `lj-awb v${version}`,
1282
+ helpTitle(`lj-awb v${version}`),
1075
1283
  '',
1076
- '灵境 AWB 命令行工具。默认输出精简文本;需要 JSON 时传 -f json。',
1284
+ '灵境 AWB 命令行工具。默认输出人类友好的 pretty 视图;Agent 建议显式传 -f text,脚本严格解析用 -f json。',
1077
1285
  '',
1078
- 'Usage:',
1079
- ` ${commandPrefix()} <domain> <command> [options]`,
1080
- ` ${commandPrefix()} <command> [options]`,
1081
- ` ${commandPrefix()} <domain> -h`,
1082
- ` ${commandPrefix()} <domain> <command> -h`,
1286
+ helpHeading('Usage:'),
1287
+ helpCommand(` ${commandPrefix()} <domain> <command> [options]`),
1288
+ helpCommand(` ${commandPrefix()} <command> [options]`),
1289
+ helpCommand(` ${commandPrefix()} <domain> -h`),
1290
+ helpCommand(` ${commandPrefix()} <domain> <command> -h`),
1083
1291
  '',
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`,
1292
+ helpHeading('Quick start:'),
1293
+ helpCommand(` ${commandPrefix()} auth status`),
1294
+ helpCommand(` ${commandPrefix()} auth login --access-key <access-key>`),
1295
+ helpCommand(` ${commandPrefix()} model image-models --model Banana`),
1296
+ helpCommand(` ${commandPrefix()} model input-guide`),
1297
+ helpCommand(` ${commandPrefix()} model options --model-group-code <code>`),
1298
+ helpCommand(` ${commandPrefix()} model create-spec --model-group-code <code>`),
1299
+ helpCommand(` ${commandPrefix()} create image --model-group-code <code> --prompt "一只小狗" --dry-run`),
1092
1300
  '',
1093
- 'Command groups:',
1301
+ helpHeading('Command groups:'),
1094
1302
  ];
1095
1303
  for (const [group] of groups.entries()) {
1096
- lines.push(` ${group.padEnd(12)} ${GROUP_DESCRIPTIONS[group] || ''}`);
1304
+ lines.push(` ${helpGroup(group.padEnd(12))} ${GROUP_DESCRIPTIONS[group] || ''}`);
1097
1305
  }
1098
1306
  lines.push(
1099
1307
  '',
1100
- 'More help:',
1101
- ` ${commandPrefix()} <domain> -h`,
1102
- ` ${commandPrefix()} <domain> <command> -h`,
1103
- ` ${commandPrefix()} schema -f json`,
1104
- ` ${commandPrefix()} doctor -h`,
1308
+ helpHeading('More help:'),
1309
+ helpCommand(` ${commandPrefix()} <domain> -h`),
1310
+ helpCommand(` ${commandPrefix()} <domain> <command> -h`),
1311
+ helpCommand(` ${commandPrefix()} schema --brief -f json`),
1312
+ helpCommand(` ${commandPrefix()} schema -f json`),
1313
+ helpCommand(` ${commandPrefix()} doctor -h`),
1105
1314
  '',
1106
- 'Global options:',
1107
- ' -f, --format json 输出 JSON;默认不传时输出精简文本',
1108
- ' --json 等同于 -f json',
1109
- ' -h, --help 查看帮助',
1110
- ' -v, --version 查看版本',
1315
+ helpHeading('Global options:'),
1316
+ ` ${helpOption('-f, --format <format>')} 输出格式:pretty(默认)、text、json、yaml、csv、auto`,
1317
+ ` ${helpOption('--json'.padEnd(22))} 等同于 -f json`,
1318
+ ` ${helpOption('-h, --help'.padEnd(22))} 查看帮助`,
1319
+ ` ${helpOption('-v, --version'.padEnd(22))} 查看版本`,
1111
1320
  );
1112
1321
  process.stdout.write(`${lines.join('\n')}\n`);
1113
1322
  }
@@ -1119,27 +1328,27 @@ function printGroupHelp(group, commands, version) {
1119
1328
  const directItems = hasSubgroups ? items.filter((command) => !commandSubgroupTokens(command)) : [];
1120
1329
 
1121
1330
  const lines = [
1122
- `lj-awb v${version}`,
1331
+ helpTitle(`lj-awb v${version}`),
1123
1332
  '',
1124
1333
  GROUP_DESCRIPTIONS[group] || `${group} 命令组`,
1125
1334
  '',
1126
- 'Usage:',
1335
+ helpHeading('Usage:'),
1127
1336
  ];
1128
1337
  if (group === 'system') {
1129
- lines.push(` ${commandPrefix()} <command> [options]`);
1338
+ lines.push(helpCommand(` ${commandPrefix()} <command> [options]`));
1130
1339
  } else if (hasSubgroups) {
1131
- if (directItems.length) lines.push(` ${commandPrefix()} ${group} <command> [options]`);
1132
- lines.push(` ${commandPrefix()} ${group} <subdomain> <command> [options]`);
1340
+ if (directItems.length) lines.push(helpCommand(` ${commandPrefix()} ${group} <command> [options]`));
1341
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> <command> [options]`));
1133
1342
  } else {
1134
- lines.push(` ${commandPrefix()} ${group} <command> [options]`);
1343
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <command> [options]`));
1135
1344
  }
1136
1345
  lines.push('');
1137
1346
  if (group === 'system') {
1138
- lines.push(`提示:system 是命令组;下面的命令可直接运行,例如 ${commandPrefix()} doctor。`);
1347
+ lines.push(helpMuted(`提示:system 是命令组;下面的命令可直接运行,例如 ${commandPrefix()} doctor。`));
1139
1348
  } else if (hasSubgroups) {
1140
- lines.push(`提示:${group} 下按子领域分组;先选子领域再看具体命令。`);
1349
+ lines.push(helpMuted(`提示:${group} 下按子领域分组;先选子领域再看具体命令。`));
1141
1350
  } else {
1142
- lines.push(`提示:${group} 是命令组;请选择下面的子命令运行。`);
1351
+ lines.push(helpMuted(`提示:${group} 是命令组;请选择下面的子命令运行。`));
1143
1352
  }
1144
1353
  lines.push('');
1145
1354
 
@@ -1147,43 +1356,43 @@ function printGroupHelp(group, commands, version) {
1147
1356
  const presentSubgroups = new Set(
1148
1357
  items
1149
1358
  .map((command) => commandSubgroupTokens(command))
1150
- .filter(Boolean)
1151
- .map(([, subgroup]) => subgroup),
1359
+ .filter(Boolean)
1360
+ .map(([, subgroup]) => subgroup),
1152
1361
  );
1153
- lines.push('Subdomains:');
1362
+ lines.push(helpHeading('Subdomains:'));
1154
1363
  for (const [subgroup, description] of Object.entries(subgroupMap)) {
1155
1364
  if (!presentSubgroups.has(subgroup)) continue;
1156
- lines.push(` ${subgroup.padEnd(10)} ${description}`);
1365
+ lines.push(` ${helpGroup(subgroup.padEnd(10))} ${description}`);
1157
1366
  }
1158
1367
  if (directItems.length) {
1159
- lines.push('', 'Commands:');
1368
+ lines.push('', helpHeading('Commands:'));
1160
1369
  for (const command of directItems) {
1161
1370
  const subcommand = commandSubcommand(command);
1162
- lines.push(` ${subcommand.padEnd(28)} ${summaryOf(command)}`);
1371
+ lines.push(` ${helpCommand(subcommand.padEnd(28))} ${summaryOf(command)}`);
1163
1372
  }
1164
1373
  }
1165
1374
  } else {
1166
- lines.push('Commands:');
1375
+ lines.push(helpHeading('Commands:'));
1167
1376
  for (const command of items) {
1168
1377
  const subcommand = commandSubcommand(command);
1169
- lines.push(` ${subcommand.padEnd(28)} ${summaryOf(command)}`);
1378
+ lines.push(` ${helpCommand(subcommand.padEnd(28))} ${summaryOf(command)}`);
1170
1379
  }
1171
1380
  }
1172
1381
 
1173
1382
  if (GROUP_EXAMPLES[group]?.length) {
1174
- lines.push('', 'Examples:');
1383
+ lines.push('', helpHeading('Examples:'));
1175
1384
  for (const example of GROUP_EXAMPLES[group]) {
1176
- lines.push(` ${renderCliText(example)}`);
1385
+ lines.push(helpCommand(` ${renderCliText(example)}`));
1177
1386
  }
1178
1387
  }
1179
- lines.push('', 'More help:');
1388
+ lines.push('', helpHeading('More help:'));
1180
1389
  if (group === 'system') {
1181
- for (const command of items) lines.push(` ${commandPrefix()} ${command.name} -h`);
1390
+ for (const command of items) lines.push(helpCommand(` ${commandPrefix()} ${command.name} -h`));
1182
1391
  } else if (hasSubgroups) {
1183
- lines.push(` ${commandPrefix()} ${group} <subdomain> -h`);
1184
- lines.push(` ${commandPrefix()} ${group} <subdomain> <command> -h`);
1392
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> -h`));
1393
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> <command> -h`));
1185
1394
  } else {
1186
- lines.push(` ${commandPrefix()} ${group} <command> -h`);
1395
+ lines.push(helpCommand(` ${commandPrefix()} ${group} <command> -h`));
1187
1396
  }
1188
1397
  process.stdout.write(`${lines.join('\n')}\n`);
1189
1398
  }
@@ -1195,43 +1404,48 @@ function printSubgroupHelp(group, subgroup, commands, version) {
1195
1404
  });
1196
1405
  const description = SUBGROUP_DESCRIPTIONS[group]?.[subgroup] || `${group} ${subgroup} 子领域`;
1197
1406
  const lines = [
1198
- `lj-awb v${version}`,
1407
+ helpTitle(`lj-awb v${version}`),
1199
1408
  '',
1200
1409
  description,
1201
1410
  '',
1202
- 'Usage:',
1203
- ` ${commandPrefix()} ${group} ${subgroup} <command> [options]`,
1411
+ helpHeading('Usage:'),
1412
+ helpCommand(` ${commandPrefix()} ${group} ${subgroup} <command> [options]`),
1204
1413
  '',
1205
- 'Commands:',
1414
+ helpHeading('Commands:'),
1206
1415
  ];
1207
1416
  for (const command of items) {
1208
1417
  const parts = String(command.name || '').split(' ').filter(Boolean);
1209
1418
  const tail = parts.slice(2).join(' ');
1210
- lines.push(` ${tail.padEnd(28)} ${summaryOf(command)}`);
1419
+ lines.push(` ${helpCommand(tail.padEnd(28))} ${summaryOf(command)}`);
1211
1420
  }
1212
1421
  const examples = SUBGROUP_EXAMPLES[`${group} ${subgroup}`];
1213
1422
  if (examples?.length) {
1214
- lines.push('', 'Examples:');
1423
+ lines.push('', helpHeading('Examples:'));
1215
1424
  for (const example of examples) {
1216
- lines.push(` ${renderCliText(example)}`);
1425
+ lines.push(helpCommand(` ${renderCliText(example)}`));
1217
1426
  }
1218
1427
  }
1219
- lines.push('', 'More help:');
1220
- lines.push(` ${commandPrefix()} ${group} ${subgroup} <command> -h`);
1428
+ lines.push('', helpHeading('More help:'));
1429
+ lines.push(helpCommand(` ${commandPrefix()} ${group} ${subgroup} <command> -h`));
1221
1430
  process.stdout.write(`${lines.join('\n')}\n`);
1222
1431
  }
1223
1432
 
1224
1433
  function printCommandHelp(command) {
1225
- const lines = [`Usage: ${commandPrefix()} ${command.name} [options]`, '', renderCliText(command.description || '')];
1434
+ const lines = [helpHeading(`Usage: ${commandPrefix()} ${command.name} [options]`), '', renderCliText(command.description || '')];
1226
1435
  if (command.args?.length) {
1227
- lines.push('', 'Options:');
1436
+ lines.push('', helpHeading('Options:'));
1228
1437
  for (const arg of command.args) {
1229
1438
  const value = arg.valueName ? ` <${arg.valueName}>` : '';
1230
1439
  const description = arg.description ? ` ${arg.description}` : '';
1231
- lines.push(` --${arg.name}${value}${description}`);
1440
+ lines.push(` ${helpOption(`--${arg.name}${value}`)}${description}`);
1232
1441
  }
1233
1442
  }
1234
- lines.push('', 'Global options:', ' -f, --format json 输出 JSON;默认不传时输出精简文本', ' --json 等同于 -f json');
1443
+ lines.push(
1444
+ '',
1445
+ helpHeading('Global options:'),
1446
+ ` ${helpOption('-f, --format <format>')} 输出格式:pretty(默认)、text、json、yaml、csv、auto`,
1447
+ ` ${helpOption('--json'.padEnd(22))} 等同于 -f json`,
1448
+ );
1235
1449
  process.stdout.write(`${lines.join('\n')}\n`);
1236
1450
  }
1237
1451
 
@@ -1272,22 +1486,45 @@ function validateCommandOptions(command, kwargs, kwargRawFlags = {}) {
1272
1486
  }
1273
1487
 
1274
1488
  function printData(data, format, meta = {}, context = {}) {
1275
- if (format === 'json') {
1489
+ const outputFormat = resolveOutputFormat(format);
1490
+ if (outputFormat === 'json') {
1276
1491
  process.stdout.write(`${JSON.stringify(createEnvelope({ status: 'success', data: normalizeJsonData(meta.command, data), meta }), null, 2)}\n`);
1277
1492
  return;
1278
1493
  }
1279
- process.stdout.write(formatTextOutput(meta.command, data, context));
1494
+ if (outputFormat === 'yaml') {
1495
+ process.stdout.write(formatYamlEnvelope(createEnvelope({ status: 'success', data: normalizeJsonData(meta.command, data), meta })));
1496
+ return;
1497
+ }
1498
+ if (outputFormat === 'csv') {
1499
+ process.stdout.write(formatCsvOutput(meta.command, data, context));
1500
+ return;
1501
+ }
1502
+ if (outputFormat === 'text') {
1503
+ process.stdout.write(formatTextOutput(meta.command, data, context));
1504
+ return;
1505
+ }
1506
+ process.stdout.write(formatPrettyOutput(meta.command, data, context));
1280
1507
  }
1281
1508
 
1282
1509
  function printError(error, format, meta = {}) {
1510
+ let outputFormat = 'text';
1511
+ try {
1512
+ outputFormat = resolveOutputFormat(format);
1513
+ } catch {
1514
+ outputFormat = 'text';
1515
+ }
1283
1516
  const payload = {
1284
1517
  type: error.type || 'error',
1285
1518
  message: error.message || String(error),
1286
1519
  ...(error.hint ? { hint: error.hint } : {}),
1287
1520
  ...(error.details ? { details: error.details } : {}),
1288
1521
  };
1289
- if (format === 'json') {
1522
+ if (outputFormat === 'json') {
1290
1523
  process.stderr.write(`${JSON.stringify(createEnvelope({ status: 'error', error: payload, meta }), null, 2)}\n`);
1524
+ } else if (outputFormat === 'yaml') {
1525
+ process.stderr.write(formatYamlEnvelope(createEnvelope({ status: 'error', error: payload, meta })));
1526
+ } else if (outputFormat === 'pretty') {
1527
+ process.stderr.write(formatPrettyError(payload));
1291
1528
  } else {
1292
1529
  process.stderr.write(formatTextError(payload));
1293
1530
  }
@@ -1351,11 +1588,12 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
1351
1588
 
1352
1589
  const startedAt = Date.now();
1353
1590
  try {
1591
+ const outputFormat = resolveOutputFormat(format);
1354
1592
  validateCommandOptions(command, kwargs, kwargRawFlags);
1355
1593
  const data = command.virtual === 'schema'
1356
1594
  ? buildCommandSchema(commands, kwargs, version)
1357
1595
  : await command.func({ command }, kwargs);
1358
- printData(data, format, { command: command.name, elapsedMs: Date.now() - startedAt }, buildOutputContext(command, kwargs));
1596
+ printData(data, outputFormat, { command: command.name, elapsedMs: Date.now() - startedAt }, buildOutputContext(command, kwargs));
1359
1597
  } catch (error) {
1360
1598
  const cliError = error instanceof LingjingAwbCliError
1361
1599
  ? error