@lingjingai/lj-awb-cli-pre 0.4.8 → 0.5.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 CHANGED
@@ -365,6 +365,19 @@ npm run version:sync -- 0.3.5
365
365
 
366
366
  `npm run check:local` 会做 Node 语法检查、shell wrapper 检查、skill 元数据和 schema 校验,不需要 access key。`npm run check` 会额外执行真实平台契约校验,需要本地或环境变量中有可用 access key。`npm run build:pre` / `npm run build:prod` 发布构建只自动执行 `check:local`,避免被本机过期平台凭据阻断;需要真实平台回归时单独运行 `npm run check:real`。`npm run smoke` 会运行帮助命令和无网络写入风险的认证状态检查。
367
367
 
368
+ ### 用 Volta 管理 Node 的机器
369
+
370
+ `lj-awb update` 会检测当前 CLI 是否由 [Volta](https://volta.sh) 管理;如果是,会使用 `volta install` 更新包并刷新 shim,否则沿用 `npm install -g`。
371
+
372
+ `npm run build:pre` 走的是 `npm install -g`。在用 Volta 管理 Node/npm 的机器上,`npm install -g` **不会**在 `~/.volta/bin` 生成 shim(软链接只落在 `~/.volta/tools/image/node/<ver>/bin/`,该目录不在 `PATH` 中),安装后会出现 `lj-awb: command not found`。这类机器请改用:
373
+
374
+ ```bash
375
+ npm run build:volta-pre # 用 volta install 安装本地 pre 构建,正确生成 shim
376
+ npm run build:volta-pre -- --dry-run # 只查看安装计划,不实际安装
377
+ ```
378
+
379
+ `build:volta-pre` 缺少 volta 时会直接报错退出。其余环境变量(`SKIP_CHECK=1` 等)与 `build:pre` 一致。
380
+
368
381
  ## 文档索引
369
382
 
370
383
  - [`docs/TECHNICAL.md`](docs/TECHNICAL.md):架构、运行时、数据契约、发布维护说明。
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import {
6
+ capture,
7
+ copyPackageFiles,
8
+ packTarball,
9
+ patchDefaultApiOrigin,
10
+ run,
11
+ } from './_shared.mjs';
12
+
13
+ const PRE_API_ORIGIN = 'https://animeworkbench-pre.lingjingai.cn';
14
+ const PACKAGE_NAME = '@lingjingai/lj-awb-cli';
15
+ const dryRun = process.env.NPM_INSTALL_DRY_RUN === '1' || process.argv.slice(2).includes('--dry-run');
16
+
17
+ function ensureVolta() {
18
+ if (!process.env.VOLTA_HOME) {
19
+ process.stderr.write(
20
+ 'Volta is required for this script but VOLTA_HOME is not set.\n'
21
+ + 'Install Volta (https://volta.sh) or use `npm run build:pre` instead.\n',
22
+ );
23
+ process.exit(1);
24
+ }
25
+ const result = capture('volta', ['--version']);
26
+ if (result.status !== 0) {
27
+ process.stderr.write(
28
+ 'Volta is required for this script but the `volta` command is not available.\n'
29
+ + 'Install Volta (https://volta.sh) or use `npm run build:pre` instead.\n',
30
+ );
31
+ process.exit(1);
32
+ }
33
+ process.stderr.write(`Using Volta ${result.stdout.trim()} for global install.\n`);
34
+ }
35
+
36
+ function installTarball(tarballPath) {
37
+ const spec = `${PACKAGE_NAME}@file:${tarballPath}`;
38
+ if (dryRun) {
39
+ process.stderr.write(`$ volta install ${spec} (dry-run)\n`);
40
+ process.stderr.write('Validated local pre build Volta install plan from packed tarball.\n');
41
+ return;
42
+ }
43
+
44
+ run('volta', ['install', spec]);
45
+ run('lj-awb', ['--version']);
46
+ process.stderr.write('Installed local pre build with Volta from packed tarball.\n');
47
+ }
48
+
49
+ ensureVolta();
50
+
51
+ if (process.env.SKIP_CHECK !== '1') {
52
+ run('npm', ['run', 'check:local']);
53
+ }
54
+
55
+ const stageDir = mkdtempSync(path.join(tmpdir(), 'lj-awb-pre-src-'));
56
+ try {
57
+ process.stderr.write(`Preparing local pre build with default API origin ${PRE_API_ORIGIN}.\n`);
58
+ copyPackageFiles(stageDir);
59
+ patchDefaultApiOrigin(stageDir, PRE_API_ORIGIN);
60
+ const tarballPath = packTarball(stageDir);
61
+ installTarball(tarballPath);
62
+ } finally {
63
+ rmSync(stageDir, { recursive: true, force: true });
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/lj-awb-cli-pre",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "Lingjing AWB CLI monorepo with shared core, standalone CLI, and agent skills (pre-release build pointing to https://animeworkbench-pre.lingjingai.cn)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,7 +8,6 @@
8
8
  "lj-awb": "packages/awb-cli/bin/lj-awb"
9
9
  },
10
10
  "dependencies": {
11
- "@lingjingai/lj-awb-cli-pre": "^0.4.8",
12
11
  "chalk": "^5.6.2",
13
12
  "cli-table3": "^0.6.5"
14
13
  },
@@ -21,10 +20,11 @@
21
20
  "scripts": {
22
21
  "build": "node build/build.mjs",
23
22
  "build:pre": "node build/pre.mjs",
23
+ "build:volta-pre": "node build/volta-pre.mjs",
24
24
  "build:pre-publish": "node build/pre-publish.mjs",
25
25
  "build:prod": "node build/prod.mjs",
26
26
  "check": "npm run check:local && npm run check:real",
27
- "check:local": "node --check install.mjs && node --check packages/awb-core/src/common.js && node --check packages/awb-core/src/api.js && node --check packages/awb-core/src/auth.js && node --check packages/awb-core/src/output.js && node --check packages/awb-core/src/update.js && node --check packages/awb-core/src/services.js && node --check packages/awb-core/src/commands.js && node --check packages/awb-core/src/standalone.js && sh -n packages/awb-cli/bin/lj-awb && bash -n skills/lj-awb/scripts/resolve-lj-awb-cmd.sh && node --check packages/awb-cli/bin/lj-awb.js && node --check build/build.mjs && node --check build/_shared.mjs && node --check build/pre.mjs && node --check build/pre-publish.mjs && node --check build/prod.mjs && node --check scripts/run-openapi-cli-examples-real.mjs && node --check scripts/validate-cli-schema.mjs && node --check scripts/validate-cli-output-contract.mjs && node --check scripts/validate-cli-command-coverage.mjs && node scripts/validate-skill-meta.mjs && node scripts/validate-cli-schema.mjs && node scripts/validate-cli-command-coverage.mjs",
27
+ "check:local": "node --check install.mjs && node --check packages/awb-core/src/common.js && node --check packages/awb-core/src/api.js && node --check packages/awb-core/src/auth.js && node --check packages/awb-core/src/output.js && node --check packages/awb-core/src/update.js && node --check packages/awb-core/src/services.js && node --check packages/awb-core/src/commands.js && node --check packages/awb-core/src/standalone.js && sh -n packages/awb-cli/bin/lj-awb && bash -n skills/lj-awb/scripts/resolve-lj-awb-cmd.sh && node --check packages/awb-cli/bin/lj-awb.js && node --check build/build.mjs && node --check build/_shared.mjs && node --check build/pre.mjs && node --check build/volta-pre.mjs && node --check build/pre-publish.mjs && node --check build/prod.mjs && node --check scripts/run-openapi-cli-examples-real.mjs && node --check scripts/validate-cli-schema.mjs && node --check scripts/validate-cli-output-contract.mjs && node --check scripts/validate-cli-command-coverage.mjs && node --check tests/test-model-filters.mjs && node scripts/validate-skill-meta.mjs && node scripts/validate-cli-schema.mjs && node scripts/validate-cli-command-coverage.mjs && node --test tests/test-model-filters.mjs",
28
28
  "check:real": "node scripts/validate-cli-output-contract.mjs && node scripts/validate-openapi-cli-examples.mjs",
29
29
  "test:openapi-real": "node scripts/run-openapi-cli-examples-real.mjs",
30
30
  "smoke": "packages/awb-cli/bin/lj-awb --help && packages/awb-cli/bin/lj-awb auth status -f json && packages/awb-cli/bin/lj-awb system && packages/awb-cli/bin/lj-awb auth && packages/awb-cli/bin/lj-awb account && packages/awb-cli/bin/lj-awb project && packages/awb-cli/bin/lj-awb credits && packages/awb-cli/bin/lj-awb upload && packages/awb-cli/bin/lj-awb model && packages/awb-cli/bin/lj-awb create && packages/awb-cli/bin/lj-awb task && packages/awb-cli/bin/lj-awb schema -f json >/dev/null",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/awb-cli-bin",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "Standalone CLI for Lingjing AWB",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -13,6 +13,6 @@
13
13
  "README.md"
14
14
  ],
15
15
  "dependencies": {
16
- "@lingjingai/awb-core": "0.4.8"
16
+ "@lingjingai/awb-core": "0.5.0"
17
17
  }
18
18
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/awb-core",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "Shared core runtime for Lingjing AWB CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -85,6 +85,14 @@ const MODEL_GROUP_ARG = { name: 'model-group-code', valueName: 'code', descripti
85
85
  const ASSET_PLATFORM_ARG = { name: 'platform', valueName: 'platform', description: '素材加白平台,必须显式传 JIMENG 或 BYTEPLUS,不做别名推断' };
86
86
  const MODEL_LIST_ARGS = [
87
87
  { name: 'model', valueName: 'keyword', description: '按模型名、编码、供应商或输入方式过滤;不传时查询全部' },
88
+ { name: 'usage', valueName: 'usage', description: '模型用途:image 可选 IMAGE_CREATE(默认)/IMAGE_EDIT;video 为 VIDEO_CREATE。取值大小写不敏感' },
89
+ { name: 'provider', valueName: 'keyword', description: '按厂商过滤(大小写不敏感子串,如 字节 / GPT / Google)' },
90
+ { name: 'input-mode', valueName: 'mode', description: '按输入方式过滤(对模型 inputModes 做成员匹配,如 reference / prompt_only / frames)' },
91
+ { name: 'supports', valueName: 'param', description: '按支持的可调参数过滤(对 params 做成员匹配,如 quality / ratio / duration / needAudio)' },
92
+ { name: 'quality', valueName: 'value', description: '按分辨率取值过滤(模型真实支持该 quality 才返回,如 4k / 2k / 1080;大小写不敏感)' },
93
+ { name: 'ratio', valueName: 'value', description: '按画面比例取值过滤(模型真实支持该 ratio 才返回,如 9:16 / 16:9 / 1:1)' },
94
+ { name: 'duration', valueName: 'sec', description: '按时长取值过滤(video,模型真实支持该秒数才返回,如 10 / 15)' },
95
+ { name: 'need-audio', description: '只返回支持音效 / 音频的模型(video,按 audio 能力)' },
88
96
  { name: 'limit', valueName: 'n', description: '默认文本最多展示 N 条,默认 8;JSON 输出始终返回全部匹配' },
89
97
  { name: 'all', description: '默认文本展示全部匹配模型(覆盖 --limit)' },
90
98
  INCLUDE_RAW_ARG,
@@ -1512,7 +1512,7 @@ const DETAIL_FIELDS_BY_KIND = {
1512
1512
  subtitle_task_submission: ['submitted', 'taskId', 'taskType', 'sourceTaskId', 'projectGroupNo', 'pointCost', 'billingPointBalance', 'billingPointRemainingAfter', 'projectBudgetBalance', 'projectBudgetMax', 'projectBudgetRemainingAfter', 'nextCommand'],
1513
1513
  task_status: ['taskId', 'publicId', 'remoteTaskId', 'taskType', 'taskStatus', 'isTerminal', 'modelGroupCode', 'projectGroupNo', 'pointNo', 'gmtCreate', 'resultCount', 'errorMessage', 'timedOut', 'waitedMs'],
1514
1514
  task_submission: ['taskId', 'taskType', 'objectName', 'modelGroupCode', 'projectGroupNo', 'pointNo', 'points', 'pointCost', 'billingPointBalance', 'billingPointRemainingAfter', 'projectBudgetBalance', 'projectBudgetMax', 'projectBudgetRemainingAfter', 'uploadCount', 'nextCommand', 'statusCommand'],
1515
- update_result: ['checked', 'updated', 'updateAvailable', 'packageName', 'previousVersion', 'currentVersion', 'latestVersion', 'command', 'skillUpdated', 'restartRecommended', 'message'],
1515
+ update_result: ['checked', 'updated', 'updateAvailable', 'packageName', 'installer', 'previousVersion', 'currentVersion', 'latestVersion', 'command', 'skillUpdated', 'restartRecommended', 'message'],
1516
1516
  upload_result: ['dryRun', 'fileCount', 'assetCount', 'localFileCount', 'resourceConversionCount'],
1517
1517
  };
1518
1518
 
@@ -997,7 +997,6 @@ function modelListMetadata(kind, paramKeys = [], rulesByKey = new Map()) {
997
997
  const controls = [];
998
998
  if (keys.has('ratio')) controls.push('ratio');
999
999
  if (keys.has('quality')) controls.push('quality');
1000
- if (kind === 'image' && keys.has('generate_num')) controls.push('generateNum');
1001
1000
  if (kind === 'video' && keys.has('generated_time')) controls.push('duration');
1002
1001
  if (kind === 'video' && (keys.has('need_audio') || keys.has('audio'))) controls.push('needAudio');
1003
1002
  return {
@@ -1032,6 +1031,14 @@ function modelSuccessRate(item = {}) {
1032
1031
  );
1033
1032
  }
1034
1033
 
1034
+ function modelParamEnumValues(rawOptions, paramKey) {
1035
+ const option = (Array.isArray(rawOptions) ? rawOptions : []).find((item) => item?.paramKey === paramKey);
1036
+ const list = Array.isArray(option?.optionList) ? option.optionList : [];
1037
+ return uniqueNonEmpty(list
1038
+ .filter((entry) => entry?.available !== false)
1039
+ .map((entry) => entry?.enumValue ?? entry?.value ?? entry?.enumName));
1040
+ }
1041
+
1035
1042
  function normalizeModelRows(payload, kind, options = {}) {
1036
1043
  const includeRaw = Boolean(options.includeRaw);
1037
1044
  const includeInternal = Boolean(options.includeInternal);
@@ -1058,6 +1065,10 @@ function normalizeModelRows(payload, kind, options = {}) {
1058
1065
  feeCalcType: item?.feeCalcType ?? item?.feeType ?? null,
1059
1066
  inputModes: metadata.inputModes,
1060
1067
  params: metadata.controls,
1068
+ ratios: modelParamEnumValues(rawOptions, 'ratio'),
1069
+ qualities: modelParamEnumValues(rawOptions, 'quality'),
1070
+ durations: modelParamEnumValues(rawOptions, 'generated_time'),
1071
+ needAudio: metadata.controls.includes('needAudio'),
1061
1072
  _searchText: JSON.stringify([
1062
1073
  modelCode,
1063
1074
  modelGroupCode,
@@ -1074,18 +1085,114 @@ function normalizeModelRows(payload, kind, options = {}) {
1074
1085
  });
1075
1086
  }
1076
1087
 
1088
+ const MODEL_USAGE_FAMILY = {
1089
+ image: { default: 'IMAGE_CREATE', allow: new Set(['IMAGE_CREATE', 'IMAGE_EDIT']) },
1090
+ video: { default: 'VIDEO_CREATE', allow: new Set(['VIDEO_CREATE']) },
1091
+ };
1092
+
1093
+ export function resolveModelUsage(kind, kwargs = {}) {
1094
+ const family = MODEL_USAGE_FAMILY[kind] ?? MODEL_USAGE_FAMILY.image;
1095
+ const explicit = trimToNull(kwargs.usage);
1096
+ if (!explicit) return family.default;
1097
+ const usage = explicit.toUpperCase();
1098
+ if (!family.allow.has(usage)) {
1099
+ throw argumentError(
1100
+ `不支持的 usage:${explicit}`,
1101
+ `${kind} 模型可选 usage:${[...family.allow].join(' / ')}。`,
1102
+ );
1103
+ }
1104
+ return usage;
1105
+ }
1106
+
1107
+ export function buildModelFilters(kwargs = {}) {
1108
+ const keyword = trimToNull(kwargs.model ?? kwargs.keyword)?.toLowerCase() ?? null;
1109
+ const provider = trimToNull(kwargs.provider)?.toLowerCase() ?? null;
1110
+ const inputMode = trimToNull(kwargs.inputMode) ?? null;
1111
+ const supports = trimToNull(kwargs.supports) ?? null;
1112
+ const quality = trimToNull(kwargs.quality) ?? null;
1113
+ const ratio = trimToNull(kwargs.ratio) ?? null;
1114
+ const duration = trimToNull(kwargs.duration) ?? null;
1115
+ const needAudio = toBool(kwargs.needAudio) ? true : null;
1116
+ const applied = [];
1117
+ if (keyword) applied.push('keyword');
1118
+ if (provider) applied.push('provider');
1119
+ if (inputMode) applied.push('inputMode');
1120
+ if (supports) applied.push('supports');
1121
+ if (quality) applied.push('quality');
1122
+ if (ratio) applied.push('ratio');
1123
+ if (duration) applied.push('duration');
1124
+ if (needAudio) applied.push('needAudio');
1125
+ return { keyword, provider, inputMode, supports, quality, ratio, duration, needAudio, applied };
1126
+ }
1127
+
1128
+ function includesCI(haystack, needle) {
1129
+ return String(haystack ?? '').toLowerCase().includes(needle);
1130
+ }
1131
+
1132
+ function memberCI(list, value) {
1133
+ const target = String(value).toLowerCase();
1134
+ return (Array.isArray(list) ? list : []).some((item) => String(item).toLowerCase() === target);
1135
+ }
1136
+
1137
+ export function applyModelFilters(rows, filters = {}) {
1138
+ return (Array.isArray(rows) ? rows : []).filter((row) => {
1139
+ if (filters.keyword && !includesCI(row._searchText, filters.keyword)) return false;
1140
+ if (filters.provider && !includesCI(row.provider, filters.provider)) return false;
1141
+ if (filters.inputMode && !memberCI(row.inputModes, filters.inputMode)) return false;
1142
+ if (filters.supports && !memberCI(row.params, filters.supports)) return false;
1143
+ if (filters.quality && !memberCI(row.qualities, filters.quality)) return false;
1144
+ if (filters.ratio && !memberCI(row.ratios, filters.ratio)) return false;
1145
+ if (filters.duration && !memberCI(row.durations, filters.duration)) return false;
1146
+ if (filters.needAudio && row.needAudio !== true) return false;
1147
+ return true;
1148
+ });
1149
+ }
1150
+
1151
+ export function availableFilterValues(rows) {
1152
+ const list = Array.isArray(rows) ? rows : [];
1153
+ const providers = new Set();
1154
+ const inputModes = new Set();
1155
+ const supports = new Set();
1156
+ const qualities = new Set();
1157
+ const ratios = new Set();
1158
+ const durations = new Set();
1159
+ for (const row of list) {
1160
+ if (row.provider) providers.add(row.provider);
1161
+ for (const mode of row.inputModes || []) inputModes.add(mode);
1162
+ for (const param of row.params || []) supports.add(param);
1163
+ for (const q of row.qualities || []) qualities.add(q);
1164
+ for (const r of row.ratios || []) ratios.add(r);
1165
+ for (const d of row.durations || []) durations.add(d);
1166
+ }
1167
+ return {
1168
+ providers: [...providers],
1169
+ inputModes: [...inputModes],
1170
+ supports: [...supports],
1171
+ qualities: [...qualities],
1172
+ ratios: [...ratios],
1173
+ durations: [...durations],
1174
+ };
1175
+ }
1176
+
1077
1177
  export async function listModels(kind, kwargs = {}) {
1078
- const usage = kind === 'image' ? 'IMAGE_CREATE' : 'VIDEO_CREATE';
1178
+ const usage = resolveModelUsage(kind, kwargs);
1079
1179
  const payload = await awbApi.fetchModelsByUsage(usage, {});
1080
- const keyword = trimToNull(kwargs.model ?? kwargs.keyword)?.toLowerCase();
1081
1180
  const includeRaw = toBool(kwargs.includeRaw);
1082
- const models = normalizeModelRows(payload, kind, { includeRaw })
1083
- .filter((item) => {
1084
- if (!keyword) return true;
1085
- return String(item._searchText ?? '').toLowerCase().includes(keyword);
1086
- })
1087
- .map(({ _searchText, _modelCode, ...item }) => item);
1088
- return { usage, models };
1181
+ const allRows = normalizeModelRows(payload, kind, { includeRaw });
1182
+ const filters = buildModelFilters(kwargs);
1183
+ const matched = applyModelFilters(allRows, filters);
1184
+ const models = matched.map(({ _searchText, _modelCode, ...item }) => item);
1185
+ const result = {
1186
+ usage,
1187
+ models,
1188
+ matchedCount: models.length,
1189
+ totalCount: allRows.length,
1190
+ filtersApplied: filters.applied,
1191
+ };
1192
+ if (models.length === 0 && allRows.length > 0) {
1193
+ result.availableValues = availableFilterValues(allRows);
1194
+ }
1195
+ return result;
1089
1196
  }
1090
1197
 
1091
1198
  function requireAssetPlatform(value, options = {}) {
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { LingjingAwbCliError, createEnvelope, toBool, toInt } from './common.js';
4
+ import { LingjingAwbCliError, createEnvelope, toBool, toInt, trimToNull } from './common.js';
5
5
  import { registerAwbCommands } from './commands.js';
6
6
  import { checkCliUpdate } from './update.js';
7
7
  import {
@@ -196,7 +196,13 @@ function buildOutputContext(command, kwargs = {}, data = {}) {
196
196
  confirmSet.autoConvert = true;
197
197
  }
198
198
  const isModelList = command.name === 'model image-models' || command.name === 'model video-models';
199
- const modelListAll = isModelList && toBool(kwargs.all);
199
+ const hasModelFilter = isModelList && !toBool(kwargs.all) && Boolean(
200
+ trimToNull(kwargs.model) || trimToNull(kwargs.usage) || trimToNull(kwargs.provider)
201
+ || trimToNull(kwargs.inputMode) || trimToNull(kwargs.supports)
202
+ || trimToNull(kwargs.quality) || trimToNull(kwargs.ratio)
203
+ || trimToNull(kwargs.duration) || toBool(kwargs.needAudio),
204
+ );
205
+ const modelListAll = isModelList && (toBool(kwargs.all) || hasModelFilter);
200
206
  const modelListLimit = isModelList
201
207
  ? (modelListAll ? Number.POSITIVE_INFINITY : Math.max(1, toInt(kwargs.limit, 8)))
202
208
  : undefined;
@@ -149,6 +149,32 @@ async function runNpm(args, options = {}) {
149
149
  }
150
150
  }
151
151
 
152
+ async function runVolta(args, options = {}) {
153
+ try {
154
+ const result = await execFileAsync('volta', args, {
155
+ cwd: currentRootDir(),
156
+ encoding: 'utf8',
157
+ timeout: options.timeoutMs || DEFAULT_NPM_TIMEOUT_MS,
158
+ maxBuffer: 1024 * 1024,
159
+ env: process.env,
160
+ });
161
+ return result;
162
+ } catch (error) {
163
+ const stderr = String(error.stderr || '').trim();
164
+ const stdout = String(error.stdout || '').trim();
165
+ throw new LingjingAwbCliError('volta 命令执行失败', {
166
+ type: 'runtime_error',
167
+ exitCode: 30,
168
+ hint: stderr || stdout || error.message || '请确认 Volta 可用,或改用 npm 全局安装方式。',
169
+ details: {
170
+ command: `volta ${args.join(' ')}`,
171
+ code: error.code,
172
+ signal: error.signal,
173
+ },
174
+ });
175
+ }
176
+ }
177
+
152
178
  export async function fetchLatestCliVersion(options = {}) {
153
179
  const mocked = trimToNull(process.env.LINGJING_AWB_UPDATE_LATEST_VERSION);
154
180
  if (mocked) return mocked;
@@ -258,6 +284,45 @@ function tailLines(value, limit = 8) {
258
284
  return lines.slice(Math.max(0, lines.length - limit));
259
285
  }
260
286
 
287
+ function isVoltaPackageImagePath(value) {
288
+ const normalized = String(value || '').replaceAll('\\', '/');
289
+ return normalized.includes('/.volta/tools/image/packages/');
290
+ }
291
+
292
+ async function isCurrentCommandVoltaManaged() {
293
+ try {
294
+ const result = await execFileAsync('volta', ['which', commandPrefix()], {
295
+ cwd: currentRootDir(),
296
+ encoding: 'utf8',
297
+ timeout: 5_000,
298
+ maxBuffer: 256 * 1024,
299
+ env: process.env,
300
+ });
301
+ return isVoltaPackageImagePath(result.stdout);
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+
307
+ async function resolveUpdateInstaller({ packageName, installVersion, installSpec, registry }) {
308
+ if (await isCurrentCommandVoltaManaged()) {
309
+ return {
310
+ name: 'volta',
311
+ args: ['install', npmInstallSpec(packageName, installVersion)],
312
+ command: `volta install ${npmInstallSpec(packageName, installVersion)}`,
313
+ run: () => runVolta(['install', npmInstallSpec(packageName, installVersion)], { timeoutMs: 120_000 }),
314
+ };
315
+ }
316
+
317
+ const args = npmArgsWithRegistry(['install', '-g', installSpec], registry);
318
+ return {
319
+ name: 'npm',
320
+ args,
321
+ command: `npm ${args.join(' ')}`,
322
+ run: () => runNpm(args, { timeoutMs: 120_000 }),
323
+ };
324
+ }
325
+
261
326
  export async function runCliUpdate(kwargs = {}) {
262
327
  const currentVersion = await readCurrentCliVersion().catch(() => 'unknown');
263
328
  const packageName = resolveCliPackageName(kwargs);
@@ -268,7 +333,6 @@ export async function runCliUpdate(kwargs = {}) {
268
333
  const force = toBool(kwargs.force);
269
334
  const installVersion = latestVersion || 'latest';
270
335
  const installSpec = npmInstallSpec(packageName, force ? 'latest' : installVersion);
271
- const installArgs = npmArgsWithRegistry(['install', '-g', installSpec], registry);
272
336
 
273
337
  if (checkOnly) {
274
338
  return {
@@ -297,7 +361,13 @@ export async function runCliUpdate(kwargs = {}) {
297
361
  };
298
362
  }
299
363
 
300
- const result = await runNpm(installArgs, { timeoutMs: 120_000 });
364
+ const installer = await resolveUpdateInstaller({
365
+ packageName,
366
+ installVersion: force ? 'latest' : installVersion,
367
+ installSpec,
368
+ registry,
369
+ });
370
+ const result = await installer.run();
301
371
  await saveState({
302
372
  updateCheck: {
303
373
  packageName,
@@ -314,10 +384,11 @@ export async function runCliUpdate(kwargs = {}) {
314
384
  updated: true,
315
385
  updateAvailable,
316
386
  packageName,
387
+ installer: installer.name,
317
388
  previousVersion: currentVersion,
318
389
  currentVersion: latestVersion,
319
390
  latestVersion,
320
- command: `npm ${installArgs.join(' ')}`,
391
+ command: installer.command,
321
392
  skillUpdated: true,
322
393
  restartRecommended: true,
323
394
  message: '更新完成。请退出并重新打开 AI Agent,以加载最新 CLI 和 skill。',
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: lj-awb
3
- version: 0.4.8
3
+ version: 0.5.0
4
4
  description: "灵境 AWB CLI skill。使用 `lj-awb` 命令调用动漫平台 / AWB 云端能力,覆盖认证、项目组、积分(含充值购买与兑换码兑换)、模型发现、上传、统一 create 创建域、任务查询、视频超分。用户说生图、生视频、视频超分、主体、音色、素材加白、去字幕、充值、购买积分、兑换码时使用。正式生成、切换项目组、清空认证、兑换码等写入或扣费动作前必须确认。"
5
5
  metadata:
6
6
  bootstrap:
7
7
  package: "@lingjingai/lj-awb-cli"
8
- version: "0.4.8"
8
+ version: "0.5.0"
9
9
  bin: "lj-awb"
10
10
  cliHelp: "lj-awb --help"
11
11
  ---
@@ -15,6 +15,13 @@ metadata:
15
15
  `lj-awb` 是确定性 CLI,skill 的职责是调度:少查、少问、少重跑,按 schema 和业务链条把命令串对。不要把本 skill 当成一堆命令片段来拼。
16
16
  普通命令如果 JSON 输出里出现 `meta._notice.update`,先完成当前用户请求,再告诉用户当前版本和最新版本,并建议执行 `lj-awb update`;更新后提醒重新打开 AI Agent。
17
17
 
18
+ ## 对话输出纪律
19
+
20
+ - 面向用户只汇报关键状态和确认点:候选模型摘要、需要用户选择的参数、费用 / dry-run 结果、任务 ID、最终产物链接或明确错误。
21
+ - 不主动暴露完整命令、长 JSON、schema 摘要、并行查询清单或内部调度过程;这些只在用户明确要求“展示命令 / 输出 / 日志”时给出。
22
+ - 中间更新保持短句,避免逐条复述每个只读查询。需要说明进度时,用“正在查询模型候选 / 正在估价 / 正在等待任务”这类概括表达。
23
+ - 最终答复聚焦结果和下一步,不把工具调用轨迹当作交付内容。
24
+
18
25
  ## 先读驱动器
19
26
 
20
27
  任何非单条查询任务,都先加载 [`modules/driver.md`](modules/driver.md)。它定义:
@@ -102,6 +109,7 @@ schema 查询必须先返回,再组织业务命令;不要把 schema 查询
102
109
  - schema 中 `safety.requiresConfirmation=true` 的命令必须先向用户确认,再追加 `--yes`。
103
110
  - schema 中 `safety.supportsDryRun=true` 的写入 / 扣费命令先 `--dry-run`,确认后再正式执行。
104
111
  - 创作类命令在 fee / dry-run 前必须确认会影响价格或效果的关键参数;用户明确说“按默认”后才使用 defaultValue。
112
+ - Prompt 默认原文直出:用户提供明确 prompt / 创作描述时,禁止语义改写、润色、扩写或从参考图补画面细节;只有用户明确要求“优化 / 润色 / 扩写 / 你来写 prompt”时才允许改写,且改写后的最终 prompt 必须先展示并等待确认,之后才能 fee / dry-run / create。
105
113
  - `fee` 是最终估价,不是参数探索工具。不要为多个 quality / duration / ratio / 渠道组合反复跑 fee。
106
114
  - 正式图片 / 视频任务要带 `--project-group-no`,除非命令 schema 没有该参数。
107
115
  - 命令返回 `nextCommand`、`nextRefSubject`、`nextVoiceArg` 时优先复用返回值,不手拼。
@@ -1 +1 @@
1
- 0.4.8
1
+ 0.5.0
@@ -1,6 +1,6 @@
1
1
  {
2
- "skillVersion": "0.4.8",
3
- "minCliVersion": "0.4.8",
2
+ "skillVersion": "0.5.0",
3
+ "minCliVersion": "0.5.0",
4
4
  "preferredCommand": "lj-awb",
5
5
  "updatedAt": "2026-06-13"
6
6
  }
@@ -7,7 +7,7 @@
7
7
  | CLI 参数 | 含义 | 组织方式 |
8
8
  |----------|------|----------|
9
9
  | `--model-group-code` | 模型组编码,决定使用哪个模型、计费方式和参数白名单 | 来自 `model image-models` / `model video-models` |
10
- | `--prompt` | 提交给模型的最终提示词 | 以用户明确创作描述为基底,组装素材输入和模型约束;视频需要占位绑定时才做 key 对齐;没有明确需求时先追问或用最小中性表达 |
10
+ | `--prompt` | 提交给模型的最终提示词 | 用户提供明确 prompt / 创作描述时默认原文直出,只做转义清理;禁止语义改写。只有用户明确要求优化 / 润色 / 扩写 / 代写 prompt 时才可改写,且必须先展示最终 prompt 等待确认 |
11
11
  | `--ratio` | 画幅比例 | 只能从 `model options.params[key=ratio].values` 选择 |
12
12
  | `--quality` | 清晰度 / 分辨率档位 | 只能从 `model options.params[key=quality].values` 选择 |
13
13
  | `--generate-num` | 生图张数 | 仅图片模型支持时使用 |
@@ -67,14 +67,16 @@ JSON 语法:
67
67
 
68
68
  ## Prompt 组装原则
69
69
 
70
- 创建任务时,Prompt 的核心能力是组装可执行请求:
70
+ 创建任务时,Prompt 的核心原则是默认原文直出:
71
71
 
72
- - 用户有明确创作描述或提示词时,以该文本为基底,只做必要的素材组织、视频 key 对齐和转义清理,不要强行补资源占位符。
73
- - “使用某模型 / 参考生 / 生成视频 / 用这张图 / 用这个音频”等操作性指令只用于选择模型和资源,不要改写成新的画面描述。
74
- - 用户没有比较明确的创作需求或提示词时,先追问关键创作意图,或使用最小中性表达;不要为了让 prompt 好看而主动补风格、镜头、剧情细节。
72
+ - 用户有明确创作描述或提示词时,`--prompt` 必须使用用户原文;只允许做 shell 转义、去掉包裹引号、首尾空白清理这类不改变语义的机械处理。
73
+ - 禁止把短 prompt 自动扩成更长描述;禁止主动补风格、镜头、剧情、光线、场景、情绪、动作细节;禁止为了“更适合模型”而润色。
74
+ - “使用某模型 / 参考生 / 生成视频 / 用这张图 / 用这个音频”等操作性指令只用于选择模型、资源和参数,不要改写成新的画面描述。
75
+ - 参考图已经作为资源输入时,不要从图片内容中推断“山谷、街道、室内、人物外观”等画面描述塞进 prompt;除非用户明确要求看图补充描述或根据图写 prompt。
75
76
  - 用户只是描述任务目标时,不要默认补资源占位符;图片生图永远不用 `<<<key>>>`,视频只有用户明确要在 prompt 中绑定某个参考对象时才使用。
76
- - 参考图已经作为资源输入时,不要从图片内容中推断“山谷、街道、室内、人物外观”等画面描述塞进 prompt;除非用户明确要求看图补充描述。
77
- - 用户要求优化 / 改写 / 创作 prompt 时,可以进行提示词创作,但创建任务前仍要展示最终 prompt
77
+ - 用户没有比较明确的创作需求或提示词时,先追问关键创作意图,或使用最小中性表达;不要为了让 prompt 好看而主动补内容。
78
+ - 只有用户明确要求“优化 / 润色 / 扩写 / 改写 / 帮我写 prompt / 你来写 prompt / 更适合生图或生视频”时,Agent 才能进行提示词创作。
79
+ - 一旦最终 prompt 与用户原文发生任何可见语义变化,必须先展示原始 prompt 和最终 prompt,等待用户确认;确认前不得运行 `fee`、`create --dry-run` 或正式 `create`。
78
80
 
79
81
  规则:
80
82
 
@@ -122,7 +124,7 @@ Agent 必须按顺序做:
122
124
  7. 用 `supportedIntents[]` 匹配用户意图;不要只看模型列表摘要就生成最终能力结论。
123
125
  8. 参数枚举值只从 `model options.params[].values` 选择,素材限制只从 `model options.resources[]` 校验,参数 / 资源联动限制按 `model options.constraints[]` 收窄。新增通用参数只要在 `params[]` 中出现且带 `cliArg`,就通过 `--model-param` / `--model-params-json` 显式传入。
124
126
  9. 不主动列举所有缺失控制项;只追问会影响价格 / 效果的关键参数。视频包括 `quality`、`duration`、约束后仍可选的 `ratio`,以及用户明确要求输出音效时的 `needAudio`;图片包括 `quality`、`ratio`、`generateNum`。
125
- 10. 组装最终 prompt;如果可见文本发生变化,先展示给用户。
127
+ 10. 组装最终 prompt;默认使用用户原文。若用户明确要求优化 / 代写且最终 prompt 与用户原文发生任何可见语义变化,先展示原始 prompt 和最终 prompt,等待用户确认。
126
128
  11. 若用户上传或指定音频文件,必须先确认 `model options.resources[]` 中存在 `mediaType=AUDIO usage=reference` 后再组织 `audio:reference` 资源;只有用户明确要求“输出是否带模型生成音效/音频”时,才检查 `model options.params[]` 是否存在 `needAudio`。
127
129
  12. 用户未提供关键参数时,把 `model options.params[].values` 和 `defaultValue` 转成候选问题;用户选择或确认“按默认”前,不跑 `create image-fee` / `create video-fee`,也不跑 `create --dry-run`。即使模型给了 `defaultValue`,也不要静默写入请求。
128
130
  13. 跑 `create image-fee` / `create video-fee` 获取预估积分——**只跑一次,参数取用户已确认的那组**。`fee` 是"用户敲定参数后的最终估价",不是参数探索 / 候选比较工具:不要为了对比价格档跑 `fee` × 多个 `quality` / `duration` / `ratio` 组合,也不要为了横向比同款不同渠道跑 `fee` × 多个 `modelGroupCode`;价格档差异从 `model options.feeCalcType` + 参数取值表里就能口算/展示给用户,比较应该发生在 `model options` 阶段。
@@ -12,6 +12,7 @@
12
12
  - 写入 / 扣费命令遵循 schema safety:`supportsDryRun=true` 先 dry-run,`requiresConfirmation=true` 经用户确认后再 `--yes`。
13
13
  - 如果 JSON 输出带 `meta._notice.update`,先继续完成当前业务链路,收尾时告知当前版本 / 最新版本并建议 `lj-awb update`;不要静默忽略更新提示。
14
14
  - 旧根域 `image` / `video` / `asset` / `subject` 已移除,不要尝试旧入口,也不要给旧命令做兼容推理。
15
+ - 对话输出默认精简:不要把完整命令、长 JSON、schema 结果或每个只读查询的轨迹暴露给用户;只输出关键确认点、费用 / dry-run 摘要、任务状态、结果链接和必要错误。用户明确要求日志或命令时再展示。
15
16
 
16
17
  ## 状态账本
17
18
 
@@ -60,6 +61,8 @@ CLI 当前能力分为:
60
61
  8. 跑一次 `create image/video --dry-run` 展示最终 prompt、资源、参数、积分。
61
62
  9. 用户确认后 `--yes`。返回 `nextCommand` 时直接执行它等待结果。
62
63
 
64
+ 对用户展示这条链路时只给阶段性摘要,不逐条列出 schema / model options / fee 命令和原始输出。
65
+
63
66
  ### 模型候选展示门
64
67
 
65
68
  只要用户给了模型口语名(如 `sd2`、`gpt`、`banana`、`可灵`)或模型查询返回候选,Agent 必须先完成这个门槛,才能推荐默认模型 / 默认参数、进入 `model create-spec`、`fee` 或 `create --dry-run`。
@@ -8,7 +8,7 @@
8
8
  - 能区分参考图、首帧、首尾帧、音频参考、主体复用。
9
9
  - 能区分 `audio:reference` 和 `--need-audio`。
10
10
  - 不使用 `audio:source`、平台旧字段、`generated_time`、`multi_param`、`frames` 作为 create 入参。
11
- - 能把用户意图、素材引用和模型约束组装成可执行 prompt;没有明确需求时不主动编内容补空白;图片生图不生成 `reference_key` 或 `<<<key>>>`。
11
+ - 能把用户意图、素材引用和模型约束组装成可执行请求;用户有明确 prompt 时默认原文直出;没有明确需求时不主动编内容补空白;图片生图不生成 `reference_key` 或 `<<<key>>>`。
12
12
  - `fee` 和 `create --dry-run` 前必须先确认用户未提供的关键价格 / 效果参数;不能静默用默认值跑估价。
13
13
  - 正式扣费前必须 fee + dry-run + 用户确认。
14
14
 
@@ -22,7 +22,7 @@
22
22
 
23
23
  - 查询 `model video-models --model "SD2"` 或相关关键词,再查被选模型的 `create-spec`。
24
24
  - 视频参考图可使用 `image:reference:hero=<image>`,音频使用 `audio:reference=<audio>`。
25
- - prompt 可以组装为 `<<<hero>>> 选择跳跃`,但不能自动加电影感、镜头推进等额外内容。
25
+ - prompt 默认保持 `选择跳跃` 并使用不带 key 的参考图;如果确需改成 `<<<hero>>> 选择跳跃`,必须先询问并获得确认。不能自动加电影感、镜头推进等额外内容。
26
26
  - 不要根据参考图画面内容自动补“山谷、街道、室内、服装”等场景描述。
27
27
  - 不先寻找 `needAudio`;不输出“该模型不支持音频参数”这类误导结论。
28
28
  - 如果用户没有指定画质、时长或约束后仍可选的比例,dry-run 前必须给出候选值和默认值让用户选择。
@@ -17,7 +17,7 @@
17
17
 
18
18
  | 参数 | 何时使用 | 约束来源 |
19
19
  |------|----------|----------|
20
- | `--prompt` | 所有生图任务 | 以用户明确创作描述为基底组装的最终提交文本;没有明确需求时先追问或用最小中性表达 |
20
+ | `--prompt` | 所有生图任务 | 用户有明确 prompt / 创作描述时默认原文直出;只有明确要求优化 / 代写时才可改写,且必须先展示最终 prompt 等待确认 |
21
21
  | `--ratio` | 需要指定画幅时 | `model options.params[key=ratio].values` |
22
22
  | `--quality` | 需要指定清晰度时 | `model options.params[key=quality].values` |
23
23
  | `--generate-num` | 指定生成张数;模型暴露该控制项时属于价格关键参数 | `model options.params[key=generateNum].values` |
@@ -56,7 +56,7 @@ lj-awb create image \
56
56
  ## 规则
57
57
 
58
58
  - 不要提交 `generated_time`、`iref`、`frames`、`multi_param`、`generated_mode` 这类平台旧字段。
59
- - `--prompt` 的重点是组装资源、参数和模型约束;用户没有明确需求时不要主动编内容补空白,用户要求优化时再做提示词创作。
59
+ - `--prompt` 默认使用用户原文;不要主动扩写、润色或从参考图补画面细节。用户要求优化 / 代写时再做提示词创作,且确认前不跑 fee / dry-run / create。
60
60
  - 参考素材统一使用 `--resource image:reference=...` 或 `--resources-json`。
61
61
  - 参考图 source.value 取值优先级、"不要复用 myqcloud 完整 URL"、图片生图不使用 `reference_key` / `<<<key>>>` 等规则统一在 [`create-contract.md`](create-contract.md) §素材组织 + §Prompt 组装原则;多张参考图用资源顺序和自然语言指代映射(例如"图一中的白发女保持表情,在图二背景里坐秋千"),不要替用户编 key。
62
62
  - `ratio` / `quality` / `generate-num` 不确定时,先查 `model options`,不要猜一个模型不支持的值。
@@ -27,7 +27,7 @@ lj-awb model video-models --model "<keyword>"
27
27
  用户可见清单至少包含:
28
28
 
29
29
  - 模型显示名、模型描述和排队状态。
30
- - `quality`、`ratio`、视频 `duration` 或图片 `generateNum` 的可选值与默认值。
30
+ - `quality`、`ratio`、视频 `duration` 的可选值与默认值。
31
31
  - 资源能力:reference 支持哪些媒体,是否支持音频参考、视频参考、首帧 / 首尾帧、主体引用。
32
32
  - 同底模不同渠道 / Fast / Pro 等差异;如果参数完全一致,可以说明主要按队列、渠道或用户偏好选择。
33
33
 
@@ -102,3 +102,52 @@ Agent 读取这些字段:
102
102
  - 模型 + 参数定下后 → [`image.md`](image.md) 或 [`video.md`](video.md) 跑 `fee` + `create --dry-run` + 确认 + `--yes`。
103
103
  - 模型 `taskQueueNum` 很大(>100)→ 向用户提示排队风险,可让用户在候选间换;不要默认换。
104
104
  - 不要重复跑 `model image-models / video-models` —— `modelGroupCode` 一旦确定,整个对话内复用。
105
+
106
+ ## 结构化过滤(Agent 翻译用户意图 → flag)
107
+
108
+ `model image-models` / `model video-models` 支持在数据源头收窄候选,避免把全量拉回再自己筛:
109
+
110
+ - `--usage IMAGE_EDIT`:查图片编辑模型池(image 默认 `IMAGE_CREATE`)。
111
+ - `--provider 字节`:按厂商过滤(大小写不敏感子串)。
112
+ - `--input-mode reference`:只要支持参考图 / 对应输入方式的模型。
113
+ - `--supports quality`:只要**能调**该参数的模型(只看参数是否存在,不看取值)。
114
+ - `--quality 4k`:只要**真实支持**该分辨率取值的模型(按 quality.optionList 筛)。
115
+ - `--ratio 9:16`:只要**真实支持**该画面比例取值的模型(按 ratio.optionList 筛)。
116
+ - `--duration 10`:只要**真实支持**该时长秒数的视频模型(按 generated_time.optionList 筛)。
117
+ - `--need-audio`:只要支持音效 / 音频的视频模型(按 audio 能力)。
118
+
119
+ > `--supports quality` 与 `--quality 4k` 的区别:前者"能不能调画质",后者"能不能出 4k"。要按具体分辨率/尺寸/时长筛用 `--quality` / `--ratio` / `--duration`,不要用 `--supports`。
120
+
121
+ 多个条件 AND 叠加,且与 `--model` 关键词一并 AND。带任一过滤 flag 时直接返回全部命中(不再截断到 8)。零命中时响应带 `matchedCount=0` 与 `availableValues`(当前数据实际可用取值),据此放宽条件,不要把空结果当"无适配模型"。取值不在 CLI 硬编码 —— 以列表输出里的 `inputModes=` / `params=` 为准。
122
+
123
+ ### 模型口语名 → 厂商(决定 `--provider` 取值)
124
+
125
+ 用户常说的是模型口语名 / 系列名,Agent 必须先映射到**厂商**,再传 `--provider`(子串匹配即可):
126
+
127
+ | 用户可能说 | 厂商 | `--provider` 取值 |
128
+ |-----------|------|------------------|
129
+ | Banana / Nano Banana | Google | `Google` |
130
+ | Sora / Sora2 | OpenAI | `OpenAI` |
131
+ | GPT / GPT Image | OpenAI | `GPT`(见下方说明) |
132
+ | vidu | 生数科技 | `生数科技` |
133
+ | 可灵 / KeLing | 快手 | `快手` |
134
+ | 即梦 / Seedream / Seedance | 字节跳动 | `字节` |
135
+ | 千问 / 万相 / HappyHorse | 阿里通义 | `阿里` |
136
+ | 海螺 | Minimax | `Minimax` |
137
+ | FLUX | Blackforest | `Blackforest` |
138
+ | Midjourney / niji | Midjourney | `Midjourney` |
139
+ | Grok | Grok | `Grok` |
140
+
141
+ - **OpenAI 品牌在数据里被拆成两个 provider 值**:图片模型 GPT Image 的 `provider=GPT`,视频模型 Sora2 的 `provider=OpenAI`。提到 GPT / Sora2 都归属 OpenAI;但实际过滤时,图片用 `--provider GPT`、视频用 `--provider OpenAI`。
142
+ - 厂商口语简称走子串:`阿里` 命中 `阿里通义`,`字节` 命中 `字节跳动`。
143
+
144
+ ### 各 flag 可传值速查
145
+
146
+ - `--usage`:image 可选 `IMAGE_CREATE`(默认) / `IMAGE_EDIT`;video 为 `VIDEO_CREATE`。
147
+ - `--input-mode`(归一化值,非原始 paramKey):image `prompt_only` / `reference`;video `prompt_only` / `reference` / `frames` / `storyboard`。
148
+ - `--supports`(归一化值):image `ratio` / `quality`;video `ratio` / `quality` / `duration` / `needAudio`。
149
+ - `--quality`(真实取值,大小写不敏感):image `1k` / `2k` / `4k`;video `480` / `720` / `768` / `1080` / `4K`。
150
+ - `--ratio`(真实取值):如 `16:9` / `9:16` / `4:3` / `3:4` / `1:1` / `21:9` 等,具体以各模型 `ratio` 取值为准。
151
+ - `--duration`(真实秒数,video):如 `5` / `10` / `15` 等,具体以各模型 `generated_time` 取值为准。
152
+ - `--need-audio`(布尔开关,video):传则只返回带音效能力的模型。
153
+ - `--provider`(按上表厂商值):image 常见 `GPT` / `字节` / `Midjourney` / `Blackforest` / `Google` / `阿里` / `Grok`;video 常见 `字节` / `Minimax` / `生数科技` / `OpenAI` / `Google` / `快手` / `阿里` / `Grok`。
@@ -10,7 +10,7 @@
10
10
  4. 选中模型的创建规格未缓存时运行 `model create-spec --model-group-code <code> -f json`。
11
11
  5. 用 `inputRequirement` 判断是否必须有视觉输入;用 `supportedIntents` 判断素材模式;用 `model options.params` 判断 `ratio`、`quality`、`duration` 等枚举。
12
12
  6. 在 `fee` 或 `create --dry-run` 前,先确认用户未提供但会影响价格 / 效果的关键参数:视频 `quality`、`duration`、约束后仍可选的 `ratio`,以及用户明确要输出音效时的 `needAudio`;图片 `quality`、`ratio`、`generateNum`。
13
- 7. 组装最终 prompt:以用户明确创作描述为基底,操作性指令只用于模型和资源选择;没有明确需求时先追问或用最小中性表达;只有视频 reference / subject 需要占位绑定时才考虑 `<<<key>>>`,并先展示最终文本。
13
+ 7. 组装最终 prompt:用户有明确 prompt / 创作描述时默认原文直出;操作性指令只用于模型和资源选择;没有明确需求时先追问或用最小中性表达;只有用户明确要求绑定参考对象时才考虑 `<<<key>>>`。任何可见语义变化都必须先展示原始 prompt 和最终 prompt,等待确认。
14
14
  8. 用户确认关键参数和最终 prompt 后,先 `fee`,再 `create --dry-run`。
15
15
  9. 用户确认模型、项目组、最终 prompt、素材、参数和预估积分后,才加 `--yes`。
16
16
 
@@ -88,7 +88,7 @@ lj-awb create video \
88
88
 
89
89
  判断要点:
90
90
 
91
- - 用户只说“动作是选择跳跃”时,可以组装为 `<<<hero>>> 选择跳跃`,但不要额外加风格词。
91
+ - 用户只说“动作是选择跳跃”时,优先保持 `--prompt "选择跳跃"` 并使用不带 key 的 `image:reference=...`。如确需改成 `<<<hero>>> 选择跳跃`,必须先询问并获得确认。
92
92
  - prompt 以用户描述为基底,**参考图作为素材输入时不要从图像内容反推画面**(“山谷风景、室内场景、某种服装”等不要塞进 prompt);具体规则见 [`create-contract.md`](create-contract.md) §Prompt 组装原则。
93
93
  - 用户明确说“prompt 就用选择跳跃”时,先确认是否允许补成 `<<<hero>>> 选择跳跃`。
94
94
  - 如果模型 `inputRequirement.visualInputRequired=true`,不能只用 prompt 创建。
@@ -183,7 +183,7 @@ lj-awb create video \
183
183
 
184
184
  - 模型组:`displayName` 和 `modelGroupCode`。
185
185
  - 项目组:名称、编号、项目组预算余额;不要把项目组预算余额当作实际可扣积分余额。
186
- - 最终 prompt:如果做了占位符补齐、资源绑定或用户要求的提示词优化,要让用户确认实际提交文本。
186
+ - 最终 prompt:默认原文直出;如果做了占位符补齐或用户要求的提示词优化,先展示原始 prompt 和最终 prompt,用户确认前不继续 fee / dry-run / create。
187
187
  - 素材:每个资源的 type、usage、key、来源。
188
188
  - 参数:ratio、quality、duration、generate-num、need-audio 等只列实际使用项;没有由用户明确给出时,必须在 fee / dry-run 前完成选择确认。
189
189
  - 费用:预估积分、`billingPointBalance -> billingPointRemainingAfter` 实际可扣积分变化,以及 `projectBudgetBalance -> projectBudgetRemainingAfter` 项目组预算变化。
@@ -24,7 +24,7 @@
24
24
 
25
25
  | 参数 | 何时使用 | 约束来源 |
26
26
  |------|----------|----------|
27
- | `--prompt` | 视频提示词 | 以用户明确创作描述为基底组装的最终提交文本;没有明确需求时先追问或用最小中性表达 |
27
+ | `--prompt` | 视频提示词 | 用户有明确 prompt / 创作描述时默认原文直出;只有明确要求优化 / 代写时才可改写,且必须先展示最终 prompt 等待确认 |
28
28
  | `--duration` | 指定视频秒数 | `model options.params[key=duration].values` |
29
29
  | `--ratio` | 指定画幅比例 | `model options.params[key=ratio].values`,并受 `model options.constraints[]` 联动限制 |
30
30
  | `--quality` | 指定清晰度 | `model options.params[key=quality].values` |
@@ -69,7 +69,7 @@ lj-awb create video \
69
69
  ## 规则
70
70
 
71
71
  - 视频时长统一用 `--duration`,不要传平台旧字段 `generated_time`。
72
- - `--prompt` 的重点是组装资源、参数和模型约束;用户没有明确需求时不要主动编内容补空白,用户要求优化时再做提示词创作。
72
+ - `--prompt` 默认使用用户原文;不要主动扩写、润色或从参考图补画面细节。用户要求优化 / 代写时再做提示词创作,且确认前不跑 fee / dry-run / create。
73
73
  - 素材统一用 `--resource type:usage[:key]=...` 或 `--resources-json`,不要直接传 `frames` / `multi_param`。
74
74
  - `generated_mode` 不需要也不允许用户填写;Material 会根据资源推导 `frames` 或 `multi_param`。
75
75
  - 创建前先看 `inputRequirement.visualInputRequired`。如果为 `true`,必须选择 `supportedIntents` 中的一种素材输入,不能纯 prompt 创建。
@@ -1,6 +1,6 @@
1
1
  # Workflows Module
2
2
 
3
- 本模块描述 Agent 面向任务的标准编排。CLI 只执行确定性命令;Agent 负责把用户输入映射为参数、确认风险、串联命令;prompt 处理重点是组装可执行请求。
3
+ 本模块描述 Agent 面向任务的标准编排。CLI 只执行确定性命令;Agent 负责把用户输入映射为参数、确认风险、串联命令;prompt 默认原文直出,参数和素材才由 Agent 组装为可执行请求。
4
4
 
5
5
  ## 目录
6
6
 
@@ -104,7 +104,7 @@
104
104
 
105
105
  | outputKind | 命令 | 必报给用户 | agent 内部用 | 可丢弃 |
106
106
  |------------|------|-----------|--------------|--------|
107
- | `update_result` | `update` / `update --check` | `updateAvailable`、`currentVersion`、`latestVersion`、`command` | `skillUpdated`、`restartRecommended` | 安装日志 |
107
+ | `update_result` | `update` / `update --check` | `updateAvailable`、`currentVersion`、`latestVersion`、`installer`、`command` | `skillUpdated`、`restartRecommended` | 安装日志 |
108
108
 
109
109
  ## Dry-run
110
110