bukesiyi-cli 0.1.2 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +24 -13
  2. package/dist/main.js +1860 -97
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -49,11 +49,13 @@ var EXIT = {
49
49
  NOT_FOUND: 10,
50
50
  INTERRUPTED: 130
51
51
  };
52
- function exitCodeForHttpStatus(status) {
53
- if (status === 401 || status === 403) return EXIT.AUTH;
54
- if (status === 404) return EXIT.NOT_FOUND;
55
- if (status === 429) return EXIT.RETRY_LATER;
56
- if (status >= 400 && status < 500) return EXIT.USAGE;
52
+ function exitCodeForHttpStatus(status4) {
53
+ if (status4 === 401 || status4 === 403) return EXIT.AUTH;
54
+ if (status4 === 402) return EXIT.FUNDS;
55
+ if (status4 === 404) return EXIT.NOT_FOUND;
56
+ if (status4 === 409) return EXIT.QUOTE_CHANGED;
57
+ if (status4 === 429) return EXIT.RETRY_LATER;
58
+ if (status4 >= 400 && status4 < 500) return EXIT.USAGE;
57
59
  return EXIT.SERVER;
58
60
  }
59
61
 
@@ -111,11 +113,11 @@ var CliError = class _CliError extends Error {
111
113
  });
112
114
  }
113
115
  };
114
- function usageError(message) {
115
- return new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${message}\u3002\u8FD0\u884C buke --help \u770B\u7528\u6CD5` });
116
+ function usageError(message2) {
117
+ return new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${message2}\u3002\u8FD0\u884C buke --help \u770B\u7528\u6CD5` });
116
118
  }
117
- function interruptedError(message = "\u5DF2\u4E2D\u65AD") {
118
- return new CliError({ code: "CLI_INTERRUPTED", exitCode: EXIT.INTERRUPTED, message });
119
+ function interruptedError(message2 = "\u5DF2\u4E2D\u65AD") {
120
+ return new CliError({ code: "CLI_INTERRUPTED", exitCode: EXIT.INTERRUPTED, message: message2 });
119
121
  }
120
122
  function isRecord(value) {
121
123
  return typeof value === "object" && value !== null;
@@ -215,8 +217,8 @@ function malformedKey() {
215
217
  message: "\u8FD9\u4E0D\u50CF\u4E00\u628A API \u5BC6\u94A5\uFF1A\u5E94\u4EE5 bk_ \u5F00\u5934\uFF0C\u4E2D\u95F4\u6CA1\u6709\u7A7A\u683C\u6216\u6362\u884C"
216
218
  });
217
219
  }
218
- function missingKey(message) {
219
- return new CliError({ code: "CLI_KEY_MISSING", exitCode: EXIT.USAGE, message });
220
+ function missingKey(message2) {
221
+ return new CliError({ code: "CLI_KEY_MISSING", exitCode: EXIT.USAGE, message: message2 });
220
222
  }
221
223
  var NO_KEY_READ = "\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF BUKE_API_KEY\uFF0C\u6216\u8005\u901A\u8FC7\u7BA1\u9053\u628A\u5BC6\u94A5\u4F20\u7ED9 buke login";
222
224
  function assertKeyFormat(key) {
@@ -239,10 +241,10 @@ function toBuffer(chunk) {
239
241
  return Buffer.from(chunk);
240
242
  }
241
243
  function readToEnd(stdin, timeoutMs, signal) {
242
- return new Promise((resolve2, reject) => {
244
+ return new Promise((resolve, reject) => {
243
245
  const state = stdin;
244
246
  if (state.readableEnded === true || state.destroyed === true) {
245
- resolve2("");
247
+ resolve("");
246
248
  return;
247
249
  }
248
250
  const chunks = [];
@@ -270,7 +272,7 @@ function readToEnd(stdin, timeoutMs, signal) {
270
272
  }
271
273
  chunks.push(piece);
272
274
  };
273
- const onEnd = () => settle(() => resolve2(Buffer.concat(chunks).toString("utf8").trim()));
275
+ const onEnd = () => settle(() => resolve(Buffer.concat(chunks).toString("utf8").trim()));
274
276
  const onError = (error) => settle(() => reject(missingKey(`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u6807\u51C6\u8F93\u5165\u51FA\u9519\uFF08${briefReason(error)}\uFF09`)));
275
277
  const timer = setTimeout(() => {
276
278
  settle(
@@ -290,7 +292,7 @@ function readToEnd(stdin, timeoutMs, signal) {
290
292
  });
291
293
  }
292
294
  function readHiddenLine(stdin, signal) {
293
- return new Promise((resolve2, reject) => {
295
+ return new Promise((resolve, reject) => {
294
296
  let typed = "";
295
297
  let escape = "none";
296
298
  let settled = false;
@@ -321,7 +323,7 @@ function readHiddenLine(stdin, signal) {
321
323
  continue;
322
324
  }
323
325
  if (char === "\r" || char === "\n") {
324
- settle(() => resolve2(typed));
326
+ settle(() => resolve(typed));
325
327
  return;
326
328
  }
327
329
  if (char === CTRL_C) {
@@ -329,7 +331,7 @@ function readHiddenLine(stdin, signal) {
329
331
  return;
330
332
  }
331
333
  if (char === CTRL_D) {
332
- settle(() => typed === "" ? reject(missingKey(NO_KEY_READ)) : resolve2(typed));
334
+ settle(() => typed === "" ? reject(missingKey(NO_KEY_READ)) : resolve(typed));
333
335
  return;
334
336
  }
335
337
  if (char === DELETE || char === BACKSPACE) {
@@ -349,7 +351,7 @@ function readHiddenLine(stdin, signal) {
349
351
  }
350
352
  if (escape === "started") escape = "carried";
351
353
  };
352
- const onEnd = () => settle(() => resolve2(typed));
354
+ const onEnd = () => settle(() => resolve(typed));
353
355
  const onError = (error) => settle(() => reject(missingKey(`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u6807\u51C6\u8F93\u5165\u51FA\u9519\uFF08${briefReason(error)}\uFF09`)));
354
356
  const onAbort = () => settle(() => reject(interruptedError(CANCELLED)));
355
357
  stdin.on("data", onData);
@@ -394,7 +396,7 @@ async function readNewKey(io) {
394
396
 
395
397
  // package.json
396
398
  var name = "bukesiyi-cli";
397
- var version = "0.1.2";
399
+ var version = "0.3.0";
398
400
 
399
401
  // src/version.ts
400
402
  var PACKAGE_NAME = name;
@@ -402,27 +404,45 @@ var VERSION = version;
402
404
 
403
405
  // src/client.ts
404
406
  var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
407
+ var DEFAULT_DOWNLOAD_TIMEOUT_MS = 12e4;
408
+ var MAX_DOWNLOAD_BYTES = 1024 * 1024 * 1024;
405
409
  var ME_PATH = "/api/open/v1/me";
406
410
  var MAX_BODY_BYTES = 1024 * 1024;
407
411
  var NOT_FOUND_HINT = "\u8FD9\u4E2A\u5730\u5740\u4E0D\u63D0\u4F9B API \u8C03\u7528\uFF1A\u53EF\u80FD\u8FD8\u6CA1\u6709\u5BF9\u5916\u5F00\u901A\uFF0C\u6216\u8005 --base-url \u5199\u9519\u4E86";
412
+ var EXIT_BY_SERVER_CODE = {
413
+ OPEN_API_INSUFFICIENT_BALANCE: EXIT.FUNDS,
414
+ OPEN_API_QUOTE_CHANGED: EXIT.QUOTE_CHANGED,
415
+ OPEN_API_QUOTE_REQUIRED: EXIT.QUOTE_CHANGED,
416
+ OPEN_API_ORDER_NOT_READY: EXIT.RETRY_LATER,
417
+ OPEN_API_QUOTA_EXCEEDED: EXIT.RETRY_LATER,
418
+ OPEN_API_ORDER_NOT_FOUND: EXIT.NOT_FOUND,
419
+ OPEN_API_PRODUCT_NOT_FOUND: EXIT.NOT_FOUND,
420
+ OPEN_API_TEMPLATE_NOT_FOUND: EXIT.NOT_FOUND,
421
+ // 「这单已交付/已失败,不能取消」也是 409,按状态码会被当成「报价变了」(6);
422
+ // 它说的是这个动作对当前状态不成立,重试也不会变,按「用法不对」(2)定。
423
+ OPEN_API_ORDER_NOT_CANCELLABLE: EXIT.USAGE
424
+ };
408
425
  function userAgent() {
409
426
  return `${PACKAGE_NAME}/${VERSION} node/${process.versions.node}`;
410
427
  }
411
428
  function isRecord2(value) {
412
429
  return typeof value === "object" && value !== null && !Array.isArray(value);
413
430
  }
414
- function retryableStatus(status) {
415
- return status === 429 || status >= 500;
431
+ function numberOrUndefined(value) {
432
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
433
+ }
434
+ function retryableStatus(status4) {
435
+ return status4 === 429 || status4 >= 500;
416
436
  }
417
- function unreadableResponse(status) {
418
- const retryable = retryableStatus(status);
437
+ function unreadableResponse(status4) {
438
+ const retryable = retryableStatus(status4);
419
439
  const advice = retryable ? "\u53EF\u80FD\u662F\u7F51\u5173\u6216\u4E2D\u95F4\u5C42\u51FA\u7684\u9519\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : "\u53EF\u80FD\u662F\u7F51\u5173\u6216\u4E2D\u95F4\u5C42\u51FA\u7684\u9519\uFF0C\u4E5F\u53EF\u80FD\u662F\u670D\u52A1\u5730\u5740\u6307\u5230\u4E86\u522B\u5904\uFF1B\u91CD\u8BD5\u591A\u534A\u6CA1\u6709\u7528\uFF0C\u8BF7\u5148\u6838\u5BF9\u670D\u52A1\u5730\u5740";
420
440
  return new CliError({
421
441
  code: "CLI_BAD_RESPONSE",
422
442
  exitCode: EXIT.SERVER,
423
443
  retryable,
424
- message: `\u670D\u52A1\u5668\u56DE\u4E86\u4E00\u4E2A\u770B\u4E0D\u61C2\u7684\u56DE\u5E94\uFF08HTTP ${status}\uFF09\uFF0C${advice}`,
425
- hints: status === 404 ? [NOT_FOUND_HINT] : []
444
+ message: `\u670D\u52A1\u5668\u56DE\u4E86\u4E00\u4E2A\u770B\u4E0D\u61C2\u7684\u56DE\u5E94\uFF08HTTP ${status4}\uFF09\uFF0C${advice}`,
445
+ hints: status4 === 404 ? [NOT_FOUND_HINT] : []
426
446
  });
427
447
  }
428
448
  function redirectTarget(response, requestUrl) {
@@ -442,11 +462,15 @@ function discardBody(response) {
442
462
  }
443
463
  }
444
464
  async function readBody(response, abandoned) {
465
+ const bytes = await readBodyBytes(response, abandoned, MAX_BODY_BYTES);
466
+ return bytes === null ? null : bytes.toString("utf8");
467
+ }
468
+ async function readBodyBytes(response, abandoned, maxBytes) {
445
469
  const body = response.body;
446
- if (body === null) return "";
470
+ if (body === null) return Buffer.alloc(0);
447
471
  if (body === void 0) {
448
472
  const text = await response.text();
449
- return Buffer.byteLength(text, "utf8") > MAX_BODY_BYTES ? null : text;
473
+ return Buffer.byteLength(text, "utf8") > maxBytes ? null : Buffer.from(text, "utf8");
450
474
  }
451
475
  const reader = body.getReader();
452
476
  const release2 = () => void reader.cancel().catch(() => void 0);
@@ -459,7 +483,7 @@ async function readBody(response, abandoned) {
459
483
  const { done, value } = await reader.read();
460
484
  if (done) break;
461
485
  size += value.byteLength;
462
- if (size > MAX_BODY_BYTES) {
486
+ if (size > maxBytes) {
463
487
  await reader.cancel().catch(() => void 0);
464
488
  return null;
465
489
  }
@@ -468,7 +492,7 @@ async function readBody(response, abandoned) {
468
492
  } finally {
469
493
  abandoned.removeEventListener("abort", release2);
470
494
  }
471
- return Buffer.concat(chunks).toString("utf8");
495
+ return Buffer.concat(chunks);
472
496
  }
473
497
  function isTimeout(error) {
474
498
  const name2 = typeof error === "object" && error !== null ? error.name : void 0;
@@ -478,9 +502,9 @@ function proxyHints(origin, deps) {
478
502
  const variable = deps.env ? envProxyFor(origin, deps.env) : null;
479
503
  return variable === null ? [] : [`\u73AF\u5883\u91CC\u5F00\u7740\u4EE3\u7406\uFF08${variable}\uFF09\uFF0C\u8FD9\u6B21\u8BF7\u6C42\u53EF\u80FD\u662F\u7ECF\u5B83\u53D1\u51FA\u7684\uFF1A\u51FA\u95EE\u9898\u7684\u4E5F\u53EF\u80FD\u662F\u4EE3\u7406`];
480
504
  }
481
- async function exchange(url, apiKey, deps, origin) {
505
+ async function exchange(url, apiKey, plan, deps, origin) {
482
506
  if (deps.signal?.aborted) throw interruptedError();
483
- const timeoutMs = deps.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
507
+ const timeoutMs = plan.timeoutMs ?? deps.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
484
508
  const controller = new AbortController();
485
509
  let timedOut = false;
486
510
  let timer;
@@ -500,15 +524,20 @@ async function exchange(url, apiKey, deps, origin) {
500
524
  };
501
525
  });
502
526
  deps.signal?.addEventListener("abort", stop, { once: true });
527
+ const headers = {
528
+ Authorization: `Bearer ${apiKey}`,
529
+ Accept: "application/json",
530
+ "User-Agent": userAgent()
531
+ };
532
+ if (plan.contentType !== void 0) headers["Content-Type"] = plan.contentType;
533
+ if (plan.idempotencyKey !== void 0) headers["Idempotency-Key"] = plan.idempotencyKey;
534
+ const asBytes = plan.asBytes === true;
503
535
  const send = deps.fetch;
504
536
  const work = (async () => {
505
537
  const response = await send(url, {
506
- method: "GET",
507
- headers: {
508
- Authorization: `Bearer ${apiKey}`,
509
- Accept: "application/json",
510
- "User-Agent": userAgent()
511
- },
538
+ method: plan.method,
539
+ headers,
540
+ ...plan.body === void 0 ? {} : { body: plan.body },
512
541
  // 跟着跳转走,密钥就会被带到一个使用者没有点头的地方。
513
542
  redirect: "manual",
514
543
  signal: controller.signal
@@ -517,6 +546,11 @@ async function exchange(url, apiKey, deps, origin) {
517
546
  discardBody(response);
518
547
  return { status: response.status, redirectedTo: redirectTarget(response, url) };
519
548
  }
549
+ if (asBytes) {
550
+ const bytes = await readBodyBytes(response, controller.signal, MAX_DOWNLOAD_BYTES);
551
+ if (bytes === null) throw unreadableResponse(response.status);
552
+ return { status: response.status, bytes, headers: response.headers };
553
+ }
520
554
  return { status: response.status, text: await readBody(response, controller.signal) };
521
555
  })();
522
556
  work.catch(() => void 0);
@@ -554,36 +588,40 @@ function parseJson(text) {
554
588
  return null;
555
589
  }
556
590
  }
557
- function serverError(status, body, apiKey) {
591
+ function serverError(status4, body, apiKey) {
558
592
  const error = isRecord2(body) ? body.error : void 0;
559
593
  if (!isRecord2(error) || typeof error.code !== "string" || typeof error.message !== "string") return null;
560
594
  const retryAfter = error.retryAfterSeconds;
561
595
  return new CliError({
562
596
  code: error.code,
563
597
  // 对面把收到的密钥抄回话里时,在这一层就抹掉。
564
- message: error.message.trim() === "" ? `\u670D\u52A1\u5668\u62D2\u7EDD\u4E86\u8FD9\u6B21\u8BF7\u6C42\uFF08HTTP ${status}\uFF09` : redactSecret(error.message, apiKey),
565
- exitCode: exitCodeForHttpStatus(status),
566
- retryable: typeof error.retryable === "boolean" ? error.retryable : retryableStatus(status),
598
+ message: error.message.trim() === "" ? `\u670D\u52A1\u5668\u62D2\u7EDD\u4E86\u8FD9\u6B21\u8BF7\u6C42\uFF08HTTP ${status4}\uFF09` : redactSecret(error.message, apiKey),
599
+ // 列进显式表的码按表定(409 在业务里分「报价变了」和「还没好」两种),其余按状态码。
600
+ exitCode: EXIT_BY_SERVER_CODE[error.code] ?? exitCodeForHttpStatus(status4),
601
+ retryable: typeof error.retryable === "boolean" ? error.retryable : retryableStatus(status4),
567
602
  traceId: typeof error.traceId === "string" ? error.traceId : null,
568
603
  retryAfterSeconds: typeof retryAfter === "number" && Number.isFinite(retryAfter) ? retryAfter : void 0,
569
- httpStatus: status,
570
- hints: status === 404 ? [NOT_FOUND_HINT] : []
604
+ httpStatus: status4,
605
+ // 那句「这个地址不提供 API 调用」只对端点级的 404 有意义:业务里的 404(单不存在、产品不存在)
606
+ // 说的是东西没找到,不是地址写错。
607
+ hints: status4 === 404 && error.code === "OPEN_API_NOT_FOUND" ? [NOT_FOUND_HINT] : []
571
608
  });
572
609
  }
573
- async function getJson(baseUrl, apiKey, path, deps) {
610
+ function redirectError(redirectedTo) {
611
+ const where = redirectedTo ? `\uFF08${redirectedTo}\uFF09` : "";
612
+ return new CliError({
613
+ code: "CLI_REDIRECT",
614
+ exitCode: EXIT.SERVER,
615
+ retryable: false,
616
+ message: `\u670D\u52A1\u5668\u628A\u8BF7\u6C42\u8F6C\u5230\u4E86\u522B\u5904${where}\uFF0C\u4E3A\u4E86\u4E0D\u628A\u5BC6\u94A5\u5E26\u8FC7\u53BB\u6CA1\u6709\u8DDF\u968F\u3002\u8BF7\u6838\u5BF9 --base-url`
617
+ });
618
+ }
619
+ async function requestJson(baseUrl, apiKey, plan, deps) {
574
620
  const origin = normalizeBaseUrl(baseUrl);
575
621
  assertKeyFormat(apiKey);
576
622
  assertReachedDirectly(origin, deps.env);
577
- const result = await exchange(`${origin}${path}`, apiKey, deps, origin);
578
- if ("redirectedTo" in result) {
579
- const where = result.redirectedTo ? `\uFF08${result.redirectedTo}\uFF09` : "";
580
- throw new CliError({
581
- code: "CLI_REDIRECT",
582
- exitCode: EXIT.SERVER,
583
- retryable: false,
584
- message: `\u670D\u52A1\u5668\u628A\u8BF7\u6C42\u8F6C\u5230\u4E86\u522B\u5904${where}\uFF0C\u4E3A\u4E86\u4E0D\u628A\u5BC6\u94A5\u5E26\u8FC7\u53BB\u6CA1\u6709\u8DDF\u968F\u3002\u8BF7\u6838\u5BF9 --base-url`
585
- });
586
- }
623
+ const result = await exchange(`${origin}${plan.path}`, apiKey, plan, deps, origin);
624
+ if ("redirectedTo" in result) throw redirectError(result.redirectedTo);
587
625
  const parsed = parseJson(result.text);
588
626
  if (result.status >= 200 && result.status < 300) {
589
627
  if (!parsed) throw unreadableResponse(result.status);
@@ -591,6 +629,357 @@ async function getJson(baseUrl, apiKey, path, deps) {
591
629
  }
592
630
  throw (parsed && serverError(result.status, parsed.value, apiKey)) ?? unreadableResponse(result.status);
593
631
  }
632
+ async function getJson(baseUrl, apiKey, path, deps) {
633
+ return requestJson(baseUrl, apiKey, { method: "GET", path }, deps);
634
+ }
635
+ async function postJson(baseUrl, apiKey, path, body, idempotencyKey, deps) {
636
+ return requestJson(
637
+ baseUrl,
638
+ apiKey,
639
+ { method: "POST", path, body: JSON.stringify(body), contentType: "application/json", idempotencyKey },
640
+ deps
641
+ );
642
+ }
643
+ async function putRaw(baseUrl, apiKey, path, bytes, contentType, idempotencyKey, deps) {
644
+ return requestJson(baseUrl, apiKey, { method: "PUT", path, body: bytes, contentType, idempotencyKey }, deps);
645
+ }
646
+ async function postRaw(baseUrl, apiKey, path, bytes, contentType, idempotencyKey, deps) {
647
+ return requestJson(baseUrl, apiKey, { method: "POST", path, body: bytes, contentType, idempotencyKey }, deps);
648
+ }
649
+ async function downloadFile(baseUrl, apiKey, path, deps) {
650
+ const origin = normalizeBaseUrl(baseUrl);
651
+ assertKeyFormat(apiKey);
652
+ assertReachedDirectly(origin, deps.env);
653
+ const result = await exchange(`${origin}${path}`, apiKey, downloadPlan(path, deps), deps, origin);
654
+ if ("redirectedTo" in result) throw redirectError(result.redirectedTo);
655
+ if (result.status < 200 || result.status >= 300) {
656
+ const text = result.bytes.toString("utf8");
657
+ const parsed = parseJson(text);
658
+ throw (parsed && serverError(result.status, parsed.value, apiKey)) ?? unreadableResponse(result.status);
659
+ }
660
+ return { bytes: result.bytes, filename: filenameFromDisposition(result.headers.get("content-disposition")) };
661
+ }
662
+ function downloadPlan(path, deps) {
663
+ return {
664
+ method: "GET",
665
+ path,
666
+ // 下载不共用 JSON 请求 15 秒的默认时限;调用方在 deps 里给了更短的,照他的。
667
+ timeoutMs: deps.requestTimeoutMs !== void 0 ? Math.max(deps.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS) : DEFAULT_DOWNLOAD_TIMEOUT_MS,
668
+ asBytes: true
669
+ };
670
+ }
671
+ function filenameFromDisposition(value) {
672
+ if (typeof value !== "string" || value === "") return null;
673
+ const star = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
674
+ if (star !== null) {
675
+ try {
676
+ const decoded = decodeURIComponent(star[1]);
677
+ if (decoded !== "") return decoded;
678
+ } catch {
679
+ }
680
+ }
681
+ const plain = /filename="?([^";]+)"?/.exec(value);
682
+ return plain !== null ? plain[1] : null;
683
+ }
684
+ function required(report) {
685
+ return (value) => {
686
+ if (typeof value !== "string" || value === "") {
687
+ report.push("\u5B57\u6BB5\u5F62\u72B6\u4E0D\u5BF9");
688
+ return "";
689
+ }
690
+ return value;
691
+ };
692
+ }
693
+ function parseBalance(body) {
694
+ const raw = isRecord2(body) ? body.balance : void 0;
695
+ if (!isRecord2(raw)) return null;
696
+ const problems = [];
697
+ const text = required(problems);
698
+ const currency = text(raw.currency);
699
+ const updatedAt = text(raw.updatedAt);
700
+ const available = numberOrUndefined(raw.available);
701
+ const held = numberOrUndefined(raw.held);
702
+ const spent = numberOrUndefined(raw.spent);
703
+ if (problems.length > 0 || available === void 0 || held === void 0 || spent === void 0) return null;
704
+ return { currency, available, held, spent, updatedAt };
705
+ }
706
+ function parseProductImages(value) {
707
+ if (!Array.isArray(value)) return null;
708
+ const images = [];
709
+ for (const item of value) {
710
+ if (!isRecord2(item)) return null;
711
+ const problems = [];
712
+ const text = required(problems);
713
+ const image = {
714
+ id: text(item.id),
715
+ angle: typeof item.angle === "string" ? item.angle : "",
716
+ url: text(item.url),
717
+ mime: text(item.mime),
718
+ bytes: numberOrUndefined(item.bytes) ?? -1,
719
+ sha256: text(item.sha256)
720
+ };
721
+ if (problems.length > 0 || image.bytes < 0) return null;
722
+ images.push(image);
723
+ }
724
+ return images;
725
+ }
726
+ function parseProduct(body) {
727
+ const product = isRecord2(body) ? body.product : void 0;
728
+ if (!isRecord2(product)) return null;
729
+ const problems = [];
730
+ const text = required(problems);
731
+ const images = parseProductImages(product.images);
732
+ const sku = typeof product.sku === "string" ? product.sku : null;
733
+ const parsed = {
734
+ id: text(product.id),
735
+ name: text(product.name),
736
+ category: text(product.category),
737
+ sku,
738
+ createdAt: text(product.createdAt),
739
+ images: images ?? []
740
+ };
741
+ if (problems.length > 0 || images === null) return null;
742
+ return parsed;
743
+ }
744
+ function parseProductLock(body) {
745
+ const lock = isRecord2(body) ? body.productLock : void 0;
746
+ if (!isRecord2(lock)) return null;
747
+ const problems = [];
748
+ const text = required(problems);
749
+ const parsed = { colorName: text(lock.colorName), silhouette: text(lock.silhouette), material: text(lock.material) };
750
+ return problems.length > 0 ? null : parsed;
751
+ }
752
+ function parseCatalogTemplate(item) {
753
+ if (!isRecord2(item)) return null;
754
+ const problems = [];
755
+ const text = required(problems);
756
+ const durationSeconds = numberOrUndefined(item.durationSeconds);
757
+ const needsList = Array.isArray(item.needs) && item.needs.every((need) => typeof need === "string") ? item.needs : null;
758
+ if (needsList === null) problems.push("needs");
759
+ const parsed = {
760
+ id: text(item.id),
761
+ title: text(item.title),
762
+ tier: text(item.tier),
763
+ summary: typeof item.summary === "string" ? item.summary : "",
764
+ durationSeconds: durationSeconds ?? -1,
765
+ needs: needsList ?? [],
766
+ defaultProps: isRecord2(item.defaultProps) ? item.defaultProps : {}
767
+ };
768
+ if (problems.length > 0 || durationSeconds === void 0) return null;
769
+ return parsed;
770
+ }
771
+ function parseCatalog(body) {
772
+ const catalog2 = isRecord2(body) ? body.catalog : void 0;
773
+ if (!isRecord2(catalog2)) return null;
774
+ const category = isRecord2(catalog2.category) ? catalog2.category : null;
775
+ const templates = Array.isArray(catalog2.templates) ? catalog2.templates : null;
776
+ if (!category || templates === null) return null;
777
+ const problems = [];
778
+ const text = required(problems);
779
+ const parsedTemplates = [];
780
+ for (const item of templates) {
781
+ const parsed2 = parseCatalogTemplate(item);
782
+ if (parsed2 === null) problems.push("template");
783
+ else parsedTemplates.push(parsed2);
784
+ }
785
+ const parsed = {
786
+ category: { id: text(category.id), name: text(category.name), badge: text(category.badge) },
787
+ templates: parsedTemplates
788
+ };
789
+ return problems.length > 0 ? null : parsed;
790
+ }
791
+ function parseUpload(body) {
792
+ if (!isRecord2(body)) return null;
793
+ const problems = [];
794
+ const text = required(problems);
795
+ const bytes = numberOrUndefined(body.bytes);
796
+ const parsed = { url: text(body.url), sha256: text(body.sha256), bytes: bytes ?? -1 };
797
+ if (problems.length > 0 || bytes === void 0) return null;
798
+ return parsed;
799
+ }
800
+ function parseQuoteValue(value) {
801
+ if (!isRecord2(value)) return null;
802
+ const problems = [];
803
+ const text = required(problems);
804
+ const numbers = ["durationMilliseconds", "memberMilliseconds", "excessMilliseconds", "credits"].map(
805
+ (field) => numberOrUndefined(value[field])
806
+ );
807
+ if (problems.length > 0 || numbers.some((entry) => entry === void 0)) return null;
808
+ const [durationMilliseconds, memberMilliseconds, excessMilliseconds, credits] = numbers;
809
+ return {
810
+ quoteHash: text(value.quoteHash),
811
+ templateId: text(value.templateId),
812
+ durationMilliseconds,
813
+ memberMilliseconds,
814
+ excessMilliseconds,
815
+ credits
816
+ };
817
+ }
818
+ function parseQuote(body) {
819
+ return parseQuoteValue(isRecord2(body) ? body.quote : void 0);
820
+ }
821
+ function parseOrder(body) {
822
+ const order2 = isRecord2(body) ? body.order : void 0;
823
+ if (!isRecord2(order2)) return null;
824
+ const problems = [];
825
+ const text = required(problems);
826
+ const durationSeconds = numberOrUndefined(order2.durationSeconds);
827
+ const bytes = numberOrUndefined(order2.bytes);
828
+ const quoteValue = order2.quote === void 0 ? void 0 : parseQuoteValue(order2.quote);
829
+ if (order2.quote !== void 0 && quoteValue === null) problems.push("quote");
830
+ const parsed = {
831
+ id: text(order2.id),
832
+ status: text(order2.status),
833
+ templateId: text(order2.templateId),
834
+ templateTitle: text(order2.templateTitle),
835
+ createdAt: text(order2.createdAt),
836
+ ...typeof order2.finishedAt === "string" ? { finishedAt: order2.finishedAt } : {},
837
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
838
+ ...typeof order2.error === "string" ? { error: order2.error } : {},
839
+ ...typeof order2.videoUrl === "string" ? { videoUrl: order2.videoUrl } : {},
840
+ ...bytes !== void 0 ? { bytes } : {},
841
+ ...quoteValue !== void 0 && quoteValue !== null ? { quote: quoteValue } : {}
842
+ };
843
+ return problems.length > 0 ? null : parsed;
844
+ }
845
+ function parseDramaMoney(value) {
846
+ if (!isRecord2(value)) return null;
847
+ const problems = [];
848
+ const text = required(problems);
849
+ const numbers = ["held", "settled", "released", "refunded"].map((field) => numberOrUndefined(value[field]));
850
+ if (numbers.some((entry) => entry === void 0)) return null;
851
+ const [held, settled, released, refunded] = numbers;
852
+ const currency = text(value.currency);
853
+ return problems.length > 0 ? null : { currency, held, settled, released, refunded };
854
+ }
855
+ function parseDramaGap(value) {
856
+ if (!isRecord2(value)) return null;
857
+ const problems = [];
858
+ const text = required(problems);
859
+ const sceneNos = Array.isArray(value.sceneNos) && value.sceneNos.every((no) => numberOrUndefined(no) !== void 0) ? value.sceneNos : null;
860
+ if (sceneNos === null) problems.push("sceneNos");
861
+ const label = typeof value.label === "string" ? value.label : "";
862
+ const parsed = { kind: text(value.kind), key: text(value.key), label, sceneNos: sceneNos ?? [] };
863
+ return problems.length > 0 ? null : parsed;
864
+ }
865
+ function parseDramaOrder(body) {
866
+ const order2 = isRecord2(body) ? body.order : void 0;
867
+ if (!isRecord2(order2)) return null;
868
+ const problems = [];
869
+ const text = required(problems);
870
+ const money = parseDramaMoney(order2.money);
871
+ if (money === null) problems.push("money");
872
+ const actionsValue = Array.isArray(order2.nextActions) ? order2.nextActions : null;
873
+ const actions = actionsValue === null ? [] : actionsValue.filter((action) => typeof action === "string");
874
+ if (actionsValue === null || actions.length !== actionsValue.length) problems.push("nextActions");
875
+ const gapsValue = order2.gaps === void 0 ? void 0 : Array.isArray(order2.gaps) ? order2.gaps : null;
876
+ if (order2.gaps !== void 0 && gapsValue === null) problems.push("gaps");
877
+ const gaps = gapsValue === void 0 || gapsValue === null ? void 0 : gapsValue.map((item) => {
878
+ const parsed2 = parseDramaGap(item);
879
+ if (parsed2 === null) problems.push("gap");
880
+ return parsed2;
881
+ });
882
+ if (gaps !== void 0 && gaps.some((gap) => gap === null)) problems.push("gap");
883
+ const parsed = {
884
+ id: text(order2.id),
885
+ projectId: text(order2.projectId),
886
+ status: text(order2.status),
887
+ productId: text(order2.productId),
888
+ genreId: text(order2.genreId),
889
+ durationKey: text(order2.durationKey),
890
+ videoModel: typeof order2.videoModel === "string" ? order2.videoModel : null,
891
+ resolution: typeof order2.resolution === "string" ? order2.resolution : null,
892
+ money: money ?? { currency: "", held: 0, settled: 0, released: 0, refunded: 0 },
893
+ nextActions: actions,
894
+ ...typeof order2.reason === "string" ? { reason: order2.reason } : {},
895
+ ...gaps === void 0 ? {} : { gaps },
896
+ createdAt: text(order2.createdAt),
897
+ updatedAt: text(order2.updatedAt)
898
+ };
899
+ return problems.length > 0 ? null : parsed;
900
+ }
901
+ function parseDramaQuote(body) {
902
+ if (!isRecord2(body)) return null;
903
+ const problems = [];
904
+ const text = required(problems);
905
+ const quote3 = isRecord2(body.quote) ? body.quote : null;
906
+ if (quote3 === null) problems.push("quote");
907
+ const totalCredits = quote3 === null ? void 0 : numberOrUndefined(quote3.totalCredits);
908
+ const shotCount = quote3 === null ? void 0 : numberOrUndefined(quote3.shotCount);
909
+ if (totalCredits === void 0 || shotCount === void 0) problems.push("quote");
910
+ const shotCreditCost = quote3 === null ? void 0 : numberOrUndefined(quote3.shotCreditCost);
911
+ const videoModel = quote3 !== null && typeof quote3.videoModel === "string" ? quote3.videoModel : void 0;
912
+ const resolution = quote3 !== null && typeof quote3.resolution === "string" ? quote3.resolution : void 0;
913
+ const parsed = {
914
+ quoteHash: text(body.quoteHash),
915
+ sufficient: typeof body.sufficient === "boolean" ? body.sufficient : null,
916
+ totalCredits: totalCredits ?? -1,
917
+ shotCount: shotCount ?? -1,
918
+ ...shotCreditCost !== void 0 ? { shotCreditCost } : {},
919
+ ...videoModel !== void 0 ? { videoModel } : {},
920
+ ...resolution !== void 0 ? { resolution } : {}
921
+ };
922
+ return problems.length > 0 ? null : parsed;
923
+ }
924
+ function parseRunAction(value) {
925
+ if (!isRecord2(value) || typeof value.type !== "string" || value.type === "") return null;
926
+ return {
927
+ type: value.type,
928
+ ...numberOrUndefined(value.credits) !== void 0 ? { credits: numberOrUndefined(value.credits) } : {},
929
+ ...typeof value.confirmKind === "string" ? { confirmKind: value.confirmKind } : {},
930
+ ...value.confirmation !== void 0 ? { confirmation: value.confirmation } : {}
931
+ };
932
+ }
933
+ function parseRun(body) {
934
+ const run2 = isRecord2(body) ? body.run : void 0;
935
+ if (!isRecord2(run2)) return null;
936
+ const problems = [];
937
+ const text = required(problems);
938
+ const version2 = numberOrUndefined(run2.version);
939
+ const pollAfterMs = numberOrUndefined(run2.pollAfterMs);
940
+ const product = isRecord2(run2.product) ? run2.product : null;
941
+ if (!product) problems.push("product");
942
+ const actions = Array.isArray(run2.nextActions) ? run2.nextActions : null;
943
+ if (actions === null) problems.push("nextActions");
944
+ const messagesValue = Array.isArray(run2.messages) ? run2.messages : null;
945
+ if (messagesValue === null) problems.push("messages");
946
+ const messages = messagesValue === null ? [] : messagesValue.map((message2) => {
947
+ if (!isRecord2(message2) || typeof message2.text !== "string") {
948
+ problems.push("message");
949
+ return { role: "", text: "" };
950
+ }
951
+ return { role: typeof message2.role === "string" ? message2.role : "", text: message2.text };
952
+ });
953
+ const failureValue = isRecord2(run2.failure) ? run2.failure : null;
954
+ const failure = failureValue ? {
955
+ code: text(failureValue.code),
956
+ title: text(failureValue.title),
957
+ detail: typeof failureValue.detail === "string" ? failureValue.detail : "",
958
+ retryable: failureValue.retryable === true
959
+ } : null;
960
+ const currentProductionCredits = numberOrUndefined(run2.currentProductionCredits);
961
+ const parsed = {
962
+ rootRunId: text(run2.rootRunId),
963
+ version: version2 ?? -1,
964
+ status: text(run2.status),
965
+ stage: text(run2.stage),
966
+ label: text(run2.label),
967
+ updatedAt: text(run2.updatedAt),
968
+ pollAfterMs: pollAfterMs ?? null,
969
+ productionConfirmed: run2.productionConfirmed === true,
970
+ ...currentProductionCredits !== void 0 ? { currentProductionCredits } : {},
971
+ product: product ? { id: text(product.id), name: text(product.name) } : { id: "", name: "" },
972
+ nextActions: actions === null ? [] : actions.map((action) => {
973
+ const parsed2 = parseRunAction(action);
974
+ if (parsed2 === null) problems.push("action");
975
+ return parsed2 ?? { type: "" };
976
+ }),
977
+ messages,
978
+ failure
979
+ };
980
+ if (problems.length > 0 || version2 === void 0) return null;
981
+ return parsed;
982
+ }
594
983
  function parseMe(body) {
595
984
  if (!isRecord2(body)) return null;
596
985
  const { key, account, workspace } = body;
@@ -613,6 +1002,274 @@ async function getMe(baseUrl, apiKey, deps) {
613
1002
  if (!me) throw unreadableResponse(reply.status);
614
1003
  return me;
615
1004
  }
1005
+ async function getBalance(baseUrl, apiKey, deps) {
1006
+ const reply = await getJson(baseUrl, apiKey, "/api/open/v1/balance", deps);
1007
+ const balance2 = parseBalance(reply.body);
1008
+ if (!balance2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1009
+ return { view: balance2, raw: reply.body };
1010
+ }
1011
+ async function createProduct(baseUrl, apiKey, name2, category, idempotencyKey, deps) {
1012
+ const reply = await postJson(
1013
+ baseUrl,
1014
+ apiKey,
1015
+ "/api/open/v1/products",
1016
+ { name: name2, ...category === void 0 ? {} : { category } },
1017
+ idempotencyKey,
1018
+ deps
1019
+ );
1020
+ const product = parseProduct(reply.body);
1021
+ if (!product || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1022
+ return { view: product, raw: reply.body, replayed: reply.status === 200 };
1023
+ }
1024
+ async function putProductImage(baseUrl, apiKey, productId, angle, bytes, contentType, idempotencyKey, deps) {
1025
+ const reply = await putRaw(
1026
+ baseUrl,
1027
+ apiKey,
1028
+ `/api/open/v1/products/${encodeURIComponent(productId)}/images?angle=${angle}`,
1029
+ bytes,
1030
+ contentType,
1031
+ idempotencyKey,
1032
+ deps
1033
+ );
1034
+ const product = parseProduct(reply.body);
1035
+ if (!product || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1036
+ return { view: product, raw: reply.body, replayed: reply.status === 200 };
1037
+ }
1038
+ async function confirmProductLock(baseUrl, apiKey, productId, lock, idempotencyKey, deps) {
1039
+ const reply = await postJson(
1040
+ baseUrl,
1041
+ apiKey,
1042
+ `/api/open/v1/products/${encodeURIComponent(productId)}/lock/confirm`,
1043
+ { productLock: lock },
1044
+ idempotencyKey,
1045
+ deps
1046
+ );
1047
+ const productLock = parseProductLock(reply.body);
1048
+ const product = parseProduct(reply.body);
1049
+ if (!productLock || !product || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1050
+ return { view: { productLock, product }, raw: reply.body };
1051
+ }
1052
+ async function getTemplateShortCatalog(baseUrl, apiKey, deps) {
1053
+ const reply = await getJson(baseUrl, apiKey, "/api/open/v1/template-shorts", deps);
1054
+ const catalog2 = parseCatalog(reply.body);
1055
+ if (!catalog2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1056
+ return { view: catalog2, raw: reply.body };
1057
+ }
1058
+ async function uploadTemplateShortAsset(baseUrl, apiKey, bytes, contentType, idempotencyKey, deps) {
1059
+ const reply = await postRaw(baseUrl, apiKey, "/api/open/v1/template-shorts/uploads", bytes, contentType, idempotencyKey, deps);
1060
+ const upload2 = parseUpload(reply.body);
1061
+ if (!upload2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1062
+ return { view: upload2, raw: reply.body };
1063
+ }
1064
+ async function quoteTemplateShort(baseUrl, apiKey, templateId, props, idempotencyKey, deps) {
1065
+ const reply = await postJson(
1066
+ baseUrl,
1067
+ apiKey,
1068
+ "/api/open/v1/template-shorts/quote",
1069
+ { templateId, props },
1070
+ idempotencyKey,
1071
+ deps
1072
+ );
1073
+ const quote3 = parseQuote(reply.body);
1074
+ if (!quote3 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1075
+ return { view: quote3, raw: reply.body };
1076
+ }
1077
+ async function createTemplateShortOrder(baseUrl, apiKey, templateId, props, expectedQuoteHash, idempotencyKey, deps) {
1078
+ return orderReplyOf(
1079
+ await postJson(
1080
+ baseUrl,
1081
+ apiKey,
1082
+ "/api/open/v1/template-shorts/orders",
1083
+ { templateId, props, ...expectedQuoteHash === void 0 ? {} : { expectedQuoteHash } },
1084
+ idempotencyKey,
1085
+ deps
1086
+ )
1087
+ );
1088
+ }
1089
+ async function getTemplateShortOrder(baseUrl, apiKey, orderId, deps) {
1090
+ return orderReplyOf(
1091
+ await getJson(baseUrl, apiKey, `/api/open/v1/template-shorts/orders/${encodeURIComponent(orderId)}`, deps)
1092
+ );
1093
+ }
1094
+ async function cancelTemplateShortOrder(baseUrl, apiKey, orderId, idempotencyKey, deps) {
1095
+ return orderReplyOf(
1096
+ await postJson(
1097
+ baseUrl,
1098
+ apiKey,
1099
+ `/api/open/v1/template-shorts/orders/${encodeURIComponent(orderId)}/cancel`,
1100
+ {},
1101
+ idempotencyKey,
1102
+ deps
1103
+ )
1104
+ );
1105
+ }
1106
+ async function orderReplyOf(reply) {
1107
+ const order2 = parseOrder(reply.body);
1108
+ if (!order2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1109
+ return { view: order2, raw: reply.body };
1110
+ }
1111
+ async function downloadTemplateShort(baseUrl, apiKey, orderId, deps) {
1112
+ return downloadFile(baseUrl, apiKey, `/api/open/v1/template-shorts/orders/${encodeURIComponent(orderId)}/download`, deps);
1113
+ }
1114
+ async function runReplyOf(reply) {
1115
+ const run2 = parseRun(reply.body);
1116
+ if (!run2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1117
+ return { view: run2, raw: reply.body };
1118
+ }
1119
+ async function createVideoRun(baseUrl, apiKey, body, idempotencyKey, deps) {
1120
+ return runReplyOf(await postJson(baseUrl, apiKey, "/api/open/v1/videos", body, idempotencyKey, deps));
1121
+ }
1122
+ async function getVideoRun(baseUrl, apiKey, runId, deps) {
1123
+ return runReplyOf(await getJson(baseUrl, apiKey, `/api/open/v1/videos/${encodeURIComponent(runId)}`, deps));
1124
+ }
1125
+ async function postVideoMessage(baseUrl, apiKey, runId, text, idempotencyKey, deps) {
1126
+ return runReplyOf(
1127
+ await postJson(
1128
+ baseUrl,
1129
+ apiKey,
1130
+ `/api/open/v1/videos/${encodeURIComponent(runId)}/messages`,
1131
+ { text },
1132
+ idempotencyKey,
1133
+ deps
1134
+ )
1135
+ );
1136
+ }
1137
+ async function confirmVideoProduction(baseUrl, apiKey, runId, expectedVersion, confirmation, idempotencyKey, deps) {
1138
+ return runReplyOf(
1139
+ await postJson(
1140
+ baseUrl,
1141
+ apiKey,
1142
+ `/api/open/v1/videos/${encodeURIComponent(runId)}/confirm`,
1143
+ { expectedVersion, confirmation },
1144
+ idempotencyKey,
1145
+ deps
1146
+ )
1147
+ );
1148
+ }
1149
+ async function downloadVideo(baseUrl, apiKey, runId, deps) {
1150
+ return downloadFile(baseUrl, apiKey, `/api/open/v1/videos/${encodeURIComponent(runId)}/download`, deps);
1151
+ }
1152
+ async function createDramaOrder(baseUrl, apiKey, body, idempotencyKey, deps) {
1153
+ const reply = await postJson(baseUrl, apiKey, "/api/open/v1/drama/orders", body, idempotencyKey, deps);
1154
+ const view = parseDramaOrder(reply.body);
1155
+ if (!view || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1156
+ return { view, raw: reply.body, replayed: reply.status === 200 };
1157
+ }
1158
+ async function getDramaOrder(baseUrl, apiKey, orderId, deps) {
1159
+ const reply = await getJson(baseUrl, apiKey, `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}`, deps);
1160
+ const view = parseDramaOrder(reply.body);
1161
+ if (!view || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1162
+ return { view, raw: reply.body, replayed: false };
1163
+ }
1164
+ async function getDramaQuote(baseUrl, apiKey, orderId, deps) {
1165
+ const reply = await getJson(baseUrl, apiKey, `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/quote`, deps);
1166
+ const view = parseDramaQuote(reply.body);
1167
+ if (!view || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1168
+ return { view, raw: reply.body };
1169
+ }
1170
+ async function confirmDramaOrder(baseUrl, apiKey, orderId, expectedQuoteHash, idempotencyKey, deps) {
1171
+ const reply = await postJson(
1172
+ baseUrl,
1173
+ apiKey,
1174
+ `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/confirm`,
1175
+ { expectedQuoteHash },
1176
+ idempotencyKey,
1177
+ deps
1178
+ );
1179
+ const view = parseDramaOrder(reply.body);
1180
+ const started = isRecord2(reply.body) ? reply.body.productionStarted : void 0;
1181
+ if (!view || !isRecord2(reply.body) || typeof started !== "boolean") throw unreadableResponse(reply.status);
1182
+ return { view, productionStarted: started, raw: reply.body };
1183
+ }
1184
+ async function cancelDramaOrder(baseUrl, apiKey, orderId, idempotencyKey, deps) {
1185
+ const reply = await postJson(
1186
+ baseUrl,
1187
+ apiKey,
1188
+ `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/cancel`,
1189
+ {},
1190
+ idempotencyKey,
1191
+ deps
1192
+ );
1193
+ const view = parseDramaOrder(reply.body);
1194
+ const canceled = isRecord2(reply.body) ? reply.body.canceled : void 0;
1195
+ if (!view || !isRecord2(reply.body) || typeof canceled !== "boolean") throw unreadableResponse(reply.status);
1196
+ return { view, canceled, raw: reply.body };
1197
+ }
1198
+ async function downloadDramaFilm(baseUrl, apiKey, orderId, deps) {
1199
+ return downloadFile(baseUrl, apiKey, `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/download`, deps);
1200
+ }
1201
+ var DEFAULT_WAIT_TIMEOUT_MS = 6e5;
1202
+ var FIRST_POLL_INTERVAL_MS = 3e3;
1203
+ var MAX_POLL_INTERVAL_MS = 12e3;
1204
+ var ORDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
1205
+ var RUN_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
1206
+ var RUN_WAITING_FOR_USER_STATUSES = /* @__PURE__ */ new Set(["needs_input", "awaiting_reply", "awaiting_confirmation"]);
1207
+ var DRAMA_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["delivered", "failed", "canceled", "partial"]);
1208
+ var DRAMA_WAITING_FOR_USER_STATUSES = /* @__PURE__ */ new Set(["needs_attention"]);
1209
+ async function awaitTemplateShortOrder(baseUrl, apiKey, start, deps, wait) {
1210
+ let current = start;
1211
+ if (ORDER_TERMINAL_STATUSES.has(current.view.status)) return { value: current, timedOut: false };
1212
+ const deadline = wait.now().getTime() + wait.timeoutMs;
1213
+ let interval = FIRST_POLL_INTERVAL_MS;
1214
+ let said = current.view.status;
1215
+ for (; ; ) {
1216
+ const remaining = deadline - wait.now().getTime();
1217
+ if (remaining <= 0) return { value: current, timedOut: true };
1218
+ await wait.sleep(Math.max(Math.min(interval, remaining), 1));
1219
+ interval = Math.min(Math.ceil(interval * 1.5), MAX_POLL_INTERVAL_MS);
1220
+ current = await getTemplateShortOrder(baseUrl, apiKey, current.view.id, deps);
1221
+ if (current.view.status !== said) {
1222
+ wait.note?.(`\u72B6\u6001\u53D8\u6210 ${current.view.status}`);
1223
+ said = current.view.status;
1224
+ }
1225
+ if (ORDER_TERMINAL_STATUSES.has(current.view.status)) return { value: current, timedOut: false };
1226
+ }
1227
+ }
1228
+ async function awaitVideoRun(baseUrl, apiKey, start, deps, wait) {
1229
+ let current = start;
1230
+ if (runStopped(current.view.status)) return { value: current, timedOut: false };
1231
+ const deadline = wait.now().getTime() + wait.timeoutMs;
1232
+ let interval = FIRST_POLL_INTERVAL_MS;
1233
+ let said = current.view.status;
1234
+ for (; ; ) {
1235
+ const remaining = deadline - wait.now().getTime();
1236
+ if (remaining <= 0) return { value: current, timedOut: true };
1237
+ const preferred = current.view.pollAfterMs !== null && current.view.pollAfterMs > 0 ? current.view.pollAfterMs : interval;
1238
+ await wait.sleep(Math.max(Math.min(preferred, remaining), 1));
1239
+ interval = Math.min(Math.ceil(interval * 1.5), MAX_POLL_INTERVAL_MS);
1240
+ current = await getVideoRun(baseUrl, apiKey, current.view.rootRunId, deps);
1241
+ if (current.view.status !== said) {
1242
+ wait.note?.(`\u72B6\u6001\u53D8\u6210 ${current.view.status}`);
1243
+ said = current.view.status;
1244
+ }
1245
+ if (runStopped(current.view.status)) return { value: current, timedOut: false };
1246
+ }
1247
+ }
1248
+ function runStopped(status4) {
1249
+ return RUN_TERMINAL_STATUSES.has(status4) || RUN_WAITING_FOR_USER_STATUSES.has(status4);
1250
+ }
1251
+ async function awaitDramaOrder(baseUrl, apiKey, start, deps, wait) {
1252
+ let current = start;
1253
+ if (dramaStopped(current.view.status)) return { value: current, timedOut: false };
1254
+ const deadline = wait.now().getTime() + wait.timeoutMs;
1255
+ let interval = FIRST_POLL_INTERVAL_MS;
1256
+ let said = current.view.status;
1257
+ for (; ; ) {
1258
+ const remaining = deadline - wait.now().getTime();
1259
+ if (remaining <= 0) return { value: current, timedOut: true };
1260
+ await wait.sleep(Math.max(Math.min(interval, remaining), 1));
1261
+ interval = Math.min(Math.ceil(interval * 1.5), MAX_POLL_INTERVAL_MS);
1262
+ current = await getDramaOrder(baseUrl, apiKey, current.view.id, deps);
1263
+ if (current.view.status !== said) {
1264
+ wait.note?.(`\u72B6\u6001\u53D8\u6210 ${current.view.status}`);
1265
+ said = current.view.status;
1266
+ }
1267
+ if (dramaStopped(current.view.status)) return { value: current, timedOut: false };
1268
+ }
1269
+ }
1270
+ function dramaStopped(status4) {
1271
+ return DRAMA_TERMINAL_STATUSES.has(status4) || DRAMA_WAITING_FOR_USER_STATUSES.has(status4);
1272
+ }
616
1273
 
617
1274
  // src/credentials.ts
618
1275
  import { randomBytes } from "node:crypto";
@@ -636,8 +1293,8 @@ function nonEmpty3(value) {
636
1293
  function absoluteOnly(value) {
637
1294
  return value !== void 0 && isAbsolute(value) ? value : void 0;
638
1295
  }
639
- function configDirError(message) {
640
- return new CliError({ code: "CLI_CONFIG_DIR_INVALID", exitCode: EXIT.USAGE, message });
1296
+ function configDirError(message2) {
1297
+ return new CliError({ code: "CLI_CONFIG_DIR_INVALID", exitCode: EXIT.USAGE, message: message2 });
641
1298
  }
642
1299
  function expandHome(value, io) {
643
1300
  if (value === "~") return io.homedir();
@@ -865,6 +1522,601 @@ async function deleteCredentials(io, warn = SILENT) {
865
1522
  return removed;
866
1523
  }
867
1524
 
1525
+ // src/resolve.ts
1526
+ async function resolveApiKey({ io, out, baseUrl: flag }) {
1527
+ const explicit = explicitBaseUrl(flag, io.env);
1528
+ const fromEnv = keyFromEnv(io.env);
1529
+ if (fromEnv !== null) {
1530
+ assertKeyFormat(fromEnv);
1531
+ out.protect(fromEnv);
1532
+ return { apiKey: fromEnv, baseUrl: explicit ?? DEFAULT_BASE_URL, source: "env" };
1533
+ }
1534
+ const stored = await readCredentials(io, (line) => out.notice(line));
1535
+ if (!stored) {
1536
+ throw new CliError({
1537
+ code: "CLI_NOT_LOGGED_IN",
1538
+ exitCode: EXIT.AUTH,
1539
+ message: "\u8FD8\u6CA1\u6709\u767B\u5F55\u3002\u5148\u8FD0\u884C buke login\uFF0C\u6216\u8005\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF BUKE_API_KEY"
1540
+ });
1541
+ }
1542
+ out.protect(stored.apiKey);
1543
+ if (explicit !== null && explicit !== stored.baseUrl) {
1544
+ throw new CliError({
1545
+ code: "CLI_BASE_URL_MISMATCH",
1546
+ exitCode: EXIT.USAGE,
1547
+ message: `\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5\u662F\u7ED9 ${stored.baseUrl} \u7528\u7684\uFF0C\u4E0D\u4F1A\u53D1\u5230 ${explicit}\u3002\u8981\u6362\u5730\u5740\u8BF7\u91CD\u65B0\u8FD0\u884C buke login --base-url ${explicit}`
1548
+ });
1549
+ }
1550
+ return { apiKey: stored.apiKey, baseUrl: stored.baseUrl, source: "file", credentialsPath: credentialsPath(io) };
1551
+ }
1552
+ function keySourceLine(resolved) {
1553
+ return resolved.source === "env" ? "\u5BC6\u94A5\u6765\u81EA\uFF1A\u73AF\u5883\u53D8\u91CF BUKE_API_KEY" : `\u5BC6\u94A5\u6765\u81EA\uFF1A${resolved.credentialsPath}`;
1554
+ }
1555
+
1556
+ // src/commands/balance.ts
1557
+ async function balance(context) {
1558
+ const { io, out } = context;
1559
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1560
+ const { view, raw } = await getBalance(baseUrl, apiKey, io);
1561
+ out.result(
1562
+ [
1563
+ `\u53EF\u7528\u4F59\u989D\uFF1A${view.available} ${singleLine(view.currency)}`,
1564
+ `\u51BB\u7ED3\u4E2D\uFF1A${view.held} ${singleLine(view.currency)}`,
1565
+ `\u5DF2\u652F\u51FA\uFF1A${view.spent} ${singleLine(view.currency)}`,
1566
+ `\u66F4\u65B0\u65F6\u95F4\uFF1A${singleLine(view.updatedAt)}`
1567
+ ],
1568
+ { balance: raw.balance }
1569
+ );
1570
+ }
1571
+
1572
+ // src/commands/shorts.ts
1573
+ import { createHash } from "node:crypto";
1574
+ import { writeFile } from "node:fs/promises";
1575
+
1576
+ // src/commands/shared.ts
1577
+ import { randomUUID } from "node:crypto";
1578
+ import { readFile } from "node:fs/promises";
1579
+ var MAX_PROPS_BYTES = 1024 * 1024;
1580
+ var MAX_IMAGE_BYTES = 64 * 1024 * 1024;
1581
+ function idempotencyKeyOf(values) {
1582
+ const supplied = values["idempotency-key"];
1583
+ if (typeof supplied === "string" && supplied.trim() !== "") return supplied.trim();
1584
+ return randomUUID();
1585
+ }
1586
+ function textOption(values, name2) {
1587
+ const value = values[name2];
1588
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
1589
+ }
1590
+ function timeoutSecondsOf(values, fallbackSeconds) {
1591
+ const raw = values.timeout;
1592
+ if (raw === void 0) return fallbackSeconds;
1593
+ if (typeof raw !== "string") return fallbackSeconds;
1594
+ const parsed = Number(raw);
1595
+ if (!Number.isInteger(parsed) || parsed <= 0) {
1596
+ throw usageError(`--timeout \u8981\u662F\u6B63\u6574\u6570\u79D2\u6570\uFF0C\u5199\u7684\u662F ${raw}`);
1597
+ }
1598
+ return parsed;
1599
+ }
1600
+ async function readPropsArgument(io, ref, what) {
1601
+ if (ref === void 0) {
1602
+ throw usageError("\u8981\u7ED9 --props\uFF1A\u4E00\u4E2A JSON \u6587\u4EF6\uFF0C\u6216 - \u8868\u793A\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB");
1603
+ }
1604
+ let text;
1605
+ if (ref === "-") {
1606
+ text = await readStdinAll(io, MAX_PROPS_BYTES, what);
1607
+ } else {
1608
+ let bytes;
1609
+ try {
1610
+ bytes = await readFile(ref);
1611
+ } catch (error) {
1612
+ throw new CliError({
1613
+ code: "CLI_USAGE",
1614
+ exitCode: EXIT.USAGE,
1615
+ message: `${what}\u6587\u4EF6\u8BFB\u4E0D\u4E86\uFF1A${ref}\uFF08${briefReason(error)}\uFF09`
1616
+ });
1617
+ }
1618
+ if (bytes.byteLength > MAX_PROPS_BYTES) {
1619
+ throw new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${what}\u6587\u4EF6\u592A\u5927\uFF08\u8D85\u8FC7 1 MiB\uFF09\uFF1A${ref}` });
1620
+ }
1621
+ text = bytes.toString("utf8");
1622
+ }
1623
+ let parsed;
1624
+ try {
1625
+ parsed = JSON.parse(text);
1626
+ } catch {
1627
+ throw usageError(`${what}\u4E0D\u662F\u5408\u6CD5\u7684 JSON`);
1628
+ }
1629
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1630
+ throw usageError(`${what}\u5E94\u662F\u4E00\u4E2A JSON \u5BF9\u8C61`);
1631
+ }
1632
+ return parsed;
1633
+ }
1634
+ async function readStdinAll(io, maxBytes, what) {
1635
+ const stdin = io.stdin;
1636
+ if (stdin.readableEnded === true) return "";
1637
+ const chunks = [];
1638
+ let size = 0;
1639
+ await new Promise((resolve, reject) => {
1640
+ const toBuffer2 = (chunk) => typeof chunk === "string" ? Buffer.from(chunk, "utf8") : Buffer.from(chunk);
1641
+ const onData = (chunk) => {
1642
+ const piece = toBuffer2(chunk);
1643
+ size += piece.byteLength;
1644
+ if (size > maxBytes) {
1645
+ cleanup();
1646
+ reject(usageError(`${what}\u592A\u5927\uFF08\u8D85\u8FC7 1 MiB\uFF09`));
1647
+ return;
1648
+ }
1649
+ chunks.push(piece);
1650
+ };
1651
+ const onEnd = () => {
1652
+ cleanup();
1653
+ resolve();
1654
+ };
1655
+ const onError = (error) => {
1656
+ cleanup();
1657
+ reject(new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `\u6807\u51C6\u8F93\u5165\u8BFB\u4E0D\u4E86\uFF1A${briefReason(error)}` }));
1658
+ };
1659
+ const cleanup = () => {
1660
+ stdin.removeListener("data", onData);
1661
+ stdin.removeListener("end", onEnd);
1662
+ stdin.removeListener("error", onError);
1663
+ stdin.pause();
1664
+ };
1665
+ stdin.on("data", onData);
1666
+ stdin.on("end", onEnd);
1667
+ stdin.on("error", onError);
1668
+ stdin.resume();
1669
+ });
1670
+ return Buffer.concat(chunks).toString("utf8");
1671
+ }
1672
+ async function readImageFile(path) {
1673
+ let bytes;
1674
+ try {
1675
+ bytes = await readFile(path);
1676
+ } catch (error) {
1677
+ throw new CliError({
1678
+ code: "CLI_USAGE",
1679
+ exitCode: EXIT.USAGE,
1680
+ message: `\u56FE\u7247\u6587\u4EF6\u8BFB\u4E0D\u4E86\uFF1A${path}\uFF08${briefReason(error)}\uFF09`
1681
+ });
1682
+ }
1683
+ if (bytes.byteLength > MAX_IMAGE_BYTES) {
1684
+ throw new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `\u56FE\u7247\u592A\u5927\uFF08\u8D85\u8FC7 64 MiB\uFF09\uFF1A${path}` });
1685
+ }
1686
+ return { bytes, contentType: sniffImageContentType(bytes) };
1687
+ }
1688
+ function sniffImageContentType(bytes) {
1689
+ if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
1690
+ if (bytes.length >= 4 && bytes.subarray(0, 4).toString("hex") === "89504e47") return "image/png";
1691
+ if (bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") {
1692
+ return "image/webp";
1693
+ }
1694
+ return void 0;
1695
+ }
1696
+
1697
+ // src/commands/shorts.ts
1698
+ var SUBCOMMANDS = "catalog\u3001upload\u3001quote\u3001order\u3001status\u3001download \u548C cancel";
1699
+ async function shorts(context) {
1700
+ const { args } = context;
1701
+ const [subcommand] = args;
1702
+ if (subcommand === void 0) {
1703
+ throw usageError(`shorts \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1A${SUBCOMMANDS}`);
1704
+ }
1705
+ if (subcommand === "catalog") return catalog(context);
1706
+ if (subcommand === "upload") return upload(context, args[1]);
1707
+ if (subcommand === "quote") return quote(context);
1708
+ if (subcommand === "order") return order(context);
1709
+ if (subcommand === "status") return status(context, args[1]);
1710
+ if (subcommand === "download") return download(context, args[1]);
1711
+ if (subcommand === "cancel") return cancel(context, args[1]);
1712
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Ashorts \u7684\u5B50\u547D\u4EE4\u662F ${SUBCOMMANDS}`);
1713
+ }
1714
+ function orderIdOf(raw) {
1715
+ if (raw === void 0 || raw.trim() === "") {
1716
+ throw usageError("\u8981\u7ED9\u8BA2\u5355 id\uFF08shorts order \u56DE\u7684\u90A3\u4E2A\uFF09");
1717
+ }
1718
+ return raw.trim();
1719
+ }
1720
+ function templateOf(values) {
1721
+ const templateId = textOption(values, "template");
1722
+ if (templateId === void 0) {
1723
+ throw usageError("\u8981\u7ED9 --template <\u6A21\u677Fid>\uFF08shorts catalog \u5217\u51FA\u7684\uFF09");
1724
+ }
1725
+ return templateId;
1726
+ }
1727
+ function waitControls(io, values, note) {
1728
+ return {
1729
+ timeoutMs: timeoutSecondsOf(values, DEFAULT_WAIT_TIMEOUT_MS / 1e3) * 1e3,
1730
+ now: io.now ?? (() => /* @__PURE__ */ new Date()),
1731
+ sleep: io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1732
+ note
1733
+ };
1734
+ }
1735
+ function settleOrder(outcome) {
1736
+ const order2 = outcome.value.view;
1737
+ if (outcome.timedOut) {
1738
+ throw new CliError({
1739
+ code: "CLI_WAIT_TIMEOUT",
1740
+ exitCode: EXIT.WAIT_TIMEOUT,
1741
+ message: `\u7B49\u4E86\u8FD9\u4E48\u4E45\u8FD8\u5728 ${order2.status}\uFF1A\u518D\u67E5\u7528 buke shorts status ${order2.id}\uFF0C\u6216\u52A0 --timeout \u5EF6\u957F\u7B49\u5F85`
1742
+ });
1743
+ }
1744
+ if (order2.status === "failed" || order2.status === "canceled") {
1745
+ throw new CliError({
1746
+ code: "CLI_TASK_FAILED",
1747
+ exitCode: EXIT.TASK_FAILED,
1748
+ message: order2.error === void 0 ? `\u8FD9\u6761\u77ED\u7247${order2.status === "canceled" ? "\u5DF2\u53D6\u6D88" : "\u6CA1\u6709\u6E32\u67D3\u6210\u529F"}` : singleLine(order2.error)
1749
+ });
1750
+ }
1751
+ return outcome.value;
1752
+ }
1753
+ function orderLines(order2) {
1754
+ return [
1755
+ `\u8BA2\u5355\uFF1A${singleLine(order2.id)}`,
1756
+ `\u72B6\u6001\uFF1A${singleLine(order2.status)}`,
1757
+ `\u6A21\u677F\uFF1A${singleLine(order2.templateTitle)}\uFF08${singleLine(order2.templateId)}\uFF09`,
1758
+ `\u521B\u5EFA\u4E8E\uFF1A${singleLine(order2.createdAt)}`,
1759
+ ...order2.durationSeconds !== void 0 ? [`\u65F6\u957F\uFF1A${order2.durationSeconds} \u79D2`] : [],
1760
+ ...order2.status === "succeeded" && order2.videoUrl !== void 0 ? [`\u6210\u7247\u5730\u5740\uFF1A${singleLine(order2.videoUrl)}`] : [],
1761
+ ...order2.bytes !== void 0 ? [`\u5927\u5C0F\uFF1A${order2.bytes} \u5B57\u8282`] : []
1762
+ ];
1763
+ }
1764
+ async function catalog(context) {
1765
+ const { io, out } = context;
1766
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1767
+ const { view, raw } = await getTemplateShortCatalog(baseUrl, apiKey, io);
1768
+ out.result(
1769
+ [
1770
+ `${singleLine(view.category.name)}\uFF08${singleLine(view.category.badge)}\uFF0C\u5171 ${view.templates.length} \u4E2A\u6A21\u677F\uFF09`,
1771
+ ...view.templates.map(
1772
+ (template) => `${template.id} ${singleLine(template.title)}\uFF08${singleLine(template.tier)}\uFF0C${template.durationSeconds} \u79D2\uFF09
1773
+ ${template.summary}${template.needs.length > 0 ? `\uFF1B\u9700\u8981\uFF1A${template.needs.join("\u3001")}` : ""}`
1774
+ )
1775
+ ],
1776
+ // --json 按文档回服务端那份完整的 catalog:slots、defaultProps 都在内。
1777
+ { catalog: raw.catalog }
1778
+ );
1779
+ }
1780
+ async function upload(context, rawPath) {
1781
+ const { io, out } = context;
1782
+ if (rawPath === void 0 || rawPath.trim() === "") {
1783
+ throw usageError("\u8981\u7ED9\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84");
1784
+ }
1785
+ const image = await readImageFile(rawPath);
1786
+ const idempotencyKey = idempotencyKeyOf(context.values);
1787
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1788
+ const { view, raw } = await uploadTemplateShortAsset(baseUrl, apiKey, image.bytes, image.contentType, idempotencyKey, io);
1789
+ out.result(
1790
+ [
1791
+ `\u5DF2\u4E0A\u4F20\uFF1A${singleLine(view.url)}`,
1792
+ `\u6821\u9A8C\uFF1Asha256 ${view.sha256}\uFF08${view.bytes} \u5B57\u8282\uFF09`,
1793
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
1794
+ ],
1795
+ { upload: raw, idempotencyKey }
1796
+ );
1797
+ }
1798
+ async function quote(context) {
1799
+ const { io, out, values } = context;
1800
+ const templateId = templateOf(values);
1801
+ const props = await readPropsArgument(io, textOption(values, "props"), "\u6A21\u677F\u53C2\u6570\uFF08--props\uFF09");
1802
+ const idempotencyKey = idempotencyKeyOf(values);
1803
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1804
+ const { view, raw } = await quoteTemplateShort(baseUrl, apiKey, templateId, props, idempotencyKey, io);
1805
+ out.result(
1806
+ [
1807
+ `\u6A21\u677F\uFF1A${singleLine(view.templateId)}\uFF0C\u65F6\u957F ${Math.round(view.durationMilliseconds / 100) / 10} \u79D2`,
1808
+ `\u62A5\u4EF7\uFF1A${view.credits} \u5E03\uFF08\u4F1A\u5458\u65F6\u957F\u5185 ${Math.round(view.memberMilliseconds / 100) / 10} \u79D2\uFF0C\u8D85\u51FA ${Math.round(view.excessMilliseconds / 100) / 10} \u79D2\uFF09`,
1809
+ `\u62A5\u4EF7\u54C8\u5E0C\uFF1A${view.quoteHash}`,
1810
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`,
1811
+ `\u4E0B\u5355\u65F6\u5E26\u4E0A --quote-hash ${view.quoteHash} \u548C\u540C\u4E00\u628A --idempotency-key ${idempotencyKey}\uFF08\u62A5\u4EF7\u54C8\u5E0C\u548C\u5E42\u7B49\u952E\u7ED1\u5B9A\uFF0C\u6362\u4E00\u628A\u5C31\u5BF9\u4E0D\u4E0A\uFF09`
1812
+ ],
1813
+ { quote: raw.quote, idempotencyKey }
1814
+ );
1815
+ }
1816
+ async function order(context) {
1817
+ const { io, out, values } = context;
1818
+ const templateId = templateOf(values);
1819
+ const props = await readPropsArgument(io, textOption(values, "props"), "\u6A21\u677F\u53C2\u6570\uFF08--props\uFF09");
1820
+ const quoteHash = textOption(values, "quote-hash");
1821
+ const idempotencyKey = idempotencyKeyOf(values);
1822
+ const shouldWait = values.wait === true;
1823
+ const controls = shouldWait ? waitControls(io, values, (line) => out.notice(line)) : void 0;
1824
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1825
+ const accepted = await createTemplateShortOrder(baseUrl, apiKey, templateId, props, quoteHash, idempotencyKey, io);
1826
+ const final = shouldWait ? settleOrder(await awaitTemplateShortOrder(baseUrl, apiKey, accepted, io, controls)) : accepted;
1827
+ out.result([...orderLines(final.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`], {
1828
+ order: final.raw.order,
1829
+ idempotencyKey,
1830
+ ...shouldWait ? { waited: true } : {}
1831
+ });
1832
+ }
1833
+ async function status(context, rawId) {
1834
+ const { io, out, values } = context;
1835
+ const orderId = orderIdOf(rawId);
1836
+ const shouldWait = values.wait === true;
1837
+ const controls = shouldWait ? waitControls(io, values, (line) => out.notice(line)) : void 0;
1838
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1839
+ const current = await getTemplateShortOrder(baseUrl, apiKey, orderId, io);
1840
+ const final = shouldWait ? settleOrder(await awaitTemplateShortOrder(baseUrl, apiKey, current, io, controls)) : current;
1841
+ out.result(orderLines(final.view), { order: final.raw.order, ...shouldWait ? { waited: true } : {} });
1842
+ }
1843
+ async function cancel(context, rawId) {
1844
+ const { io, out, values } = context;
1845
+ const orderId = orderIdOf(rawId);
1846
+ const idempotencyKey = idempotencyKeyOf(values);
1847
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1848
+ const canceled = await cancelTemplateShortOrder(baseUrl, apiKey, orderId, idempotencyKey, io);
1849
+ out.result([...orderLines(canceled.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`], { order: canceled.raw.order, idempotencyKey });
1850
+ }
1851
+ async function download(context, rawId) {
1852
+ const { io, out, values } = context;
1853
+ const orderId = orderIdOf(rawId);
1854
+ const defaultName = `template-short-${orderId}.mp4`;
1855
+ const target = textOption(values, "out") ?? defaultName;
1856
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1857
+ const result = await downloadTemplateShort(baseUrl, apiKey, orderId, io);
1858
+ const saved = await saveDownload(target, result.bytes);
1859
+ out.result(
1860
+ [`\u5DF2\u4E0B\u8F7D\uFF1A${saved.path}`, `\u5927\u5C0F\uFF1A${saved.bytes} \u5B57\u8282`, `\u6821\u9A8C\uFF1Asha256 ${saved.sha256}`],
1861
+ { download: { ...saved, filename: result.filename ?? defaultName } }
1862
+ );
1863
+ }
1864
+ async function saveDownload(target, bytes) {
1865
+ try {
1866
+ await writeFile(target, bytes);
1867
+ } catch (error) {
1868
+ throw new CliError({
1869
+ code: "CLI_INTERNAL",
1870
+ exitCode: EXIT.SERVER,
1871
+ message: `\u6210\u7247\u5199\u4E0D\u8FDB ${target}\uFF08${briefReason(error)}\uFF09`
1872
+ });
1873
+ }
1874
+ return { path: target, bytes: bytes.byteLength, sha256: createHash("sha256").update(bytes).digest("hex") };
1875
+ }
1876
+
1877
+ // src/commands/drama.ts
1878
+ var SUBCOMMANDS2 = "create\u3001status\u3001quote\u3001confirm\u3001cancel \u548C download";
1879
+ var DURATION_KEYS = ["30s", "60s", "90s"];
1880
+ async function drama(context) {
1881
+ const { args } = context;
1882
+ const [subcommand] = args;
1883
+ if (subcommand === void 0) {
1884
+ throw usageError(`drama \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1A${SUBCOMMANDS2}`);
1885
+ }
1886
+ if (subcommand === "create") return create(context);
1887
+ if (subcommand === "status") return status2(context, args[1]);
1888
+ if (subcommand === "quote") return quote2(context, args[1]);
1889
+ if (subcommand === "confirm") return confirm(context, args[1]);
1890
+ if (subcommand === "cancel") return cancel2(context, args[1]);
1891
+ if (subcommand === "download") return download2(context, args[1]);
1892
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Adrama \u7684\u5B50\u547D\u4EE4\u662F ${SUBCOMMANDS2}`);
1893
+ }
1894
+ function orderIdOf2(raw) {
1895
+ if (raw === void 0 || raw.trim() === "") {
1896
+ throw usageError("\u8981\u7ED9\u8BA2\u5355 id\uFF08drama create \u56DE\u7684\u90A3\u4E2A\uFF09");
1897
+ }
1898
+ return raw.trim();
1899
+ }
1900
+ function waitControls2(io, values, note) {
1901
+ return {
1902
+ timeoutMs: timeoutSecondsOf(values, DEFAULT_WAIT_TIMEOUT_MS / 1e3) * 1e3,
1903
+ now: io.now ?? (() => /* @__PURE__ */ new Date()),
1904
+ sleep: io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1905
+ note
1906
+ };
1907
+ }
1908
+ function settleOrder2(outcome) {
1909
+ const order2 = outcome.value.view;
1910
+ if (outcome.timedOut) {
1911
+ throw new CliError({
1912
+ code: "CLI_WAIT_TIMEOUT",
1913
+ exitCode: EXIT.WAIT_TIMEOUT,
1914
+ message: `\u7B49\u4E86\u8FD9\u4E48\u4E45\u8FD8\u5728 ${order2.status}\uFF1A\u518D\u67E5\u7528 buke drama status ${order2.id}\uFF0C\u6216\u52A0 --timeout \u5EF6\u957F\u7B49\u5F85`
1915
+ });
1916
+ }
1917
+ if (order2.status === "failed") {
1918
+ throw new CliError({
1919
+ code: "CLI_TASK_FAILED",
1920
+ exitCode: EXIT.TASK_FAILED,
1921
+ message: order2.reason === void 0 || order2.reason.trim() === "" ? "\u8FD9\u5355\u77ED\u5267\u6CA1\u6709\u62CD\u6210" : singleLine(order2.reason)
1922
+ });
1923
+ }
1924
+ if (order2.status === "canceled") {
1925
+ throw new CliError({
1926
+ code: "CLI_TASK_FAILED",
1927
+ exitCode: EXIT.TASK_FAILED,
1928
+ message: "\u8FD9\u5355\u77ED\u5267\u5DF2\u53D6\u6D88\uFF1A\u6CA1\u62CD\u7684\u955C\u5934\u9000\u56DE\u51BB\u7ED3\u989D\u5EA6\uFF0C\u5DF2\u7ECF\u62CD\u6210\u7684\u4FDD\u7559\u7167\u6263"
1929
+ });
1930
+ }
1931
+ return outcome.value;
1932
+ }
1933
+ function orderLines2(order2) {
1934
+ const money = order2.money;
1935
+ const extra = [];
1936
+ if (money.released > 0) extra.push(`\u5DF2\u91CA\u653E ${money.released} ${money.currency}`);
1937
+ if (money.refunded > 0) extra.push(`\u5DF2\u9000\u6B3E ${money.refunded} ${money.currency}`);
1938
+ return [
1939
+ `\u8BA2\u5355\uFF1A${singleLine(order2.id)}`,
1940
+ `\u72B6\u6001\uFF1A${singleLine(order2.status)}`,
1941
+ `\u4EA7\u54C1\uFF1A${singleLine(order2.productId)}\uFF08\u9898\u6750 ${singleLine(order2.genreId)}\uFF0C\u4F53\u91CF ${singleLine(order2.durationKey)}\uFF09`,
1942
+ `\u66F4\u65B0\u65F6\u95F4\uFF1A${singleLine(order2.updatedAt)}`,
1943
+ `\u8D26\uFF1A\u51BB\u7ED3 ${money.held} ${money.currency}\uFF0C\u5DF2\u7ED3\u7B97 ${money.settled} ${money.currency}` + (extra.length > 0 ? `\uFF08${extra.join("\uFF0C")}\uFF09` : "")
1944
+ ];
1945
+ }
1946
+ function guidanceLines(order2) {
1947
+ const lines = [];
1948
+ if (order2.reason !== void 0 && order2.reason.trim() !== "") {
1949
+ const label = order2.status === "failed" ? "\u5931\u8D25\u539F\u56E0" : order2.status === "partial" ? "\u6CA1\u62CD\u5B8C\u7684\u90E8\u5206" : "\u505C\u9760\u539F\u56E0";
1950
+ lines.push(`${label}\uFF1A${singleLine(order2.reason)}`);
1951
+ }
1952
+ for (const gap of order2.gaps ?? []) {
1953
+ const kind = gap.kind === "character" ? "\u89D2\u8272" : gap.kind === "scene" ? "\u573A\u666F" : "\u7D20\u6750";
1954
+ const scenes = gap.sceneNos.length > 0 ? `\uFF0C\u51FA\u73B0\u5728\u7B2C ${gap.sceneNos.join("\u3001")} \u955C` : "";
1955
+ lines.push(`\u7F3A\u53E3\uFF1A${kind}\u300C${singleLine(gap.label !== "" ? gap.label : gap.key)}\u300D\u8FD8\u6CA1\u7ED1\u5B9A${scenes}`);
1956
+ }
1957
+ const next = nextStepOf(order2);
1958
+ return next === null ? lines : [...lines, next];
1959
+ }
1960
+ function nextStepOf(order2) {
1961
+ switch (order2.status) {
1962
+ case "preparing":
1963
+ return `\u4E0B\u4E00\u6B65\uFF1A\u7B49\u5199\u7A3F\u4E0E\u6392\u7248\u8DD1\u5B8C\uFF0C\u91CD\u65B0\u67E5\u8BE2\uFF1Abuke drama status ${order2.id}`;
1964
+ case "needs_attention":
1965
+ return `\u4E0B\u4E00\u6B65\uFF1A\u6309\u4E0A\u9762\u7684\u63D0\u793A\u5728 Studio \u6253\u5F00\u8FD9\u90E8\u77ED\u5267\u5904\u7406\uFF0C\u5904\u7406\u5B8C\u91CD\u65B0\u67E5\u8BE2\uFF1Abuke drama status ${order2.id}`;
1966
+ case "quotable":
1967
+ return `\u4E0B\u4E00\u6B65\uFF1A\u770B\u62A5\u4EF7\uFF1Abuke drama quote ${order2.id}\uFF0C\u518D\u786E\u8BA4\u5F00\u62CD\uFF1Abuke drama confirm ${order2.id} --quote-hash <\u62A5\u4EF7\u54C8\u5E0C>`;
1968
+ case "producing":
1969
+ case "stitching":
1970
+ return `\u4E0B\u4E00\u6B65\uFF1A\u7B49\u5B83\u62CD\u5B8C\uFF1Abuke drama status ${order2.id} --wait`;
1971
+ case "partial":
1972
+ return `\u4E0B\u4E00\u6B65\uFF1A\u53EA\u8865\u7F3A\u7684\u955C\u5934\uFF1Abuke drama confirm ${order2.id} --quote-hash <\u62A5\u4EF7\u54C8\u5E0C>\uFF1B\u4E0D\u8981\u4E86\uFF1Abuke drama cancel ${order2.id}`;
1973
+ case "delivered":
1974
+ return `\u4E0B\u8F7D\uFF1Abuke drama download ${order2.id}`;
1975
+ default:
1976
+ return null;
1977
+ }
1978
+ }
1979
+ function sufficientLine(sufficient) {
1980
+ if (sufficient === true) return "\u591F\u8FD9\u4E00\u5355";
1981
+ if (sufficient === false) return "\u4E0D\u591F\u8FD9\u4E00\u5355";
1982
+ return "\u6682\u65F6\u7B97\u4E0D\u51FA\u6765";
1983
+ }
1984
+ function quoteLines(orderId, quote3) {
1985
+ const price = [`\u603B\u4EF7 ${quote3.totalCredits} \u5E03`, `${quote3.shotCount} \u4E2A\u955C\u5934`];
1986
+ if (quote3.shotCreditCost !== void 0) price.push(`\u6BCF\u955C ${quote3.shotCreditCost} \u5E03`);
1987
+ const spec = [quote3.videoModel, quote3.resolution].filter((entry) => entry !== void 0);
1988
+ return [
1989
+ `\u62A5\u4EF7\uFF1A${price.join("\uFF0C")}`,
1990
+ ...spec.length > 0 ? [`\u6863\u4F4D\uFF1A${spec.join(" / ")}`] : [],
1991
+ `\u62A5\u4EF7\u54C8\u5E0C\uFF1A${quote3.quoteHash}`,
1992
+ `\u4F59\u989D\uFF1A${sufficientLine(quote3.sufficient)}`,
1993
+ `\u786E\u8BA4\u5F00\u62CD\uFF1Abuke drama confirm ${orderId} --quote-hash ${quote3.quoteHash}`
1994
+ ];
1995
+ }
1996
+ async function create(context) {
1997
+ const { io, out, values } = context;
1998
+ const productId = textOption(values, "product");
1999
+ if (productId === void 0) {
2000
+ throw usageError("\u8981\u7ED9 --product <\u4EA7\u54C1id>\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2001
+ }
2002
+ const genreId = textOption(values, "genre");
2003
+ const durationKey = textOption(values, "duration");
2004
+ if (durationKey !== void 0 && !DURATION_KEYS.includes(durationKey)) {
2005
+ throw usageError(`--duration \u53EA\u80FD\u662F ${DURATION_KEYS.join("\u3001")}\uFF0C\u5199\u7684\u662F ${durationKey}`);
2006
+ }
2007
+ const briefRef = textOption(values, "brief");
2008
+ const brief = briefRef === void 0 ? void 0 : await readPropsArgument(io, briefRef, "\u6545\u4E8B\u8BBE\u5B9A\uFF08--brief\uFF09");
2009
+ const idempotencyKey = idempotencyKeyOf(values);
2010
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2011
+ const created = await createDramaOrder(
2012
+ baseUrl,
2013
+ apiKey,
2014
+ {
2015
+ productId,
2016
+ ...genreId === void 0 ? {} : { genreId },
2017
+ ...durationKey === void 0 ? {} : { durationKey },
2018
+ ...brief === void 0 ? {} : { brief }
2019
+ },
2020
+ idempotencyKey,
2021
+ io
2022
+ );
2023
+ out.result(
2024
+ [...orderLines2(created.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`, `\u67E5\u8FDB\u5EA6\uFF1Abuke drama status ${created.view.id}`],
2025
+ // --json 按文档回服务端那份完整的 order;replayed 说明这次是新建还是同键重放。
2026
+ { order: created.raw.order, replayed: created.replayed, idempotencyKey }
2027
+ );
2028
+ }
2029
+ async function status2(context, rawId) {
2030
+ const { io, out, values } = context;
2031
+ const orderId = orderIdOf2(rawId);
2032
+ const shouldWait = values.wait === true;
2033
+ const controls = shouldWait ? waitControls2(io, values, (line) => out.notice(line)) : void 0;
2034
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2035
+ const current = await getDramaOrder(baseUrl, apiKey, orderId, io);
2036
+ const final = shouldWait ? settleOrder2(await awaitDramaOrder(baseUrl, apiKey, current, io, controls)) : current;
2037
+ out.result([...orderLines2(final.view), ...guidanceLines(final.view)], {
2038
+ order: final.raw.order,
2039
+ ...shouldWait ? { waited: true } : {}
2040
+ });
2041
+ }
2042
+ async function quote2(context, rawId) {
2043
+ const { io, out } = context;
2044
+ const orderId = orderIdOf2(rawId);
2045
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2046
+ const { view, raw } = await getDramaQuote(baseUrl, apiKey, orderId, io);
2047
+ out.result(quoteLines(orderId, view), { quote: raw.quote, quoteHash: view.quoteHash, sufficient: view.sufficient });
2048
+ }
2049
+ async function confirm(context, rawId) {
2050
+ const { io, out, values } = context;
2051
+ const orderId = orderIdOf2(rawId);
2052
+ const quoteHash = textOption(values, "quote-hash");
2053
+ if (quoteHash === void 0) {
2054
+ throw usageError("\u8981\u7ED9 --quote-hash <\u62A5\u4EF7\u54C8\u5E0C>\uFF08buke drama quote \u56DE\u7684\u90A3\u4E2A\uFF09");
2055
+ }
2056
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2057
+ const current = await getDramaOrder(baseUrl, apiKey, orderId, io);
2058
+ const quoted = current.view.status === "quotable" ? await getDramaQuote(baseUrl, apiKey, orderId, io) : null;
2059
+ if (values.yes !== true) {
2060
+ if (!out.json) {
2061
+ out.result(
2062
+ quoted === null ? [`\u8FD9\u5355\u73B0\u5728\u4E0D\u662F\u53EF\u62A5\u4EF7\u72B6\u6001\uFF08${singleLine(current.view.status)}\uFF09\uFF1A\u6CA1\u6709\u5F53\u524D\u62A5\u4EF7\u53EF\u770B\uFF0C\u786E\u8BA4\u4F1A\u6309\u4F60\u5E26\u6765\u7684\u62A5\u4EF7\u54C8\u5E0C\u63D0\u4EA4`] : quoteLines(orderId, quoted.view),
2063
+ void 0
2064
+ );
2065
+ }
2066
+ throw new CliError({
2067
+ code: "CLI_USAGE",
2068
+ exitCode: EXIT.USAGE,
2069
+ message: quoted === null ? "\u52A0 --yes \u6309\u4F60\u5E26\u6765\u7684\u62A5\u4EF7\u54C8\u5E0C\u63D0\u4EA4\u8FD9\u6B21\u786E\u8BA4" : `\u8FD9\u4E00\u5355\u73B0\u5728\u62A5\u4EF7 ${quoted.view.totalCredits} \u5E03\u3002\u52A0 --yes \u786E\u8BA4\u8FD9\u7B14\u5E03\u652F\u51FA`,
2070
+ hints: [`\u770B\u8FC7\u518D\u786E\u8BA4\uFF1Abuke drama status ${orderId}`]
2071
+ });
2072
+ }
2073
+ const idempotencyKey = idempotencyKeyOf(values);
2074
+ const confirmed = await confirmDramaOrder(baseUrl, apiKey, orderId, quoteHash, idempotencyKey, io);
2075
+ out.result(
2076
+ [
2077
+ ...orderLines2(confirmed.view),
2078
+ confirmed.productionStarted ? quoted === null ? "\u5DF2\u786E\u8BA4\uFF1A\u6309\u539F\u6765\u6536\u4E0B\u7684\u62A5\u4EF7\u8865\u62CD\u7F3A\u7684\u955C\u5934" : `\u5DF2\u786E\u8BA4\uFF1A\u8FD9\u7B14\u6700\u591A ${quoted.view.totalCredits} \u5E03` : "\u5DF2\u786E\u8BA4\uFF1A\u8FD9\u6B21\u6CA1\u6709\u5F00\u65B0\u4EFB\u52A1\uFF08\u5B83\u53EF\u80FD\u5DF2\u7ECF\u5728\u62CD\uFF0C\u6216\u5DF2\u7ECF\u62CD\u597D\u7B49\u62FC\u63A5\uFF09",
2079
+ ...guidanceLines(confirmed.view),
2080
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2081
+ ],
2082
+ {
2083
+ order: confirmed.raw.order,
2084
+ productionStarted: confirmed.productionStarted,
2085
+ // 计价的那一份:可报价时是刚取回的报价;续拍按原签字走,没有当前报价。
2086
+ confirmed: { totalCredits: quoted === null ? null : quoted.view.totalCredits, quoteHash },
2087
+ idempotencyKey
2088
+ }
2089
+ );
2090
+ }
2091
+ async function cancel2(context, rawId) {
2092
+ const { io, out, values } = context;
2093
+ const orderId = orderIdOf2(rawId);
2094
+ const idempotencyKey = idempotencyKeyOf(values);
2095
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2096
+ const canceled = await cancelDramaOrder(baseUrl, apiKey, orderId, idempotencyKey, io);
2097
+ out.result(
2098
+ [
2099
+ ...orderLines2(canceled.view),
2100
+ canceled.canceled ? "\u5DF2\u53D6\u6D88\uFF1A\u6CA1\u62CD\u7684\u955C\u5934\u9000\u56DE\u51BB\u7ED3\u989D\u5EA6\uFF0C\u5DF2\u7ECF\u62CD\u6210\u7684\u4FDD\u7559\u7167\u6263" : "\u8FD9\u5355\u4E4B\u524D\u5DF2\u7ECF\u53D6\u6D88\u8FC7\uFF0C\u6CA1\u6709\u518D\u53D6\u6D88\u4E00\u6B21",
2101
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2102
+ ],
2103
+ { order: canceled.raw.order, canceled: canceled.canceled, idempotencyKey }
2104
+ );
2105
+ }
2106
+ async function download2(context, rawId) {
2107
+ const { io, out, values } = context;
2108
+ const orderId = orderIdOf2(rawId);
2109
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2110
+ const result = await downloadDramaFilm(baseUrl, apiKey, orderId, io);
2111
+ const defaultName = result.filename ?? `drama-${orderId}.mp4`;
2112
+ const target = textOption(values, "out") ?? defaultName;
2113
+ const saved = await saveDownload(target, result.bytes);
2114
+ out.result(
2115
+ [`\u5DF2\u4E0B\u8F7D\uFF1A${saved.path}`, `\u5927\u5C0F\uFF1A${saved.bytes} \u5B57\u8282`, `\u6821\u9A8C\uFF1Asha256 ${saved.sha256}`],
2116
+ { download: { ...saved, filename: defaultName } }
2117
+ );
2118
+ }
2119
+
868
2120
  // src/output.ts
869
2121
  function errorDocument(error) {
870
2122
  const document = {
@@ -996,36 +2248,330 @@ async function logout({ io, out }) {
996
2248
  }
997
2249
  }
998
2250
 
999
- // src/commands/whoami.ts
1000
- async function resolve({ io, out, baseUrl: flag }) {
1001
- const explicit = explicitBaseUrl(flag, io.env);
1002
- const fromEnv = keyFromEnv(io.env);
1003
- if (fromEnv !== null) {
1004
- assertKeyFormat(fromEnv);
1005
- out.protect(fromEnv);
1006
- return { apiKey: fromEnv, baseUrl: explicit ?? DEFAULT_BASE_URL, source: "env" };
2251
+ // src/commands/products.ts
2252
+ var PRODUCT_ANGLES = ["front", "side", "back", "detail"];
2253
+ async function products(context) {
2254
+ const { args, values } = context;
2255
+ const [subcommand] = args;
2256
+ if (subcommand === void 0) {
2257
+ throw usageError("products \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1Acreate\u3001set-images \u6216 confirm-lock");
1007
2258
  }
1008
- const stored = await readCredentials(io, (line) => out.notice(line));
1009
- if (!stored) {
2259
+ if (subcommand === "create") return create2(context, values);
2260
+ if (subcommand === "set-images") return setImages(context, values, args[1]);
2261
+ if (subcommand === "confirm-lock") return confirmLock(context, values, args[1]);
2262
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Aproducts \u7684\u5B50\u547D\u4EE4\u662F create\u3001set-images \u548C confirm-lock`);
2263
+ }
2264
+ function productIdOf(raw, what) {
2265
+ if (raw === void 0 || raw.trim() === "") {
2266
+ throw usageError(`\u8981\u7ED9${what}`);
2267
+ }
2268
+ return raw.trim();
2269
+ }
2270
+ async function create2(context, values) {
2271
+ const { io, out } = context;
2272
+ const name2 = textOption(values, "name");
2273
+ if (name2 === void 0) {
2274
+ throw usageError("\u8981\u7ED9 --name <\u540D\u79F0>");
2275
+ }
2276
+ const category = textOption(values, "category");
2277
+ const idempotencyKey = idempotencyKeyOf(values);
2278
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2279
+ const { view, raw, replayed } = await createProduct(baseUrl, apiKey, name2, category, idempotencyKey, io);
2280
+ out.result(
2281
+ [
2282
+ replayed ? `\u4EA7\u54C1\u5DF2\u5B58\u5728\uFF1A${singleLine(view.id)}\uFF08\u540C\u952E\u91CD\u653E\uFF09` : `\u5DF2\u521B\u5EFA\u4EA7\u54C1\uFF1A${singleLine(view.id)}`,
2283
+ `\u540D\u79F0\uFF1A${singleLine(view.name)}`,
2284
+ `\u7C7B\u76EE\uFF1A${singleLine(view.category)}`,
2285
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2286
+ ],
2287
+ { product: raw.product, replayed, idempotencyKey }
2288
+ );
2289
+ }
2290
+ async function setImages(context, values, rawId) {
2291
+ const { io, out } = context;
2292
+ const productId = productIdOf(rawId, "\u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2293
+ const planned = [];
2294
+ for (const angle of PRODUCT_ANGLES) {
2295
+ const path = textOption(values, angle);
2296
+ if (path !== void 0) planned.push({ angle, path });
2297
+ }
2298
+ if (planned.length === 0) {
2299
+ throw usageError("\u81F3\u5C11\u7ED9\u4E00\u4E2A\u89D2\u5EA6\u7684\u56FE\uFF1A--front\u3001--side\u3001--back \u6216 --detail");
2300
+ }
2301
+ const images = await Promise.all(planned.map(async ({ angle, path }) => ({ angle, ...await readImageFile(path) })));
2302
+ const idempotencyKey = idempotencyKeyOf(values);
2303
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2304
+ let latest;
2305
+ const uploaded = [];
2306
+ for (const image of images) {
2307
+ const result = await putProductImage(
2308
+ baseUrl,
2309
+ apiKey,
2310
+ productId,
2311
+ image.angle,
2312
+ image.bytes,
2313
+ image.contentType,
2314
+ idempotencyKey,
2315
+ io
2316
+ );
2317
+ latest = result;
2318
+ uploaded.push({ angle: image.angle, replayed: result.replayed });
2319
+ }
2320
+ out.result(
2321
+ [
2322
+ ...uploaded.map(
2323
+ ({ angle, replayed }) => replayed ? `${angle}\uFF1A\u8FD9\u5F20\u5DF2\u7ECF\u4F20\u8FC7\u4E86\uFF08\u540C\u5B57\u8282\u91CD\u653E\uFF09` : `${angle}\uFF1A\u5DF2\u4E0A\u4F20`
2324
+ ),
2325
+ `\u4EA7\u54C1\uFF1A${singleLine(latest?.view.id ?? productId)}\uFF08\u73B0\u5728 ${latest?.view.images.length ?? 0} \u5F20\u56FE\uFF09`,
2326
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2327
+ ],
2328
+ { product: latest?.raw.product, uploaded, idempotencyKey }
2329
+ );
2330
+ }
2331
+ async function confirmLock(context, values, rawId) {
2332
+ const { io, out } = context;
2333
+ const productId = productIdOf(rawId, "\u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2334
+ const colorName = textOption(values, "color");
2335
+ const silhouette = textOption(values, "silhouette");
2336
+ const material = textOption(values, "material");
2337
+ for (const [option, value] of [
2338
+ ["--color", colorName],
2339
+ ["--silhouette", silhouette],
2340
+ ["--material", material]
2341
+ ]) {
2342
+ if (value === void 0) {
2343
+ throw usageError(`\u5916\u89C2\u9501\u8981\u7ED9\u5168\uFF1A\u7F3A ${option}`);
2344
+ }
2345
+ }
2346
+ const idempotencyKey = idempotencyKeyOf(values);
2347
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2348
+ const { view, raw } = await confirmProductLock(
2349
+ baseUrl,
2350
+ apiKey,
2351
+ productId,
2352
+ { colorName, silhouette, material },
2353
+ idempotencyKey,
2354
+ io
2355
+ );
2356
+ out.result(
2357
+ [
2358
+ `\u5916\u89C2\u9501\u5DF2\u786E\u8BA4\uFF1A\u4EA7\u54C1 ${singleLine(view.product.id)}`,
2359
+ `\u989C\u8272\uFF1A${singleLine(view.productLock.colorName)}`,
2360
+ `\u5ED3\u5F62\uFF1A${singleLine(view.productLock.silhouette)}`,
2361
+ `\u6750\u8D28\uFF1A${singleLine(view.productLock.material)}`,
2362
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2363
+ ],
2364
+ { productLock: raw.productLock, product: raw.product, idempotencyKey }
2365
+ );
2366
+ }
2367
+
2368
+ // src/commands/videos.ts
2369
+ var SUBCOMMANDS3 = "create\u3001status\u3001message\u3001confirm \u548C download";
2370
+ async function videos(context) {
2371
+ const { args } = context;
2372
+ const [subcommand] = args;
2373
+ if (subcommand === void 0) {
2374
+ throw usageError(`videos \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1A${SUBCOMMANDS3}`);
2375
+ }
2376
+ if (subcommand === "create") return create3(context);
2377
+ if (subcommand === "status") return status3(context, args[1]);
2378
+ if (subcommand === "message") return message(context, args[1]);
2379
+ if (subcommand === "confirm") return confirm2(context, args[1]);
2380
+ if (subcommand === "download") return download3(context, args[1]);
2381
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Avideos \u7684\u5B50\u547D\u4EE4\u662F ${SUBCOMMANDS3}`);
2382
+ }
2383
+ function runIdOf(raw) {
2384
+ if (raw === void 0 || raw.trim() === "") {
2385
+ throw usageError("\u8981\u7ED9\u89C6\u9891\u4EFB\u52A1 id\uFF08videos create \u56DE\u7684\u90A3\u4E2A\uFF09");
2386
+ }
2387
+ return raw.trim();
2388
+ }
2389
+ function waitControls3(io, values) {
2390
+ return {
2391
+ timeoutMs: timeoutSecondsOf(values, DEFAULT_WAIT_TIMEOUT_MS / 1e3) * 1e3,
2392
+ now: io.now ?? (() => /* @__PURE__ */ new Date()),
2393
+ sleep: io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))
2394
+ };
2395
+ }
2396
+ function settleRun(outcome) {
2397
+ const run2 = outcome.value.view;
2398
+ if (outcome.timedOut) {
1010
2399
  throw new CliError({
1011
- code: "CLI_NOT_LOGGED_IN",
1012
- exitCode: EXIT.AUTH,
1013
- message: "\u8FD8\u6CA1\u6709\u767B\u5F55\u3002\u5148\u8FD0\u884C buke login\uFF0C\u6216\u8005\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF BUKE_API_KEY"
2400
+ code: "CLI_WAIT_TIMEOUT",
2401
+ exitCode: EXIT.WAIT_TIMEOUT,
2402
+ message: `\u7B49\u4E86\u8FD9\u4E48\u4E45\u8FD8\u5728 ${run2.status}\uFF08${run2.stage}\uFF09\uFF1A\u518D\u67E5\u7528 buke videos status ${run2.rootRunId}\uFF0C\u6216\u52A0 --timeout \u5EF6\u957F\u7B49\u5F85`
1014
2403
  });
1015
2404
  }
1016
- out.protect(stored.apiKey);
1017
- if (explicit !== null && explicit !== stored.baseUrl) {
2405
+ if (run2.status === "failed" || run2.status === "canceled") {
1018
2406
  throw new CliError({
1019
- code: "CLI_BASE_URL_MISMATCH",
2407
+ code: "CLI_TASK_FAILED",
2408
+ exitCode: EXIT.TASK_FAILED,
2409
+ message: run2.failure === null ? `\u8FD9\u6761\u89C6\u9891${run2.status === "canceled" ? "\u5DF2\u53D6\u6D88" : "\u6CA1\u6709\u505A\u6210"}` : singleLine(run2.failure.title) + (run2.failure.detail === "" ? "" : `\uFF1A${singleLine(run2.failure.detail)}`)
2410
+ });
2411
+ }
2412
+ return outcome.value;
2413
+ }
2414
+ function runLines(run2) {
2415
+ return [
2416
+ `\u89C6\u9891\u4EFB\u52A1\uFF1A${singleLine(run2.rootRunId)}`,
2417
+ `\u72B6\u6001\uFF1A${singleLine(run2.status)}\uFF08${singleLine(run2.stage)}\uFF09`,
2418
+ `\u4EA7\u54C1\uFF1A${singleLine(run2.product.name)}`,
2419
+ `\u66F4\u65B0\u65F6\u95F4\uFF1A${singleLine(run2.updatedAt)}`
2420
+ ];
2421
+ }
2422
+ function waitingLines(run2) {
2423
+ if (run2.status === "awaiting_confirmation") return [`\u7B49\u4F60\u786E\u8BA4\uFF1Abuke videos confirm ${run2.rootRunId}`];
2424
+ if (run2.status === "awaiting_reply" || run2.status === "needs_input") {
2425
+ return [`\u7B49\u4F60\u56DE\u7B54\uFF1Abuke videos message ${run2.rootRunId} --text <\u5185\u5BB9>`];
2426
+ }
2427
+ return [];
2428
+ }
2429
+ async function create3(context) {
2430
+ const { io, out, values } = context;
2431
+ const productId = textOption(values, "product");
2432
+ if (productId === void 0) {
2433
+ throw usageError("\u8981\u7ED9 --product <\u4EA7\u54C1id>\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2434
+ }
2435
+ const intent = textOption(values, "intent");
2436
+ const durationRaw = textOption(values, "duration");
2437
+ let durationSeconds;
2438
+ if (durationRaw !== void 0) {
2439
+ const parsed = Number(durationRaw);
2440
+ if (!Number.isInteger(parsed) || parsed <= 0) {
2441
+ throw usageError(`--duration \u8981\u662F\u6B63\u6574\u6570\u79D2\u6570\uFF0C\u5199\u7684\u662F ${durationRaw}`);
2442
+ }
2443
+ durationSeconds = parsed;
2444
+ }
2445
+ const preferences = {};
2446
+ if (durationSeconds !== void 0) preferences.durationSeconds = durationSeconds;
2447
+ for (const [option, field] of [
2448
+ ["resolution", "resolution"],
2449
+ ["speed", "speed"],
2450
+ ["aspect-ratio", "aspectRatio"],
2451
+ ["style-preset", "stylePreset"]
2452
+ ]) {
2453
+ const value = textOption(values, option);
2454
+ if (value !== void 0) preferences[field] = value;
2455
+ }
2456
+ const idempotencyKey = idempotencyKeyOf(values);
2457
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2458
+ const { view, raw } = await createVideoRun(
2459
+ baseUrl,
2460
+ apiKey,
2461
+ {
2462
+ productId,
2463
+ ...intent === void 0 ? {} : { intent },
2464
+ ...Object.keys(preferences).length > 0 ? { preferences } : {}
2465
+ },
2466
+ idempotencyKey,
2467
+ io
2468
+ );
2469
+ out.result(
2470
+ [
2471
+ ...runLines(view),
2472
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`,
2473
+ `\u67E5\u8FDB\u5EA6\uFF1Abuke videos status ${view.rootRunId}`
2474
+ ],
2475
+ // --json 按文档回服务端那份完整的 run 视图。
2476
+ { run: raw.run, idempotencyKey }
2477
+ );
2478
+ }
2479
+ async function status3(context, rawId) {
2480
+ const { io, out, values } = context;
2481
+ const runId = runIdOf(rawId);
2482
+ const shouldWait = values.wait === true;
2483
+ const controls = shouldWait ? waitControls3(io, values) : void 0;
2484
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2485
+ const current = await getVideoRun(baseUrl, apiKey, runId, io);
2486
+ const final = shouldWait ? settleRun(await awaitVideoRun(baseUrl, apiKey, current, io, controls)) : current;
2487
+ const lastDirectorLine = [...final.view.messages].reverse().find((message2) => message2.role === "assistant" && message2.text.trim() !== "");
2488
+ out.result(
2489
+ [
2490
+ ...runLines(final.view),
2491
+ ...lastDirectorLine === void 0 ? [] : [`\u5BFC\u6F14\u8BF4\uFF1A${singleLine(lastDirectorLine.text)}`],
2492
+ ...waitingLines(final.view),
2493
+ ...final.view.status === "succeeded" ? [`\u4E0B\u8F7D\uFF1Abuke videos download ${final.view.rootRunId}`] : []
2494
+ ],
2495
+ { run: final.raw.run, ...shouldWait ? { waited: true } : {} }
2496
+ );
2497
+ }
2498
+ async function message(context, rawId) {
2499
+ const { io, out, values } = context;
2500
+ const runId = runIdOf(rawId);
2501
+ const text = textOption(values, "text");
2502
+ if (text === void 0) {
2503
+ throw usageError("\u8981\u7ED9 --text <\u5185\u5BB9>\uFF1A\u60F3\u5BF9\u5BFC\u6F14\u8BF4\u7684\u90A3\u53E5\u8BDD");
2504
+ }
2505
+ const idempotencyKey = idempotencyKeyOf(values);
2506
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2507
+ const { view, raw } = await postVideoMessage(baseUrl, apiKey, runId, text, idempotencyKey, io);
2508
+ const lastDirectorLine = [...view.messages].reverse().find((entry) => entry.role === "assistant" && entry.text.trim() !== "");
2509
+ out.result(
2510
+ [
2511
+ ...runLines(view),
2512
+ ...lastDirectorLine === void 0 ? [] : [`\u5BFC\u6F14\u8BF4\uFF1A${singleLine(lastDirectorLine.text)}`],
2513
+ ...waitingLines(view),
2514
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2515
+ ],
2516
+ { run: raw.run, idempotencyKey }
2517
+ );
2518
+ }
2519
+ async function confirm2(context, rawId) {
2520
+ const { io, out, values } = context;
2521
+ const runId = runIdOf(rawId);
2522
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2523
+ const fetched = await getVideoRun(baseUrl, apiKey, runId, io);
2524
+ const run2 = fetched.view;
2525
+ const card = run2.nextActions.find((action) => action.type === "confirm_production");
2526
+ if (card === void 0) {
2527
+ throw new CliError({
2528
+ code: "CLI_USAGE",
1020
2529
  exitCode: EXIT.USAGE,
1021
- message: `\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5\u662F\u7ED9 ${stored.baseUrl} \u7528\u7684\uFF0C\u4E0D\u4F1A\u53D1\u5230 ${explicit}\u3002\u8981\u6362\u5730\u5740\u8BF7\u91CD\u65B0\u8FD0\u884C buke login --base-url ${explicit}`
2530
+ message: `\u73B0\u5728\u6CA1\u6709\u7B49\u7740\u786E\u8BA4\u7684\u5361\uFF08\u5F53\u524D\u72B6\u6001\uFF1A${run2.status}\uFF09\u3002\u5BFC\u6F14\u505C\u4E0B\u6765\u8981\u786E\u8BA4\u65F6\u518D\u8FD0\u884C\u8FD9\u6761\u547D\u4EE4`
1022
2531
  });
1023
2532
  }
1024
- return { apiKey: stored.apiKey, baseUrl: stored.baseUrl, source: "file", credentialsPath: credentialsPath(io) };
2533
+ if (card.confirmation === void 0) {
2534
+ throw new CliError({
2535
+ code: "CLI_USAGE",
2536
+ exitCode: EXIT.USAGE,
2537
+ message: "\u670D\u52A1\u7AEF\u6302\u51FA\u7684\u8FD9\u5F20\u5361\u4E0D\u5E26\u786E\u8BA4\u8EAB\u4EFD\uFF0C\u786E\u8BA4\u4E0D\u4E86\uFF1B\u7A0D\u540E\u91CD\u65B0\u8FD0\u884C buke videos status \u770B\u6700\u65B0\u72B6\u6001"
2538
+ });
2539
+ }
2540
+ const credits = card.credits ?? 0;
2541
+ const kind = card.confirmKind === void 0 ? "\u786E\u8BA4" : `\u786E\u8BA4\uFF08${card.confirmKind}\uFF09`;
2542
+ if (values.yes !== true) {
2543
+ throw new CliError({
2544
+ code: "CLI_USAGE",
2545
+ exitCode: EXIT.USAGE,
2546
+ message: `\u8FD9\u5F20\u5361\u8981\u82B1 ${credits} \u5E03\u3002\u52A0 --yes \u786E\u8BA4\u8FD9\u7B14\u5E03\u652F\u51FA`,
2547
+ hints: [`${kind}\uFF1A${singleLine(run2.label)}`, `\u770B\u8FC7\u518D\u786E\u8BA4\uFF1Abuke videos status ${run2.rootRunId}`]
2548
+ });
2549
+ }
2550
+ const idempotencyKey = idempotencyKeyOf(values);
2551
+ const outcome = await confirmVideoProduction(baseUrl, apiKey, runId, run2.version, card.confirmation, idempotencyKey, io);
2552
+ out.result(
2553
+ [...runLines(outcome.view), `\u5DF2\u786E\u8BA4\uFF1A\u8FD9\u7B14\u6700\u591A ${credits} \u5E03`, ...waitingLines(outcome.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`],
2554
+ { run: outcome.raw.run, confirmed: { credits, confirmKind: card.confirmKind ?? null }, idempotencyKey }
2555
+ );
1025
2556
  }
2557
+ async function download3(context, rawId) {
2558
+ const { io, out, values } = context;
2559
+ const runId = runIdOf(rawId);
2560
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2561
+ const result = await downloadVideo(baseUrl, apiKey, runId, io);
2562
+ const defaultName = result.filename ?? `video-${runId}.mp4`;
2563
+ const target = textOption(values, "out") ?? defaultName;
2564
+ const saved = await saveDownload(target, result.bytes);
2565
+ out.result(
2566
+ [`\u5DF2\u4E0B\u8F7D\uFF1A${saved.path}`, `\u5927\u5C0F\uFF1A${saved.bytes} \u5B57\u8282`, `\u6821\u9A8C\uFF1Asha256 ${saved.sha256}`],
2567
+ { download: { ...saved, filename: defaultName } }
2568
+ );
2569
+ }
2570
+
2571
+ // src/commands/whoami.ts
1026
2572
  async function whoami(context) {
1027
2573
  const { io, out } = context;
1028
- const resolved = await resolve(context);
2574
+ const resolved = await resolveApiKey(context);
1029
2575
  const { apiKey, baseUrl } = resolved;
1030
2576
  let me;
1031
2577
  try {
@@ -1041,11 +2587,7 @@ async function whoami(context) {
1041
2587
  const displayPrefix = displayPrefixOf(apiKey, me.key.id);
1042
2588
  const identity = identityDocument(baseUrl, me, displayPrefix);
1043
2589
  out.result(
1044
- [
1045
- `\u5F53\u524D\u767B\u5F55 ${baseUrl}`,
1046
- ...identityLines(me, displayPrefix),
1047
- resolved.source === "env" ? "\u5BC6\u94A5\u6765\u81EA\uFF1A\u73AF\u5883\u53D8\u91CF BUKE_API_KEY" : `\u5BC6\u94A5\u6765\u81EA\uFF1A${resolved.credentialsPath}`
1048
- ],
2590
+ [`\u5F53\u524D\u767B\u5F55 ${baseUrl}`, ...identityLines(me, displayPrefix), keySourceLine(resolved)],
1049
2591
  resolved.source === "env" ? { ...identity, source: resolved.source } : { ...identity, source: resolved.source, credentialsPath: resolved.credentialsPath },
1050
2592
  [displayPrefix]
1051
2593
  );
@@ -1062,6 +2604,8 @@ var COMMANDS = {
1062
2604
  login: {
1063
2605
  run: login,
1064
2606
  acceptsBaseUrl: true,
2607
+ options: [],
2608
+ positionals: [],
1065
2609
  help: [
1066
2610
  "\u7528\u6CD5\uFF1Abuke login [--base-url <\u5730\u5740>] [--json]",
1067
2611
  "",
@@ -1078,6 +2622,8 @@ var COMMANDS = {
1078
2622
  whoami: {
1079
2623
  run: whoami,
1080
2624
  acceptsBaseUrl: true,
2625
+ options: [],
2626
+ positionals: [],
1081
2627
  help: [
1082
2628
  "\u7528\u6CD5\uFF1Abuke whoami [--base-url <\u5730\u5740>] [--json]",
1083
2629
  "",
@@ -1093,6 +2639,8 @@ var COMMANDS = {
1093
2639
  logout: {
1094
2640
  run: logout,
1095
2641
  acceptsBaseUrl: false,
2642
+ options: [],
2643
+ positionals: [],
1096
2644
  help: [
1097
2645
  "\u7528\u6CD5\uFF1Abuke logout [--json]",
1098
2646
  "",
@@ -1101,15 +2649,189 @@ var COMMANDS = {
1101
2649
  "\u9009\u9879\uFF1A",
1102
2650
  " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
1103
2651
  ].join("\n")
2652
+ },
2653
+ balance: {
2654
+ run: balance,
2655
+ acceptsBaseUrl: true,
2656
+ options: [],
2657
+ positionals: [],
2658
+ help: [
2659
+ "\u7528\u6CD5\uFF1Abuke balance [--json]",
2660
+ "",
2661
+ "\u67E5\u8BE2\u8FD9\u4E2A\u8D26\u53F7\u7684\u4F59\u989D\uFF1A\u53EF\u7528\u591A\u5C11\u3001\u51BB\u7740\u591A\u5C11\u3001\u5DF2\u7ECF\u82B1\u4E86\u591A\u5C11\u3002",
2662
+ "",
2663
+ "\u9009\u9879\uFF1A",
2664
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2665
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2666
+ ].join("\n")
2667
+ },
2668
+ products: {
2669
+ run: products,
2670
+ acceptsBaseUrl: true,
2671
+ options: ["name", "category", "front", "side", "back", "detail", "color", "silhouette", "material", "idempotency-key"],
2672
+ positionals: ["\u5B50\u547D\u4EE4", "\u4EA7\u54C1id"],
2673
+ help: [
2674
+ "\u7528\u6CD5\uFF1A",
2675
+ " buke products create --name <\u540D\u79F0> [--category <\u7C7B\u76EE>] [--json]",
2676
+ " buke products set-images <\u4EA7\u54C1id> --front <\u56FE> [--side <\u56FE>] [--back <\u56FE>] [--detail <\u56FE>]",
2677
+ " buke products confirm-lock <\u4EA7\u54C1id> --color <\u8272> --silhouette <\u5ED3\u5F62> --material <\u6750\u8D28>",
2678
+ "",
2679
+ "\u4EA7\u54C1\u5EFA\u6863\uFF0C\u662F\u5546\u54C1\u89C6\u9891\u4E0B\u5355\u7684\u524D\u7F6E\uFF1A",
2680
+ " create \u5EFA\u4E00\u4EF6\u4EA7\u54C1\uFF08\u540D\u79F0\u5FC5\u586B\uFF1B\u7C7B\u76EE\u53EF\u7ED9\u53EF\u4E0D\u7ED9\uFF0C\u4E0D\u7ED9\u65F6\u7531\u670D\u52A1\u7AEF\u6309\u540D\u79F0\u8BC6\u522B\uFF09",
2681
+ " set-images \u4F20\u4EA7\u54C1\u56FE\uFF08JPEG/PNG/WebP \u7684\u6587\u4EF6\u8DEF\u5F84\uFF1B\u81F3\u5C11\u7ED9\u4E00\u4E2A\u89D2\u5EA6\uFF09",
2682
+ " confirm-lock \u786E\u8BA4\u5916\u89C2\u9501\uFF1A\u989C\u8272\u3001\u5ED3\u5F62\u3001\u6750\u8D28\u4E09\u6837\u90FD\u8981\u7ED9",
2683
+ "",
2684
+ "\u9009\u9879\uFF1A",
2685
+ " --name <\u540D\u79F0> \u4EA7\u54C1\u540D\u79F0",
2686
+ " --category <\u7C7B\u76EE> \u7C7B\u76EE\uFF1B\u4E0D\u7ED9\u65F6\u670D\u52A1\u7AEF\u6309\u540D\u79F0\u548C\u4ECB\u7ECD\u8BC6\u522B",
2687
+ " --front/--side/--back/--detail <\u56FE> \u4EA7\u54C1\u56FE\u7684\u89D2\u5EA6\u4E0E\u6587\u4EF6\u8DEF\u5F84",
2688
+ " --color <\u8272> \u5916\u89C2\u9501\uFF1A\u989C\u8272",
2689
+ " --silhouette <\u5ED3\u5F62> \u5916\u89C2\u9501\uFF1A\u5ED3\u5F62",
2690
+ " --material <\u6750\u8D28> \u5916\u89C2\u9501\uFF1A\u6750\u8D28",
2691
+ " --idempotency-key <\u952E> \u5E42\u7B49\u952E\uFF1B\u4E0D\u7ED9\u65F6\u81EA\u52A8\u751F\u6210\u5E76\u5728\u8F93\u51FA\u91CC\u56DE\u663E\uFF0C\u91CD\u8BD5\u540C\u4E00\u6B21\u8BF7\u6C42\u65F6\u539F\u6837\u590D\u7528",
2692
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2693
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2694
+ ].join("\n")
2695
+ },
2696
+ shorts: {
2697
+ run: shorts,
2698
+ acceptsBaseUrl: true,
2699
+ options: ["template", "props", "quote-hash", "wait", "timeout", "out", "idempotency-key"],
2700
+ positionals: ["\u5B50\u547D\u4EE4", "\u8BA2\u5355id"],
2701
+ help: [
2702
+ "\u7528\u6CD5\uFF1A",
2703
+ " buke shorts catalog [--json]",
2704
+ " buke shorts upload <\u56FE\u7247\u6587\u4EF6>",
2705
+ " buke shorts quote --template <\u6A21\u677Fid> --props <props.json|->",
2706
+ " buke shorts order --template <\u6A21\u677Fid> --props <props.json|-> [--quote-hash <hash>] [--wait] [--timeout <\u79D2>]",
2707
+ " buke shorts status <\u8BA2\u5355id> [--wait] [--timeout <\u79D2>]",
2708
+ " buke shorts download <\u8BA2\u5355id> [--out <\u8DEF\u5F84>]",
2709
+ " buke shorts cancel <\u8BA2\u5355id>",
2710
+ "",
2711
+ "\u6E32\u67D3\u77ED\u7247\uFF08\u6A21\u677F\u76F4\u51FA\u7684\u7AD6\u5C4F\u5546\u54C1\u77ED\u7247\uFF09\uFF1A",
2712
+ " catalog \u5217\u51FA\u5168\u90E8\u6A21\u677F\uFF08id\u3001\u65F6\u957F\u3001\u8981\u54EA\u4E9B\u7D20\u6750\uFF09",
2713
+ " upload \u6258\u7BA1\u4E00\u5F20\u7D20\u6750\u56FE\uFF0C\u6362\u56DE\u53EF\u4EE5\u76F4\u63A5\u586B\u8FDB\u6A21\u677F\u56FE\u7247\u69FD\u7684\u5730\u5740",
2714
+ " quote \u53EA\u8BFB\u62A5\u4EF7\uFF1B\u6536\u8D39\u7684\u6A21\u677F\u4E0B\u5355\u524D\u5FC5\u987B\u5148\u62FF quoteHash",
2715
+ " order \u4E0B\u5355\uFF1B\u540C\u952E\u540C\u5185\u5BB9\u91CD\u8BD5\u56DE\u540C\u4E00\u5F20\u5355",
2716
+ " status \u67E5\u8FDB\u5EA6\uFF1B--wait \u4E00\u76F4\u7B49\u5230\u51FA\u7ED3\u679C",
2717
+ " download \u4E0B\u8F7D\u6E32\u67D3\u597D\u7684\u6210\u7247\uFF08mp4\uFF09",
2718
+ " cancel \u53D6\u6D88\u8FD8\u6CA1\u5F00\u59CB\u7684\u8BA2\u5355",
2719
+ "",
2720
+ "\u9009\u9879\uFF1A",
2721
+ " --template <\u6A21\u677Fid> \u6A21\u677F id\uFF08catalog \u91CC\u5217\u51FA\u7684\uFF09",
2722
+ " --props <props.json|-> \u6A21\u677F\u53C2\u6570\uFF1A\u4E00\u4E2A JSON \u6587\u4EF6\uFF0C\u6216 - \u8868\u793A\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB",
2723
+ " --quote-hash <hash> \u62A5\u4EF7\u54C8\u5E0C\uFF08quote \u547D\u4EE4\u56DE\u7684\uFF09",
2724
+ " --wait \u4E00\u76F4\u7B49\u5230\u7EC8\u6001\uFF1A\u6210\u529F\u9000\u51FA 0\uFF0C\u4EFB\u52A1\u5931\u8D25\u9000\u51FA 9\uFF0C\u5230\u65F6\u9650\u9000\u51FA 8",
2725
+ " --timeout <\u79D2> --wait \u7684\u65F6\u9650\uFF0C\u9ED8\u8BA4 600",
2726
+ " --out <\u8DEF\u5F84> \u4E0B\u8F7D\u4FDD\u5B58\u7684\u4F4D\u7F6E\uFF1B\u4E0D\u7ED9\u65F6\u5B58\u5230\u5F53\u524D\u76EE\u5F55",
2727
+ " --idempotency-key <\u952E> \u5E42\u7B49\u952E\uFF1B\u4E0D\u7ED9\u65F6\u81EA\u52A8\u751F\u6210\u5E76\u5728\u8F93\u51FA\u91CC\u56DE\u663E\uFF0C\u91CD\u8BD5\u540C\u4E00\u6B21\u8BF7\u6C42\u65F6\u539F\u6837\u590D\u7528",
2728
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2729
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2730
+ ].join("\n")
2731
+ },
2732
+ videos: {
2733
+ run: videos,
2734
+ acceptsBaseUrl: true,
2735
+ options: [
2736
+ "product",
2737
+ "intent",
2738
+ "duration",
2739
+ "resolution",
2740
+ "speed",
2741
+ "aspect-ratio",
2742
+ "style-preset",
2743
+ "text",
2744
+ "yes",
2745
+ "wait",
2746
+ "timeout",
2747
+ "out",
2748
+ "idempotency-key"
2749
+ ],
2750
+ positionals: ["\u5B50\u547D\u4EE4", "\u89C6\u9891id"],
2751
+ help: [
2752
+ "\u7528\u6CD5\uFF1A",
2753
+ " buke videos create --product <\u4EA7\u54C1id> [--intent <\u4E00\u53E5\u8BDD>] [--duration <\u79D2>] [--resolution <r>]",
2754
+ " [--speed <\u6863>] [--aspect-ratio <a>] [--style-preset <p>] [--json]",
2755
+ " buke videos status <\u89C6\u9891id> [--wait] [--timeout <\u79D2>]",
2756
+ " buke videos message <\u89C6\u9891id> --text <\u5185\u5BB9>",
2757
+ " buke videos confirm <\u89C6\u9891id> [--yes]",
2758
+ " buke videos download <\u89C6\u9891id> [--out <\u8DEF\u5F84>]",
2759
+ "",
2760
+ "\u5546\u54C1\u89C6\u9891\uFF08\u5BFC\u6F14\u5168\u7A0B\u7F16\u6392\u7684\u90A3\u6761\u4EA7\u7EBF\uFF09\uFF1A",
2761
+ " create \u4E0B\u5355\uFF1B\u4EA7\u54C1\u5148\u7528 buke products \u5EFA\u597D\u3002\u53D7\u7406\u540E\u8F6E\u8BE2 status \u53D6\u8FDB\u5EA6",
2762
+ " status \u67E5\u8FDB\u5EA6\uFF1B--wait \u4E00\u76F4\u7B49\u5230\u7EC8\u6001",
2763
+ " message \u7ED9\u5BFC\u6F14\u56DE\u4E00\u53E5\u8BDD\uFF08\u8865\u610F\u56FE\u3001\u56DE\u7B54\u63D0\u95EE\uFF09",
2764
+ " confirm \u786E\u8BA4\u5F00\u62CD\uFF1A\u628A\u670D\u52A1\u7AEF\u6302\u51FA\u7684\u90A3\u5F20\u5361\u539F\u6837\u56DE\u4F20\uFF0C\u94B1\u5728\u505C\u70B9\u5F53\u9762\u8981\u6388\u6743",
2765
+ " download \u4E0B\u8F7D\u6210\u7247\uFF08mp4\uFF09",
2766
+ "",
2767
+ "\u9009\u9879\uFF1A",
2768
+ " --product <\u4EA7\u54C1id> \u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\uFF09",
2769
+ " --intent <\u4E00\u53E5\u8BDD> \u8FD9\u6761\u7247\u5B50\u60F3\u8BB2\u4EC0\u4E48\uFF1B\u4E0D\u7ED9\u65F6\u7531\u5BFC\u6F14\u6309\u4EA7\u54C1\u81EA\u5DF1\u60F3",
2770
+ " --duration <\u79D2> \u671F\u671B\u65F6\u957F",
2771
+ " --resolution <r> \u5206\u8FA8\u7387\u6863\u4F4D",
2772
+ " --speed <\u6863> \u751F\u6210\u901F\u5EA6\u6863\u4F4D",
2773
+ " --aspect-ratio <a> \u753B\u5E45\uFF08\u5982 9:16\uFF09",
2774
+ " --style-preset <p> \u753B\u9762\u98CE\u683C",
2775
+ " --text <\u5185\u5BB9> \u8981\u5E26\u7ED9\u5BFC\u6F14\u7684\u90A3\u53E5\u8BDD",
2776
+ " --yes confirm \u65F6\u771F\u7684\u63D0\u4EA4\uFF1B\u4E0D\u7ED9\u65F6\u53EA\u663E\u793A\u90A3\u5F20\u5361",
2777
+ " --wait \u4E00\u76F4\u7B49\u5230\u7EC8\u6001\uFF1A\u6210\u529F\u9000\u51FA 0\uFF0C\u4EFB\u52A1\u5931\u8D25\u9000\u51FA 9\uFF0C\u5230\u65F6\u9650\u9000\u51FA 8",
2778
+ " --timeout <\u79D2> --wait \u7684\u65F6\u9650\uFF0C\u9ED8\u8BA4 600",
2779
+ " --out <\u8DEF\u5F84> \u4E0B\u8F7D\u4FDD\u5B58\u7684\u4F4D\u7F6E\uFF1B\u4E0D\u7ED9\u65F6\u5B58\u5230\u5F53\u524D\u76EE\u5F55",
2780
+ " --idempotency-key <\u952E> \u5E42\u7B49\u952E\uFF1B\u4E0D\u7ED9\u65F6\u81EA\u52A8\u751F\u6210\u5E76\u5728\u8F93\u51FA\u91CC\u56DE\u663E\uFF0C\u91CD\u8BD5\u540C\u4E00\u6B21\u8BF7\u6C42\u65F6\u539F\u6837\u590D\u7528",
2781
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2782
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2783
+ ].join("\n")
2784
+ },
2785
+ drama: {
2786
+ run: drama,
2787
+ acceptsBaseUrl: true,
2788
+ options: ["product", "genre", "duration", "brief", "quote-hash", "yes", "wait", "timeout", "out", "idempotency-key"],
2789
+ positionals: ["\u5B50\u547D\u4EE4", "\u8BA2\u5355id"],
2790
+ help: [
2791
+ "\u7528\u6CD5\uFF1A",
2792
+ " buke drama create --product <\u4EA7\u54C1id> [--genre <\u9898\u6750id>] [--duration 30s|60s|90s] [--brief <JSON \u6587\u4EF6|->] [--json]",
2793
+ " buke drama status <\u8BA2\u5355id> [--wait] [--timeout <\u79D2>]",
2794
+ " buke drama quote <\u8BA2\u5355id>",
2795
+ " buke drama confirm <\u8BA2\u5355id> --quote-hash <\u62A5\u4EF7\u54C8\u5E0C> [--yes]",
2796
+ " buke drama cancel <\u8BA2\u5355id>",
2797
+ " buke drama download <\u8BA2\u5355id> [--out <\u8DEF\u5F84>]",
2798
+ "",
2799
+ "\u77ED\u5267\uFF08\u65E0\u4EBA\u503C\u5B88\u7F16\u6392\u5668\uFF09\uFF1A\u5E73\u53F0\u5148\u8DD1\u5199\u7A3F\u3001\u7ED1\u4EBA\u4E0E\u6392\u7248\u5206\u955C\uFF0C\u5230\u53EF\u62A5\u4EF7\u505C\u4E0B\u7B49\u4F60\u786E\u8BA4\uFF1B\u786E\u8BA4\u5F00\u62CD\u662F\u552F\u4E00\u6263\u8D39\u5165\u53E3\u3002",
2800
+ " create \u4E0B\u5355\uFF1B\u4EA7\u54C1\u5148\u7528 buke products \u5EFA\u597D\u3002\u53D7\u7406\u540E\u8F6E\u8BE2 status \u53D6\u8FDB\u5EA6",
2801
+ " status \u67E5\u8FDB\u5EA6\uFF08\u67E5\u8BE2\u672C\u8EAB\u4F1A\u63A8\u8FDB\u6392\u7248\uFF09\uFF1B--wait \u4E00\u76F4\u7B49\u5230\u7EC8\u6001\u6216\u505C\u9760",
2802
+ " quote \u53EA\u8BFB\u62A5\u4EF7\uFF1A\u62A5\u4EF7\u8981\u7D20\u4E0E\u62A5\u4EF7\u54C8\u5E0C\uFF0C\u786E\u8BA4\u5F00\u62CD\u65F6\u539F\u6837\u5E26\u56DE",
2803
+ " confirm \u786E\u8BA4\u5F00\u62CD\uFF1A\u5148\u53D6\u4E00\u6B21\u5F53\u524D\u62A5\u4EF7\u7ED9\u4EBA\u770B\uFF0C\u52A0 --yes \u624D\u63D0\u4EA4\u8FD9\u7B14\u5E03\u652F\u51FA",
2804
+ " cancel \u53D6\u6D88\uFF1A\u6CA1\u62CD\u7684\u955C\u5934\u9000\u56DE\u51BB\u7ED3\u989D\u5EA6\uFF0C\u5DF2\u7ECF\u62CD\u6210\u7684\u4FDD\u7559\u7167\u6263",
2805
+ " download \u4E0B\u8F7D\u76F4\u62FC\u597D\u7684\u6210\u7247\uFF08mp4\uFF09",
2806
+ "",
2807
+ "\u9009\u9879\uFF1A",
2808
+ " --product <\u4EA7\u54C1id> \u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\uFF09",
2809
+ " --genre <\u9898\u6750id> \u9898\u6750 id\uFF08\u5982 counterattack\u3001urban-ceo\u3001family\uFF09",
2810
+ " --duration <\u4F53\u91CF> \u6210\u7247\u4F53\u91CF\uFF1A30s\u300160s \u6216 90s\uFF1B\u4E0D\u7ED9\u65F6 30s",
2811
+ " --brief <JSON \u6587\u4EF6|-> \u6545\u4E8B\u8BBE\u5B9A\uFF1Adescription\u3001forbidden\u3001storyTone \u4E09\u9879\uFF0C\u4E00\u4E2A JSON \u6587\u4EF6\u6216 - \u8868\u793A\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB",
2812
+ " --quote-hash <\u54C8\u5E0C> \u62A5\u4EF7\u54C8\u5E0C\uFF08buke drama quote \u56DE\u7684\uFF09\uFF0C\u786E\u8BA4\u5F00\u62CD\u65F6\u539F\u6837\u5E26\u56DE",
2813
+ " --yes confirm \u65F6\u771F\u7684\u63D0\u4EA4\u5E76\u5F00\u62CD\uFF1B\u4E0D\u7ED9\u65F6\u53EA\u770B\u5F53\u524D\u62A5\u4EF7",
2814
+ " --wait \u4E00\u76F4\u7B49\u5230\u7EC8\u6001\u6216\u505C\u9760\uFF1A\u6210\u529F\u9000\u51FA 0\uFF0C\u5931\u8D25\u6216\u53D6\u6D88\u9000\u51FA 9\uFF0C\u5230\u65F6\u9650\u9000\u51FA 8",
2815
+ " --timeout <\u79D2> --wait \u7684\u65F6\u9650\uFF0C\u9ED8\u8BA4 600",
2816
+ " --out <\u8DEF\u5F84> \u4E0B\u8F7D\u4FDD\u5B58\u7684\u4F4D\u7F6E\uFF1B\u4E0D\u7ED9\u65F6\u5B58\u5230\u5F53\u524D\u76EE\u5F55",
2817
+ " --idempotency-key <\u952E> \u5E42\u7B49\u952E\uFF1B\u4E0D\u7ED9\u65F6\u81EA\u52A8\u751F\u6210\u5E76\u5728\u8F93\u51FA\u91CC\u56DE\u663E\uFF0C\u91CD\u8BD5\u540C\u4E00\u6B21\u8BF7\u6C42\u65F6\u539F\u6837\u590D\u7528",
2818
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2819
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2820
+ ].join("\n")
1104
2821
  }
1105
2822
  };
1106
2823
  var GENERAL_HELP = [
1107
2824
  "buke\uFF1A\u300A\u4E0D\u53EF\u601D\u8BAE\u300B\u7684\u547D\u4EE4\u884C\u5DE5\u5177",
1108
2825
  "",
1109
2826
  "\u7528\u6CD5\uFF1A",
1110
- " buke login [--base-url <\u5730\u5740>] [--json] \u7528\u4E00\u628A API \u5BC6\u94A5\u767B\u5F55\uFF0C\u5E76\u628A\u5B83\u4FDD\u5B58\u5728\u672C\u673A",
1111
- " buke whoami [--base-url <\u5730\u5740>] [--json] \u67E5\u770B\u5F53\u524D\u7528\u7684\u662F\u54EA\u628A\u5BC6\u94A5\u3001\u5C5E\u4E8E\u54EA\u4E2A\u56E2\u961F",
1112
- " buke logout [--json] \u9000\u51FA\u767B\u5F55\uFF0C\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5",
2827
+ " buke login [--base-url <\u5730\u5740>] [--json] \u7528\u4E00\u628A API \u5BC6\u94A5\u767B\u5F55\uFF0C\u5E76\u628A\u5B83\u4FDD\u5B58\u5728\u672C\u673A",
2828
+ " buke whoami [--base-url <\u5730\u5740>] [--json] \u67E5\u770B\u5F53\u524D\u7528\u7684\u662F\u54EA\u628A\u5BC6\u94A5\u3001\u5C5E\u4E8E\u54EA\u4E2A\u56E2\u961F",
2829
+ " buke logout [--json] \u9000\u51FA\u767B\u5F55\uFF0C\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5",
2830
+ " buke balance [--json] \u67E5\u8BE2\u8D26\u6237\u4F59\u989D",
2831
+ " buke products <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u4EA7\u54C1\u5EFA\u6863\uFF1Acreate\u3001set-images\u3001confirm-lock",
2832
+ " buke shorts <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u6E32\u67D3\u77ED\u7247\uFF1Acatalog\u3001upload\u3001quote\u3001order\u3001status\u3001download\u3001cancel",
2833
+ " buke videos <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u5546\u54C1\u89C6\u9891\uFF1Acreate\u3001status\u3001message\u3001confirm\u3001download",
2834
+ " buke drama <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u77ED\u5267\uFF1Acreate\u3001status\u3001quote\u3001confirm\u3001cancel\u3001download",
1113
2835
  " buke --help | -h \u663E\u793A\u8FD9\u4EFD\u8BF4\u660E\uFF1Bbuke <\u547D\u4EE4> --help \u770B\u5355\u6761\u547D\u4EE4",
1114
2836
  " buke --version | -v \u663E\u793A\u7248\u672C\u53F7",
1115
2837
  "",
@@ -1122,7 +2844,7 @@ var GENERAL_HELP = [
1122
2844
  ...KEY_HOWTO,
1123
2845
  "",
1124
2846
  "\u73AF\u5883\u53D8\u91CF\uFF1A",
1125
- " BUKE_API_KEY API \u5BC6\u94A5\uFF1B\u8BBE\u4E86\u5B83\uFF0Clogin \u548C whoami \u90FD\u76F4\u63A5\u7528\u5B83",
2847
+ " BUKE_API_KEY API \u5BC6\u94A5\uFF1B\u8BBE\u4E86\u5B83\uFF0Clogin \u548C\u522B\u7684\u547D\u4EE4\u90FD\u76F4\u63A5\u7528\u5B83",
1126
2848
  " BUKE_BASE_URL \u670D\u52A1\u5730\u5740\uFF0C\u4F5C\u7528\u540C --base-url",
1127
2849
  " BUKE_CONFIG_DIR \u4FDD\u5B58\u51ED\u636E\u7684\u76EE\u5F55\uFF0C\u8981\u5199\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u8BBE\u65F6\u662F $XDG_CONFIG_HOME/buke\uFF1B",
1128
2850
  " \u8FD9\u4E2A\u4E5F\u6CA1\u8BBE\u5C31\u662F ~/.config/buke\uFF08Windows \u4E0A\u662F %APPDATA%\\buke\uFF09",
@@ -1134,34 +2856,63 @@ var GENERAL_HELP = [
1134
2856
  " 4 \u4F59\u989D\u4E0D\u8DB3",
1135
2857
  " 5 \u670D\u52A1\u7AEF\u6216\u7F51\u7EDC\u51FA\u9519\uFF1B\u672C\u673A\u7684\u51ED\u636E\u6587\u4EF6\u8BFB\u4E0D\u4E86\u3001\u5199\u4E0D\u4E86\u4E5F\u662F\u8FD9\u4E2A\u7801",
1136
2858
  " 6 \u62A5\u4EF7\u53D8\u4E86",
1137
- " 7 \u8BF7\u6C42\u592A\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
2859
+ " 7 \u8BF7\u6C42\u592A\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\uFF1B\u6216\u8FD9\u4E00\u6B65\u8FD8\u6CA1\u5230\u65F6\u5019\uFF0C\u7A0D\u540E\u518D\u6765",
1138
2860
  " 8 \u7B49\u5F85\u8D85\u65F6",
1139
2861
  " 9 \u4EFB\u52A1\u5931\u8D25",
1140
2862
  " 10 \u8981\u627E\u7684\u4E1C\u897F\u4E0D\u5B58\u5728",
1141
2863
  " 130 \u88AB Ctrl-C \u6253\u65AD",
1142
2864
  "",
1143
- "\u5F53\u524D\u7248\u672C\u53EA\u6709\u767B\u5F55\u76F8\u5173\u7684\u547D\u4EE4\uFF1B\u4E0B\u5355\u3001\u67E5\u8FDB\u5EA6\u3001\u4E0B\u8F7D\u7B49\u547D\u4EE4\u4F1A\u5728\u540E\u7EED\u7248\u672C\u63D0\u4F9B\u3002"
2865
+ "\u8FD9\u4E00\u7248\u80FD\u4E0B\u5355\u3001\u67E5\u8FDB\u5EA6\u3001\u4E0B\u8F7D\u6210\u7247\uFF08\u6E32\u67D3\u77ED\u7247\u3001\u5546\u54C1\u89C6\u9891\u4E0E\u77ED\u5267\u4E09\u6761\u7EBF\uFF09\u3002"
1144
2866
  ].join("\n");
1145
2867
  var MAX_ECHO_LENGTH = 40;
1146
2868
  function echo(item) {
1147
2869
  return item.length > MAX_ECHO_LENGTH ? `${item.slice(0, MAX_ECHO_LENGTH)}\u2026` : item;
1148
2870
  }
2871
+ var GLOBAL_OPTIONS = /* @__PURE__ */ new Set(["help", "version", "json", "base-url"]);
2872
+ var STRING_OPTIONS = /* @__PURE__ */ new Set([
2873
+ "base-url",
2874
+ "idempotency-key",
2875
+ "name",
2876
+ "category",
2877
+ "front",
2878
+ "side",
2879
+ "back",
2880
+ "detail",
2881
+ "color",
2882
+ "silhouette",
2883
+ "material",
2884
+ "template",
2885
+ "props",
2886
+ "quote-hash",
2887
+ "timeout",
2888
+ "out",
2889
+ "product",
2890
+ "intent",
2891
+ "duration",
2892
+ "resolution",
2893
+ "speed",
2894
+ "aspect-ratio",
2895
+ "style-preset",
2896
+ "text",
2897
+ "genre",
2898
+ "brief"
2899
+ ]);
2900
+ var BOOLEAN_OPTIONS = /* @__PURE__ */ new Set(["help", "version", "json", "wait", "yes"]);
1149
2901
  function parse(argv) {
1150
2902
  try {
2903
+ const options = {};
2904
+ for (const name2 of STRING_OPTIONS) options[name2] = { type: "string" };
2905
+ for (const name2 of BOOLEAN_OPTIONS) options[name2] = { type: "boolean", ...name2 === "help" ? { short: "h" } : name2 === "version" ? { short: "v" } : {} };
1151
2906
  const { values, positionals } = parseArgs({
1152
2907
  args: argv,
1153
- options: {
1154
- help: { type: "boolean", short: "h" },
1155
- version: { type: "boolean", short: "v" },
1156
- json: { type: "boolean" },
1157
- "base-url": { type: "string" }
1158
- },
2908
+ options,
1159
2909
  strict: true,
1160
2910
  allowPositionals: true
1161
2911
  });
1162
2912
  return {
1163
2913
  command: positionals[0],
1164
- extraPositionals: Math.max(0, positionals.length - 1),
2914
+ positionals,
2915
+ values,
1165
2916
  help: values.help === true,
1166
2917
  version: values.version === true,
1167
2918
  baseUrl: values["base-url"]
@@ -1193,9 +2944,21 @@ async function dispatch(argv, io, out) {
1193
2944
  const parsed = parse(argv);
1194
2945
  const command = parsed.command !== void 0 && Object.hasOwn(COMMANDS, parsed.command) ? COMMANDS[parsed.command] : void 0;
1195
2946
  if (parsed.command !== void 0 && !command) throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u547D\u4EE4 ${echo(parsed.command)}`);
1196
- if (parsed.extraPositionals > 0) throw usageError(`${parsed.command} \u540E\u9762\u4E0D\u63A5\u522B\u7684\u53C2\u6570`);
1197
- if (command && !command.acceptsBaseUrl && parsed.baseUrl !== void 0) {
1198
- throw usageError(`${parsed.command} \u4E0D\u8BA4\u8BC6\u9009\u9879 --base-url`);
2947
+ const args = parsed.positionals.slice(1);
2948
+ if (command) {
2949
+ if (command.positionals.length === 0 && args.length > 0) {
2950
+ throw usageError(`${parsed.command} \u540E\u9762\u4E0D\u63A5\u522B\u7684\u53C2\u6570`);
2951
+ }
2952
+ if (args.length > command.positionals.length) {
2953
+ throw usageError(`${parsed.command} \u540E\u9762\u7684\u53C2\u6570\u591A\u4E86\uFF08${echo(args[command.positionals.length])}\uFF09`);
2954
+ }
2955
+ if (!command.acceptsBaseUrl && parsed.baseUrl !== void 0) {
2956
+ throw usageError(`${parsed.command} \u4E0D\u8BA4\u8BC6\u9009\u9879 --base-url`);
2957
+ }
2958
+ for (const [name2, value] of Object.entries(parsed.values)) {
2959
+ if (value === void 0 || GLOBAL_OPTIONS.has(name2)) continue;
2960
+ if (!command.options.includes(name2)) throw usageError(`${parsed.command} \u4E0D\u8BA4\u8BC6\u9009\u9879 --${name2}`);
2961
+ }
1199
2962
  }
1200
2963
  if (parsed.help || !command && !parsed.version) {
1201
2964
  const text = command ? command.help : GENERAL_HELP;
@@ -1206,7 +2969,7 @@ async function dispatch(argv, io, out) {
1206
2969
  out.result([`${PACKAGE_NAME}/${VERSION}`], { name: PACKAGE_NAME, version: VERSION });
1207
2970
  return;
1208
2971
  }
1209
- if (command) await command.run({ io, out, baseUrl: parsed.baseUrl });
2972
+ if (command) await command.run({ io, out, baseUrl: parsed.baseUrl, args, values: parsed.values });
1210
2973
  }
1211
2974
  async function run(argv, io) {
1212
2975
  let out;