@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
@@ -0,0 +1,508 @@
1
+ import fsSync from 'node:fs';
2
+ import fs from 'node:fs/promises';
3
+ import crypto from 'node:crypto';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ export const DEFAULT_API_ORIGIN = 'https://animeworkbench-pre.lingjingai.cn';
8
+ export const API_ORIGIN = (
9
+ process.env.LINGJING_AWB_API_ORIGIN
10
+ || process.env.ANIME_API_ORIGIN
11
+ || process.env.AWB_BASE_URL
12
+ || DEFAULT_API_ORIGIN
13
+ ).replace(/\/+$/, '');
14
+ export const APP_HOME_DIR = process.env.LINGJING_AWB_STATE_DIR || process.env.ANIME_STATE_DIR || path.join(os.homedir(), '.lingjingai', 'awb');
15
+ export const AUTH_PATH = process.env.LINGJING_AWB_AUTH_PATH || process.env.ANIME_AUTH_PATH || path.join(APP_HOME_DIR, 'auth.json');
16
+ export const STATE_PATH = process.env.LINGJING_AWB_STATE_PATH || process.env.ANIME_STATE_PATH || path.join(APP_HOME_DIR, 'state.json');
17
+ export const ACCESS_KEY_ENV_NAMES = ['LINGJING_AWB_ACCESS_KEY', 'AWB_ACCESS_KEY', 'AWB_CODE', 'ANIME_ACCESS_KEY'];
18
+ export const TASK_UPLOAD_SCENE = {
19
+ IMAGE_CREATE: 'material-image-draw',
20
+ IMAGE_EDIT: 'material-image-edit',
21
+ VIDEO_CREATE: 'material-video-create',
22
+ VIDEO_GROUP: 'material-video-create',
23
+ SUBJECT: 'material-video-create',
24
+ DEFAULT: 'material-image-edit',
25
+ };
26
+ export const TERMINAL_TASK_STATES = new Set([
27
+ 'SUCCESS',
28
+ 'COMPLETED',
29
+ 'DONE',
30
+ 'SUCCEEDED',
31
+ 'FAIL',
32
+ 'FAILED',
33
+ 'ERROR',
34
+ 'CANCEL',
35
+ 'CANCELED',
36
+ 'CANCELLED',
37
+ 'TIMEOUT',
38
+ ]);
39
+ export const SUCCESS_TASK_STATES = new Set(['SUCCESS', 'COMPLETED', 'DONE', 'SUCCEEDED']);
40
+
41
+ function parseDotenvValue(value) {
42
+ let text = String(value ?? '').trim();
43
+ if (text.length >= 2 && text[0] === text.at(-1) && ['"', "'"].includes(text[0])) {
44
+ const quote = text[0];
45
+ text = text.slice(1, -1);
46
+ if (quote === '"') {
47
+ text = text
48
+ .replaceAll('\\n', '\n')
49
+ .replaceAll('\\r', '\r')
50
+ .replaceAll('\\t', '\t')
51
+ .replaceAll('\\"', '"')
52
+ .replaceAll('\\\\', '\\');
53
+ }
54
+ }
55
+ return text;
56
+ }
57
+
58
+ function loadDotenvFile(envPath, options = {}) {
59
+ if (!envPath || !fsSync.existsSync(envPath)) return false;
60
+ const allowKey = options.allowKey ?? (() => true);
61
+ let loaded = false;
62
+ try {
63
+ for (const rawLine of fsSync.readFileSync(envPath, 'utf8').split(/\r?\n/)) {
64
+ const line = rawLine.trim();
65
+ if (!line || line.startsWith('#') || !line.includes('=')) continue;
66
+ const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
67
+ if (!match) continue;
68
+ const [, key, rawValue] = match;
69
+ if (!allowKey(key)) continue;
70
+ if (process.env[key] === undefined) {
71
+ process.env[key] = parseDotenvValue(rawValue);
72
+ loaded = true;
73
+ }
74
+ }
75
+ } catch {
76
+ return false;
77
+ }
78
+ return loaded;
79
+ }
80
+
81
+ function loadNearestDotenv() {
82
+ if (
83
+ process.env.LINGJING_AWB_DISABLE_DOTENV === '1'
84
+ || process.env.LINGJING_AWB_DISABLE_DOTENV === 'true'
85
+ || process.env.ANIME_DISABLE_DOTENV === '1'
86
+ || process.env.ANIME_DISABLE_DOTENV === 'true'
87
+ ) return;
88
+ const configuredEnvPath = process.env.LINGJING_AWB_ENV_PATH || process.env.ANIME_ENV_PATH;
89
+ let envPath = configuredEnvPath ? path.resolve(configuredEnvPath) : null;
90
+ if (!envPath) {
91
+ let current = process.cwd();
92
+ while (current) {
93
+ const candidate = path.join(current, '.env');
94
+ if (fsSync.existsSync(candidate)) {
95
+ envPath = candidate;
96
+ break;
97
+ }
98
+ const parent = path.dirname(current);
99
+ if (parent === current) break;
100
+ current = parent;
101
+ }
102
+ }
103
+ loadDotenvFile(envPath);
104
+ }
105
+
106
+ function loadUserEnvFiles() {
107
+ if (
108
+ process.env.LINGJING_AWB_DISABLE_USER_ENV === '1'
109
+ || process.env.LINGJING_AWB_DISABLE_USER_ENV === 'true'
110
+ || process.env.ANIME_DISABLE_USER_ENV === '1'
111
+ || process.env.ANIME_DISABLE_USER_ENV === 'true'
112
+ ) return;
113
+ const allowKey = (key) => /^LINGJING_AWB_/i.test(key) || /^ANIME_/i.test(key) || /^AWB_/i.test(key);
114
+ for (const filePath of [
115
+ path.join(os.homedir(), '.lj-awb.env'),
116
+ path.join(os.homedir(), '.anime.env'),
117
+ path.join(os.homedir(), '.awb.env'),
118
+ path.join(os.homedir(), '.zshenv'),
119
+ path.join(os.homedir(), '.zprofile'),
120
+ path.join(os.homedir(), '.zshrc'),
121
+ path.join(os.homedir(), '.profile'),
122
+ path.join(os.homedir(), '.bash_profile'),
123
+ path.join(os.homedir(), '.bashrc'),
124
+ ]) {
125
+ loadDotenvFile(filePath, { allowKey });
126
+ }
127
+ }
128
+
129
+ loadNearestDotenv();
130
+ loadUserEnvFiles();
131
+
132
+ export async function ensureParent(filePath) {
133
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
134
+ }
135
+
136
+ export async function readJson(filePath, fallback = null) {
137
+ try {
138
+ return JSON.parse(await fs.readFile(filePath, 'utf8'));
139
+ } catch {
140
+ return fallback;
141
+ }
142
+ }
143
+
144
+ export async function writeJson(filePath, value, options = {}) {
145
+ await ensureParent(filePath);
146
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
147
+ if (options.secure) {
148
+ await fs.chmod(filePath, 0o600).catch(() => {});
149
+ }
150
+ }
151
+
152
+ export async function loadState() {
153
+ return await readJson(STATE_PATH, {});
154
+ }
155
+
156
+ export async function saveState(value) {
157
+ const current = await loadState();
158
+ const next = {
159
+ ...current,
160
+ ...value,
161
+ updatedAt: Date.now(),
162
+ updatedAtText: nowIso(),
163
+ };
164
+ await writeJson(STATE_PATH, next);
165
+ return next;
166
+ }
167
+
168
+ export function trimSecret(value) {
169
+ const text = String(value ?? '').trim();
170
+ return text || null;
171
+ }
172
+
173
+ export function resolveRuntimeAccessKey(auth = null) {
174
+ for (const envName of ACCESS_KEY_ENV_NAMES) {
175
+ const accessKey = trimSecret(process.env[envName]);
176
+ if (accessKey) {
177
+ return { accessKey, source: 'env', sourceName: envName };
178
+ }
179
+ }
180
+ const saved = trimSecret(auth?.accessKey || auth?.awbCode || auth?.awb_code || auth?.access_key);
181
+ if (saved) {
182
+ return { accessKey: saved, source: 'saved', sourceName: 'auth' };
183
+ }
184
+ return null;
185
+ }
186
+
187
+ export function maskSecret(value) {
188
+ const text = trimSecret(value);
189
+ if (!text) return null;
190
+ if (text.length <= 8) return `${'*'.repeat(Math.max(0, text.length - 2))}${text.slice(-2)}`;
191
+ return `${text.slice(0, 4)}...${text.slice(-4)}`;
192
+ }
193
+
194
+ export function nowIso() {
195
+ return new Date().toISOString();
196
+ }
197
+
198
+ export function trimToNull(value) {
199
+ const text = String(value ?? '').trim();
200
+ return text || null;
201
+ }
202
+
203
+ export function toBool(value) {
204
+ if (value === true || value === 1) return true;
205
+ if (value == null) return false;
206
+ return ['true', '1', 'yes', 'y', 'on'].includes(String(value).trim().toLowerCase());
207
+ }
208
+
209
+ export function toInt(value, fallback = 0) {
210
+ const parsed = Number.parseInt(String(value ?? ''), 10);
211
+ return Number.isFinite(parsed) ? parsed : fallback;
212
+ }
213
+
214
+ export function toNumberOrNull(value) {
215
+ if (value === undefined || value === null || value === '') return null;
216
+ const parsed = Number(value);
217
+ return Number.isFinite(parsed) ? parsed : null;
218
+ }
219
+
220
+ export function parseJsonArg(value, fallback = undefined) {
221
+ if (value === undefined || value === null || value === '') return fallback;
222
+ if (typeof value !== 'string') return value;
223
+ return JSON.parse(value);
224
+ }
225
+
226
+ export function splitCsv(value) {
227
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
228
+ return String(value ?? '')
229
+ .split(',')
230
+ .map((item) => item.trim())
231
+ .filter(Boolean);
232
+ }
233
+
234
+ export function parseListArg(value) {
235
+ if (value === undefined || value === null || value === '') return [];
236
+ if (Array.isArray(value)) return value.flatMap((item) => parseListArg(item));
237
+ const text = String(value).trim();
238
+ if (!text) return [];
239
+ if (text.startsWith('[')) {
240
+ const parsed = JSON.parse(text);
241
+ return Array.isArray(parsed) ? parsed.map((item) => String(item).trim()).filter(Boolean) : [];
242
+ }
243
+ return splitCsv(text);
244
+ }
245
+
246
+ export function firstArray(value) {
247
+ if (Array.isArray(value)) return value;
248
+ if (!value || typeof value !== 'object') return [];
249
+ for (const nested of Object.values(value)) {
250
+ const found = firstArray(nested);
251
+ if (found.length) return found;
252
+ }
253
+ return [];
254
+ }
255
+
256
+ export function flattenRecord(record, prefix = '', out = {}) {
257
+ if (record == null) return out;
258
+ if (typeof record !== 'object' || Array.isArray(record)) {
259
+ out[prefix || 'value'] = record;
260
+ return out;
261
+ }
262
+ for (const [key, value] of Object.entries(record)) {
263
+ const nextKey = prefix ? `${prefix}_${key}` : key;
264
+ if (value == null) {
265
+ out[nextKey] = value;
266
+ } else if (Array.isArray(value)) {
267
+ out[nextKey] = JSON.stringify(value);
268
+ } else if (typeof value === 'object') {
269
+ flattenRecord(value, nextKey, out);
270
+ } else {
271
+ out[nextKey] = value;
272
+ }
273
+ }
274
+ return out;
275
+ }
276
+
277
+ export function uniqueNonEmpty(values) {
278
+ return [...new Set((Array.isArray(values) ? values : [])
279
+ .map((item) => String(item ?? '').trim())
280
+ .filter(Boolean))];
281
+ }
282
+
283
+ export function sleep(ms) {
284
+ return new Promise((resolve) => setTimeout(resolve, ms));
285
+ }
286
+
287
+ export function normalizeFeedTaskType(value) {
288
+ const text = String(value || '').trim().toUpperCase();
289
+ if (!text) return 'IMAGE_CREATE';
290
+ if (text === 'VIDEO_CREATE') return 'VIDEO_GROUP';
291
+ return text;
292
+ }
293
+
294
+ export function taskStatusText(value) {
295
+ return String(value ?? '').trim().toUpperCase();
296
+ }
297
+
298
+ export function isTerminalTaskStatus(value) {
299
+ return TERMINAL_TASK_STATES.has(taskStatusText(value));
300
+ }
301
+
302
+ export function isSuccessTaskStatus(value) {
303
+ return SUCCESS_TASK_STATES.has(taskStatusText(value));
304
+ }
305
+
306
+ export function taskTypeToStatusKind(taskType) {
307
+ const normalized = normalizeFeedTaskType(taskType);
308
+ return normalized.startsWith('VIDEO') ? 'video' : 'image';
309
+ }
310
+
311
+ export function guessMimeType(filePath) {
312
+ const ext = path.extname(String(filePath ?? '')).toLowerCase();
313
+ switch (ext) {
314
+ case '.bmp':
315
+ return 'image/bmp';
316
+ case '.png':
317
+ return 'image/png';
318
+ case '.jpg':
319
+ case '.jpeg':
320
+ return 'image/jpeg';
321
+ case '.webp':
322
+ return 'image/webp';
323
+ case '.gif':
324
+ return 'image/gif';
325
+ case '.mp3':
326
+ return 'audio/mpeg';
327
+ case '.wav':
328
+ return 'audio/wav';
329
+ case '.m4a':
330
+ return 'audio/mp4';
331
+ case '.mp4':
332
+ return 'video/mp4';
333
+ case '.mov':
334
+ return 'video/quicktime';
335
+ default:
336
+ return 'application/octet-stream';
337
+ }
338
+ }
339
+
340
+ export function safeFileName(filePath) {
341
+ return path
342
+ .basename(String(filePath || 'upload.bin'))
343
+ .replace(/[^0-9A-Za-z._-]+/g, '-')
344
+ .replace(/-+/g, '-')
345
+ .replace(/^-|-$/g, '') || 'upload.bin';
346
+ }
347
+
348
+ function parsePngSize(buffer) {
349
+ const signature = '89504e470d0a1a0a';
350
+ if (buffer.length < 24 || buffer.subarray(0, 8).toString('hex') !== signature) return null;
351
+ return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20), format: 'png' };
352
+ }
353
+
354
+ function parseJpegSize(buffer) {
355
+ if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) return null;
356
+ let offset = 2;
357
+ while (offset + 9 < buffer.length) {
358
+ if (buffer[offset] !== 0xff) {
359
+ offset += 1;
360
+ continue;
361
+ }
362
+ const marker = buffer[offset + 1];
363
+ const size = buffer.readUInt16BE(offset + 2);
364
+ if ((marker >= 0xc0 && marker <= 0xc3) || (marker >= 0xc5 && marker <= 0xc7) || (marker >= 0xc9 && marker <= 0xcb) || (marker >= 0xcd && marker <= 0xcf)) {
365
+ return { width: buffer.readUInt16BE(offset + 7), height: buffer.readUInt16BE(offset + 5), format: 'jpeg' };
366
+ }
367
+ offset += 2 + size;
368
+ }
369
+ return null;
370
+ }
371
+
372
+ function parseWebpSize(buffer) {
373
+ if (buffer.length < 30) return null;
374
+ if (buffer.subarray(0, 4).toString('ascii') !== 'RIFF') return null;
375
+ if (buffer.subarray(8, 12).toString('ascii') !== 'WEBP') return null;
376
+ const chunkType = buffer.subarray(12, 16).toString('ascii');
377
+ if (chunkType === 'VP8 ') {
378
+ return { width: buffer.readUInt16LE(26) & 0x3fff, height: buffer.readUInt16LE(28) & 0x3fff, format: 'webp' };
379
+ }
380
+ if (chunkType === 'VP8L') {
381
+ const bits = buffer.readUInt32LE(21);
382
+ return { width: (bits & 0x3fff) + 1, height: ((bits >> 14) & 0x3fff) + 1, format: 'webp' };
383
+ }
384
+ if (chunkType === 'VP8X') {
385
+ return { width: 1 + buffer.readUIntLE(24, 3), height: 1 + buffer.readUIntLE(27, 3), format: 'webp' };
386
+ }
387
+ return null;
388
+ }
389
+
390
+ export async function inspectLocalFile(filePath) {
391
+ const absolutePath = path.resolve(String(filePath));
392
+ const stat = await fs.stat(absolutePath).catch((error) => {
393
+ if (error?.code === 'ENOENT') return null;
394
+ throw error;
395
+ });
396
+ const mimeType = guessMimeType(absolutePath);
397
+ if (!stat) {
398
+ return {
399
+ filePath: absolutePath,
400
+ fileName: path.basename(absolutePath),
401
+ exists: false,
402
+ size: null,
403
+ mimeType,
404
+ width: null,
405
+ height: null,
406
+ };
407
+ }
408
+ let meta = null;
409
+ if (mimeType.startsWith('image/')) {
410
+ const buffer = await fs.readFile(absolutePath).catch(() => null);
411
+ meta = buffer ? parsePngSize(buffer) ?? parseJpegSize(buffer) ?? parseWebpSize(buffer) : null;
412
+ }
413
+ return {
414
+ filePath: absolutePath,
415
+ fileName: path.basename(absolutePath),
416
+ exists: true,
417
+ size: stat.size,
418
+ mimeType,
419
+ width: meta?.width ?? null,
420
+ height: meta?.height ?? null,
421
+ format: meta?.format ?? null,
422
+ };
423
+ }
424
+
425
+ export async function inspectLocalFiles(filePaths) {
426
+ const result = [];
427
+ for (const filePath of parseListArg(filePaths)) {
428
+ result.push(await inspectLocalFile(filePath));
429
+ }
430
+ return result;
431
+ }
432
+
433
+ function sha1(input, encoding = 'hex') {
434
+ return crypto.createHash('sha1').update(input).digest(encoding);
435
+ }
436
+
437
+ function hmacSha1(key, input, encoding = 'hex') {
438
+ return crypto.createHmac('sha1', key).update(input).digest(encoding);
439
+ }
440
+
441
+ function encodeCosValue(value) {
442
+ return encodeURIComponent(String(value)).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
443
+ }
444
+
445
+ export function encodeObjectNamePath(value) {
446
+ return String(value ?? '')
447
+ .split('/')
448
+ .map((segment) => encodeURIComponent(segment))
449
+ .join('/');
450
+ }
451
+
452
+ export function buildCosAuthorization({
453
+ secretKey,
454
+ secretId,
455
+ method,
456
+ objectName,
457
+ contentLength,
458
+ host,
459
+ startTime,
460
+ expiredTime,
461
+ }) {
462
+ const signTime = `${startTime};${expiredTime}`;
463
+ const signKey = hmacSha1(secretKey, signTime);
464
+ const httpString = [
465
+ method.toLowerCase(),
466
+ `/${objectName}`,
467
+ '',
468
+ `content-length=${encodeCosValue(contentLength)}&host=${encodeCosValue(host)}`,
469
+ '',
470
+ ].join('\n');
471
+ const stringToSign = ['sha1', signTime, sha1(httpString), ''].join('\n');
472
+ const signature = hmacSha1(signKey, stringToSign);
473
+ return [
474
+ 'q-sign-algorithm=sha1',
475
+ `q-ak=${secretId}`,
476
+ `q-sign-time=${signTime}`,
477
+ `q-key-time=${signTime}`,
478
+ 'q-header-list=content-length;host',
479
+ 'q-url-param-list=',
480
+ `q-signature=${signature}`,
481
+ ].join('&');
482
+ }
483
+
484
+ export class LingjingAwbCliError extends Error {
485
+ constructor(message, options = {}) {
486
+ super(message);
487
+ this.name = 'LingjingAwbCliError';
488
+ this.type = options.type || 'error';
489
+ this.exitCode = options.exitCode || 1;
490
+ this.hint = options.hint || '';
491
+ this.details = options.details || null;
492
+ }
493
+ }
494
+
495
+ export function createEnvelope({ status = 'success', data = null, error = null, meta = {} } = {}) {
496
+ const { elapsedMs, ...restMeta } = meta;
497
+ const success = status === 'success';
498
+ return {
499
+ status,
500
+ ...(success ? { data } : { error }),
501
+ meta: {
502
+ command: meta.command || '',
503
+ elapsed_ms: elapsedMs ?? null,
504
+ api_origin: API_ORIGIN,
505
+ ...restMeta,
506
+ },
507
+ };
508
+ }