@lingjingai/lj-awb-cli-pre 0.3.15

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.
Files changed (49) hide show
  1. package/README.md +335 -0
  2. package/build/_shared.mjs +130 -0
  3. package/build/build.mjs +50 -0
  4. package/build/pre-publish.mjs +57 -0
  5. package/build/pre.mjs +42 -0
  6. package/build/prod.mjs +52 -0
  7. package/install.mjs +53 -0
  8. package/package.json +44 -0
  9. package/packages/awb-cli/README.md +19 -0
  10. package/packages/awb-cli/bin/lj-awb +19 -0
  11. package/packages/awb-cli/bin/lj-awb.js +11 -0
  12. package/packages/awb-cli/package.json +18 -0
  13. package/packages/awb-core/README.md +12 -0
  14. package/packages/awb-core/package.json +21 -0
  15. package/packages/awb-core/src/api.js +349 -0
  16. package/packages/awb-core/src/artifact.js +936 -0
  17. package/packages/awb-core/src/auth.js +80 -0
  18. package/packages/awb-core/src/commands.js +1321 -0
  19. package/packages/awb-core/src/common.js +508 -0
  20. package/packages/awb-core/src/output.js +1189 -0
  21. package/packages/awb-core/src/services.js +3811 -0
  22. package/packages/awb-core/src/standalone.js +1213 -0
  23. package/skills/lj-awb/SKILL.md +160 -0
  24. package/skills/lj-awb/VERSION +1 -0
  25. package/skills/lj-awb/compat.json +6 -0
  26. package/skills/lj-awb/modules/account.md +30 -0
  27. package/skills/lj-awb/modules/artifact/asset.md +64 -0
  28. package/skills/lj-awb/modules/artifact/clip.md +65 -0
  29. package/skills/lj-awb/modules/artifact/script.md +37 -0
  30. package/skills/lj-awb/modules/artifact/video.md +65 -0
  31. package/skills/lj-awb/modules/artifact.md +65 -0
  32. package/skills/lj-awb/modules/asset.md +53 -0
  33. package/skills/lj-awb/modules/auth.md +30 -0
  34. package/skills/lj-awb/modules/create-contract.md +118 -0
  35. package/skills/lj-awb/modules/credits.md +28 -0
  36. package/skills/lj-awb/modules/evals.md +186 -0
  37. package/skills/lj-awb/modules/image.md +75 -0
  38. package/skills/lj-awb/modules/model.md +110 -0
  39. package/skills/lj-awb/modules/project.md +30 -0
  40. package/skills/lj-awb/modules/subject.md +32 -0
  41. package/skills/lj-awb/modules/task-manual.md +185 -0
  42. package/skills/lj-awb/modules/task.md +62 -0
  43. package/skills/lj-awb/modules/upload.md +33 -0
  44. package/skills/lj-awb/modules/video.md +102 -0
  45. package/skills/lj-awb/modules/workflows.md +482 -0
  46. package/skills/lj-awb/references/error-codes.md +102 -0
  47. package/skills/lj-awb/references/model-options-read.md +49 -0
  48. package/skills/lj-awb/references/output-fields.md +113 -0
  49. package/skills/lj-awb/scripts/resolve-lj-awb-cmd.sh +10 -0
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # lj-awb
2
+
3
+ `lj-awb` 是灵境 AWB / 动漫创作平台的命令行客户端。它把平台底层能力封装成稳定、可脚本化、Agent 友好的命令,并随包分发一套 `lj-awb` skill,方便 Codex、Claude 等 Agent 把自然语言任务编排成确定性的 CLI 调用。
4
+
5
+ CLI 本身只负责平台调用、参数校验、精简输出和安全确认;自然语言理解、任务规划、项目产物写回由上层 Agent / skill 完成。
6
+
7
+ ```text
8
+ 用户 / Agent / CI
9
+ -> lj-awb
10
+ -> 灵境 AWB / 动漫平台 API
11
+ -> compact text / JSON 结果
12
+ ```
13
+
14
+ ## 能力概览
15
+
16
+ | 能力域 | 命令入口 | 说明 |
17
+ | --- | --- | --- |
18
+ | 系统 | `doctor` / `schema` | 环境体检、联网校验、机器可读命令 schema |
19
+ | 认证 | `auth` | access key 状态、校验、保存、清空 |
20
+ | 账号 | `account` | 用户、团队和登录上下文 |
21
+ | 项目 | `project` | 项目组查询、切换、创建和更新 |
22
+ | 积分 | `credits` | 可扣积分余额、项目组预算、任务用量统计 |
23
+ | 模型 | `model` | 生图 / 生视频模型发现、模型参数白名单 |
24
+ | 上传 | `upload` | 本地图片 / 视频 / 音频上传为平台可访问素材 |
25
+ | 图片 | `image` | 生图估价、提交、批量提交、状态查询 |
26
+ | 视频 | `video` | 生视频估价、提交、批量提交、状态查询、去字幕 |
27
+ | 任务 | `task` | 任务列表、等待、任务台账和台账轮询 |
28
+ | 资产 | `asset` | 素材库匹配、素材组、素材注册 |
29
+ | 最终产物 | `artifact` | Workbench 剧本、资产、视频最终产物 CRUD 与本地 JSON 导入 |
30
+ | 主体 | `subject` | 可复用主体资产发布与查询 |
31
+
32
+ ## 环境要求
33
+
34
+ - Node.js `>=20`
35
+ - 可访问灵境 AWB / 动漫平台 API
36
+ - 平台 access key
37
+
38
+ 默认 API 地址由代码内置,也可以通过环境变量覆盖:
39
+
40
+ ```bash
41
+ export LINGJING_AWB_API_ORIGIN=https://animeworkbench.lingjingai.cn
42
+ export LINGJING_AWB_ACCESS_KEY=<access_key>
43
+ ```
44
+
45
+ 兼容的 access key 变量:`LINGJING_AWB_ACCESS_KEY`、`AWB_ACCESS_KEY`、`AWB_CODE`、`ANIME_ACCESS_KEY`。
46
+
47
+ ## 安装与运行
48
+
49
+ 全局安装:
50
+
51
+ ```bash
52
+ npm install -g @lingjingai/lj-awb-cli
53
+ lj-awb --help
54
+ lj-awb auth status
55
+ lj-awb auth verify
56
+ ```
57
+
58
+ 安装包的 `postinstall` 会默认把随包分发的 `lj-awb` skill 安装到:
59
+
60
+ ```text
61
+ ~/.cc-switch/skills/lj-awb
62
+ ~/.codex/skills/lj-awb
63
+ ```
64
+
65
+ 如果设置了 `CODEX_HOME`,Codex skill 会安装到 `$CODEX_HOME/skills/lj-awb`。
66
+
67
+ 可用 `LINGJING_AWB_SKILL_INSTALL_DIR` 指定单一安装目录;如果传入的是以 `skills` 结尾的根目录,安装器会自动追加 `lj-awb`。也可用 `LINGJING_AWB_SKIP_SKILL_INSTALL=1` 跳过 skill 安装。
68
+
69
+ 源码运行:
70
+
71
+ ```bash
72
+ cd anime-cli
73
+ npm run check
74
+ npm run smoke
75
+ node packages/awb-cli/bin/lj-awb.js --help
76
+ packages/awb-cli/bin/lj-awb auth status
77
+ ```
78
+
79
+ macOS 本地 wrapper `packages/awb-cli/bin/lj-awb` 会自动补充 Node 证书链,适合直接调试 HTTPS 平台接口。
80
+
81
+ ## 认证
82
+
83
+ 保存 access key 到本地认证文件:
84
+
85
+ ```bash
86
+ lj-awb auth login-key --access-key <access_key>
87
+ ```
88
+
89
+ 从环境变量读取并保存:
90
+
91
+ ```bash
92
+ LINGJING_AWB_ACCESS_KEY=<access_key> lj-awb auth login-key --from-env
93
+ ```
94
+
95
+ 不落盘,直接让每次调用读取环境变量:
96
+
97
+ ```bash
98
+ export LINGJING_AWB_ACCESS_KEY=<access_key>
99
+ lj-awb account info
100
+ ```
101
+
102
+ 本地状态文件默认写入:
103
+
104
+ ```text
105
+ ~/.lingjingai/awb/auth.json
106
+ ~/.lingjingai/awb/state.json
107
+ ```
108
+
109
+ 可用 `LINGJING_AWB_STATE_DIR`、`LINGJING_AWB_AUTH_PATH`、`LINGJING_AWB_STATE_PATH` 覆盖。
110
+
111
+ ## 常用命令
112
+
113
+ ```bash
114
+ lj-awb doctor
115
+ lj-awb doctor --verify
116
+ lj-awb schema -f json
117
+ lj-awb schema --domain video -f json
118
+ lj-awb auth status
119
+ lj-awb auth verify
120
+ lj-awb account info
121
+ lj-awb account teams
122
+ lj-awb project list
123
+ lj-awb project use --project-group-no <no> --yes
124
+ lj-awb credits balance
125
+ lj-awb credits usage --project-group-no <no> --last-hours 24
126
+ lj-awb model image-models --model Banana
127
+ lj-awb model video-models --model Seedance
128
+ lj-awb model input-guide
129
+ lj-awb model options --model-group-code <modelGroupCode>
130
+ lj-awb model create-spec --model-group-code <modelGroupCode>
131
+ lj-awb artifact script import --project-id <projectId> --input-file 1_script/output/script.json --dry-run
132
+ lj-awb artifact asset import --project-id <projectId> --input-dir 2_asset/output --dry-run
133
+ lj-awb artifact video import-storyboard --project-id <projectId> --input-file 3_footage/output/ep001/ep001_storyboard.json --dry-run
134
+ ```
135
+
136
+ 正式写入、切换上下文、批量和扣费命令都支持安全确认:
137
+
138
+ ```bash
139
+ lj-awb image create ... --dry-run
140
+ lj-awb image create ... --yes
141
+ ```
142
+
143
+ 缺少 `--yes` 时,CLI 返回 `confirmation_required`,退出码为 `10`。`task wait` 超时返回 `task_still_running`,退出码为 `20`。
144
+
145
+ ## Agent 接入建议
146
+
147
+ Agent 不应该靠记忆猜命令参数。进入任务前推荐先读取机器可读契约:
148
+
149
+ ```bash
150
+ lj-awb schema -f json
151
+ lj-awb schema --domain image -f json
152
+ lj-awb schema --domain video --command create -f json
153
+ ```
154
+
155
+ 正式创作前推荐做一次体检:
156
+
157
+ ```bash
158
+ lj-awb doctor --verify
159
+ ```
160
+
161
+ `auth status` 只检查本地是否配置 access key;`auth verify` 会联网校验 key 是否远端有效。`doctor` 默认只做本地检查;追加 `--verify` 后会联网校验 access key、当前用户和项目组。`schema` 返回每个命令的 `options[].key`,Agent 应使用这些 key 生成 CLI 参数,而不是解析自然语言 help。
162
+
163
+ `schema` 还包含面向 Agent 的执行约束:
164
+
165
+ - `requiredOptions`:必须提供的参数 key。
166
+ - `requiredAnyOptions`:每个分组至少提供一个参数 key。
167
+ - `safety.safeToAutoRun`:是否允许 Agent 静默执行。
168
+ - `safety.requiresConfirmation`:是否必须用户确认后追加 `--yes`。
169
+ - `safety.supportsDryRun`:正式执行前是否可以先 `--dry-run`。
170
+ - `workflow.recommendedPreflight`:推荐前置步骤,例如 `doctor`、模型查询、估价和 dry-run。
171
+
172
+ 创作任务前按三层读取模型信息:
173
+
174
+ ```bash
175
+ lj-awb model input-guide
176
+ lj-awb model options --model-group-code <modelGroupCode>
177
+ lj-awb model create-spec --model-group-code <modelGroupCode>
178
+ ```
179
+
180
+ `model input-guide` 返回跨模型统一参数、`resources` 字段和 `reference_key` 规则。`model options` 专门返回模型参数、资源媒体约束和条件约束,例如 `params[].values`、`params[].defaultValue`、`resources[].mediaType`、`resources[].usage`、`resources[].fileTypes`、`resources[].maxFiles`、`resources[].supportLastFrameOnly`、`resources[].minDurationMs/maxDurationMs`、`constraints[].target`、`constraints[].conditions`。`model create-spec` 专门返回如何创建任务,例如 `inputRequirement`、`supportedIntents`、`preflight` 和 `examples`。默认 text 已经覆盖常用决策字段;只有脚本严格解析或需要完整嵌套结构时才追加 `-f json`。Agent 应用 `options` 选参数值、校验素材并判断联动约束,再用 `create-spec` 组装 `image create` / `video create`,不要直接把平台旧参数传给 create。上传音频是 `resources` 素材输入,具体 `audio:reference` 写法看 `input-guide` / `create-spec`;是否需要输出音效才使用 `--need-audio true/false`。
181
+
182
+ ## 示例:参考图生成视频
183
+
184
+ ```bash
185
+ lj-awb project current
186
+ lj-awb model video-models --model Seedance
187
+ lj-awb model input-guide -f json
188
+ lj-awb model options --model-group-code <modelGroupCode> -f json
189
+ lj-awb model create-spec --model-group-code <modelGroupCode> -f json
190
+
191
+ lj-awb video fee \
192
+ --model-group-code <modelGroupCode> \
193
+ --prompt "参考女主首帧,在雨夜街头奔跑,电影感运镜" \
194
+ --resource image:first_frame=./actor.png \
195
+ --ratio 9:16 \
196
+ --quality 720 \
197
+ --duration 5 \
198
+ --project-group-no <projectGroupNo>
199
+
200
+ lj-awb video create \
201
+ --model-group-code <modelGroupCode> \
202
+ --prompt "参考女主首帧,在雨夜街头奔跑,电影感运镜" \
203
+ --resource image:first_frame=./actor.png \
204
+ --ratio 9:16 \
205
+ --quality 720 \
206
+ --duration 5 \
207
+ --project-group-no <projectGroupNo> \
208
+ --task-record-file .awb/tasks.jsonl \
209
+ --yes
210
+
211
+ lj-awb task wait \
212
+ --task-id <taskId> \
213
+ --task-type VIDEO_GROUP \
214
+ --project-group-no <projectGroupNo> \
215
+ --wait-seconds 300
216
+ ```
217
+
218
+ 帧素材也可以直接使用平台资产 ID;是否可传尾帧以 `model options.resources[].usage` 是否包含 `last_frame` 为准:
219
+
220
+ ```bash
221
+ lj-awb video create \
222
+ --model-group-code <modelGroupCode> \
223
+ --prompt "参考首帧生成一段镜头推进视频" \
224
+ --resource image:first_frame=asset:<assetId> \
225
+ --duration 5 \
226
+ --dry-run
227
+ ```
228
+
229
+ 批量创建支持 JSON / JSONL / 纯文本 prompt。JSON/JSONL 每项只写单个任务的差异字段,模型组、项目组、台账文件等公共参数仍放在命令行:
230
+
231
+ ```json
232
+ {"prompt":"镜头推进","duration":5,"quality":"720","resources":[{"type":"image","usage":"first_frame","source":{"kind":"url","value":"./first.png"}}]}
233
+ ```
234
+
235
+ 如果参考图会复用,推荐先发布主体资产:
236
+
237
+ ```bash
238
+ lj-awb subject publish --name 女主 --primary-file ./actor.png --yes
239
+ lj-awb subject wait --element-id <elementId> --wait-seconds 300
240
+ lj-awb video create \
241
+ --model-group-code <modelGroupCode> \
242
+ --prompt "<<<女主>>> 在雨夜街头奔跑,电影感运镜" \
243
+ --resource subject:reference:女主=asset:<externalId> \
244
+ --project-group-no <projectGroupNo> \
245
+ --yes
246
+ ```
247
+
248
+ ## JSON 输出契约
249
+
250
+ 默认输出是 compact text,适合终端和 Agent 低 token 读取;只有需要完整嵌套结构、稳定 JSON envelope 或脚本严格解析时才追加 `-f json` 或 `--json`。`schema` 通常使用 JSON;`model options` 和 `model create-spec` 默认 text 先读,必要时再切 JSON。
251
+
252
+ 输出字段只保留后续动作不可替代的信息。默认不返回平台原始 `raw`、COS 签名细节、重复的首个结果字段;任务结果统一使用 `resultUrls` 表达。
253
+
254
+ 成功输出:
255
+
256
+ ```json
257
+ {
258
+ "status": "success",
259
+ "data": {},
260
+ "meta": {
261
+ "command": "auth status",
262
+ "elapsed_ms": 12,
263
+ "api_origin": "https://animeworkbench.lingjingai.cn"
264
+ }
265
+ }
266
+ ```
267
+
268
+ 失败输出:
269
+
270
+ ```json
271
+ {
272
+ "status": "error",
273
+ "error": {
274
+ "type": "auth_required",
275
+ "message": "缺少 access key",
276
+ "hint": "请设置 LINGJING_AWB_ACCESS_KEY / AWB_ACCESS_KEY,或运行 lj-awb auth login-key。"
277
+ },
278
+ "meta": {
279
+ "command": "account info",
280
+ "elapsed_ms": 1,
281
+ "api_origin": "https://animeworkbench.lingjingai.cn"
282
+ }
283
+ }
284
+ ```
285
+
286
+ 常见退出码:
287
+
288
+ | 退出码 | 含义 |
289
+ | --- | --- |
290
+ | `0` | 成功 |
291
+ | `1` | 普通运行失败或平台业务失败 |
292
+ | `2` | 参数错误或未知命令 |
293
+ | `3` | 缺少认证或认证失败 |
294
+ | `10` | 需要用户确认 |
295
+ | `20` | 任务仍在运行或等待超时 |
296
+ | `30` | 网络、TLS、HTTP 或外部平台不可用 |
297
+
298
+ ## 仓库结构
299
+
300
+ ```text
301
+ anime-cli/
302
+ ├── package.json
303
+ ├── install.mjs
304
+ ├── packages/
305
+ │ ├── awb-core/ # 共享认证、API、服务、命令注册、standalone runtime
306
+ │ └── awb-cli/ # lj-awb bin 入口
307
+ ├── skills/
308
+ │ └── lj-awb/ # 随包分发的 Agent skill
309
+ ├── scripts/ # 版本同步和 skill 元数据校验
310
+ └── docs/ # 设计、规划、变更和技术文档
311
+ ```
312
+
313
+ 更多实现细节见 [`docs/TECHNICAL.md`](docs/TECHNICAL.md)。
314
+
315
+ ## 开发命令
316
+
317
+ ```bash
318
+ npm run check:local
319
+ npm run check
320
+ npm run smoke
321
+ npm run version:sync -- 0.3.5
322
+ ```
323
+
324
+ `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` 会运行帮助命令和无网络写入风险的认证状态检查。
325
+
326
+ ## 文档索引
327
+
328
+ - [`docs/TECHNICAL.md`](docs/TECHNICAL.md):架构、运行时、数据契约、发布维护说明。
329
+ - [`docs/01-cli-case-study.md`](docs/01-cli-case-study.md):CLI 框架案例学习。
330
+ - [`docs/02-e2b-interface-inventory.md`](docs/02-e2b-interface-inventory.md):动漫平台基建接口分类。
331
+ - [`docs/03-anime-cli-design.md`](docs/03-anime-cli-design.md):产品边界和早期技术设计。
332
+ - [`docs/04-implementation-roadmap.md`](docs/04-implementation-roadmap.md):分阶段开发路线。
333
+ - [`docs/05-cli-command-planning-from-api.md`](docs/05-cli-command-planning-from-api.md):基于 API 清单的命令规划。
334
+ - [`docs/06-lingjing-awb-functional-baseline.md`](docs/06-lingjing-awb-functional-baseline.md):功能基线回归报告。
335
+ - [`docs/changes/`](docs/changes):重要实现变更记录。
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { cpSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
+
9
+ export function findArgValue(args, name) {
10
+ for (let index = 0; index < args.length; index += 1) {
11
+ const arg = args[index];
12
+ if (arg === name) return args[index + 1];
13
+ if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
14
+ }
15
+ return undefined;
16
+ }
17
+
18
+ export function run(command, args, options = {}) {
19
+ process.stderr.write(`$ ${[command, ...args].join(' ')}\n`);
20
+ const result = spawnSync(command, args, {
21
+ cwd: options.cwd || repoRoot,
22
+ stdio: 'inherit',
23
+ });
24
+ if (result.error) {
25
+ process.stderr.write(`${result.error.message}\n`);
26
+ process.exit(1);
27
+ }
28
+ if (result.status !== 0) {
29
+ process.exit(result.status || 1);
30
+ }
31
+ }
32
+
33
+ export function capture(command, args, options = {}) {
34
+ return spawnSync(command, args, {
35
+ cwd: options.cwd || repoRoot,
36
+ encoding: 'utf8',
37
+ stdio: ['inherit', 'pipe', 'pipe'],
38
+ });
39
+ }
40
+
41
+ export function packageFilePaths(cwd = repoRoot) {
42
+ const result = capture('npm', ['pack', '--dry-run', '--json'], { cwd });
43
+ if (result.error) {
44
+ process.stderr.write(`${result.error.message}\n`);
45
+ process.exit(1);
46
+ }
47
+ if (result.status !== 0) {
48
+ process.exit(result.status || 1);
49
+ }
50
+ const packPlan = JSON.parse(result.stdout || '[]');
51
+ const files = packPlan[0]?.files?.map((item) => item.path).filter(Boolean) || [];
52
+ if (!files.length) {
53
+ process.stderr.write('npm pack --dry-run did not return package files.\n');
54
+ process.exit(1);
55
+ }
56
+ return files;
57
+ }
58
+
59
+ export function copyPackageFiles(stageDir, sourceRoot = repoRoot) {
60
+ for (const relativePath of packageFilePaths(sourceRoot)) {
61
+ const sourcePath = path.join(sourceRoot, relativePath);
62
+ const targetPath = path.join(stageDir, relativePath);
63
+ mkdirSync(path.dirname(targetPath), { recursive: true });
64
+ cpSync(sourcePath, targetPath, { recursive: true });
65
+ }
66
+ }
67
+
68
+ export function patchDefaultApiOrigin(stageDir, apiOrigin) {
69
+ const commonPath = path.join(stageDir, 'packages/awb-core/src/common.js');
70
+ const source = readFileSync(commonPath, 'utf8');
71
+ const next = source.replace(
72
+ /export const DEFAULT_API_ORIGIN = '[^']+';/,
73
+ `export const DEFAULT_API_ORIGIN = '${apiOrigin}';`,
74
+ );
75
+ if (next === source) {
76
+ process.stderr.write(`Could not patch DEFAULT_API_ORIGIN in ${commonPath}.\n`);
77
+ process.exit(1);
78
+ }
79
+ writeFileSync(commonPath, next, 'utf8');
80
+ }
81
+
82
+ export function packTarball(stageDir) {
83
+ process.stderr.write(`$ npm pack --pack-destination ${stageDir} --silent\n`);
84
+ const result = spawnSync('npm', ['pack', '--pack-destination', stageDir, '--silent'], {
85
+ cwd: stageDir,
86
+ encoding: 'utf8',
87
+ stdio: ['inherit', 'pipe', 'inherit'],
88
+ });
89
+ if (result.error) {
90
+ process.stderr.write(`${result.error.message}\n`);
91
+ process.exit(1);
92
+ }
93
+ if (result.status !== 0) {
94
+ process.exit(result.status || 1);
95
+ }
96
+ const tarball = result.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
97
+ if (!tarball) {
98
+ process.stderr.write('npm pack did not return a tarball name.\n');
99
+ process.exit(1);
100
+ }
101
+ return path.join(stageDir, tarball);
102
+ }
103
+
104
+ export function ensureLoggedIn(registry) {
105
+ const result = capture('npm', ['whoami', `--registry=${registry}`]);
106
+ if (result.status === 0) {
107
+ process.stderr.write(`npm user: ${result.stdout.trim()}\n`);
108
+ return;
109
+ }
110
+ process.stderr.write([
111
+ `npm is not logged in for ${registry}.`,
112
+ `Run: npm login --registry=${registry}`,
113
+ '',
114
+ ].join('\n'));
115
+ process.exit(1);
116
+ }
117
+
118
+ export function ensureVersionNotPublished({ name, version, registry }) {
119
+ const spec = `${name}@${version}`;
120
+ const result = capture('npm', ['view', spec, 'version', `--registry=${registry}`]);
121
+ if (result.status === 0) {
122
+ process.stderr.write(`${spec} already exists on npm. Bump the version before publishing.\n`);
123
+ process.exit(1);
124
+ }
125
+ if (!/E404|404 Not Found|Not found/i.test(`${result.stderr}\n${result.stdout}`)) {
126
+ process.stderr.write(`Could not verify whether ${spec} exists on npm.\n`);
127
+ process.stderr.write(result.stderr || result.stdout || '');
128
+ process.exit(1);
129
+ }
130
+ }
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7
+ const rawArgs = process.argv.slice(2);
8
+ const target = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'pre';
9
+ const passthroughArgs = target === rawArgs[0] ? rawArgs.slice(1) : rawArgs;
10
+
11
+ const scriptByTarget = {
12
+ pre: 'pre.mjs',
13
+ 'pre-publish': 'pre-publish.mjs',
14
+ prod: 'prod.mjs',
15
+ };
16
+
17
+ function printUsage() {
18
+ process.stderr.write([
19
+ 'Usage:',
20
+ ' npm run build -- pre',
21
+ ' npm run build -- pre --dry-run',
22
+ ' npm run build -- pre-publish',
23
+ ' npm run build -- pre-publish --dry-run',
24
+ ' npm run build -- prod',
25
+ ' npm run build -- prod --dry-run',
26
+ '',
27
+ ].join('\n'));
28
+ }
29
+
30
+ const script = scriptByTarget[target];
31
+ if (!script) {
32
+ printUsage();
33
+ process.exit(2);
34
+ }
35
+
36
+ const result = spawnSync(process.execPath, [
37
+ path.join(repoRoot, 'build', script),
38
+ ...passthroughArgs,
39
+ ], {
40
+ cwd: repoRoot,
41
+ env: process.env,
42
+ stdio: 'inherit',
43
+ });
44
+
45
+ if (result.error) {
46
+ process.stderr.write(`${result.error.message}\n`);
47
+ process.exit(1);
48
+ }
49
+
50
+ process.exit(result.status || 0);
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import {
6
+ copyPackageFiles,
7
+ ensureLoggedIn,
8
+ ensureVersionNotPublished,
9
+ findArgValue,
10
+ patchDefaultApiOrigin,
11
+ repoRoot,
12
+ run,
13
+ } from './_shared.mjs';
14
+
15
+ const pkg = JSON.parse(readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
16
+ const passthroughArgs = process.argv.slice(2);
17
+ const registry = findArgValue(passthroughArgs, '--registry') || process.env.NPM_CONFIG_REGISTRY || 'https://registry.npmjs.org/';
18
+ const dryRun = process.env.NPM_PUBLISH_DRY_RUN === '1' || passthroughArgs.includes('--dry-run');
19
+ const PRE_API_ORIGIN = 'https://animeworkbench-pre.lingjingai.cn';
20
+ const PRE_PACKAGE_NAME = '@lingjingai/lj-awb-cli-pre';
21
+
22
+ function patchPackageName(stageDir) {
23
+ const pkgPath = path.join(stageDir, 'package.json');
24
+ const stagedPkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
25
+ stagedPkg.name = PRE_PACKAGE_NAME;
26
+ if (typeof stagedPkg.description === 'string') {
27
+ stagedPkg.description = `${stagedPkg.description} (pre-release build pointing to ${PRE_API_ORIGIN})`;
28
+ }
29
+ writeFileSync(pkgPath, `${JSON.stringify(stagedPkg, null, 2)}\n`, 'utf8');
30
+ }
31
+
32
+ process.stderr.write(`Preparing ${PRE_PACKAGE_NAME}@${pkg.version} for npm ${dryRun ? 'dry-run' : 'publish'} with default API origin ${PRE_API_ORIGIN}.\n`);
33
+
34
+ if (process.env.SKIP_CHECK !== '1') {
35
+ run('npm', ['run', 'check:local']);
36
+ }
37
+
38
+ ensureVersionNotPublished({ name: PRE_PACKAGE_NAME, version: pkg.version, registry });
39
+ if (!dryRun) {
40
+ ensureLoggedIn(registry);
41
+ }
42
+
43
+ const stageDir = mkdtempSync(path.join(tmpdir(), 'lj-awb-pre-publish-'));
44
+ try {
45
+ copyPackageFiles(stageDir);
46
+ patchDefaultApiOrigin(stageDir, PRE_API_ORIGIN);
47
+ patchPackageName(stageDir);
48
+ run('npm', [
49
+ 'publish',
50
+ '--access',
51
+ 'public',
52
+ `--registry=${registry}`,
53
+ ...passthroughArgs,
54
+ ], { cwd: stageDir });
55
+ } finally {
56
+ rmSync(stageDir, { recursive: true, force: true });
57
+ }
package/build/pre.mjs ADDED
@@ -0,0 +1,42 @@
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
+ copyPackageFiles,
7
+ packTarball,
8
+ patchDefaultApiOrigin,
9
+ run,
10
+ } from './_shared.mjs';
11
+
12
+ const PRE_API_ORIGIN = 'https://animeworkbench-pre.lingjingai.cn';
13
+ const dryRun = process.env.NPM_INSTALL_DRY_RUN === '1' || process.argv.slice(2).includes('--dry-run');
14
+ const forceInstall = process.env.NPM_INSTALL_FORCE === '1' || process.argv.slice(2).includes('--force');
15
+
16
+ if (process.env.SKIP_CHECK !== '1') {
17
+ run('npm', ['run', 'check:local']);
18
+ }
19
+
20
+ const stageDir = mkdtempSync(path.join(tmpdir(), 'lj-awb-pre-src-'));
21
+ try {
22
+ process.stderr.write(`Preparing local pre build with default API origin ${PRE_API_ORIGIN}.\n`);
23
+ copyPackageFiles(stageDir);
24
+ patchDefaultApiOrigin(stageDir, PRE_API_ORIGIN);
25
+ const tarballPath = packTarball(stageDir);
26
+ run('npm', [
27
+ 'install',
28
+ '-g',
29
+ tarballPath,
30
+ '--registry=https://registry.npmjs.org/',
31
+ ...(forceInstall ? ['--force'] : []),
32
+ ...(dryRun ? ['--dry-run'] : []),
33
+ ]);
34
+ if (dryRun) {
35
+ process.stderr.write('Validated local pre build install plan from packed tarball.\n');
36
+ } else {
37
+ run('lj-awb', ['--version']);
38
+ process.stderr.write('Installed local pre build globally from packed tarball.\n');
39
+ }
40
+ } finally {
41
+ rmSync(stageDir, { recursive: true, force: true });
42
+ }
package/build/prod.mjs ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import {
5
+ ensureLoggedIn,
6
+ ensureVersionNotPublished,
7
+ findArgValue,
8
+ repoRoot,
9
+ run,
10
+ } from './_shared.mjs';
11
+
12
+ const pkg = JSON.parse(readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
13
+ const passthroughArgs = process.argv.slice(2);
14
+ const registry = findArgValue(passthroughArgs, '--registry') || process.env.NPM_CONFIG_REGISTRY || 'https://registry.npmjs.org/';
15
+ const dryRun = process.env.NPM_PUBLISH_DRY_RUN === '1' || passthroughArgs.includes('--dry-run');
16
+ const PROD_API_ORIGIN = 'https://animeworkbench.lingjingai.cn';
17
+
18
+ function ensureProductionDefaultApiOrigin() {
19
+ const commonPath = path.join(repoRoot, 'packages/awb-core/src/common.js');
20
+ const source = readFileSync(commonPath, 'utf8');
21
+ const match = source.match(/export const DEFAULT_API_ORIGIN = '([^']+)';/);
22
+ const actual = match?.[1] || null;
23
+ if (actual !== PROD_API_ORIGIN) {
24
+ process.stderr.write([
25
+ `Refusing to publish prod build with DEFAULT_API_ORIGIN=${actual || '(missing)'}.`,
26
+ `Expected ${PROD_API_ORIGIN}.`,
27
+ '',
28
+ ].join('\n'));
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ ensureProductionDefaultApiOrigin();
34
+
35
+ process.stderr.write(`Preparing ${pkg.name}@${pkg.version} for npm ${dryRun ? 'dry-run' : 'publish'} with default API origin ${PROD_API_ORIGIN}.\n`);
36
+
37
+ if (process.env.SKIP_CHECK !== '1') {
38
+ run('npm', ['run', 'check:local']);
39
+ }
40
+
41
+ ensureVersionNotPublished({ name: pkg.name, version: pkg.version, registry });
42
+ if (!dryRun) {
43
+ ensureLoggedIn(registry);
44
+ }
45
+
46
+ run('npm', [
47
+ 'publish',
48
+ '--access',
49
+ 'public',
50
+ `--registry=${registry}`,
51
+ ...passthroughArgs,
52
+ ]);