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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,6 +63,18 @@ lj-awb auth verify
63
63
 
64
64
  可用 `LINGJING_AWB_SKILL_INSTALL_DIR` 指定单一安装目录;如果传入的是以 `skills` 结尾的根目录,安装器会自动追加 `lj-awb`。也可用 `LINGJING_AWB_SKIP_SKILL_INSTALL=1` 跳过 skill 安装。
65
65
 
66
+ 只分发 skill 也可以作为完整入口:`skills/lj-awb/scripts/resolve-lj-awb-cmd.sh` 会在首次使用时检查 `lj-awb`,缺失或版本低于 `skills/lj-awb/compat.json` 的 `minCliVersion` 时自动执行:
67
+
68
+ ```bash
69
+ npm install -g @lingjingai/lj-awb-cli@<minCliVersion>
70
+ ```
71
+
72
+ 预发环境可在 bootstrap 前设置:
73
+
74
+ ```bash
75
+ export LINGJING_AWB_CLI_PACKAGE=@lingjingai/lj-awb-cli-pre
76
+ ```
77
+
66
78
  源码运行:
67
79
 
68
80
  ```bash
@@ -75,8 +87,37 @@ packages/awb-cli/bin/lj-awb auth status
75
87
 
76
88
  macOS 本地 wrapper `packages/awb-cli/bin/lj-awb` 会自动补充 Node 证书链,适合直接调试 HTTPS 平台接口。
77
89
 
90
+ 自动更新检查默认每天最多访问一次 npm registry,并会在源码 checkout、CI 或设置 `LINGJING_AWB_DISABLE_UPDATE_CHECK=1` / `AWB_DISABLE_UPDATE_CHECK=1` 时跳过。调试时可用 `LINGJING_AWB_UPDATE_CHECK=force` 强制检查。
91
+
78
92
  ## 认证
79
93
 
94
+ ### 浏览器授权登录(推荐)
95
+
96
+ 不需要手动复制 access key,直接登录:
97
+
98
+ ```bash
99
+ lj-awb auth login
100
+ ```
101
+
102
+ CLI 会创建登录任务、展示授权链接,等你在浏览器登录并确认授权后,自动拿到 access key 并保存到本地。命令默认阻塞轮询,最长约 10 分钟。
103
+
104
+ AI agent / 无法实时查看命令输出的场景,分两步走(避免阻塞):
105
+
106
+ ```bash
107
+ lj-awb auth login --no-wait --json # 返回 flowId / verifyUrl,把链接给用户去授权
108
+ lj-awb auth login --flow-id <flowId> # 用户授权后续轮询,拿到并保存 access key
109
+ ```
110
+
111
+ > 不要重复执行不带 `--flow-id` 的 login,否则会生成新的 flowId 导致旧授权链接失效。
112
+
113
+ 退出登录(清除本地 access key):
114
+
115
+ ```bash
116
+ lj-awb auth logout
117
+ ```
118
+
119
+ ### 直接使用 access key
120
+
80
121
  保存 access key 到本地认证文件:
81
122
 
82
123
  ```bash
@@ -110,6 +151,8 @@ lj-awb account info
110
151
  ```bash
111
152
  lj-awb doctor
112
153
  lj-awb doctor --verify
154
+ lj-awb update --check
155
+ lj-awb update
113
156
  lj-awb schema --brief -f json
114
157
  lj-awb schema -f json
115
158
  lj-awb schema --domain create -f json
@@ -159,6 +202,8 @@ lj-awb doctor --verify
159
202
 
160
203
  `auth status` 只检查本地是否配置 access key;`auth verify` 会联网校验 key 是否远端有效。`doctor` 默认只做本地检查;追加 `--verify` 后会联网校验 access key、当前用户和项目组。精确 `schema` 返回每个命令的 `options[].key`,Agent 应使用这些 key 生成 CLI 参数,而不是解析自然语言 help。
161
204
 
205
+ `lj-awb` 在普通命令结束后会做非阻塞更新检查;如果有新版本,JSON 输出会在 `meta._notice.update` 里带上更新提示。Skill / Agent 看到这个提示后,应在完成当前任务后告诉用户当前版本和最新版本,并建议或直接运行 `lj-awb update`。真正安装更新只走显式 `lj-awb update`,`--check` 只检查不安装。
206
+
162
207
  `schema` 还包含面向 Agent 的执行约束:
163
208
 
164
209
  - `requiredOptions`:必须提供的参数 key。
package/build/_shared.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process';
3
- import { cpSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { cpSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import os from 'node:os';
4
5
  import path from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
@@ -19,13 +20,16 @@ export function run(command, args, options = {}) {
19
20
  process.stderr.write(`$ ${[command, ...args].join(' ')}\n`);
20
21
  const result = spawnSync(command, args, {
21
22
  cwd: options.cwd || repoRoot,
23
+ env: options.env ? { ...process.env, ...options.env } : process.env,
22
24
  stdio: 'inherit',
23
25
  });
24
26
  if (result.error) {
27
+ options.beforeExit?.();
25
28
  process.stderr.write(`${result.error.message}\n`);
26
29
  process.exit(1);
27
30
  }
28
31
  if (result.status !== 0) {
32
+ options.beforeExit?.();
29
33
  process.exit(result.status || 1);
30
34
  }
31
35
  }
@@ -33,11 +37,53 @@ export function run(command, args, options = {}) {
33
37
  export function capture(command, args, options = {}) {
34
38
  return spawnSync(command, args, {
35
39
  cwd: options.cwd || repoRoot,
40
+ env: options.env ? { ...process.env, ...options.env } : process.env,
36
41
  encoding: 'utf8',
37
42
  stdio: ['inherit', 'pipe', 'pipe'],
38
43
  });
39
44
  }
40
45
 
46
+ export function createNpmPublishAuthConfig(registry) {
47
+ const tokenSource = ['NPM_PUBLISH_TOKEN', 'NPM_TOKEN', 'NODE_AUTH_TOKEN']
48
+ .find((name) => process.env[name]?.trim());
49
+ if (!tokenSource) {
50
+ return {
51
+ env: {},
52
+ cleanup() {},
53
+ tokenSource: null,
54
+ };
55
+ }
56
+
57
+ const token = process.env[tokenSource].trim();
58
+ const normalizedRegistry = registry.endsWith('/') ? registry : `${registry}/`;
59
+ const registryUrl = new URL(normalizedRegistry);
60
+ const registryPath = registryUrl.pathname.endsWith('/') ? registryUrl.pathname : `${registryUrl.pathname}/`;
61
+ const npmrcDir = mkdtempSync(path.join(os.tmpdir(), 'lj-awb-npm-'));
62
+ const npmrcPath = path.join(npmrcDir, '.npmrc');
63
+ writeFileSync(
64
+ npmrcPath,
65
+ [
66
+ `registry=${normalizedRegistry}`,
67
+ `//${registryUrl.host}${registryPath}:_authToken=${token}`,
68
+ 'strict-ssl=false',
69
+ 'always-auth=true',
70
+ '',
71
+ ].join('\n'),
72
+ { mode: 0o600 },
73
+ );
74
+
75
+ return {
76
+ env: {
77
+ NPM_CONFIG_USERCONFIG: npmrcPath,
78
+ NPM_CONFIG_REGISTRY: normalizedRegistry,
79
+ },
80
+ cleanup() {
81
+ rmSync(npmrcDir, { recursive: true, force: true });
82
+ },
83
+ tokenSource,
84
+ };
85
+ }
86
+
41
87
  export function packageFilePaths(cwd = repoRoot) {
42
88
  const result = capture('npm', ['pack', '--dry-run', '--json'], { cwd });
43
89
  if (result.error) {
@@ -101,12 +147,13 @@ export function packTarball(stageDir) {
101
147
  return path.join(stageDir, tarball);
102
148
  }
103
149
 
104
- export function ensureLoggedIn(registry) {
105
- const result = capture('npm', ['whoami', `--registry=${registry}`]);
150
+ export function ensureLoggedIn(registry, options = {}) {
151
+ const result = capture('npm', ['whoami', `--registry=${registry}`], { env: options.env });
106
152
  if (result.status === 0) {
107
153
  process.stderr.write(`npm user: ${result.stdout.trim()}\n`);
108
154
  return;
109
155
  }
156
+ options.beforeExit?.();
110
157
  process.stderr.write([
111
158
  `npm is not logged in for ${registry}.`,
112
159
  `Run: npm login --registry=${registry}`,
@@ -115,14 +162,16 @@ export function ensureLoggedIn(registry) {
115
162
  process.exit(1);
116
163
  }
117
164
 
118
- export function ensureVersionNotPublished({ name, version, registry }) {
165
+ export function ensureVersionNotPublished({ name, version, registry }, options = {}) {
119
166
  const spec = `${name}@${version}`;
120
- const result = capture('npm', ['view', spec, 'version', `--registry=${registry}`]);
167
+ const result = capture('npm', ['view', spec, 'version', `--registry=${registry}`], { env: options.env });
121
168
  if (result.status === 0) {
169
+ options.beforeExit?.();
122
170
  process.stderr.write(`${spec} already exists on npm. Bump the version before publishing.\n`);
123
171
  process.exit(1);
124
172
  }
125
173
  if (!/E404|404 Not Found|Not found/i.test(`${result.stderr}\n${result.stdout}`)) {
174
+ options.beforeExit?.();
126
175
  process.stderr.write(`Could not verify whether ${spec} exists on npm.\n`);
127
176
  process.stderr.write(result.stderr || result.stdout || '');
128
177
  process.exit(1);
package/build/prod.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  import { readFileSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import {
5
+ createNpmPublishAuthConfig,
5
6
  ensureLoggedIn,
6
7
  ensureVersionNotPublished,
7
8
  findArgValue,
@@ -33,14 +34,21 @@ function ensureProductionDefaultApiOrigin() {
33
34
  ensureProductionDefaultApiOrigin();
34
35
 
35
36
  process.stderr.write(`Preparing ${pkg.name}@${pkg.version} for npm ${dryRun ? 'dry-run' : 'publish'} with default API origin ${PROD_API_ORIGIN}.\n`);
37
+ const publishAuth = createNpmPublishAuthConfig(registry);
38
+ if (!dryRun && publishAuth.tokenSource) {
39
+ process.stderr.write(`Using npm publish token from ${publishAuth.tokenSource}; NPM_CONFIG_USERCONFIG points to an isolated temporary npmrc.\n`);
40
+ }
36
41
 
37
42
  if (process.env.SKIP_CHECK !== '1') {
38
43
  run('npm', ['run', 'check:local']);
39
44
  }
40
45
 
41
- ensureVersionNotPublished({ name: pkg.name, version: pkg.version, registry });
46
+ ensureVersionNotPublished(
47
+ { name: pkg.name, version: pkg.version, registry },
48
+ { env: publishAuth.env, beforeExit: publishAuth.cleanup },
49
+ );
42
50
  if (!dryRun) {
43
- ensureLoggedIn(registry);
51
+ ensureLoggedIn(registry, { env: publishAuth.env, beforeExit: publishAuth.cleanup });
44
52
  }
45
53
 
46
54
  run('npm', [
@@ -49,4 +57,5 @@ run('npm', [
49
57
  'public',
50
58
  `--registry=${registry}`,
51
59
  ...passthroughArgs,
52
- ]);
60
+ ], { env: publishAuth.env, beforeExit: publishAuth.cleanup });
61
+ publishAuth.cleanup();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/lj-awb-cli-pre",
3
- "version": "0.4.0",
3
+ "version": "0.4.5",
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",
@@ -23,7 +23,7 @@
23
23
  "build:pre-publish": "node build/pre-publish.mjs",
24
24
  "build:prod": "node build/prod.mjs",
25
25
  "check": "npm run check:local && npm run check:real",
26
- "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/artifact.js && node --check packages/awb-core/src/auth.js && node --check packages/awb-core/src/output.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 && 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",
26
+ "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/artifact.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
27
  "check:real": "node scripts/validate-cli-output-contract.mjs && node scripts/validate-openapi-cli-examples.mjs",
28
28
  "test:openapi-real": "node scripts/run-openapi-cli-examples-real.mjs",
29
29
  "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 artifact && 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.0",
3
+ "version": "0.4.5",
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.0"
16
+ "@lingjingai/awb-core": "0.4.5"
17
17
  }
18
18
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/awb-core",
3
- "version": "0.4.0",
3
+ "version": "0.4.5",
4
4
  "description": "Shared core runtime for Lingjing AWB CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -109,6 +109,14 @@ export async function fetchUserInfo() {
109
109
  return await apiFetch('/api/anime/user/account/userInfo', { body: {} });
110
110
  }
111
111
 
112
+ export async function createLoginFlow() {
113
+ return await apiFetch('/api/anime/user/cli/createLoginFlow', { body: {}, auth: false });
114
+ }
115
+
116
+ export async function queryLoginFlowStatus(flowId) {
117
+ return await apiFetch('/api/anime/user/cli/queryFlowStatus', { body: { flowId }, auth: false });
118
+ }
119
+
112
120
  export async function fetchTeams() {
113
121
  return await apiFetch('/api/anime/user/group/getOwnGroupList', { body: {} });
114
122
  }
@@ -230,6 +238,20 @@ export async function createVideoSubtitleRemovalTask(payload = {}) {
230
238
  return await apiFetch('/api/material/creation/videoSubtitleRemoval', { body: payload });
231
239
  }
232
240
 
241
+ export async function fetchVideoSuperResolutionFee(payload = {}) {
242
+ return await apiFetch('/api/material/creation/videoUpResolutionCal', {
243
+ method: 'GET',
244
+ query: payload,
245
+ });
246
+ }
247
+
248
+ export async function createVideoSuperResolutionTask(payload = {}) {
249
+ return await apiFetch('/api/material/creation/videoUpResolution', {
250
+ method: 'POST',
251
+ query: payload,
252
+ });
253
+ }
254
+
233
255
  export async function fetchTaskFeed(payload = {}) {
234
256
  return await apiFetch('/api/material/creation/task/feedPull', {
235
257
  query: payload,
@@ -2,6 +2,7 @@ import { fetchProjectGroupUsers, fetchTeams, fetchUserInfo } from './api.js';
2
2
  import * as artifact from './artifact.js';
3
3
  import { clearAuth, loadAuth, saveAccessKey, summarizeAuth } from './auth.js';
4
4
  import { LingjingAwbCliError, maskSecret, toBool, trimSecret } from './common.js';
5
+ import { runCliUpdate } from './update.js';
5
6
  import {
6
7
  assetGroupCreate,
7
8
  assetGroupGet,
@@ -10,6 +11,8 @@ import {
10
11
  assetMatchActor,
11
12
  assetReviewModels,
12
13
  assetRegister,
14
+ authLogin,
15
+ authLogout,
13
16
  creditsBalance,
14
17
  creditsUsageSummary,
15
18
  createProjectGroupCommand,
@@ -39,6 +42,9 @@ import {
39
42
  subjectWait,
40
43
  subtitleRemove,
41
44
  subtitleStatus,
45
+ videoSuperResolution,
46
+ videoSuperResolutionFee,
47
+ videoSuperResolutionStatus,
42
48
  taskList,
43
49
  taskRecordPoll,
44
50
  taskRecords,
@@ -85,6 +91,9 @@ const MODEL_LIST_ARGS = [
85
91
  const TASK_RECORD_ARG = { name: 'task-record-file', valueName: 'path', description: '任务台账 JSONL 文件;也可用 LINGJING_AWB_TASK_RECORD_FILE' };
86
92
  const RESOURCE_ARG = { name: 'resource', valueName: 'spec', description: '统一素材,格式 type:usage[:key]=path|url|asset,可重复传;音频参考用 audio:reference=./music.mp3' };
87
93
  const RESOURCES_JSON_ARG = { name: 'resources-json', valueName: 'json|path', description: 'resources 数组 JSON 或 JSON 文件路径' };
94
+ const MODEL_PARAM_ARG = { name: 'model-param', valueName: 'key=value', description: '通用模型配置参数,可重复传;key 来自 model options.params[],例如 generation_effort=high' };
95
+ const MODEL_PARAMS_JSON_ARG = { name: 'model-params-json', valueName: 'json|path', description: '通用模型配置参数对象 JSON 或 JSON 文件路径;显式 --ratio/--quality 等命令参数优先' };
96
+ const MODEL_PARAM_ARGS = [MODEL_PARAM_ARG, MODEL_PARAMS_JSON_ARG];
88
97
  const CREATE_WAIT_ARGS = [
89
98
  { name: 'wait-seconds', valueName: 'seconds', description: '提交后顺带等待结果的秒数;默认 0 不等待' },
90
99
  { name: 'poll-interval-ms', valueName: 'ms', description: '任务轮询间隔毫秒数,默认 5000' },
@@ -125,6 +134,25 @@ export function registerAwbCommands(cli) {
125
134
  func: async (_ctx, kwargs) => doctor(kwargs),
126
135
  });
127
136
 
137
+ cli({
138
+ name: 'update',
139
+ description: commandHelp('检查并更新 CLI 到最新版本;默认只在需要时安装新版本', {
140
+ examples: [
141
+ 'lj-awb update --check',
142
+ 'lj-awb update',
143
+ 'lj-awb update --force',
144
+ ],
145
+ hint: '普通命令只会做非阻塞更新检查;真正安装请显式运行 update。',
146
+ }),
147
+ args: [
148
+ { name: 'check', description: '只检查是否有新版本,不安装' },
149
+ { name: 'force', description: '强制重新检查并执行安装,忽略缓存' },
150
+ { name: 'registry', valueName: 'url', description: 'npm registry 地址;默认读取环境变量或 npm 默认 registry' },
151
+ { name: 'package-name', valueName: 'name', description: '要更新的包名;默认自动识别当前 CLI 包' },
152
+ ],
153
+ func: async (_ctx, kwargs) => runCliUpdate(kwargs),
154
+ });
155
+
128
156
  cli({
129
157
  name: 'auth status',
130
158
  description: commandHelp('查看本地 access key 是否已配置;不联网', {
@@ -163,50 +191,27 @@ export function registerAwbCommands(cli) {
163
191
 
164
192
  cli({
165
193
  name: 'auth login',
166
- description: commandHelp('登录:使用 access key 校验身份,并保存到本地认证文件', {
194
+ description: commandHelp('登录:默认走浏览器授权登录;也支持直接传 access key 保存', {
167
195
  examples: [
196
+ 'lj-awb auth login',
197
+ 'lj-awb auth login --no-wait',
198
+ 'lj-awb auth login --flow-id <flowId>',
168
199
  'lj-awb auth login --access-key <key>',
169
200
  'LINGJING_AWB_ACCESS_KEY=<key> lj-awb auth login',
170
201
  'lj-awb auth login --access-key <key> --skip-verify',
171
202
  ],
172
- hint: '不要写成 lj-awb auth <key>。不传 --access-key 时只读取 LINGJING_AWB_ACCESS_KEY 环境变量。',
203
+ hint: '不传 --access-key(且无 LINGJING_AWB_ACCESS_KEY 环境变量)时走浏览器授权:创建登录任务、展示链接、轮询直到授权成功并自动保存 access key。命令最长阻塞约 10 分钟;AI agent 可先用 --no-wait --json flow_id,再用 --flow-id <id> 续轮询,不要重复发起新的 login。',
173
204
  }),
174
205
  args: [
175
- { name: 'access-key', valueName: 'key', description: '平台 access key;不传时读取环境变量' },
206
+ { name: 'access-key', valueName: 'key', description: '平台 access key;传了则跳过浏览器授权直接保存,不传时读取环境变量' },
207
+ { name: 'flow-id', valueName: 'flowId', description: '复用已有登录任务的 flow_id 继续轮询;不传则新建登录任务' },
208
+ { name: 'no-wait', description: '只创建登录任务并返回 flow_id / verify_url,不阻塞轮询;适合 AI agent / 脚本' },
209
+ { name: 'wait-seconds', valueName: 'seconds', description: '浏览器授权最长轮询秒数,默认 600' },
210
+ { name: 'poll-interval-ms', valueName: 'ms', description: '授权状态轮询间隔毫秒数,默认 3000' },
176
211
  { name: 'skip-verify', description: '只保存 key,不调用 userInfo 校验;仅用于本地调试' },
177
212
  DRY_RUN_ARG,
178
213
  ],
179
- func: async (_ctx, kwargs) => {
180
- const skipVerify = toBool(kwargs.skipVerify);
181
- const accessKey = trimSecret(kwargs.accessKey || process.env.LINGJING_AWB_ACCESS_KEY || '');
182
- if (!accessKey) {
183
- throw new LingjingAwbCliError('缺少 access key', {
184
- type: 'argument_error',
185
- exitCode: 2,
186
- hint: '传 --access-key <key>,或设置 LINGJING_AWB_ACCESS_KEY 环境变量。',
187
- });
188
- }
189
- if (toBool(kwargs.dryRun)) {
190
- return {
191
- dryRun: true,
192
- saved: false,
193
- verified: false,
194
- accessKey: maskSecret(accessKey),
195
- };
196
- }
197
- let user = null;
198
- if (!skipVerify) {
199
- process.env.LINGJING_AWB_ACCESS_KEY = accessKey;
200
- user = normalizeUserInfo(await fetchUserInfo());
201
- }
202
- const { auth } = await saveAccessKey(accessKey, { verified: !skipVerify });
203
- return {
204
- auth: summarizeAuth(auth, { accessKey, source: 'saved', sourceName: 'auth' }),
205
- accessKey: maskSecret(accessKey),
206
- verified: !skipVerify,
207
- user,
208
- };
209
- },
214
+ func: async (_ctx, kwargs) => authLogin(kwargs),
210
215
  });
211
216
 
212
217
  cli({
@@ -224,6 +229,16 @@ export function registerAwbCommands(cli) {
224
229
  },
225
230
  });
226
231
 
232
+ cli({
233
+ name: 'auth logout',
234
+ description: commandHelp('退出登录:清除本地保存的 access key', {
235
+ examples: ['lj-awb auth logout'],
236
+ hint: '只清本地认证文件,不清当前 shell 中的 LINGJING_AWB_ACCESS_KEY 环境变量;下次使用前重新运行 lj-awb auth login。',
237
+ }),
238
+ args: [],
239
+ func: async () => authLogout(),
240
+ });
241
+
227
242
  cli({
228
243
  name: 'account info',
229
244
  description: commandHelp('查看当前 AWB 用户、团队、项目组和积分摘要', {
@@ -497,10 +512,12 @@ export function registerAwbCommands(cli) {
497
512
  { name: 'ratio', valueName: 'ratio', description: '画幅比例,如 9:16' },
498
513
  { name: 'quality', valueName: 'quality', description: '清晰度,如 2K' },
499
514
  { name: 'generate-num', valueName: 'n', description: '生成张数' },
515
+ ...MODEL_PARAM_ARGS,
500
516
  RESOURCE_ARG,
501
517
  RESOURCES_JSON_ARG,
502
518
  FEE_DRY_RUN_ARG,
503
519
  ],
520
+ allowDynamicModelParams: true,
504
521
  func: async (_ctx, kwargs) => imageFee(kwargs),
505
522
  });
506
523
 
@@ -521,6 +538,7 @@ export function registerAwbCommands(cli) {
521
538
  { name: 'ratio', valueName: 'ratio', description: '画幅比例' },
522
539
  { name: 'quality', valueName: 'quality', description: '清晰度' },
523
540
  { name: 'generate-num', valueName: 'n', description: '生成张数' },
541
+ ...MODEL_PARAM_ARGS,
524
542
  RESOURCE_ARG,
525
543
  RESOURCES_JSON_ARG,
526
544
  TASK_RECORD_ARG,
@@ -528,6 +546,7 @@ export function registerAwbCommands(cli) {
528
546
  DRY_RUN_ARG,
529
547
  YES_ARG,
530
548
  ],
549
+ allowDynamicModelParams: true,
531
550
  func: async (_ctx, kwargs) => imageCreate(kwargs),
532
551
  });
533
552
 
@@ -538,17 +557,19 @@ export function registerAwbCommands(cli) {
538
557
  'lj-awb create image-batch --input-file ./prompts.txt --model-group-code <code> --dry-run',
539
558
  'lj-awb create image-batch --input-file ./image-input.jsonl --model-group-code <code> --concurrency 3 --yes',
540
559
  ],
541
- hint: 'JSON/JSONL 每项只写任务差异字段:prompt、ratio、quality、generate_num、resources、resource、customBizId',
560
+ hint: 'JSON/JSONL 每项只写任务差异字段:prompt、ratio、quality、generate_num、resources、resource、model_params、customBizId;新增模型配置参数也可直接写入每项。',
542
561
  }),
543
562
  args: [
544
563
  { name: 'input-file', valueName: 'path', description: '批量输入文件' },
545
564
  MODEL_GROUP_ARG,
546
565
  PROJECT_GROUP_ARG,
566
+ ...MODEL_PARAM_ARGS,
547
567
  { name: 'concurrency', valueName: 'n', description: '并发数,默认 1' },
548
568
  TASK_RECORD_ARG,
549
569
  DRY_RUN_ARG,
550
570
  YES_ARG,
551
571
  ],
572
+ allowDynamicModelParams: true,
552
573
  func: async (_ctx, kwargs) => imageCreateBatch(kwargs),
553
574
  });
554
575
 
@@ -577,10 +598,12 @@ export function registerAwbCommands(cli) {
577
598
  { name: 'quality', valueName: 'quality', description: '清晰度,如 720' },
578
599
  { name: 'duration', valueName: 'seconds', description: '生成时长秒数' },
579
600
  { name: 'need-audio', valueName: 'bool', description: '输出音效开关:是否让模型在视频中生成音效(true/false)。不接收音频文件;参考音频请用 --resource audio:reference=...' },
601
+ ...MODEL_PARAM_ARGS,
580
602
  RESOURCE_ARG,
581
603
  RESOURCES_JSON_ARG,
582
604
  FEE_DRY_RUN_ARG,
583
605
  ],
606
+ allowDynamicModelParams: true,
584
607
  func: async (_ctx, kwargs) => videoFee(kwargs),
585
608
  });
586
609
 
@@ -604,6 +627,7 @@ export function registerAwbCommands(cli) {
604
627
  { name: 'quality', valueName: 'quality', description: '清晰度' },
605
628
  { name: 'duration', valueName: 'seconds', description: '生成时长秒数' },
606
629
  { name: 'need-audio', valueName: 'bool', description: '输出音效开关:是否让模型在视频中生成音效(true/false)。不接收音频文件;参考音频请用 --resource audio:reference=...' },
630
+ ...MODEL_PARAM_ARGS,
607
631
  RESOURCE_ARG,
608
632
  RESOURCES_JSON_ARG,
609
633
  TASK_RECORD_ARG,
@@ -611,6 +635,7 @@ export function registerAwbCommands(cli) {
611
635
  DRY_RUN_ARG,
612
636
  YES_ARG,
613
637
  ],
638
+ allowDynamicModelParams: true,
614
639
  func: async (_ctx, kwargs) => videoCreate(kwargs),
615
640
  });
616
641
 
@@ -621,17 +646,19 @@ export function registerAwbCommands(cli) {
621
646
  'lj-awb create video-batch --input-file ./prompts.txt --model-group-code <code> --dry-run',
622
647
  'lj-awb create video-batch --input-file ./video-input.jsonl --model-group-code <code> --concurrency 2 --yes',
623
648
  ],
624
- hint: 'JSON/JSONL 每项只写任务差异字段:prompt、ratio、quality、duration、need_audio、resources、resource、customBizId',
649
+ hint: 'JSON/JSONL 每项只写任务差异字段:prompt、ratio、quality、duration、need_audio、resources、resource、model_params、customBizId;新增模型配置参数也可直接写入每项。',
625
650
  }),
626
651
  args: [
627
652
  { name: 'input-file', valueName: 'path', description: '批量输入文件' },
628
653
  MODEL_GROUP_ARG,
629
654
  PROJECT_GROUP_ARG,
655
+ ...MODEL_PARAM_ARGS,
630
656
  { name: 'concurrency', valueName: 'n', description: '并发数,默认 1' },
631
657
  TASK_RECORD_ARG,
632
658
  DRY_RUN_ARG,
633
659
  YES_ARG,
634
660
  ],
661
+ allowDynamicModelParams: true,
635
662
  func: async (_ctx, kwargs) => videoCreateBatch(kwargs),
636
663
  });
637
664
 
@@ -660,6 +687,35 @@ export function registerAwbCommands(cli) {
660
687
  func: async (_ctx, kwargs) => subtitleRemove(kwargs),
661
688
  });
662
689
 
690
+ cli({
691
+ name: 'create video-super-resolution-fee',
692
+ description: commandHelp('计算视频超分预计积分;传入 objectName', {
693
+ examples: ['lj-awb create video-super-resolution-fee --object-name material/video-super/example.mp4 --project-group-no <no>'],
694
+ hint: 'objectName 通常是 material backendPath 或 COS 对象路径;正式超分前先看一次估价。',
695
+ }),
696
+ args: [
697
+ { name: 'object-name', valueName: 'path', description: '视频对象路径;通常是 material backendPath 或 COS 对象路径' },
698
+ PROJECT_GROUP_ARG,
699
+ ],
700
+ func: async (_ctx, kwargs) => videoSuperResolutionFee(kwargs),
701
+ });
702
+
703
+ cli({
704
+ name: 'create video-super-resolution',
705
+ description: commandHelp('基于 objectName 提交视频超分任务', {
706
+ examples: ['lj-awb create video-super-resolution --object-name material/video-super/example.mp4 --project-group-no <no> --dry-run'],
707
+ hint: '正式执行需要 --yes;会先按 objectName 读取视频信息,再选择 1080P / 2K 模型组。',
708
+ }),
709
+ args: [
710
+ { name: 'object-name', valueName: 'path', description: '视频对象路径;通常是 material backendPath 或 COS 对象路径' },
711
+ PROJECT_GROUP_ARG,
712
+ TASK_RECORD_ARG,
713
+ DRY_RUN_ARG,
714
+ YES_ARG,
715
+ ],
716
+ func: async (_ctx, kwargs) => videoSuperResolution(kwargs),
717
+ });
718
+
663
719
  cli({
664
720
  name: 'task video-subtitle-status',
665
721
  description: commandHelp('查询 material 去字幕任务状态', {
@@ -672,13 +728,25 @@ export function registerAwbCommands(cli) {
672
728
  func: async (_ctx, kwargs) => subtitleStatus(kwargs),
673
729
  });
674
730
 
731
+ cli({
732
+ name: 'task video-super-resolution-status',
733
+ description: commandHelp('查询 material 视频超分任务状态', {
734
+ examples: ['lj-awb task video-super-resolution-status --task-id <superResolutionTaskId>'],
735
+ }),
736
+ args: [
737
+ { name: 'task-id', valueName: 'id', description: 'material 视频超分任务 ID' },
738
+ PROJECT_GROUP_ARG,
739
+ ],
740
+ func: async (_ctx, kwargs) => videoSuperResolutionStatus(kwargs),
741
+ });
742
+
675
743
  cli({
676
744
  name: 'task list',
677
745
  description: commandHelp('查询项目组近期任务流', {
678
746
  examples: ['lj-awb task list --task-type IMAGE_CREATE --project-group-no <no>'],
679
747
  }),
680
748
  args: [
681
- { name: 'task-type', valueName: 'type', description: 'IMAGE_CREATE / IMAGE_EDIT / VIDEO_GROUP' },
749
+ { name: 'task-type', valueName: 'type', description: 'IMAGE_CREATE / IMAGE_EDIT / VIDEO_GROUP / VIDEO_SUPER_RESOLUTION / VIDEO_SUBTITLE_REMOVAL' },
682
750
  PROJECT_GROUP_ARG,
683
751
  { name: 'page-size', valueName: 'n', description: '每页任务数' },
684
752
  { name: 'min-time', valueName: 'ms', description: '任务流 minTime,默认当前时间' },
@@ -694,7 +762,7 @@ export function registerAwbCommands(cli) {
694
762
  }),
695
763
  args: [
696
764
  { name: 'task-id', valueName: 'id', description: '任务 ID' },
697
- { name: 'task-type', valueName: 'type', description: 'IMAGE_CREATE / VIDEO_GROUP' },
765
+ { name: 'task-type', valueName: 'type', description: 'IMAGE_CREATE / VIDEO_GROUP;material 扩展任务优先用 task video-subtitle-status / task video-super-resolution-status' },
698
766
  PROJECT_GROUP_ARG,
699
767
  ...TASK_WAIT_ARGS,
700
768
  ],
@@ -811,7 +879,11 @@ export function registerAwbCommands(cli) {
811
879
  cli({
812
880
  name: 'create asset',
813
881
  description: commandHelp('把 COS 路径 / URL / 本地文件注册成平台素材', {
814
- examples: ['lj-awb create asset --group-id <id> --platform JIMENG --url "asset-review/upload/a.png" --name "女主正面" --dry-run'],
882
+ examples: [
883
+ 'lj-awb create asset --group-id <id> --platform JIMENG --url "asset-review/upload/a.png" --name "女主正面" --dry-run',
884
+ 'lj-awb create asset --group-id <id> --platform JIMENG --file ./clip.mp4 --name "参考视频" --dry-run',
885
+ ],
886
+ hint: 'CLI 会按扩展名自动设置 assetType=Image/Video/Audio;本地文件会用 ffprobe 预校验图片/视频尺寸、视频时长/FPS/像素数和音频时长,缺少 ffprobe/ffmpeg 时 macOS + Homebrew 会自动安装 ffmpeg。',
815
887
  }),
816
888
  args: [
817
889
  { name: 'group-id', valueName: 'id', description: '素材组 ID' },
@@ -819,6 +891,7 @@ export function registerAwbCommands(cli) {
819
891
  { name: 'file', valueName: 'path', description: '本地素材文件;会先上传' },
820
892
  { name: 'url', valueName: 'url', description: '素材 URL 或 COS 路径' },
821
893
  { name: 'backend-path', valueName: 'path', description: 'upload files 返回的 backendPath' },
894
+ { name: 'auto-convert', description: '本地素材不符合加白规格时自动转换为合法规格后继续' },
822
895
  ASSET_PLATFORM_ARG,
823
896
  DRY_RUN_ARG,
824
897
  YES_ARG,
@@ -318,11 +318,19 @@ export function guessMimeType(filePath) {
318
318
  return 'image/png';
319
319
  case '.jpg':
320
320
  case '.jpeg':
321
+ case '.jfif':
321
322
  return 'image/jpeg';
323
+ case '.tif':
324
+ case '.tiff':
325
+ return 'image/tiff';
322
326
  case '.webp':
323
327
  return 'image/webp';
324
328
  case '.gif':
325
329
  return 'image/gif';
330
+ case '.heic':
331
+ return 'image/heic';
332
+ case '.heif':
333
+ return 'image/heif';
326
334
  case '.mp3':
327
335
  return 'audio/mpeg';
328
336
  case '.wav':