@lingjingai/lj-awb-cli-pre 0.3.17 → 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.
- package/README.md +16 -12
- package/package.json +5 -1
- package/packages/awb-cli/README.md +0 -3
- package/packages/awb-cli/package.json +2 -2
- package/packages/awb-core/package.json +6 -2
- package/packages/awb-core/src/auth.js +1 -1
- package/packages/awb-core/src/commands.js +2 -2
- package/packages/awb-core/src/common.js +1 -1
- package/packages/awb-core/src/output.js +2011 -80
- package/packages/awb-core/src/services.js +150 -3
- package/packages/awb-core/src/standalone.js +433 -133
- package/skills/lj-awb/SKILL.md +10 -4
- package/skills/lj-awb/VERSION +1 -1
- package/skills/lj-awb/compat.json +3 -3
- package/skills/lj-awb/modules/artifact/asset.md +1 -1
- package/skills/lj-awb/modules/artifact/clip.md +1 -1
- package/skills/lj-awb/modules/artifact/script.md +1 -1
- package/skills/lj-awb/modules/artifact/video.md +1 -1
- package/skills/lj-awb/modules/auth.md +1 -1
- package/skills/lj-awb/modules/create-contract.md +1 -1
- package/skills/lj-awb/modules/driver.md +20 -4
- package/skills/lj-awb/modules/model.md +17 -6
- package/skills/lj-awb/modules/task-manual.md +3 -2
- package/skills/lj-awb/references/error-codes.md +1 -1
- package/skills/lj-awb/references/model-options-read.md +17 -10
- package/skills/lj-awb/references/output-fields.md +3 -3
|
@@ -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 {
|
|
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());
|
|
@@ -24,6 +32,7 @@ const OPTION_SYNONYMS = {
|
|
|
24
32
|
threads: ['concurrency'],
|
|
25
33
|
output: ['format'],
|
|
26
34
|
json: ['format'],
|
|
35
|
+
keyword: ['model'],
|
|
27
36
|
input: ['inputFile'],
|
|
28
37
|
in: ['inputFile'],
|
|
29
38
|
dry: ['dryRun'],
|
|
@@ -60,6 +69,16 @@ function suggestSimilarOptions(unknownKey, allowedKeys) {
|
|
|
60
69
|
for (const syn of synonyms) {
|
|
61
70
|
if (allowedKeys.includes(syn)) record(syn, -1);
|
|
62
71
|
}
|
|
72
|
+
if (unknownKey.length === 1) {
|
|
73
|
+
const ch = unknownKey.toLowerCase();
|
|
74
|
+
for (const allowed of allowedKeys) {
|
|
75
|
+
if (allowed.toLowerCase().startsWith(ch)) record(allowed, 1);
|
|
76
|
+
}
|
|
77
|
+
return [...scored.entries()]
|
|
78
|
+
.sort((x, y) => x[1] - y[1])
|
|
79
|
+
.slice(0, 3)
|
|
80
|
+
.map(([key]) => formatOptionName(key));
|
|
81
|
+
}
|
|
63
82
|
for (const allowed of allowedKeys) {
|
|
64
83
|
const a = unknownKey.toLowerCase();
|
|
65
84
|
const b = allowed.toLowerCase();
|
|
@@ -85,6 +104,57 @@ function renderCliText(value) {
|
|
|
85
104
|
return String(value ?? '').replace(/\blj-awb\b/g, commandPrefix());
|
|
86
105
|
}
|
|
87
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
|
+
|
|
88
158
|
function shellArg(value) {
|
|
89
159
|
const text = String(value ?? '');
|
|
90
160
|
return /^[A-Za-z0-9_./:=@,+-]+$/.test(text) ? text : JSON.stringify(text);
|
|
@@ -130,6 +200,8 @@ function buildOutputContext(command, kwargs = {}) {
|
|
|
130
200
|
: null;
|
|
131
201
|
return {
|
|
132
202
|
jsonCommand: `${commandInvocation(command.name, kwargs)} -f json`,
|
|
203
|
+
textCommand: `${commandInvocation(command.name, kwargs)} -f text`,
|
|
204
|
+
outputKind: buildCommandWorkflow(command).outputKind,
|
|
133
205
|
executeCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) ? commandInvocation(command.name, kwargs, withoutDryRun) : null,
|
|
134
206
|
confirmCommand: canSuggestDryRunExecution && toBool(kwargs.dryRun) && optionKeys.has('yes')
|
|
135
207
|
? commandInvocation(command.name, kwargs, { omit: ['dryRun'], set: { yes: true } })
|
|
@@ -138,6 +210,28 @@ function buildOutputContext(command, kwargs = {}) {
|
|
|
138
210
|
};
|
|
139
211
|
}
|
|
140
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
|
+
|
|
141
235
|
function assignKwarg(kwargs, key, value) {
|
|
142
236
|
if (kwargs[key] === undefined) {
|
|
143
237
|
kwargs[key] = value;
|
|
@@ -153,7 +247,8 @@ function assignKwarg(kwargs, key, value) {
|
|
|
153
247
|
function parseArgv(argv) {
|
|
154
248
|
const commandParts = [];
|
|
155
249
|
const kwargs = {};
|
|
156
|
-
|
|
250
|
+
const kwargRawFlags = {};
|
|
251
|
+
let format = 'pretty';
|
|
157
252
|
|
|
158
253
|
for (let index = 0; index < argv.length; index += 1) {
|
|
159
254
|
const token = argv[index];
|
|
@@ -182,10 +277,27 @@ function parseArgv(argv) {
|
|
|
182
277
|
}
|
|
183
278
|
continue;
|
|
184
279
|
}
|
|
280
|
+
if (token.length > 1 && token.startsWith('-') && /^-[A-Za-z]/.test(token)) {
|
|
281
|
+
const raw = token.slice(1);
|
|
282
|
+
const eqIndex = raw.indexOf('=');
|
|
283
|
+
const keyPart = eqIndex >= 0 ? raw.slice(0, eqIndex) : raw;
|
|
284
|
+
const inlineValue = eqIndex >= 0 ? raw.slice(eqIndex + 1) : undefined;
|
|
285
|
+
const key = normalizeKey(keyPart);
|
|
286
|
+
if (!(key in kwargRawFlags)) kwargRawFlags[key] = token;
|
|
287
|
+
if (inlineValue !== undefined) {
|
|
288
|
+
assignKwarg(kwargs, key, inlineValue);
|
|
289
|
+
} else if (argv[index + 1] && !argv[index + 1].startsWith('-')) {
|
|
290
|
+
assignKwarg(kwargs, key, argv[index + 1]);
|
|
291
|
+
index += 1;
|
|
292
|
+
} else {
|
|
293
|
+
assignKwarg(kwargs, key, true);
|
|
294
|
+
}
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
185
297
|
commandParts.push(token);
|
|
186
298
|
}
|
|
187
299
|
|
|
188
|
-
return { commandName: commandParts.join(' '), kwargs, format };
|
|
300
|
+
return { commandName: commandParts.join(' '), kwargs, kwargRawFlags, format };
|
|
189
301
|
}
|
|
190
302
|
|
|
191
303
|
async function readVersion() {
|
|
@@ -203,13 +315,15 @@ const VIRTUAL_COMMANDS = [
|
|
|
203
315
|
'输出机器可读命令 schema,供 Agent / 脚本理解命令、参数和示例',
|
|
204
316
|
'',
|
|
205
317
|
'Examples:',
|
|
318
|
+
' lj-awb schema --brief -f json',
|
|
206
319
|
' lj-awb schema -f json',
|
|
207
320
|
' lj-awb schema --domain create -f json',
|
|
208
321
|
' lj-awb schema --domain create --command image -f json',
|
|
209
322
|
'',
|
|
210
|
-
'Hint: Agent
|
|
323
|
+
'Hint: Agent 先读 --brief 掌握能力地图;执行具体命令前再读精确 schema。',
|
|
211
324
|
].join('\n'),
|
|
212
325
|
args: [
|
|
326
|
+
{ name: 'brief', description: '输出面向 Agent 的轻量能力摘要,不展开每个参数' },
|
|
213
327
|
{ name: 'domain', valueName: 'name', description: '按命令域过滤,如 create / task / artifact / system' },
|
|
214
328
|
{ name: 'command', valueName: 'name', description: '按子命令过滤,如 image / video / wait / doctor' },
|
|
215
329
|
],
|
|
@@ -366,6 +480,7 @@ const GROUP_EXAMPLES = {
|
|
|
366
480
|
system: [
|
|
367
481
|
'lj-awb doctor',
|
|
368
482
|
'lj-awb doctor --verify',
|
|
483
|
+
'lj-awb schema --brief -f json',
|
|
369
484
|
'lj-awb schema -f json',
|
|
370
485
|
'lj-awb schema --domain create -f json',
|
|
371
486
|
],
|
|
@@ -812,6 +927,23 @@ function buildCommandWorkflow(command) {
|
|
|
812
927
|
if (command.name === 'create asset') {
|
|
813
928
|
next.push('读取 data.assetPath', '若后端返回审核 taskId,再运行 task wait --task-type ASSET_REGISTER');
|
|
814
929
|
}
|
|
930
|
+
if (command.name === 'model image-models' || command.name === 'model video-models') {
|
|
931
|
+
next.push('读取 data.models[] 得到全部候选;用户给了模型口语名时保留同族全部候选,不只取第一个');
|
|
932
|
+
next.push('对每个候选运行 model options --model-group-code <modelGroupCode> 查看真实参数和素材约束');
|
|
933
|
+
next.push('向用户展示候选模型 + 参数取值 + 资源能力后 STOP;等待用户选择或明确授权默认,未确认不得进入 create-spec/fee/dry-run/create');
|
|
934
|
+
}
|
|
935
|
+
if (command.name === 'model asset-review-models') {
|
|
936
|
+
next.push('读取 data.models[].platform', '用 create asset-groups --platform <platform> 查重或创建素材组');
|
|
937
|
+
}
|
|
938
|
+
if (command.name === 'model options') {
|
|
939
|
+
next.push('读取 params/resources/constraints 确认参数与素材约束', '运行 model create-spec --model-group-code <code> 查看创建写法和示例');
|
|
940
|
+
}
|
|
941
|
+
if (command.name === 'model create-spec') {
|
|
942
|
+
next.push('按 examples / supportedIntents 组装命令', '用户确认关键参数后先运行 create image-fee 或 create video-fee,再 dry-run');
|
|
943
|
+
}
|
|
944
|
+
if (command.name === 'model input-guide') {
|
|
945
|
+
next.push('运行 model image-models 或 model video-models 选择模型', '选定模型后运行 model options --model-group-code <code>');
|
|
946
|
+
}
|
|
815
947
|
if (['create image-batch', 'create video-batch', 'create subject-batch'].includes(command.name)) {
|
|
816
948
|
next.push('读取每项 status / taskId / error', '使用 task record-poll 或对应 wait 命令恢复批量结果');
|
|
817
949
|
}
|
|
@@ -822,11 +954,167 @@ function buildCommandWorkflow(command) {
|
|
|
822
954
|
};
|
|
823
955
|
}
|
|
824
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
|
+
|
|
825
1107
|
function buildAgentContract() {
|
|
826
1108
|
return {
|
|
827
|
-
defaultOutputFormat: '
|
|
1109
|
+
defaultOutputFormat: 'pretty',
|
|
1110
|
+
humanDefaultOutputFormat: 'pretty',
|
|
1111
|
+
agentRecommendedOutputFormat: 'text',
|
|
1112
|
+
agentCompactOutputFormat: 'text',
|
|
1113
|
+
scriptRecommendedOutputFormat: 'json',
|
|
828
1114
|
jsonFlag: '-f json',
|
|
829
|
-
|
|
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。',
|
|
830
1118
|
statePolicy: {
|
|
831
1119
|
purpose: 'Agent 应在同一轮 / 同一项目中维护 AWB 上下文账本,减少重复查询和重复提交。',
|
|
832
1120
|
cacheKeys: [
|
|
@@ -844,6 +1132,7 @@ function buildAgentContract() {
|
|
|
844
1132
|
invalidation: '用户切换账号、团队、项目组、模型、素材、prompt 或关键参数后,只刷新受影响的缓存;不要重复跑未变化的 model options / create-spec / fee。',
|
|
845
1133
|
},
|
|
846
1134
|
workflowPolicy: [
|
|
1135
|
+
'模型口语名命中后必须先展示候选模型、真实参数取值和资源能力,并在清单后 STOP 等用户选择或明确授权默认;没有完成用户可见候选清单和确认前,不得代选默认模型 / 参数,也不得进入 create-spec、fee、dry-run 或 create。',
|
|
847
1136
|
'模型探索阶段只读 model list / options / create-spec;fee 只在用户确认关键参数后跑一次。',
|
|
848
1137
|
'supportsDryRun=true 的写入 / 扣费命令先 dry-run,确认后 yes;不要把 dry-run 当参数探索工具反复跑。',
|
|
849
1138
|
'用户给出多条同模型同参数任务时优先 batch + task-record-file,不要单条循环 create。',
|
|
@@ -862,7 +1151,7 @@ function buildAgentContract() {
|
|
|
862
1151
|
{ canonical: 'pointCost', aliases: ['points', 'point', 'costPoint', 'estimatePoint'], meaning: '本次任务预计 / 实际消耗积分' },
|
|
863
1152
|
],
|
|
864
1153
|
},
|
|
865
|
-
schemaPolicy: 'schema
|
|
1154
|
+
schemaPolicy: 'Agent 先读 schema --brief 建立能力地图;执行具体命令前再读精确 schema --domain <domain> --command <command>。完整 schema -f json 只用于覆盖校验、脚本生成或调试 schema 本身;查看长帮助用 schema.commands[].helpCommand 或 lj-awb <domain> <command> -h。',
|
|
866
1155
|
confirmationPolicy: 'schema.commands[].safety.requiresConfirmation=true 的命令,Agent 必须先向用户确认,再追加 --yes。',
|
|
867
1156
|
dryRunPolicy: 'schema.commands[].safety.supportsDryRun=true 的写入 / 扣费命令,正式执行前优先跑 --dry-run。',
|
|
868
1157
|
resourceShortcut: {
|
|
@@ -886,24 +1175,36 @@ function buildAgentContract() {
|
|
|
886
1175
|
},
|
|
887
1176
|
],
|
|
888
1177
|
},
|
|
1178
|
+
modelCandidatePresentation: {
|
|
1179
|
+
purpose: '把 model image-models / video-models 与每个候选的 model options 转成用户可见清单,避免 Agent 内部看完 options 后直接代选。',
|
|
1180
|
+
trigger: '用户问有哪些模型、给出口语名、或模型列表返回候选时都触发;即使只有一个候选也要展示真实可选项。',
|
|
1181
|
+
gateType: 'hard_stop',
|
|
1182
|
+
stopAfterPresentation: true,
|
|
1183
|
+
resumeCondition: '用户选择模型和关键参数,或明确说“按默认 / 你来选”。',
|
|
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'],
|
|
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'],
|
|
1186
|
+
},
|
|
889
1187
|
modelOptions: {
|
|
890
1188
|
command: `${commandPrefix()} model options --model-group-code <code>`,
|
|
1189
|
+
textCommand: `${commandPrefix()} model options --model-group-code <code> -f text`,
|
|
891
1190
|
jsonCommand: `${commandPrefix()} model options --model-group-code <code> -f json`,
|
|
892
|
-
purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。',
|
|
1191
|
+
purpose: '查询指定模型支持的 CLI 参数、枚举值、默认值、素材约束和条件约束。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
|
|
893
1192
|
useBefore: ['model create-spec', 'create image-fee', 'create image', 'create video-fee', 'create video'],
|
|
894
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'],
|
|
895
1194
|
},
|
|
896
1195
|
modelCreateSpec: {
|
|
897
1196
|
command: `${commandPrefix()} model create-spec --model-group-code <code>`,
|
|
1197
|
+
textCommand: `${commandPrefix()} model create-spec --model-group-code <code> -f text`,
|
|
898
1198
|
jsonCommand: `${commandPrefix()} model create-spec --model-group-code <code> -f json`,
|
|
899
|
-
purpose: '查看指定模型如何组织 create 命令:输入模式、素材绑定规则、示例和创建前检查项。',
|
|
1199
|
+
purpose: '查看指定模型如何组织 create 命令:输入模式、素材绑定规则、示例和创建前检查项。Agent 默认读 textCommand;只有程序化校验完整嵌套结构时读 jsonCommand。',
|
|
900
1200
|
useBefore: ['create image-fee', 'create image', 'create video-fee', 'create video'],
|
|
901
1201
|
keyFields: ['inputRequirement', 'supportedIntents', 'validationRules', 'agentGuidance', 'preflight', 'examples'],
|
|
902
1202
|
},
|
|
903
1203
|
modelInputGuide: {
|
|
904
1204
|
command: `${commandPrefix()} model input-guide`,
|
|
1205
|
+
textCommand: `${commandPrefix()} model input-guide -f text`,
|
|
905
1206
|
jsonCommand: `${commandPrefix()} model input-guide -f json`,
|
|
906
|
-
purpose: '查看跨模型统一创建参数、resources 字段和素材绑定规则。',
|
|
1207
|
+
purpose: '查看跨模型统一创建参数、resources 字段和素材绑定规则。Agent 默认读 textCommand;脚本严格解析时读 jsonCommand。',
|
|
907
1208
|
useBefore: ['model options', 'model create-spec'],
|
|
908
1209
|
},
|
|
909
1210
|
taskTypes: {
|
|
@@ -929,17 +1230,10 @@ function buildAgentContract() {
|
|
|
929
1230
|
}
|
|
930
1231
|
|
|
931
1232
|
function buildCommandSchema(commands, kwargs = {}, version = 'unknown') {
|
|
1233
|
+
if (toBool(kwargs.brief)) return buildAgentBrief(commands, version);
|
|
932
1234
|
const domainFilter = String(kwargs.domain ?? '').trim();
|
|
933
1235
|
const commandFilter = String(kwargs.command ?? '').trim();
|
|
934
|
-
const domains =
|
|
935
|
-
for (const command of commands) {
|
|
936
|
-
const group = commandGroup(command);
|
|
937
|
-
domains.set(group, {
|
|
938
|
-
name: group,
|
|
939
|
-
description: GROUP_DESCRIPTIONS[group] || '',
|
|
940
|
-
commandCount: (domains.get(group)?.commandCount || 0) + 1,
|
|
941
|
-
});
|
|
942
|
-
}
|
|
1236
|
+
const domains = buildDomainSummaries(commands);
|
|
943
1237
|
const filteredCommands = commands
|
|
944
1238
|
.filter((command) => !domainFilter || commandGroup(command) === domainFilter)
|
|
945
1239
|
.filter((command) => !commandFilter || command.name === commandFilter || commandSubcommand(command) === commandFilter);
|
|
@@ -963,49 +1257,16 @@ function buildCommandSchema(commands, kwargs = {}, version = 'unknown') {
|
|
|
963
1257
|
`${commandPrefix()} <domain> <command> -h`,
|
|
964
1258
|
],
|
|
965
1259
|
globalOptions: [
|
|
966
|
-
{ flag: '-f, --format
|
|
1260
|
+
{ flag: '-f, --format <format>', key: 'format', description: '输出格式:pretty(默认)、text、json、yaml、csv、auto' },
|
|
967
1261
|
{ flag: '--json', key: 'format', value: 'json', description: '等同于 -f json' },
|
|
968
1262
|
{ flag: '-h, --help', key: 'help', description: '查看帮助' },
|
|
969
1263
|
{ flag: '-v, --version', key: 'version', description: '查看版本' },
|
|
970
1264
|
],
|
|
971
1265
|
agentContract: buildAgentContract(),
|
|
972
|
-
domains:
|
|
1266
|
+
domains: domains
|
|
973
1267
|
.filter((domain) => !shouldFilterDomains || matchedDomains.has(domain.name))
|
|
974
1268
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
975
|
-
commands: filteredCommands.map(
|
|
976
|
-
const requiredOptions = new Set();
|
|
977
|
-
if (commandGroup(command) === 'artifact') requiredOptions.add('projectId');
|
|
978
|
-
for (const key of COMMAND_REQUIRED_OPTIONS[command.name] || []) requiredOptions.add(key);
|
|
979
|
-
const requiredAnyOptions = COMMAND_REQUIRED_ANY_OPTIONS[command.name] || [];
|
|
980
|
-
const optionKeys = new Set((command.args || []).map((arg) => normalizeKey(arg.name)));
|
|
981
|
-
return {
|
|
982
|
-
name: command.name,
|
|
983
|
-
domain: commandGroup(command),
|
|
984
|
-
command: commandSubcommand(command),
|
|
985
|
-
invocation: `${commandPrefix()} ${command.name}`,
|
|
986
|
-
helpCommand: `${commandPrefix()} ${command.name} -h`,
|
|
987
|
-
summary: summaryOf(command),
|
|
988
|
-
description: renderCliText(summaryOf(command)),
|
|
989
|
-
examples: extractExamples(command).map(renderCliText),
|
|
990
|
-
jsonExamples: extractExamples(command).map(asJsonExample),
|
|
991
|
-
requiredOptions: [...requiredOptions],
|
|
992
|
-
requiredAnyOptions,
|
|
993
|
-
safety: buildCommandSafety(command, optionKeys),
|
|
994
|
-
workflow: buildCommandWorkflow(command),
|
|
995
|
-
options: (command.args || []).map((arg) => {
|
|
996
|
-
const key = normalizeKey(arg.name);
|
|
997
|
-
return {
|
|
998
|
-
name: arg.name,
|
|
999
|
-
key,
|
|
1000
|
-
flag: formatOptionName(arg.name),
|
|
1001
|
-
valueName: arg.valueName || null,
|
|
1002
|
-
required: requiredOptions.has(key),
|
|
1003
|
-
requiredAnyGroup: requiredAnyOptions.find((group) => group.includes(key)) || null,
|
|
1004
|
-
description: arg.description || '',
|
|
1005
|
-
};
|
|
1006
|
-
}),
|
|
1007
|
-
};
|
|
1008
|
-
}),
|
|
1269
|
+
commands: filteredCommands.map(buildCommandSchemaEntry),
|
|
1009
1270
|
};
|
|
1010
1271
|
}
|
|
1011
1272
|
|
|
@@ -1018,43 +1279,44 @@ function printRootHelp(commands, version) {
|
|
|
1018
1279
|
}
|
|
1019
1280
|
|
|
1020
1281
|
const lines = [
|
|
1021
|
-
`lj-awb v${version}
|
|
1282
|
+
helpTitle(`lj-awb v${version}`),
|
|
1022
1283
|
'',
|
|
1023
|
-
'灵境 AWB
|
|
1284
|
+
'灵境 AWB 命令行工具。默认输出人类友好的 pretty 视图;Agent 建议显式传 -f text,脚本严格解析用 -f json。',
|
|
1024
1285
|
'',
|
|
1025
|
-
'Usage:',
|
|
1026
|
-
` ${commandPrefix()} <domain> <command> [options]
|
|
1027
|
-
` ${commandPrefix()} <command> [options]
|
|
1028
|
-
` ${commandPrefix()} <domain> -h
|
|
1029
|
-
` ${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`),
|
|
1030
1291
|
'',
|
|
1031
|
-
'Quick start:',
|
|
1032
|
-
` ${commandPrefix()} auth status
|
|
1033
|
-
` ${commandPrefix()} auth login --access-key <access-key
|
|
1034
|
-
` ${commandPrefix()} model image-models --model Banana
|
|
1035
|
-
` ${commandPrefix()} model input-guide
|
|
1036
|
-
` ${commandPrefix()} model options --model-group-code <code
|
|
1037
|
-
` ${commandPrefix()} model create-spec --model-group-code <code
|
|
1038
|
-
` ${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`),
|
|
1039
1300
|
'',
|
|
1040
|
-
'Command groups:',
|
|
1301
|
+
helpHeading('Command groups:'),
|
|
1041
1302
|
];
|
|
1042
1303
|
for (const [group] of groups.entries()) {
|
|
1043
|
-
lines.push(` ${group.padEnd(12)} ${GROUP_DESCRIPTIONS[group] || ''}`);
|
|
1304
|
+
lines.push(` ${helpGroup(group.padEnd(12))} ${GROUP_DESCRIPTIONS[group] || ''}`);
|
|
1044
1305
|
}
|
|
1045
1306
|
lines.push(
|
|
1046
1307
|
'',
|
|
1047
|
-
'More help:',
|
|
1048
|
-
` ${commandPrefix()} <domain> -h
|
|
1049
|
-
` ${commandPrefix()} <domain> <command> -h
|
|
1050
|
-
` ${commandPrefix()} schema -f json
|
|
1051
|
-
` ${commandPrefix()}
|
|
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`),
|
|
1052
1314
|
'',
|
|
1053
|
-
'Global options:',
|
|
1054
|
-
'
|
|
1055
|
-
'
|
|
1056
|
-
'
|
|
1057
|
-
'
|
|
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))} 查看版本`,
|
|
1058
1320
|
);
|
|
1059
1321
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1060
1322
|
}
|
|
@@ -1066,27 +1328,27 @@ function printGroupHelp(group, commands, version) {
|
|
|
1066
1328
|
const directItems = hasSubgroups ? items.filter((command) => !commandSubgroupTokens(command)) : [];
|
|
1067
1329
|
|
|
1068
1330
|
const lines = [
|
|
1069
|
-
`lj-awb v${version}
|
|
1331
|
+
helpTitle(`lj-awb v${version}`),
|
|
1070
1332
|
'',
|
|
1071
1333
|
GROUP_DESCRIPTIONS[group] || `${group} 命令组`,
|
|
1072
1334
|
'',
|
|
1073
|
-
'Usage:',
|
|
1335
|
+
helpHeading('Usage:'),
|
|
1074
1336
|
];
|
|
1075
1337
|
if (group === 'system') {
|
|
1076
|
-
lines.push(` ${commandPrefix()} <command> [options]`);
|
|
1338
|
+
lines.push(helpCommand(` ${commandPrefix()} <command> [options]`));
|
|
1077
1339
|
} else if (hasSubgroups) {
|
|
1078
|
-
if (directItems.length) lines.push(` ${commandPrefix()} ${group} <command> [options]`);
|
|
1079
|
-
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]`));
|
|
1080
1342
|
} else {
|
|
1081
|
-
lines.push(` ${commandPrefix()} ${group} <command> [options]`);
|
|
1343
|
+
lines.push(helpCommand(` ${commandPrefix()} ${group} <command> [options]`));
|
|
1082
1344
|
}
|
|
1083
1345
|
lines.push('');
|
|
1084
1346
|
if (group === 'system') {
|
|
1085
|
-
lines.push(`提示:system 是命令组;下面的命令可直接运行,例如 ${commandPrefix()} doctor。`);
|
|
1347
|
+
lines.push(helpMuted(`提示:system 是命令组;下面的命令可直接运行,例如 ${commandPrefix()} doctor。`));
|
|
1086
1348
|
} else if (hasSubgroups) {
|
|
1087
|
-
lines.push(`提示:${group} 下按子领域分组;先选子领域再看具体命令。`);
|
|
1349
|
+
lines.push(helpMuted(`提示:${group} 下按子领域分组;先选子领域再看具体命令。`));
|
|
1088
1350
|
} else {
|
|
1089
|
-
lines.push(`提示:${group} 是命令组;请选择下面的子命令运行。`);
|
|
1351
|
+
lines.push(helpMuted(`提示:${group} 是命令组;请选择下面的子命令运行。`));
|
|
1090
1352
|
}
|
|
1091
1353
|
lines.push('');
|
|
1092
1354
|
|
|
@@ -1094,43 +1356,43 @@ function printGroupHelp(group, commands, version) {
|
|
|
1094
1356
|
const presentSubgroups = new Set(
|
|
1095
1357
|
items
|
|
1096
1358
|
.map((command) => commandSubgroupTokens(command))
|
|
1097
|
-
|
|
1098
|
-
|
|
1359
|
+
.filter(Boolean)
|
|
1360
|
+
.map(([, subgroup]) => subgroup),
|
|
1099
1361
|
);
|
|
1100
|
-
lines.push('Subdomains:');
|
|
1362
|
+
lines.push(helpHeading('Subdomains:'));
|
|
1101
1363
|
for (const [subgroup, description] of Object.entries(subgroupMap)) {
|
|
1102
1364
|
if (!presentSubgroups.has(subgroup)) continue;
|
|
1103
|
-
lines.push(` ${subgroup.padEnd(10)} ${description}`);
|
|
1365
|
+
lines.push(` ${helpGroup(subgroup.padEnd(10))} ${description}`);
|
|
1104
1366
|
}
|
|
1105
1367
|
if (directItems.length) {
|
|
1106
|
-
lines.push('', 'Commands:');
|
|
1368
|
+
lines.push('', helpHeading('Commands:'));
|
|
1107
1369
|
for (const command of directItems) {
|
|
1108
1370
|
const subcommand = commandSubcommand(command);
|
|
1109
|
-
lines.push(` ${subcommand.padEnd(28)} ${summaryOf(command)}`);
|
|
1371
|
+
lines.push(` ${helpCommand(subcommand.padEnd(28))} ${summaryOf(command)}`);
|
|
1110
1372
|
}
|
|
1111
1373
|
}
|
|
1112
1374
|
} else {
|
|
1113
|
-
lines.push('Commands:');
|
|
1375
|
+
lines.push(helpHeading('Commands:'));
|
|
1114
1376
|
for (const command of items) {
|
|
1115
1377
|
const subcommand = commandSubcommand(command);
|
|
1116
|
-
lines.push(` ${subcommand.padEnd(28)} ${summaryOf(command)}`);
|
|
1378
|
+
lines.push(` ${helpCommand(subcommand.padEnd(28))} ${summaryOf(command)}`);
|
|
1117
1379
|
}
|
|
1118
1380
|
}
|
|
1119
1381
|
|
|
1120
1382
|
if (GROUP_EXAMPLES[group]?.length) {
|
|
1121
|
-
lines.push('', 'Examples:');
|
|
1383
|
+
lines.push('', helpHeading('Examples:'));
|
|
1122
1384
|
for (const example of GROUP_EXAMPLES[group]) {
|
|
1123
|
-
lines.push(` ${renderCliText(example)}`);
|
|
1385
|
+
lines.push(helpCommand(` ${renderCliText(example)}`));
|
|
1124
1386
|
}
|
|
1125
1387
|
}
|
|
1126
|
-
lines.push('', 'More help:');
|
|
1388
|
+
lines.push('', helpHeading('More help:'));
|
|
1127
1389
|
if (group === 'system') {
|
|
1128
|
-
for (const command of items) lines.push(` ${commandPrefix()} ${command.name} -h`);
|
|
1390
|
+
for (const command of items) lines.push(helpCommand(` ${commandPrefix()} ${command.name} -h`));
|
|
1129
1391
|
} else if (hasSubgroups) {
|
|
1130
|
-
lines.push(` ${commandPrefix()} ${group} <subdomain> -h`);
|
|
1131
|
-
lines.push(` ${commandPrefix()} ${group} <subdomain> <command> -h`);
|
|
1392
|
+
lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> -h`));
|
|
1393
|
+
lines.push(helpCommand(` ${commandPrefix()} ${group} <subdomain> <command> -h`));
|
|
1132
1394
|
} else {
|
|
1133
|
-
lines.push(` ${commandPrefix()} ${group} <command> -h`);
|
|
1395
|
+
lines.push(helpCommand(` ${commandPrefix()} ${group} <command> -h`));
|
|
1134
1396
|
}
|
|
1135
1397
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1136
1398
|
}
|
|
@@ -1142,90 +1404,127 @@ function printSubgroupHelp(group, subgroup, commands, version) {
|
|
|
1142
1404
|
});
|
|
1143
1405
|
const description = SUBGROUP_DESCRIPTIONS[group]?.[subgroup] || `${group} ${subgroup} 子领域`;
|
|
1144
1406
|
const lines = [
|
|
1145
|
-
`lj-awb v${version}
|
|
1407
|
+
helpTitle(`lj-awb v${version}`),
|
|
1146
1408
|
'',
|
|
1147
1409
|
description,
|
|
1148
1410
|
'',
|
|
1149
|
-
'Usage:',
|
|
1150
|
-
` ${commandPrefix()} ${group} ${subgroup} <command> [options]
|
|
1411
|
+
helpHeading('Usage:'),
|
|
1412
|
+
helpCommand(` ${commandPrefix()} ${group} ${subgroup} <command> [options]`),
|
|
1151
1413
|
'',
|
|
1152
|
-
'Commands:',
|
|
1414
|
+
helpHeading('Commands:'),
|
|
1153
1415
|
];
|
|
1154
1416
|
for (const command of items) {
|
|
1155
1417
|
const parts = String(command.name || '').split(' ').filter(Boolean);
|
|
1156
1418
|
const tail = parts.slice(2).join(' ');
|
|
1157
|
-
lines.push(` ${tail.padEnd(28)} ${summaryOf(command)}`);
|
|
1419
|
+
lines.push(` ${helpCommand(tail.padEnd(28))} ${summaryOf(command)}`);
|
|
1158
1420
|
}
|
|
1159
1421
|
const examples = SUBGROUP_EXAMPLES[`${group} ${subgroup}`];
|
|
1160
1422
|
if (examples?.length) {
|
|
1161
|
-
lines.push('', 'Examples:');
|
|
1423
|
+
lines.push('', helpHeading('Examples:'));
|
|
1162
1424
|
for (const example of examples) {
|
|
1163
|
-
lines.push(` ${renderCliText(example)}`);
|
|
1425
|
+
lines.push(helpCommand(` ${renderCliText(example)}`));
|
|
1164
1426
|
}
|
|
1165
1427
|
}
|
|
1166
|
-
lines.push('', 'More help:');
|
|
1167
|
-
lines.push(` ${commandPrefix()} ${group} ${subgroup} <command> -h`);
|
|
1428
|
+
lines.push('', helpHeading('More help:'));
|
|
1429
|
+
lines.push(helpCommand(` ${commandPrefix()} ${group} ${subgroup} <command> -h`));
|
|
1168
1430
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1169
1431
|
}
|
|
1170
1432
|
|
|
1171
1433
|
function printCommandHelp(command) {
|
|
1172
|
-
const lines = [`Usage: ${commandPrefix()} ${command.name} [options]
|
|
1434
|
+
const lines = [helpHeading(`Usage: ${commandPrefix()} ${command.name} [options]`), '', renderCliText(command.description || '')];
|
|
1173
1435
|
if (command.args?.length) {
|
|
1174
|
-
lines.push('', 'Options:');
|
|
1436
|
+
lines.push('', helpHeading('Options:'));
|
|
1175
1437
|
for (const arg of command.args) {
|
|
1176
1438
|
const value = arg.valueName ? ` <${arg.valueName}>` : '';
|
|
1177
1439
|
const description = arg.description ? ` ${arg.description}` : '';
|
|
1178
|
-
lines.push(`
|
|
1440
|
+
lines.push(` ${helpOption(`--${arg.name}${value}`)}${description}`);
|
|
1179
1441
|
}
|
|
1180
1442
|
}
|
|
1181
|
-
lines.push(
|
|
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
|
+
);
|
|
1182
1449
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1183
1450
|
}
|
|
1184
1451
|
|
|
1185
|
-
function validateCommandOptions(command, kwargs) {
|
|
1452
|
+
function validateCommandOptions(command, kwargs, kwargRawFlags = {}) {
|
|
1186
1453
|
const allowedKeys = (command.args || []).map((arg) => normalizeKey(arg.name));
|
|
1187
1454
|
const allowed = new Set(allowedKeys);
|
|
1188
1455
|
const unknown = Object.keys(kwargs).filter((key) => !allowed.has(key));
|
|
1189
1456
|
if (!unknown.length) return;
|
|
1457
|
+
const displayFlag = (key) => kwargRawFlags[key] || formatOptionName(key);
|
|
1190
1458
|
const suggestions = {};
|
|
1191
1459
|
for (const unk of unknown) {
|
|
1192
1460
|
const matches = suggestSimilarOptions(unk, allowedKeys);
|
|
1193
|
-
if (matches.length) suggestions[
|
|
1461
|
+
if (matches.length) suggestions[displayFlag(unk)] = matches;
|
|
1194
1462
|
}
|
|
1463
|
+
const usedShortForm = unknown.some((key) => {
|
|
1464
|
+
const flag = kwargRawFlags[key];
|
|
1465
|
+
return flag && flag.startsWith('-') && !flag.startsWith('--');
|
|
1466
|
+
});
|
|
1195
1467
|
const baseHint = `运行 ${commandPrefix()} ${command.name} -h 查看可用参数。`;
|
|
1468
|
+
const shortHint = usedShortForm
|
|
1469
|
+
? `本 CLI 仅支持 -f / -h / -v 等少数短选项,业务参数请使用 --xxx 长形式。`
|
|
1470
|
+
: '';
|
|
1196
1471
|
const suggestionParts = Object.entries(suggestions).map(([opt, matches]) => `${opt} → ${matches.join(' / ')}`);
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1472
|
+
const hintParts = [];
|
|
1473
|
+
if (suggestionParts.length) hintParts.push(`你是不是想用:${suggestionParts.join(';')}?`);
|
|
1474
|
+
if (shortHint) hintParts.push(shortHint);
|
|
1475
|
+
hintParts.push(baseHint);
|
|
1476
|
+
throw new LingjingAwbCliError(`未知参数:${unknown.map(displayFlag).join(', ')}`, {
|
|
1201
1477
|
type: 'unknown_option',
|
|
1202
1478
|
exitCode: 2,
|
|
1203
|
-
hint,
|
|
1479
|
+
hint: hintParts.join(' '),
|
|
1204
1480
|
details: {
|
|
1205
1481
|
command: command.name,
|
|
1206
|
-
unknownOptions: unknown.map(
|
|
1482
|
+
unknownOptions: unknown.map(displayFlag),
|
|
1207
1483
|
...(Object.keys(suggestions).length ? { suggestions } : {}),
|
|
1208
1484
|
},
|
|
1209
1485
|
});
|
|
1210
1486
|
}
|
|
1211
1487
|
|
|
1212
1488
|
function printData(data, format, meta = {}, context = {}) {
|
|
1213
|
-
|
|
1489
|
+
const outputFormat = resolveOutputFormat(format);
|
|
1490
|
+
if (outputFormat === 'json') {
|
|
1214
1491
|
process.stdout.write(`${JSON.stringify(createEnvelope({ status: 'success', data: normalizeJsonData(meta.command, data), meta }), null, 2)}\n`);
|
|
1215
1492
|
return;
|
|
1216
1493
|
}
|
|
1217
|
-
|
|
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));
|
|
1218
1507
|
}
|
|
1219
1508
|
|
|
1220
1509
|
function printError(error, format, meta = {}) {
|
|
1510
|
+
let outputFormat = 'text';
|
|
1511
|
+
try {
|
|
1512
|
+
outputFormat = resolveOutputFormat(format);
|
|
1513
|
+
} catch {
|
|
1514
|
+
outputFormat = 'text';
|
|
1515
|
+
}
|
|
1221
1516
|
const payload = {
|
|
1222
1517
|
type: error.type || 'error',
|
|
1223
1518
|
message: error.message || String(error),
|
|
1224
1519
|
...(error.hint ? { hint: error.hint } : {}),
|
|
1225
1520
|
...(error.details ? { details: error.details } : {}),
|
|
1226
1521
|
};
|
|
1227
|
-
if (
|
|
1522
|
+
if (outputFormat === 'json') {
|
|
1228
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));
|
|
1229
1528
|
} else {
|
|
1230
1529
|
process.stderr.write(formatTextError(payload));
|
|
1231
1530
|
}
|
|
@@ -1265,7 +1564,7 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
|
|
|
1265
1564
|
return;
|
|
1266
1565
|
}
|
|
1267
1566
|
|
|
1268
|
-
const { commandName: rawCommandName, kwargs, format } = parseArgv(argv);
|
|
1567
|
+
const { commandName: rawCommandName, kwargs, kwargRawFlags, format } = parseArgv(argv);
|
|
1269
1568
|
const commandName = resolveCommandName(rawCommandName, commands);
|
|
1270
1569
|
const command = commands.find((item) => item.name === commandName);
|
|
1271
1570
|
if (!command) {
|
|
@@ -1289,11 +1588,12 @@ export async function runStandaloneCli(argv = process.argv.slice(2)) {
|
|
|
1289
1588
|
|
|
1290
1589
|
const startedAt = Date.now();
|
|
1291
1590
|
try {
|
|
1292
|
-
|
|
1591
|
+
const outputFormat = resolveOutputFormat(format);
|
|
1592
|
+
validateCommandOptions(command, kwargs, kwargRawFlags);
|
|
1293
1593
|
const data = command.virtual === 'schema'
|
|
1294
1594
|
? buildCommandSchema(commands, kwargs, version)
|
|
1295
1595
|
: await command.func({ command }, kwargs);
|
|
1296
|
-
printData(data,
|
|
1596
|
+
printData(data, outputFormat, { command: command.name, elapsedMs: Date.now() - startedAt }, buildOutputContext(command, kwargs));
|
|
1297
1597
|
} catch (error) {
|
|
1298
1598
|
const cliError = error instanceof LingjingAwbCliError
|
|
1299
1599
|
? error
|