bukesiyi-cli 0.1.3 → 0.3.1

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 +21 -8
  2. package/dist/main.js +1963 -99
  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
 
@@ -87,6 +89,7 @@ var CliError = class _CliError extends Error {
87
89
  retryAfterSeconds;
88
90
  httpStatus;
89
91
  hints;
92
+ recovery;
90
93
  constructor(init) {
91
94
  super(redactKeys(init.message));
92
95
  this.name = "CliError";
@@ -97,6 +100,7 @@ var CliError = class _CliError extends Error {
97
100
  if (init.retryAfterSeconds !== void 0) this.retryAfterSeconds = init.retryAfterSeconds;
98
101
  if (init.httpStatus !== void 0) this.httpStatus = init.httpStatus;
99
102
  this.hints = init.hints ?? [];
103
+ if (init.recovery !== void 0) this.recovery = init.recovery;
100
104
  }
101
105
  withHint(hint) {
102
106
  return new _CliError({
@@ -107,15 +111,29 @@ var CliError = class _CliError extends Error {
107
111
  traceId: this.traceId,
108
112
  retryAfterSeconds: this.retryAfterSeconds,
109
113
  httpStatus: this.httpStatus,
110
- hints: [...this.hints, hint]
114
+ hints: [...this.hints, hint],
115
+ recovery: this.recovery
116
+ });
117
+ }
118
+ withRecovery(recovery) {
119
+ return new _CliError({
120
+ code: this.code,
121
+ message: this.message,
122
+ exitCode: this.exitCode,
123
+ retryable: this.retryable,
124
+ traceId: this.traceId,
125
+ retryAfterSeconds: this.retryAfterSeconds,
126
+ httpStatus: this.httpStatus,
127
+ hints: this.hints,
128
+ recovery
111
129
  });
112
130
  }
113
131
  };
114
- function usageError(message) {
115
- return new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${message}\u3002\u8FD0\u884C buke --help \u770B\u7528\u6CD5` });
132
+ function usageError(message2) {
133
+ return new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${message2}\u3002\u8FD0\u884C buke --help \u770B\u7528\u6CD5` });
116
134
  }
117
- function interruptedError(message = "\u5DF2\u4E2D\u65AD") {
118
- return new CliError({ code: "CLI_INTERRUPTED", exitCode: EXIT.INTERRUPTED, message });
135
+ function interruptedError(message2 = "\u5DF2\u4E2D\u65AD") {
136
+ return new CliError({ code: "CLI_INTERRUPTED", exitCode: EXIT.INTERRUPTED, message: message2 });
119
137
  }
120
138
  function isRecord(value) {
121
139
  return typeof value === "object" && value !== null;
@@ -215,8 +233,8 @@ function malformedKey() {
215
233
  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
234
  });
217
235
  }
218
- function missingKey(message) {
219
- return new CliError({ code: "CLI_KEY_MISSING", exitCode: EXIT.USAGE, message });
236
+ function missingKey(message2) {
237
+ return new CliError({ code: "CLI_KEY_MISSING", exitCode: EXIT.USAGE, message: message2 });
220
238
  }
221
239
  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
240
  function assertKeyFormat(key) {
@@ -239,10 +257,10 @@ function toBuffer(chunk) {
239
257
  return Buffer.from(chunk);
240
258
  }
241
259
  function readToEnd(stdin, timeoutMs, signal) {
242
- return new Promise((resolve2, reject) => {
260
+ return new Promise((resolve, reject) => {
243
261
  const state = stdin;
244
262
  if (state.readableEnded === true || state.destroyed === true) {
245
- resolve2("");
263
+ resolve("");
246
264
  return;
247
265
  }
248
266
  const chunks = [];
@@ -270,7 +288,7 @@ function readToEnd(stdin, timeoutMs, signal) {
270
288
  }
271
289
  chunks.push(piece);
272
290
  };
273
- const onEnd = () => settle(() => resolve2(Buffer.concat(chunks).toString("utf8").trim()));
291
+ const onEnd = () => settle(() => resolve(Buffer.concat(chunks).toString("utf8").trim()));
274
292
  const onError = (error) => settle(() => reject(missingKey(`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u6807\u51C6\u8F93\u5165\u51FA\u9519\uFF08${briefReason(error)}\uFF09`)));
275
293
  const timer = setTimeout(() => {
276
294
  settle(
@@ -290,7 +308,7 @@ function readToEnd(stdin, timeoutMs, signal) {
290
308
  });
291
309
  }
292
310
  function readHiddenLine(stdin, signal) {
293
- return new Promise((resolve2, reject) => {
311
+ return new Promise((resolve, reject) => {
294
312
  let typed = "";
295
313
  let escape = "none";
296
314
  let settled = false;
@@ -321,7 +339,7 @@ function readHiddenLine(stdin, signal) {
321
339
  continue;
322
340
  }
323
341
  if (char === "\r" || char === "\n") {
324
- settle(() => resolve2(typed));
342
+ settle(() => resolve(typed));
325
343
  return;
326
344
  }
327
345
  if (char === CTRL_C) {
@@ -329,7 +347,7 @@ function readHiddenLine(stdin, signal) {
329
347
  return;
330
348
  }
331
349
  if (char === CTRL_D) {
332
- settle(() => typed === "" ? reject(missingKey(NO_KEY_READ)) : resolve2(typed));
350
+ settle(() => typed === "" ? reject(missingKey(NO_KEY_READ)) : resolve(typed));
333
351
  return;
334
352
  }
335
353
  if (char === DELETE || char === BACKSPACE) {
@@ -349,7 +367,7 @@ function readHiddenLine(stdin, signal) {
349
367
  }
350
368
  if (escape === "started") escape = "carried";
351
369
  };
352
- const onEnd = () => settle(() => resolve2(typed));
370
+ const onEnd = () => settle(() => resolve(typed));
353
371
  const onError = (error) => settle(() => reject(missingKey(`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u6807\u51C6\u8F93\u5165\u51FA\u9519\uFF08${briefReason(error)}\uFF09`)));
354
372
  const onAbort = () => settle(() => reject(interruptedError(CANCELLED)));
355
373
  stdin.on("data", onData);
@@ -394,35 +412,100 @@ async function readNewKey(io) {
394
412
 
395
413
  // package.json
396
414
  var name = "bukesiyi-cli";
397
- var version = "0.1.3";
415
+ var version = "0.3.1";
398
416
 
399
417
  // src/version.ts
400
418
  var PACKAGE_NAME = name;
401
419
  var VERSION = version;
402
420
 
421
+ // src/task-recovery.ts
422
+ function shellArgument(value) {
423
+ return /^[A-Za-z0-9_][A-Za-z0-9_.:-]*$/.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
424
+ }
425
+ function taskRecovery(taskType, taskId, status4, baseUrl, failureCode) {
426
+ const customOrigin = baseUrl !== DEFAULT_BASE_URL;
427
+ return {
428
+ taskType,
429
+ acceptance: "confirmed",
430
+ taskId,
431
+ status: status4,
432
+ ...customOrigin ? { baseUrl } : {},
433
+ ...failureCode === void 0 || failureCode === "" ? {} : { failureCode },
434
+ nextAction: "query",
435
+ statusCommand: `buke ${taskType} status ${shellArgument(taskId)}` + (customOrigin ? ` --base-url ${shellArgument(baseUrl)}` : "")
436
+ };
437
+ }
438
+ async function retainTaskOnError(recovery, operation) {
439
+ try {
440
+ return await operation();
441
+ } catch (error) {
442
+ const failure = toCliError(error);
443
+ throw failure.withRecovery({ ...recovery(), ...failure.recovery });
444
+ }
445
+ }
446
+ async function retainSubmissionOnError(taskType, idempotencyKey, baseUrl, operation) {
447
+ try {
448
+ return await operation();
449
+ } catch (error) {
450
+ const failure = toCliError(error);
451
+ const rejectedResponse = failure.httpStatus !== void 0 && failure.httpStatus >= 400 && failure.httpStatus < 500 && failure.httpStatus !== 408;
452
+ const uncertainResponse = failure.httpStatus === 408 || failure.httpStatus !== void 0 && failure.httpStatus >= 500 && failure.httpStatus < 600;
453
+ const uncertainTransport = ["CLI_NETWORK", "CLI_TIMEOUT", "CLI_BAD_RESPONSE", "CLI_INTERRUPTED"].includes(failure.code);
454
+ if (!rejectedResponse && (uncertainResponse || uncertainTransport)) {
455
+ throw failure.withRecovery({
456
+ taskType,
457
+ acceptance: "unknown",
458
+ idempotencyKey,
459
+ nextAction: "query",
460
+ ...baseUrl !== DEFAULT_BASE_URL ? { baseUrl } : {}
461
+ });
462
+ }
463
+ throw failure;
464
+ }
465
+ }
466
+
403
467
  // src/client.ts
404
468
  var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
469
+ var DEFAULT_DOWNLOAD_TIMEOUT_MS = 12e4;
470
+ var MAX_DOWNLOAD_BYTES = 1024 * 1024 * 1024;
405
471
  var ME_PATH = "/api/open/v1/me";
406
472
  var MAX_BODY_BYTES = 1024 * 1024;
407
473
  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";
474
+ var EXIT_BY_SERVER_CODE = {
475
+ OPEN_API_INSUFFICIENT_BALANCE: EXIT.FUNDS,
476
+ OPEN_API_QUOTE_CHANGED: EXIT.QUOTE_CHANGED,
477
+ OPEN_API_QUOTE_REQUIRED: EXIT.QUOTE_CHANGED,
478
+ OPEN_API_ORDER_NOT_READY: EXIT.RETRY_LATER,
479
+ OPEN_API_QUOTA_EXCEEDED: EXIT.RETRY_LATER,
480
+ OPEN_API_ORDER_NOT_FOUND: EXIT.NOT_FOUND,
481
+ OPEN_API_PRODUCT_NOT_FOUND: EXIT.NOT_FOUND,
482
+ OPEN_API_TEMPLATE_NOT_FOUND: EXIT.NOT_FOUND,
483
+ // 「这单已交付/已失败,不能取消」也是 409,按状态码会被当成「报价变了」(6);
484
+ // 它说的是这个动作对当前状态不成立,重试也不会变,按「用法不对」(2)定。
485
+ OPEN_API_ORDER_NOT_CANCELLABLE: EXIT.USAGE
486
+ };
408
487
  function userAgent() {
409
488
  return `${PACKAGE_NAME}/${VERSION} node/${process.versions.node}`;
410
489
  }
411
490
  function isRecord2(value) {
412
491
  return typeof value === "object" && value !== null && !Array.isArray(value);
413
492
  }
414
- function retryableStatus(status) {
415
- return status === 429 || status >= 500;
493
+ function numberOrUndefined(value) {
494
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
416
495
  }
417
- function unreadableResponse(status) {
418
- const retryable = retryableStatus(status);
496
+ function retryableStatus(status4) {
497
+ return status4 === 429 || status4 >= 500;
498
+ }
499
+ function unreadableResponse(status4) {
500
+ const retryable = retryableStatus(status4);
419
501
  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
502
  return new CliError({
421
503
  code: "CLI_BAD_RESPONSE",
422
504
  exitCode: EXIT.SERVER,
423
505
  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] : []
506
+ httpStatus: status4,
507
+ message: `\u670D\u52A1\u5668\u56DE\u4E86\u4E00\u4E2A\u770B\u4E0D\u61C2\u7684\u56DE\u5E94\uFF08HTTP ${status4}\uFF09\uFF0C${advice}`,
508
+ hints: status4 === 404 ? [NOT_FOUND_HINT] : []
426
509
  });
427
510
  }
428
511
  function redirectTarget(response, requestUrl) {
@@ -442,11 +525,15 @@ function discardBody(response) {
442
525
  }
443
526
  }
444
527
  async function readBody(response, abandoned) {
528
+ const bytes = await readBodyBytes(response, abandoned, MAX_BODY_BYTES);
529
+ return bytes === null ? null : bytes.toString("utf8");
530
+ }
531
+ async function readBodyBytes(response, abandoned, maxBytes) {
445
532
  const body = response.body;
446
- if (body === null) return "";
533
+ if (body === null) return Buffer.alloc(0);
447
534
  if (body === void 0) {
448
535
  const text = await response.text();
449
- return Buffer.byteLength(text, "utf8") > MAX_BODY_BYTES ? null : text;
536
+ return Buffer.byteLength(text, "utf8") > maxBytes ? null : Buffer.from(text, "utf8");
450
537
  }
451
538
  const reader = body.getReader();
452
539
  const release2 = () => void reader.cancel().catch(() => void 0);
@@ -459,7 +546,7 @@ async function readBody(response, abandoned) {
459
546
  const { done, value } = await reader.read();
460
547
  if (done) break;
461
548
  size += value.byteLength;
462
- if (size > MAX_BODY_BYTES) {
549
+ if (size > maxBytes) {
463
550
  await reader.cancel().catch(() => void 0);
464
551
  return null;
465
552
  }
@@ -468,7 +555,7 @@ async function readBody(response, abandoned) {
468
555
  } finally {
469
556
  abandoned.removeEventListener("abort", release2);
470
557
  }
471
- return Buffer.concat(chunks).toString("utf8");
558
+ return Buffer.concat(chunks);
472
559
  }
473
560
  function isTimeout(error) {
474
561
  const name2 = typeof error === "object" && error !== null ? error.name : void 0;
@@ -478,9 +565,9 @@ function proxyHints(origin, deps) {
478
565
  const variable = deps.env ? envProxyFor(origin, deps.env) : null;
479
566
  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
567
  }
481
- async function exchange(url, apiKey, deps, origin) {
568
+ async function exchange(url, apiKey, plan, deps, origin) {
482
569
  if (deps.signal?.aborted) throw interruptedError();
483
- const timeoutMs = deps.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
570
+ const timeoutMs = plan.timeoutMs ?? deps.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
484
571
  const controller = new AbortController();
485
572
  let timedOut = false;
486
573
  let timer;
@@ -500,15 +587,20 @@ async function exchange(url, apiKey, deps, origin) {
500
587
  };
501
588
  });
502
589
  deps.signal?.addEventListener("abort", stop, { once: true });
590
+ const headers = {
591
+ Authorization: `Bearer ${apiKey}`,
592
+ Accept: "application/json",
593
+ "User-Agent": userAgent()
594
+ };
595
+ if (plan.contentType !== void 0) headers["Content-Type"] = plan.contentType;
596
+ if (plan.idempotencyKey !== void 0) headers["Idempotency-Key"] = plan.idempotencyKey;
597
+ const asBytes = plan.asBytes === true;
503
598
  const send = deps.fetch;
504
599
  const work = (async () => {
505
600
  const response = await send(url, {
506
- method: "GET",
507
- headers: {
508
- Authorization: `Bearer ${apiKey}`,
509
- Accept: "application/json",
510
- "User-Agent": userAgent()
511
- },
601
+ method: plan.method,
602
+ headers,
603
+ ...plan.body === void 0 ? {} : { body: plan.body },
512
604
  // 跟着跳转走,密钥就会被带到一个使用者没有点头的地方。
513
605
  redirect: "manual",
514
606
  signal: controller.signal
@@ -517,6 +609,11 @@ async function exchange(url, apiKey, deps, origin) {
517
609
  discardBody(response);
518
610
  return { status: response.status, redirectedTo: redirectTarget(response, url) };
519
611
  }
612
+ if (asBytes) {
613
+ const bytes = await readBodyBytes(response, controller.signal, MAX_DOWNLOAD_BYTES);
614
+ if (bytes === null) throw unreadableResponse(response.status);
615
+ return { status: response.status, bytes, headers: response.headers };
616
+ }
520
617
  return { status: response.status, text: await readBody(response, controller.signal) };
521
618
  })();
522
619
  work.catch(() => void 0);
@@ -554,36 +651,40 @@ function parseJson(text) {
554
651
  return null;
555
652
  }
556
653
  }
557
- function serverError(status, body, apiKey) {
654
+ function serverError(status4, body, apiKey) {
558
655
  const error = isRecord2(body) ? body.error : void 0;
559
656
  if (!isRecord2(error) || typeof error.code !== "string" || typeof error.message !== "string") return null;
560
657
  const retryAfter = error.retryAfterSeconds;
561
658
  return new CliError({
562
659
  code: error.code,
563
660
  // 对面把收到的密钥抄回话里时,在这一层就抹掉。
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),
661
+ message: error.message.trim() === "" ? `\u670D\u52A1\u5668\u62D2\u7EDD\u4E86\u8FD9\u6B21\u8BF7\u6C42\uFF08HTTP ${status4}\uFF09` : redactSecret(error.message, apiKey),
662
+ // 列进显式表的码按表定(409 在业务里分「报价变了」和「还没好」两种),其余按状态码。
663
+ exitCode: EXIT_BY_SERVER_CODE[error.code] ?? exitCodeForHttpStatus(status4),
664
+ retryable: typeof error.retryable === "boolean" ? error.retryable : retryableStatus(status4),
567
665
  traceId: typeof error.traceId === "string" ? error.traceId : null,
568
666
  retryAfterSeconds: typeof retryAfter === "number" && Number.isFinite(retryAfter) ? retryAfter : void 0,
569
- httpStatus: status,
570
- hints: status === 404 ? [NOT_FOUND_HINT] : []
667
+ httpStatus: status4,
668
+ // 那句「这个地址不提供 API 调用」只对端点级的 404 有意义:业务里的 404(单不存在、产品不存在)
669
+ // 说的是东西没找到,不是地址写错。
670
+ hints: status4 === 404 && error.code === "OPEN_API_NOT_FOUND" ? [NOT_FOUND_HINT] : []
571
671
  });
572
672
  }
573
- async function getJson(baseUrl, apiKey, path, deps) {
673
+ function redirectError(redirectedTo) {
674
+ const where = redirectedTo ? `\uFF08${redirectedTo}\uFF09` : "";
675
+ return new CliError({
676
+ code: "CLI_REDIRECT",
677
+ exitCode: EXIT.SERVER,
678
+ retryable: false,
679
+ 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`
680
+ });
681
+ }
682
+ async function requestJson(baseUrl, apiKey, plan, deps) {
574
683
  const origin = normalizeBaseUrl(baseUrl);
575
684
  assertKeyFormat(apiKey);
576
685
  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
- }
686
+ const result = await exchange(`${origin}${plan.path}`, apiKey, plan, deps, origin);
687
+ if ("redirectedTo" in result) throw redirectError(result.redirectedTo);
587
688
  const parsed = parseJson(result.text);
588
689
  if (result.status >= 200 && result.status < 300) {
589
690
  if (!parsed) throw unreadableResponse(result.status);
@@ -591,6 +692,357 @@ async function getJson(baseUrl, apiKey, path, deps) {
591
692
  }
592
693
  throw (parsed && serverError(result.status, parsed.value, apiKey)) ?? unreadableResponse(result.status);
593
694
  }
695
+ async function getJson(baseUrl, apiKey, path, deps) {
696
+ return requestJson(baseUrl, apiKey, { method: "GET", path }, deps);
697
+ }
698
+ async function postJson(baseUrl, apiKey, path, body, idempotencyKey, deps) {
699
+ return requestJson(
700
+ baseUrl,
701
+ apiKey,
702
+ { method: "POST", path, body: JSON.stringify(body), contentType: "application/json", idempotencyKey },
703
+ deps
704
+ );
705
+ }
706
+ async function putRaw(baseUrl, apiKey, path, bytes, contentType, idempotencyKey, deps) {
707
+ return requestJson(baseUrl, apiKey, { method: "PUT", path, body: bytes, contentType, idempotencyKey }, deps);
708
+ }
709
+ async function postRaw(baseUrl, apiKey, path, bytes, contentType, idempotencyKey, deps) {
710
+ return requestJson(baseUrl, apiKey, { method: "POST", path, body: bytes, contentType, idempotencyKey }, deps);
711
+ }
712
+ async function downloadFile(baseUrl, apiKey, path, deps) {
713
+ const origin = normalizeBaseUrl(baseUrl);
714
+ assertKeyFormat(apiKey);
715
+ assertReachedDirectly(origin, deps.env);
716
+ const result = await exchange(`${origin}${path}`, apiKey, downloadPlan(path, deps), deps, origin);
717
+ if ("redirectedTo" in result) throw redirectError(result.redirectedTo);
718
+ if (result.status < 200 || result.status >= 300) {
719
+ const text = result.bytes.toString("utf8");
720
+ const parsed = parseJson(text);
721
+ throw (parsed && serverError(result.status, parsed.value, apiKey)) ?? unreadableResponse(result.status);
722
+ }
723
+ return { bytes: result.bytes, filename: filenameFromDisposition(result.headers.get("content-disposition")) };
724
+ }
725
+ function downloadPlan(path, deps) {
726
+ return {
727
+ method: "GET",
728
+ path,
729
+ // 下载不共用 JSON 请求 15 秒的默认时限;调用方在 deps 里给了更短的,照他的。
730
+ timeoutMs: deps.requestTimeoutMs !== void 0 ? Math.max(deps.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS) : DEFAULT_DOWNLOAD_TIMEOUT_MS,
731
+ asBytes: true
732
+ };
733
+ }
734
+ function filenameFromDisposition(value) {
735
+ if (typeof value !== "string" || value === "") return null;
736
+ const star = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
737
+ if (star !== null) {
738
+ try {
739
+ const decoded = decodeURIComponent(star[1]);
740
+ if (decoded !== "") return decoded;
741
+ } catch {
742
+ }
743
+ }
744
+ const plain = /filename="?([^";]+)"?/.exec(value);
745
+ return plain !== null ? plain[1] : null;
746
+ }
747
+ function required(report) {
748
+ return (value) => {
749
+ if (typeof value !== "string" || value === "") {
750
+ report.push("\u5B57\u6BB5\u5F62\u72B6\u4E0D\u5BF9");
751
+ return "";
752
+ }
753
+ return value;
754
+ };
755
+ }
756
+ function parseBalance(body) {
757
+ const raw = isRecord2(body) ? body.balance : void 0;
758
+ if (!isRecord2(raw)) return null;
759
+ const problems = [];
760
+ const text = required(problems);
761
+ const currency = text(raw.currency);
762
+ const updatedAt = text(raw.updatedAt);
763
+ const available = numberOrUndefined(raw.available);
764
+ const held = numberOrUndefined(raw.held);
765
+ const spent = numberOrUndefined(raw.spent);
766
+ if (problems.length > 0 || available === void 0 || held === void 0 || spent === void 0) return null;
767
+ return { currency, available, held, spent, updatedAt };
768
+ }
769
+ function parseProductImages(value) {
770
+ if (!Array.isArray(value)) return null;
771
+ const images = [];
772
+ for (const item of value) {
773
+ if (!isRecord2(item)) return null;
774
+ const problems = [];
775
+ const text = required(problems);
776
+ const image = {
777
+ id: text(item.id),
778
+ angle: typeof item.angle === "string" ? item.angle : "",
779
+ url: text(item.url),
780
+ mime: text(item.mime),
781
+ bytes: numberOrUndefined(item.bytes) ?? -1,
782
+ sha256: text(item.sha256)
783
+ };
784
+ if (problems.length > 0 || image.bytes < 0) return null;
785
+ images.push(image);
786
+ }
787
+ return images;
788
+ }
789
+ function parseProduct(body) {
790
+ const product = isRecord2(body) ? body.product : void 0;
791
+ if (!isRecord2(product)) return null;
792
+ const problems = [];
793
+ const text = required(problems);
794
+ const images = parseProductImages(product.images);
795
+ const sku = typeof product.sku === "string" ? product.sku : null;
796
+ const parsed = {
797
+ id: text(product.id),
798
+ name: text(product.name),
799
+ category: text(product.category),
800
+ sku,
801
+ createdAt: text(product.createdAt),
802
+ images: images ?? []
803
+ };
804
+ if (problems.length > 0 || images === null) return null;
805
+ return parsed;
806
+ }
807
+ function parseProductLock(body) {
808
+ const lock = isRecord2(body) ? body.productLock : void 0;
809
+ if (!isRecord2(lock)) return null;
810
+ const problems = [];
811
+ const text = required(problems);
812
+ const parsed = { colorName: text(lock.colorName), silhouette: text(lock.silhouette), material: text(lock.material) };
813
+ return problems.length > 0 ? null : parsed;
814
+ }
815
+ function parseCatalogTemplate(item) {
816
+ if (!isRecord2(item)) return null;
817
+ const problems = [];
818
+ const text = required(problems);
819
+ const durationSeconds = numberOrUndefined(item.durationSeconds);
820
+ const needsList = Array.isArray(item.needs) && item.needs.every((need) => typeof need === "string") ? item.needs : null;
821
+ if (needsList === null) problems.push("needs");
822
+ const parsed = {
823
+ id: text(item.id),
824
+ title: text(item.title),
825
+ tier: text(item.tier),
826
+ summary: typeof item.summary === "string" ? item.summary : "",
827
+ durationSeconds: durationSeconds ?? -1,
828
+ needs: needsList ?? [],
829
+ defaultProps: isRecord2(item.defaultProps) ? item.defaultProps : {}
830
+ };
831
+ if (problems.length > 0 || durationSeconds === void 0) return null;
832
+ return parsed;
833
+ }
834
+ function parseCatalog(body) {
835
+ const catalog2 = isRecord2(body) ? body.catalog : void 0;
836
+ if (!isRecord2(catalog2)) return null;
837
+ const category = isRecord2(catalog2.category) ? catalog2.category : null;
838
+ const templates = Array.isArray(catalog2.templates) ? catalog2.templates : null;
839
+ if (!category || templates === null) return null;
840
+ const problems = [];
841
+ const text = required(problems);
842
+ const parsedTemplates = [];
843
+ for (const item of templates) {
844
+ const parsed2 = parseCatalogTemplate(item);
845
+ if (parsed2 === null) problems.push("template");
846
+ else parsedTemplates.push(parsed2);
847
+ }
848
+ const parsed = {
849
+ category: { id: text(category.id), name: text(category.name), badge: text(category.badge) },
850
+ templates: parsedTemplates
851
+ };
852
+ return problems.length > 0 ? null : parsed;
853
+ }
854
+ function parseUpload(body) {
855
+ if (!isRecord2(body)) return null;
856
+ const problems = [];
857
+ const text = required(problems);
858
+ const bytes = numberOrUndefined(body.bytes);
859
+ const parsed = { url: text(body.url), sha256: text(body.sha256), bytes: bytes ?? -1 };
860
+ if (problems.length > 0 || bytes === void 0) return null;
861
+ return parsed;
862
+ }
863
+ function parseQuoteValue(value) {
864
+ if (!isRecord2(value)) return null;
865
+ const problems = [];
866
+ const text = required(problems);
867
+ const numbers = ["durationMilliseconds", "memberMilliseconds", "excessMilliseconds", "credits"].map(
868
+ (field) => numberOrUndefined(value[field])
869
+ );
870
+ if (problems.length > 0 || numbers.some((entry) => entry === void 0)) return null;
871
+ const [durationMilliseconds, memberMilliseconds, excessMilliseconds, credits] = numbers;
872
+ return {
873
+ quoteHash: text(value.quoteHash),
874
+ templateId: text(value.templateId),
875
+ durationMilliseconds,
876
+ memberMilliseconds,
877
+ excessMilliseconds,
878
+ credits
879
+ };
880
+ }
881
+ function parseQuote(body) {
882
+ return parseQuoteValue(isRecord2(body) ? body.quote : void 0);
883
+ }
884
+ function parseOrder(body) {
885
+ const order2 = isRecord2(body) ? body.order : void 0;
886
+ if (!isRecord2(order2)) return null;
887
+ const problems = [];
888
+ const text = required(problems);
889
+ const durationSeconds = numberOrUndefined(order2.durationSeconds);
890
+ const bytes = numberOrUndefined(order2.bytes);
891
+ const quoteValue = order2.quote === void 0 ? void 0 : parseQuoteValue(order2.quote);
892
+ if (order2.quote !== void 0 && quoteValue === null) problems.push("quote");
893
+ const parsed = {
894
+ id: text(order2.id),
895
+ status: text(order2.status),
896
+ templateId: text(order2.templateId),
897
+ templateTitle: text(order2.templateTitle),
898
+ createdAt: text(order2.createdAt),
899
+ ...typeof order2.finishedAt === "string" ? { finishedAt: order2.finishedAt } : {},
900
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
901
+ ...typeof order2.error === "string" ? { error: order2.error } : {},
902
+ ...typeof order2.videoUrl === "string" ? { videoUrl: order2.videoUrl } : {},
903
+ ...bytes !== void 0 ? { bytes } : {},
904
+ ...quoteValue !== void 0 && quoteValue !== null ? { quote: quoteValue } : {}
905
+ };
906
+ return problems.length > 0 ? null : parsed;
907
+ }
908
+ function parseDramaMoney(value) {
909
+ if (!isRecord2(value)) return null;
910
+ const problems = [];
911
+ const text = required(problems);
912
+ const numbers = ["held", "settled", "released", "refunded"].map((field) => numberOrUndefined(value[field]));
913
+ if (numbers.some((entry) => entry === void 0)) return null;
914
+ const [held, settled, released, refunded] = numbers;
915
+ const currency = text(value.currency);
916
+ return problems.length > 0 ? null : { currency, held, settled, released, refunded };
917
+ }
918
+ function parseDramaGap(value) {
919
+ if (!isRecord2(value)) return null;
920
+ const problems = [];
921
+ const text = required(problems);
922
+ const sceneNos = Array.isArray(value.sceneNos) && value.sceneNos.every((no) => numberOrUndefined(no) !== void 0) ? value.sceneNos : null;
923
+ if (sceneNos === null) problems.push("sceneNos");
924
+ const label = typeof value.label === "string" ? value.label : "";
925
+ const parsed = { kind: text(value.kind), key: text(value.key), label, sceneNos: sceneNos ?? [] };
926
+ return problems.length > 0 ? null : parsed;
927
+ }
928
+ function parseDramaOrder(body) {
929
+ const order2 = isRecord2(body) ? body.order : void 0;
930
+ if (!isRecord2(order2)) return null;
931
+ const problems = [];
932
+ const text = required(problems);
933
+ const money = parseDramaMoney(order2.money);
934
+ if (money === null) problems.push("money");
935
+ const actionsValue = Array.isArray(order2.nextActions) ? order2.nextActions : null;
936
+ const actions = actionsValue === null ? [] : actionsValue.filter((action) => typeof action === "string");
937
+ if (actionsValue === null || actions.length !== actionsValue.length) problems.push("nextActions");
938
+ const gapsValue = order2.gaps === void 0 ? void 0 : Array.isArray(order2.gaps) ? order2.gaps : null;
939
+ if (order2.gaps !== void 0 && gapsValue === null) problems.push("gaps");
940
+ const gaps = gapsValue === void 0 || gapsValue === null ? void 0 : gapsValue.map((item) => {
941
+ const parsed2 = parseDramaGap(item);
942
+ if (parsed2 === null) problems.push("gap");
943
+ return parsed2;
944
+ });
945
+ if (gaps !== void 0 && gaps.some((gap) => gap === null)) problems.push("gap");
946
+ const parsed = {
947
+ id: text(order2.id),
948
+ projectId: text(order2.projectId),
949
+ status: text(order2.status),
950
+ productId: text(order2.productId),
951
+ genreId: text(order2.genreId),
952
+ durationKey: text(order2.durationKey),
953
+ videoModel: typeof order2.videoModel === "string" ? order2.videoModel : null,
954
+ resolution: typeof order2.resolution === "string" ? order2.resolution : null,
955
+ money: money ?? { currency: "", held: 0, settled: 0, released: 0, refunded: 0 },
956
+ nextActions: actions,
957
+ ...typeof order2.reason === "string" ? { reason: order2.reason } : {},
958
+ ...gaps === void 0 ? {} : { gaps },
959
+ createdAt: text(order2.createdAt),
960
+ updatedAt: text(order2.updatedAt)
961
+ };
962
+ return problems.length > 0 ? null : parsed;
963
+ }
964
+ function parseDramaQuote(body) {
965
+ if (!isRecord2(body)) return null;
966
+ const problems = [];
967
+ const text = required(problems);
968
+ const quote3 = isRecord2(body.quote) ? body.quote : null;
969
+ if (quote3 === null) problems.push("quote");
970
+ const totalCredits = quote3 === null ? void 0 : numberOrUndefined(quote3.totalCredits);
971
+ const shotCount = quote3 === null ? void 0 : numberOrUndefined(quote3.shotCount);
972
+ if (totalCredits === void 0 || shotCount === void 0) problems.push("quote");
973
+ const shotCreditCost = quote3 === null ? void 0 : numberOrUndefined(quote3.shotCreditCost);
974
+ const videoModel = quote3 !== null && typeof quote3.videoModel === "string" ? quote3.videoModel : void 0;
975
+ const resolution = quote3 !== null && typeof quote3.resolution === "string" ? quote3.resolution : void 0;
976
+ const parsed = {
977
+ quoteHash: text(body.quoteHash),
978
+ sufficient: typeof body.sufficient === "boolean" ? body.sufficient : null,
979
+ totalCredits: totalCredits ?? -1,
980
+ shotCount: shotCount ?? -1,
981
+ ...shotCreditCost !== void 0 ? { shotCreditCost } : {},
982
+ ...videoModel !== void 0 ? { videoModel } : {},
983
+ ...resolution !== void 0 ? { resolution } : {}
984
+ };
985
+ return problems.length > 0 ? null : parsed;
986
+ }
987
+ function parseRunAction(value) {
988
+ if (!isRecord2(value) || typeof value.type !== "string" || value.type === "") return null;
989
+ return {
990
+ type: value.type,
991
+ ...numberOrUndefined(value.credits) !== void 0 ? { credits: numberOrUndefined(value.credits) } : {},
992
+ ...typeof value.confirmKind === "string" ? { confirmKind: value.confirmKind } : {},
993
+ ...value.confirmation !== void 0 ? { confirmation: value.confirmation } : {}
994
+ };
995
+ }
996
+ function parseRun(body) {
997
+ const run2 = isRecord2(body) ? body.run : void 0;
998
+ if (!isRecord2(run2)) return null;
999
+ const problems = [];
1000
+ const text = required(problems);
1001
+ const version2 = numberOrUndefined(run2.version);
1002
+ const pollAfterMs = numberOrUndefined(run2.pollAfterMs);
1003
+ const product = isRecord2(run2.product) ? run2.product : null;
1004
+ if (!product) problems.push("product");
1005
+ const actions = Array.isArray(run2.nextActions) ? run2.nextActions : null;
1006
+ if (actions === null) problems.push("nextActions");
1007
+ const messagesValue = Array.isArray(run2.messages) ? run2.messages : null;
1008
+ if (messagesValue === null) problems.push("messages");
1009
+ const messages = messagesValue === null ? [] : messagesValue.map((message2) => {
1010
+ if (!isRecord2(message2) || typeof message2.text !== "string") {
1011
+ problems.push("message");
1012
+ return { role: "", text: "" };
1013
+ }
1014
+ return { role: typeof message2.role === "string" ? message2.role : "", text: message2.text };
1015
+ });
1016
+ const failureValue = isRecord2(run2.failure) ? run2.failure : null;
1017
+ const failure = failureValue ? {
1018
+ code: text(failureValue.code),
1019
+ title: text(failureValue.title),
1020
+ detail: typeof failureValue.detail === "string" ? failureValue.detail : "",
1021
+ retryable: failureValue.retryable === true
1022
+ } : null;
1023
+ const currentProductionCredits = numberOrUndefined(run2.currentProductionCredits);
1024
+ const parsed = {
1025
+ rootRunId: text(run2.rootRunId),
1026
+ version: version2 ?? -1,
1027
+ status: text(run2.status),
1028
+ stage: text(run2.stage),
1029
+ label: text(run2.label),
1030
+ updatedAt: text(run2.updatedAt),
1031
+ pollAfterMs: pollAfterMs ?? null,
1032
+ productionConfirmed: run2.productionConfirmed === true,
1033
+ ...currentProductionCredits !== void 0 ? { currentProductionCredits } : {},
1034
+ product: product ? { id: text(product.id), name: text(product.name) } : { id: "", name: "" },
1035
+ nextActions: actions === null ? [] : actions.map((action) => {
1036
+ const parsed2 = parseRunAction(action);
1037
+ if (parsed2 === null) problems.push("action");
1038
+ return parsed2 ?? { type: "" };
1039
+ }),
1040
+ messages,
1041
+ failure
1042
+ };
1043
+ if (problems.length > 0 || version2 === void 0) return null;
1044
+ return parsed;
1045
+ }
594
1046
  function parseMe(body) {
595
1047
  if (!isRecord2(body)) return null;
596
1048
  const { key, account, workspace } = body;
@@ -613,6 +1065,280 @@ async function getMe(baseUrl, apiKey, deps) {
613
1065
  if (!me) throw unreadableResponse(reply.status);
614
1066
  return me;
615
1067
  }
1068
+ async function getBalance(baseUrl, apiKey, deps) {
1069
+ const reply = await getJson(baseUrl, apiKey, "/api/open/v1/balance", deps);
1070
+ const balance2 = parseBalance(reply.body);
1071
+ if (!balance2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1072
+ return { view: balance2, raw: reply.body };
1073
+ }
1074
+ async function createProduct(baseUrl, apiKey, name2, category, idempotencyKey, deps) {
1075
+ const reply = await postJson(
1076
+ baseUrl,
1077
+ apiKey,
1078
+ "/api/open/v1/products",
1079
+ { name: name2, ...category === void 0 ? {} : { category } },
1080
+ idempotencyKey,
1081
+ deps
1082
+ );
1083
+ const product = parseProduct(reply.body);
1084
+ if (!product || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1085
+ return { view: product, raw: reply.body, replayed: reply.status === 200 };
1086
+ }
1087
+ async function putProductImage(baseUrl, apiKey, productId, angle, bytes, contentType, idempotencyKey, deps) {
1088
+ const reply = await putRaw(
1089
+ baseUrl,
1090
+ apiKey,
1091
+ `/api/open/v1/products/${encodeURIComponent(productId)}/images?angle=${angle}`,
1092
+ bytes,
1093
+ contentType,
1094
+ idempotencyKey,
1095
+ deps
1096
+ );
1097
+ const product = parseProduct(reply.body);
1098
+ if (!product || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1099
+ return { view: product, raw: reply.body, replayed: reply.status === 200 };
1100
+ }
1101
+ async function confirmProductLock(baseUrl, apiKey, productId, lock, idempotencyKey, deps) {
1102
+ const reply = await postJson(
1103
+ baseUrl,
1104
+ apiKey,
1105
+ `/api/open/v1/products/${encodeURIComponent(productId)}/lock/confirm`,
1106
+ { productLock: lock },
1107
+ idempotencyKey,
1108
+ deps
1109
+ );
1110
+ const productLock = parseProductLock(reply.body);
1111
+ const product = parseProduct(reply.body);
1112
+ if (!productLock || !product || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1113
+ return { view: { productLock, product }, raw: reply.body };
1114
+ }
1115
+ async function getTemplateShortCatalog(baseUrl, apiKey, deps) {
1116
+ const reply = await getJson(baseUrl, apiKey, "/api/open/v1/template-shorts", deps);
1117
+ const catalog2 = parseCatalog(reply.body);
1118
+ if (!catalog2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1119
+ return { view: catalog2, raw: reply.body };
1120
+ }
1121
+ async function uploadTemplateShortAsset(baseUrl, apiKey, bytes, contentType, idempotencyKey, deps) {
1122
+ const reply = await postRaw(baseUrl, apiKey, "/api/open/v1/template-shorts/uploads", bytes, contentType, idempotencyKey, deps);
1123
+ const upload2 = parseUpload(reply.body);
1124
+ if (!upload2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1125
+ return { view: upload2, raw: reply.body };
1126
+ }
1127
+ async function quoteTemplateShort(baseUrl, apiKey, templateId, props, idempotencyKey, deps) {
1128
+ const reply = await postJson(
1129
+ baseUrl,
1130
+ apiKey,
1131
+ "/api/open/v1/template-shorts/quote",
1132
+ { templateId, props },
1133
+ idempotencyKey,
1134
+ deps
1135
+ );
1136
+ const quote3 = parseQuote(reply.body);
1137
+ if (!quote3 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1138
+ return { view: quote3, raw: reply.body };
1139
+ }
1140
+ async function createTemplateShortOrder(baseUrl, apiKey, templateId, props, expectedQuoteHash, idempotencyKey, deps) {
1141
+ return orderReplyOf(
1142
+ await postJson(
1143
+ baseUrl,
1144
+ apiKey,
1145
+ "/api/open/v1/template-shorts/orders",
1146
+ { templateId, props, ...expectedQuoteHash === void 0 ? {} : { expectedQuoteHash } },
1147
+ idempotencyKey,
1148
+ deps
1149
+ )
1150
+ );
1151
+ }
1152
+ async function getTemplateShortOrder(baseUrl, apiKey, orderId, deps) {
1153
+ return orderReplyOf(
1154
+ await getJson(baseUrl, apiKey, `/api/open/v1/template-shorts/orders/${encodeURIComponent(orderId)}`, deps)
1155
+ );
1156
+ }
1157
+ async function cancelTemplateShortOrder(baseUrl, apiKey, orderId, idempotencyKey, deps) {
1158
+ return orderReplyOf(
1159
+ await postJson(
1160
+ baseUrl,
1161
+ apiKey,
1162
+ `/api/open/v1/template-shorts/orders/${encodeURIComponent(orderId)}/cancel`,
1163
+ {},
1164
+ idempotencyKey,
1165
+ deps
1166
+ )
1167
+ );
1168
+ }
1169
+ async function orderReplyOf(reply) {
1170
+ const order2 = parseOrder(reply.body);
1171
+ if (!order2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1172
+ return { view: order2, raw: reply.body };
1173
+ }
1174
+ async function downloadTemplateShort(baseUrl, apiKey, orderId, deps) {
1175
+ return downloadFile(baseUrl, apiKey, `/api/open/v1/template-shorts/orders/${encodeURIComponent(orderId)}/download`, deps);
1176
+ }
1177
+ async function runReplyOf(reply) {
1178
+ const run2 = parseRun(reply.body);
1179
+ if (!run2 || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1180
+ return { view: run2, raw: reply.body };
1181
+ }
1182
+ async function createVideoRun(baseUrl, apiKey, body, idempotencyKey, deps) {
1183
+ return runReplyOf(await postJson(baseUrl, apiKey, "/api/open/v1/videos", body, idempotencyKey, deps));
1184
+ }
1185
+ async function getVideoRun(baseUrl, apiKey, runId, deps) {
1186
+ return runReplyOf(await getJson(baseUrl, apiKey, `/api/open/v1/videos/${encodeURIComponent(runId)}`, deps));
1187
+ }
1188
+ async function postVideoMessage(baseUrl, apiKey, runId, text, idempotencyKey, deps) {
1189
+ return runReplyOf(
1190
+ await postJson(
1191
+ baseUrl,
1192
+ apiKey,
1193
+ `/api/open/v1/videos/${encodeURIComponent(runId)}/messages`,
1194
+ { text },
1195
+ idempotencyKey,
1196
+ deps
1197
+ )
1198
+ );
1199
+ }
1200
+ async function confirmVideoProduction(baseUrl, apiKey, runId, expectedVersion, confirmation, idempotencyKey, deps) {
1201
+ return runReplyOf(
1202
+ await postJson(
1203
+ baseUrl,
1204
+ apiKey,
1205
+ `/api/open/v1/videos/${encodeURIComponent(runId)}/confirm`,
1206
+ { expectedVersion, confirmation },
1207
+ idempotencyKey,
1208
+ deps
1209
+ )
1210
+ );
1211
+ }
1212
+ async function downloadVideo(baseUrl, apiKey, runId, deps) {
1213
+ return downloadFile(baseUrl, apiKey, `/api/open/v1/videos/${encodeURIComponent(runId)}/download`, deps);
1214
+ }
1215
+ async function createDramaOrder(baseUrl, apiKey, body, idempotencyKey, deps) {
1216
+ const reply = await postJson(baseUrl, apiKey, "/api/open/v1/drama/orders", body, idempotencyKey, deps);
1217
+ const view = parseDramaOrder(reply.body);
1218
+ if (!view || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1219
+ return { view, raw: reply.body, replayed: reply.status === 200 };
1220
+ }
1221
+ async function getDramaOrder(baseUrl, apiKey, orderId, deps) {
1222
+ const reply = await getJson(baseUrl, apiKey, `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}`, deps);
1223
+ const view = parseDramaOrder(reply.body);
1224
+ if (!view || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1225
+ return { view, raw: reply.body, replayed: false };
1226
+ }
1227
+ async function getDramaQuote(baseUrl, apiKey, orderId, deps) {
1228
+ const reply = await getJson(baseUrl, apiKey, `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/quote`, deps);
1229
+ const view = parseDramaQuote(reply.body);
1230
+ if (!view || !isRecord2(reply.body)) throw unreadableResponse(reply.status);
1231
+ return { view, raw: reply.body };
1232
+ }
1233
+ async function confirmDramaOrder(baseUrl, apiKey, orderId, expectedQuoteHash, idempotencyKey, deps) {
1234
+ const reply = await postJson(
1235
+ baseUrl,
1236
+ apiKey,
1237
+ `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/confirm`,
1238
+ { expectedQuoteHash },
1239
+ idempotencyKey,
1240
+ deps
1241
+ );
1242
+ const view = parseDramaOrder(reply.body);
1243
+ const started = isRecord2(reply.body) ? reply.body.productionStarted : void 0;
1244
+ if (!view || !isRecord2(reply.body) || typeof started !== "boolean") throw unreadableResponse(reply.status);
1245
+ return { view, productionStarted: started, raw: reply.body };
1246
+ }
1247
+ async function cancelDramaOrder(baseUrl, apiKey, orderId, idempotencyKey, deps) {
1248
+ const reply = await postJson(
1249
+ baseUrl,
1250
+ apiKey,
1251
+ `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/cancel`,
1252
+ {},
1253
+ idempotencyKey,
1254
+ deps
1255
+ );
1256
+ const view = parseDramaOrder(reply.body);
1257
+ const canceled = isRecord2(reply.body) ? reply.body.canceled : void 0;
1258
+ if (!view || !isRecord2(reply.body) || typeof canceled !== "boolean") throw unreadableResponse(reply.status);
1259
+ return { view, canceled, raw: reply.body };
1260
+ }
1261
+ async function downloadDramaFilm(baseUrl, apiKey, orderId, deps) {
1262
+ return downloadFile(baseUrl, apiKey, `/api/open/v1/drama/orders/${encodeURIComponent(orderId)}/download`, deps);
1263
+ }
1264
+ var DEFAULT_WAIT_TIMEOUT_MS = 6e5;
1265
+ var FIRST_POLL_INTERVAL_MS = 3e3;
1266
+ var MAX_POLL_INTERVAL_MS = 12e3;
1267
+ var ORDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
1268
+ var RUN_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
1269
+ var RUN_WAITING_FOR_USER_STATUSES = /* @__PURE__ */ new Set(["needs_input", "awaiting_reply", "awaiting_confirmation"]);
1270
+ var DRAMA_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["delivered", "failed", "canceled", "partial"]);
1271
+ var DRAMA_WAITING_FOR_USER_STATUSES = /* @__PURE__ */ new Set(["needs_attention"]);
1272
+ async function awaitTemplateShortOrder(baseUrl, apiKey, start, deps, wait) {
1273
+ let current = start;
1274
+ if (ORDER_TERMINAL_STATUSES.has(current.view.status)) return { value: current, timedOut: false };
1275
+ const deadline = wait.now().getTime() + wait.timeoutMs;
1276
+ let interval = FIRST_POLL_INTERVAL_MS;
1277
+ let said = current.view.status;
1278
+ for (; ; ) {
1279
+ const remaining = deadline - wait.now().getTime();
1280
+ if (remaining <= 0) return { value: current, timedOut: true };
1281
+ current = await retainTaskOnError(() => taskRecovery("shorts", current.view.id, current.view.status, baseUrl), async () => {
1282
+ await wait.sleep(Math.max(Math.min(interval, remaining), 1));
1283
+ interval = Math.min(Math.ceil(interval * 1.5), MAX_POLL_INTERVAL_MS);
1284
+ return getTemplateShortOrder(baseUrl, apiKey, current.view.id, deps);
1285
+ });
1286
+ if (current.view.status !== said) {
1287
+ wait.note?.(`\u72B6\u6001\u53D8\u6210 ${current.view.status}`);
1288
+ said = current.view.status;
1289
+ }
1290
+ if (ORDER_TERMINAL_STATUSES.has(current.view.status)) return { value: current, timedOut: false };
1291
+ }
1292
+ }
1293
+ async function awaitVideoRun(baseUrl, apiKey, start, deps, wait) {
1294
+ let current = start;
1295
+ if (runStopped(current.view.status)) return { value: current, timedOut: false };
1296
+ const deadline = wait.now().getTime() + wait.timeoutMs;
1297
+ let interval = FIRST_POLL_INTERVAL_MS;
1298
+ let said = current.view.status;
1299
+ for (; ; ) {
1300
+ const remaining = deadline - wait.now().getTime();
1301
+ if (remaining <= 0) return { value: current, timedOut: true };
1302
+ const preferred = current.view.pollAfterMs !== null && current.view.pollAfterMs > 0 ? current.view.pollAfterMs : interval;
1303
+ current = await retainTaskOnError(() => taskRecovery("videos", current.view.rootRunId, current.view.status, baseUrl, current.view.failure?.code), async () => {
1304
+ await wait.sleep(Math.max(Math.min(preferred, remaining), 1));
1305
+ interval = Math.min(Math.ceil(interval * 1.5), MAX_POLL_INTERVAL_MS);
1306
+ return getVideoRun(baseUrl, apiKey, current.view.rootRunId, deps);
1307
+ });
1308
+ if (current.view.status !== said) {
1309
+ wait.note?.(`\u72B6\u6001\u53D8\u6210 ${current.view.status}`);
1310
+ said = current.view.status;
1311
+ }
1312
+ if (runStopped(current.view.status)) return { value: current, timedOut: false };
1313
+ }
1314
+ }
1315
+ function runStopped(status4) {
1316
+ return RUN_TERMINAL_STATUSES.has(status4) || RUN_WAITING_FOR_USER_STATUSES.has(status4);
1317
+ }
1318
+ async function awaitDramaOrder(baseUrl, apiKey, start, deps, wait) {
1319
+ let current = start;
1320
+ if (dramaStopped(current.view.status)) return { value: current, timedOut: false };
1321
+ const deadline = wait.now().getTime() + wait.timeoutMs;
1322
+ let interval = FIRST_POLL_INTERVAL_MS;
1323
+ let said = current.view.status;
1324
+ for (; ; ) {
1325
+ const remaining = deadline - wait.now().getTime();
1326
+ if (remaining <= 0) return { value: current, timedOut: true };
1327
+ current = await retainTaskOnError(() => taskRecovery("drama", current.view.id, current.view.status, baseUrl), async () => {
1328
+ await wait.sleep(Math.max(Math.min(interval, remaining), 1));
1329
+ interval = Math.min(Math.ceil(interval * 1.5), MAX_POLL_INTERVAL_MS);
1330
+ return getDramaOrder(baseUrl, apiKey, current.view.id, deps);
1331
+ });
1332
+ if (current.view.status !== said) {
1333
+ wait.note?.(`\u72B6\u6001\u53D8\u6210 ${current.view.status}`);
1334
+ said = current.view.status;
1335
+ }
1336
+ if (dramaStopped(current.view.status)) return { value: current, timedOut: false };
1337
+ }
1338
+ }
1339
+ function dramaStopped(status4) {
1340
+ return DRAMA_TERMINAL_STATUSES.has(status4) || DRAMA_WAITING_FOR_USER_STATUSES.has(status4);
1341
+ }
616
1342
 
617
1343
  // src/credentials.ts
618
1344
  import { randomBytes } from "node:crypto";
@@ -636,8 +1362,8 @@ function nonEmpty3(value) {
636
1362
  function absoluteOnly(value) {
637
1363
  return value !== void 0 && isAbsolute(value) ? value : void 0;
638
1364
  }
639
- function configDirError(message) {
640
- return new CliError({ code: "CLI_CONFIG_DIR_INVALID", exitCode: EXIT.USAGE, message });
1365
+ function configDirError(message2) {
1366
+ return new CliError({ code: "CLI_CONFIG_DIR_INVALID", exitCode: EXIT.USAGE, message: message2 });
641
1367
  }
642
1368
  function expandHome(value, io) {
643
1369
  if (value === "~") return io.homedir();
@@ -865,6 +1591,616 @@ async function deleteCredentials(io, warn = SILENT) {
865
1591
  return removed;
866
1592
  }
867
1593
 
1594
+ // src/resolve.ts
1595
+ async function resolveApiKey({ io, out, baseUrl: flag }) {
1596
+ const explicit = explicitBaseUrl(flag, io.env);
1597
+ const fromEnv = keyFromEnv(io.env);
1598
+ if (fromEnv !== null) {
1599
+ assertKeyFormat(fromEnv);
1600
+ out.protect(fromEnv);
1601
+ return { apiKey: fromEnv, baseUrl: explicit ?? DEFAULT_BASE_URL, source: "env" };
1602
+ }
1603
+ const stored = await readCredentials(io, (line) => out.notice(line));
1604
+ if (!stored) {
1605
+ throw new CliError({
1606
+ code: "CLI_NOT_LOGGED_IN",
1607
+ exitCode: EXIT.AUTH,
1608
+ message: "\u8FD8\u6CA1\u6709\u767B\u5F55\u3002\u5148\u8FD0\u884C buke login\uFF0C\u6216\u8005\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF BUKE_API_KEY"
1609
+ });
1610
+ }
1611
+ out.protect(stored.apiKey);
1612
+ if (explicit !== null && explicit !== stored.baseUrl) {
1613
+ throw new CliError({
1614
+ code: "CLI_BASE_URL_MISMATCH",
1615
+ exitCode: EXIT.USAGE,
1616
+ 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}`
1617
+ });
1618
+ }
1619
+ return { apiKey: stored.apiKey, baseUrl: stored.baseUrl, source: "file", credentialsPath: credentialsPath(io) };
1620
+ }
1621
+ function keySourceLine(resolved) {
1622
+ return resolved.source === "env" ? "\u5BC6\u94A5\u6765\u81EA\uFF1A\u73AF\u5883\u53D8\u91CF BUKE_API_KEY" : `\u5BC6\u94A5\u6765\u81EA\uFF1A${resolved.credentialsPath}`;
1623
+ }
1624
+
1625
+ // src/commands/balance.ts
1626
+ async function balance(context) {
1627
+ const { io, out } = context;
1628
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1629
+ const { view, raw } = await getBalance(baseUrl, apiKey, io);
1630
+ out.result(
1631
+ [
1632
+ `\u53EF\u7528\u4F59\u989D\uFF1A${view.available} ${singleLine(view.currency)}`,
1633
+ `\u51BB\u7ED3\u4E2D\uFF1A${view.held} ${singleLine(view.currency)}`,
1634
+ `\u5DF2\u652F\u51FA\uFF1A${view.spent} ${singleLine(view.currency)}`,
1635
+ `\u66F4\u65B0\u65F6\u95F4\uFF1A${singleLine(view.updatedAt)}`
1636
+ ],
1637
+ { balance: raw.balance }
1638
+ );
1639
+ }
1640
+
1641
+ // src/commands/shorts.ts
1642
+ import { createHash } from "node:crypto";
1643
+ import { writeFile } from "node:fs/promises";
1644
+
1645
+ // src/commands/shared.ts
1646
+ import { randomUUID } from "node:crypto";
1647
+ import { readFile } from "node:fs/promises";
1648
+ var MAX_PROPS_BYTES = 1024 * 1024;
1649
+ var MAX_IMAGE_BYTES = 64 * 1024 * 1024;
1650
+ function idempotencyKeyOf(values) {
1651
+ const supplied = values["idempotency-key"];
1652
+ if (typeof supplied === "string" && supplied.trim() !== "") return supplied.trim();
1653
+ return randomUUID();
1654
+ }
1655
+ function textOption(values, name2) {
1656
+ const value = values[name2];
1657
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
1658
+ }
1659
+ function timeoutSecondsOf(values, fallbackSeconds) {
1660
+ const raw = values.timeout;
1661
+ if (raw === void 0) return fallbackSeconds;
1662
+ if (typeof raw !== "string") return fallbackSeconds;
1663
+ const parsed = Number(raw);
1664
+ if (!Number.isInteger(parsed) || parsed <= 0) {
1665
+ throw usageError(`--timeout \u8981\u662F\u6B63\u6574\u6570\u79D2\u6570\uFF0C\u5199\u7684\u662F ${raw}`);
1666
+ }
1667
+ return parsed;
1668
+ }
1669
+ async function readPropsArgument(io, ref, what) {
1670
+ if (ref === void 0) {
1671
+ throw usageError("\u8981\u7ED9 --props\uFF1A\u4E00\u4E2A JSON \u6587\u4EF6\uFF0C\u6216 - \u8868\u793A\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB");
1672
+ }
1673
+ let text;
1674
+ if (ref === "-") {
1675
+ text = await readStdinAll(io, MAX_PROPS_BYTES, what);
1676
+ } else {
1677
+ let bytes;
1678
+ try {
1679
+ bytes = await readFile(ref);
1680
+ } catch (error) {
1681
+ throw new CliError({
1682
+ code: "CLI_USAGE",
1683
+ exitCode: EXIT.USAGE,
1684
+ message: `${what}\u6587\u4EF6\u8BFB\u4E0D\u4E86\uFF1A${ref}\uFF08${briefReason(error)}\uFF09`
1685
+ });
1686
+ }
1687
+ if (bytes.byteLength > MAX_PROPS_BYTES) {
1688
+ throw new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${what}\u6587\u4EF6\u592A\u5927\uFF08\u8D85\u8FC7 1 MiB\uFF09\uFF1A${ref}` });
1689
+ }
1690
+ text = bytes.toString("utf8");
1691
+ }
1692
+ let parsed;
1693
+ try {
1694
+ parsed = JSON.parse(text);
1695
+ } catch {
1696
+ throw usageError(`${what}\u4E0D\u662F\u5408\u6CD5\u7684 JSON`);
1697
+ }
1698
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1699
+ throw usageError(`${what}\u5E94\u662F\u4E00\u4E2A JSON \u5BF9\u8C61`);
1700
+ }
1701
+ return parsed;
1702
+ }
1703
+ async function readStdinAll(io, maxBytes, what) {
1704
+ const stdin = io.stdin;
1705
+ if (stdin.readableEnded === true) return "";
1706
+ const chunks = [];
1707
+ let size = 0;
1708
+ await new Promise((resolve, reject) => {
1709
+ const toBuffer2 = (chunk) => typeof chunk === "string" ? Buffer.from(chunk, "utf8") : Buffer.from(chunk);
1710
+ const onData = (chunk) => {
1711
+ const piece = toBuffer2(chunk);
1712
+ size += piece.byteLength;
1713
+ if (size > maxBytes) {
1714
+ cleanup();
1715
+ reject(usageError(`${what}\u592A\u5927\uFF08\u8D85\u8FC7 1 MiB\uFF09`));
1716
+ return;
1717
+ }
1718
+ chunks.push(piece);
1719
+ };
1720
+ const onEnd = () => {
1721
+ cleanup();
1722
+ resolve();
1723
+ };
1724
+ const onError = (error) => {
1725
+ cleanup();
1726
+ reject(new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `\u6807\u51C6\u8F93\u5165\u8BFB\u4E0D\u4E86\uFF1A${briefReason(error)}` }));
1727
+ };
1728
+ const cleanup = () => {
1729
+ stdin.removeListener("data", onData);
1730
+ stdin.removeListener("end", onEnd);
1731
+ stdin.removeListener("error", onError);
1732
+ stdin.pause();
1733
+ };
1734
+ stdin.on("data", onData);
1735
+ stdin.on("end", onEnd);
1736
+ stdin.on("error", onError);
1737
+ stdin.resume();
1738
+ });
1739
+ return Buffer.concat(chunks).toString("utf8");
1740
+ }
1741
+ async function readImageFile(path) {
1742
+ let bytes;
1743
+ try {
1744
+ bytes = await readFile(path);
1745
+ } catch (error) {
1746
+ throw new CliError({
1747
+ code: "CLI_USAGE",
1748
+ exitCode: EXIT.USAGE,
1749
+ message: `\u56FE\u7247\u6587\u4EF6\u8BFB\u4E0D\u4E86\uFF1A${path}\uFF08${briefReason(error)}\uFF09`
1750
+ });
1751
+ }
1752
+ if (bytes.byteLength > MAX_IMAGE_BYTES) {
1753
+ throw new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `\u56FE\u7247\u592A\u5927\uFF08\u8D85\u8FC7 64 MiB\uFF09\uFF1A${path}` });
1754
+ }
1755
+ return { bytes, contentType: sniffImageContentType(bytes) };
1756
+ }
1757
+ function sniffImageContentType(bytes) {
1758
+ if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
1759
+ if (bytes.length >= 4 && bytes.subarray(0, 4).toString("hex") === "89504e47") return "image/png";
1760
+ if (bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") {
1761
+ return "image/webp";
1762
+ }
1763
+ return void 0;
1764
+ }
1765
+
1766
+ // src/commands/shorts.ts
1767
+ var SUBCOMMANDS = "catalog\u3001upload\u3001quote\u3001order\u3001status\u3001download \u548C cancel";
1768
+ async function shorts(context) {
1769
+ const { args } = context;
1770
+ const [subcommand] = args;
1771
+ if (subcommand === void 0) {
1772
+ throw usageError(`shorts \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1A${SUBCOMMANDS}`);
1773
+ }
1774
+ if (subcommand === "catalog") return catalog(context);
1775
+ if (subcommand === "upload") return upload(context, args[1]);
1776
+ if (subcommand === "quote") return quote(context);
1777
+ if (subcommand === "order") return order(context);
1778
+ if (subcommand === "status") return status(context, args[1]);
1779
+ if (subcommand === "download") return download(context, args[1]);
1780
+ if (subcommand === "cancel") return cancel(context, args[1]);
1781
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Ashorts \u7684\u5B50\u547D\u4EE4\u662F ${SUBCOMMANDS}`);
1782
+ }
1783
+ function orderIdOf(raw) {
1784
+ if (raw === void 0 || raw.trim() === "") {
1785
+ throw usageError("\u8981\u7ED9\u8BA2\u5355 id\uFF08shorts order \u56DE\u7684\u90A3\u4E2A\uFF09");
1786
+ }
1787
+ return raw.trim();
1788
+ }
1789
+ function templateOf(values) {
1790
+ const templateId = textOption(values, "template");
1791
+ if (templateId === void 0) {
1792
+ throw usageError("\u8981\u7ED9 --template <\u6A21\u677Fid>\uFF08shorts catalog \u5217\u51FA\u7684\uFF09");
1793
+ }
1794
+ return templateId;
1795
+ }
1796
+ function waitControls(io, values, note) {
1797
+ return {
1798
+ timeoutMs: timeoutSecondsOf(values, DEFAULT_WAIT_TIMEOUT_MS / 1e3) * 1e3,
1799
+ now: io.now ?? (() => /* @__PURE__ */ new Date()),
1800
+ sleep: io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1801
+ note
1802
+ };
1803
+ }
1804
+ function settleOrder(outcome, baseUrl) {
1805
+ const order2 = outcome.value.view;
1806
+ const recovery = taskRecovery("shorts", order2.id, order2.status, baseUrl);
1807
+ if (outcome.timedOut) {
1808
+ throw new CliError({
1809
+ code: "CLI_WAIT_TIMEOUT",
1810
+ exitCode: EXIT.WAIT_TIMEOUT,
1811
+ message: `\u7B49\u4E86\u8FD9\u4E48\u4E45\u8FD8\u5728 ${order2.status}\uFF1A\u518D\u67E5\u7528 ${recovery.statusCommand}\uFF0C\u7ED9\u8BE5\u67E5\u8BE2\u52A0 --wait --timeout <\u79D2\u6570> \u53EF\u7EE7\u7EED\u7B49\u5F85`,
1812
+ recovery
1813
+ });
1814
+ }
1815
+ if (order2.status === "failed" || order2.status === "canceled") {
1816
+ throw new CliError({
1817
+ code: "CLI_TASK_FAILED",
1818
+ exitCode: EXIT.TASK_FAILED,
1819
+ message: order2.error === void 0 ? `\u8FD9\u6761\u77ED\u7247${order2.status === "canceled" ? "\u5DF2\u53D6\u6D88" : "\u6CA1\u6709\u6E32\u67D3\u6210\u529F"}` : singleLine(order2.error),
1820
+ recovery
1821
+ });
1822
+ }
1823
+ return outcome.value;
1824
+ }
1825
+ function orderLines(order2) {
1826
+ return [
1827
+ `\u8BA2\u5355\uFF1A${singleLine(order2.id)}`,
1828
+ `\u72B6\u6001\uFF1A${singleLine(order2.status)}`,
1829
+ `\u6A21\u677F\uFF1A${singleLine(order2.templateTitle)}\uFF08${singleLine(order2.templateId)}\uFF09`,
1830
+ `\u521B\u5EFA\u4E8E\uFF1A${singleLine(order2.createdAt)}`,
1831
+ ...order2.durationSeconds !== void 0 ? [`\u65F6\u957F\uFF1A${order2.durationSeconds} \u79D2`] : [],
1832
+ ...order2.status === "succeeded" && order2.videoUrl !== void 0 ? [`\u6210\u7247\u5730\u5740\uFF1A${singleLine(order2.videoUrl)}`] : [],
1833
+ ...order2.bytes !== void 0 ? [`\u5927\u5C0F\uFF1A${order2.bytes} \u5B57\u8282`] : []
1834
+ ];
1835
+ }
1836
+ async function catalog(context) {
1837
+ const { io, out } = context;
1838
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1839
+ const { view, raw } = await getTemplateShortCatalog(baseUrl, apiKey, io);
1840
+ out.result(
1841
+ [
1842
+ `${singleLine(view.category.name)}\uFF08${singleLine(view.category.badge)}\uFF0C\u5171 ${view.templates.length} \u4E2A\u6A21\u677F\uFF09`,
1843
+ ...view.templates.map(
1844
+ (template) => `${template.id} ${singleLine(template.title)}\uFF08${singleLine(template.tier)}\uFF0C${template.durationSeconds} \u79D2\uFF09
1845
+ ${template.summary}${template.needs.length > 0 ? `\uFF1B\u9700\u8981\uFF1A${template.needs.join("\u3001")}` : ""}`
1846
+ )
1847
+ ],
1848
+ // --json 按文档回服务端那份完整的 catalog:slots、defaultProps 都在内。
1849
+ { catalog: raw.catalog }
1850
+ );
1851
+ }
1852
+ async function upload(context, rawPath) {
1853
+ const { io, out } = context;
1854
+ if (rawPath === void 0 || rawPath.trim() === "") {
1855
+ throw usageError("\u8981\u7ED9\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84");
1856
+ }
1857
+ const image = await readImageFile(rawPath);
1858
+ const idempotencyKey = idempotencyKeyOf(context.values);
1859
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1860
+ const { view, raw } = await uploadTemplateShortAsset(baseUrl, apiKey, image.bytes, image.contentType, idempotencyKey, io);
1861
+ out.result(
1862
+ [
1863
+ `\u5DF2\u4E0A\u4F20\uFF1A${singleLine(view.url)}`,
1864
+ `\u6821\u9A8C\uFF1Asha256 ${view.sha256}\uFF08${view.bytes} \u5B57\u8282\uFF09`,
1865
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
1866
+ ],
1867
+ { upload: raw, idempotencyKey }
1868
+ );
1869
+ }
1870
+ async function quote(context) {
1871
+ const { io, out, values } = context;
1872
+ const templateId = templateOf(values);
1873
+ const props = await readPropsArgument(io, textOption(values, "props"), "\u6A21\u677F\u53C2\u6570\uFF08--props\uFF09");
1874
+ const idempotencyKey = idempotencyKeyOf(values);
1875
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1876
+ const { view, raw } = await quoteTemplateShort(baseUrl, apiKey, templateId, props, idempotencyKey, io);
1877
+ out.result(
1878
+ [
1879
+ `\u6A21\u677F\uFF1A${singleLine(view.templateId)}\uFF0C\u65F6\u957F ${Math.round(view.durationMilliseconds / 100) / 10} \u79D2`,
1880
+ `\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`,
1881
+ `\u62A5\u4EF7\u54C8\u5E0C\uFF1A${view.quoteHash}`,
1882
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`,
1883
+ `\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`
1884
+ ],
1885
+ { quote: raw.quote, idempotencyKey }
1886
+ );
1887
+ }
1888
+ async function order(context) {
1889
+ const { io, out, values } = context;
1890
+ const templateId = templateOf(values);
1891
+ const props = await readPropsArgument(io, textOption(values, "props"), "\u6A21\u677F\u53C2\u6570\uFF08--props\uFF09");
1892
+ const quoteHash = textOption(values, "quote-hash");
1893
+ const idempotencyKey = idempotencyKeyOf(values);
1894
+ const shouldWait = values.wait === true;
1895
+ const controls = shouldWait ? waitControls(io, values, (line) => out.notice(line)) : void 0;
1896
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1897
+ const accepted = await retainSubmissionOnError(
1898
+ "shorts",
1899
+ idempotencyKey,
1900
+ baseUrl,
1901
+ () => createTemplateShortOrder(baseUrl, apiKey, templateId, props, quoteHash, idempotencyKey, io)
1902
+ );
1903
+ const final = shouldWait ? await retainTaskOnError(
1904
+ () => ({ ...taskRecovery("shorts", accepted.view.id, accepted.view.status, baseUrl), idempotencyKey }),
1905
+ async () => settleOrder(await awaitTemplateShortOrder(baseUrl, apiKey, accepted, io, controls), baseUrl)
1906
+ ) : accepted;
1907
+ out.result([...orderLines(final.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`], {
1908
+ order: final.raw.order,
1909
+ idempotencyKey,
1910
+ ...shouldWait ? { waited: true } : {}
1911
+ });
1912
+ }
1913
+ async function status(context, rawId) {
1914
+ const { io, out, values } = context;
1915
+ const orderId = orderIdOf(rawId);
1916
+ const shouldWait = values.wait === true;
1917
+ const controls = shouldWait ? waitControls(io, values, (line) => out.notice(line)) : void 0;
1918
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1919
+ const current = await getTemplateShortOrder(baseUrl, apiKey, orderId, io);
1920
+ const final = shouldWait ? settleOrder(await awaitTemplateShortOrder(baseUrl, apiKey, current, io, controls), baseUrl) : current;
1921
+ out.result(orderLines(final.view), { order: final.raw.order, ...shouldWait ? { waited: true } : {} });
1922
+ }
1923
+ async function cancel(context, rawId) {
1924
+ const { io, out, values } = context;
1925
+ const orderId = orderIdOf(rawId);
1926
+ const idempotencyKey = idempotencyKeyOf(values);
1927
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1928
+ const canceled = await cancelTemplateShortOrder(baseUrl, apiKey, orderId, idempotencyKey, io);
1929
+ out.result([...orderLines(canceled.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`], { order: canceled.raw.order, idempotencyKey });
1930
+ }
1931
+ async function download(context, rawId) {
1932
+ const { io, out, values } = context;
1933
+ const orderId = orderIdOf(rawId);
1934
+ const defaultName = `template-short-${orderId}.mp4`;
1935
+ const target = textOption(values, "out") ?? defaultName;
1936
+ const { apiKey, baseUrl } = await resolveApiKey(context);
1937
+ const result = await downloadTemplateShort(baseUrl, apiKey, orderId, io);
1938
+ const saved = await saveDownload(target, result.bytes);
1939
+ out.result(
1940
+ [`\u5DF2\u4E0B\u8F7D\uFF1A${saved.path}`, `\u5927\u5C0F\uFF1A${saved.bytes} \u5B57\u8282`, `\u6821\u9A8C\uFF1Asha256 ${saved.sha256}`],
1941
+ { download: { ...saved, filename: result.filename ?? defaultName } }
1942
+ );
1943
+ }
1944
+ async function saveDownload(target, bytes) {
1945
+ try {
1946
+ await writeFile(target, bytes);
1947
+ } catch (error) {
1948
+ throw new CliError({
1949
+ code: "CLI_INTERNAL",
1950
+ exitCode: EXIT.SERVER,
1951
+ message: `\u6210\u7247\u5199\u4E0D\u8FDB ${target}\uFF08${briefReason(error)}\uFF09`
1952
+ });
1953
+ }
1954
+ return { path: target, bytes: bytes.byteLength, sha256: createHash("sha256").update(bytes).digest("hex") };
1955
+ }
1956
+
1957
+ // src/commands/drama.ts
1958
+ var SUBCOMMANDS2 = "create\u3001status\u3001quote\u3001confirm\u3001cancel \u548C download";
1959
+ var DURATION_KEYS = ["30s", "60s", "90s"];
1960
+ async function drama(context) {
1961
+ const { args } = context;
1962
+ const [subcommand] = args;
1963
+ if (subcommand === void 0) {
1964
+ throw usageError(`drama \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1A${SUBCOMMANDS2}`);
1965
+ }
1966
+ if (subcommand === "create") return create(context);
1967
+ if (subcommand === "status") return status2(context, args[1]);
1968
+ if (subcommand === "quote") return quote2(context, args[1]);
1969
+ if (subcommand === "confirm") return confirm(context, args[1]);
1970
+ if (subcommand === "cancel") return cancel2(context, args[1]);
1971
+ if (subcommand === "download") return download2(context, args[1]);
1972
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Adrama \u7684\u5B50\u547D\u4EE4\u662F ${SUBCOMMANDS2}`);
1973
+ }
1974
+ function orderIdOf2(raw) {
1975
+ if (raw === void 0 || raw.trim() === "") {
1976
+ throw usageError("\u8981\u7ED9\u8BA2\u5355 id\uFF08drama create \u56DE\u7684\u90A3\u4E2A\uFF09");
1977
+ }
1978
+ return raw.trim();
1979
+ }
1980
+ function waitControls2(io, values, note) {
1981
+ return {
1982
+ timeoutMs: timeoutSecondsOf(values, DEFAULT_WAIT_TIMEOUT_MS / 1e3) * 1e3,
1983
+ now: io.now ?? (() => /* @__PURE__ */ new Date()),
1984
+ sleep: io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1985
+ note
1986
+ };
1987
+ }
1988
+ function settleOrder2(outcome, baseUrl) {
1989
+ const order2 = outcome.value.view;
1990
+ const recovery = taskRecovery("drama", order2.id, order2.status, baseUrl);
1991
+ if (outcome.timedOut) {
1992
+ throw new CliError({
1993
+ code: "CLI_WAIT_TIMEOUT",
1994
+ exitCode: EXIT.WAIT_TIMEOUT,
1995
+ message: `\u7B49\u4E86\u8FD9\u4E48\u4E45\u8FD8\u5728 ${order2.status}\uFF1A\u518D\u67E5\u7528 ${recovery.statusCommand}\uFF0C\u7ED9\u8BE5\u67E5\u8BE2\u52A0 --wait --timeout <\u79D2\u6570> \u53EF\u7EE7\u7EED\u7B49\u5F85`,
1996
+ recovery
1997
+ });
1998
+ }
1999
+ if (order2.status === "failed") {
2000
+ throw new CliError({
2001
+ code: "CLI_TASK_FAILED",
2002
+ exitCode: EXIT.TASK_FAILED,
2003
+ message: order2.reason === void 0 || order2.reason.trim() === "" ? "\u8FD9\u5355\u77ED\u5267\u6CA1\u6709\u62CD\u6210" : singleLine(order2.reason),
2004
+ recovery
2005
+ });
2006
+ }
2007
+ if (order2.status === "canceled") {
2008
+ throw new CliError({
2009
+ code: "CLI_TASK_FAILED",
2010
+ exitCode: EXIT.TASK_FAILED,
2011
+ 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",
2012
+ recovery
2013
+ });
2014
+ }
2015
+ return outcome.value;
2016
+ }
2017
+ function orderLines2(order2) {
2018
+ const money = order2.money;
2019
+ const extra = [];
2020
+ if (money.released > 0) extra.push(`\u5DF2\u91CA\u653E ${money.released} ${money.currency}`);
2021
+ if (money.refunded > 0) extra.push(`\u5DF2\u9000\u6B3E ${money.refunded} ${money.currency}`);
2022
+ return [
2023
+ `\u8BA2\u5355\uFF1A${singleLine(order2.id)}`,
2024
+ `\u72B6\u6001\uFF1A${singleLine(order2.status)}`,
2025
+ `\u4EA7\u54C1\uFF1A${singleLine(order2.productId)}\uFF08\u9898\u6750 ${singleLine(order2.genreId)}\uFF0C\u4F53\u91CF ${singleLine(order2.durationKey)}\uFF09`,
2026
+ `\u66F4\u65B0\u65F6\u95F4\uFF1A${singleLine(order2.updatedAt)}`,
2027
+ `\u8D26\uFF1A\u51BB\u7ED3 ${money.held} ${money.currency}\uFF0C\u5DF2\u7ED3\u7B97 ${money.settled} ${money.currency}` + (extra.length > 0 ? `\uFF08${extra.join("\uFF0C")}\uFF09` : "")
2028
+ ];
2029
+ }
2030
+ function guidanceLines(order2) {
2031
+ const lines = [];
2032
+ if (order2.reason !== void 0 && order2.reason.trim() !== "") {
2033
+ const label = order2.status === "failed" ? "\u5931\u8D25\u539F\u56E0" : order2.status === "partial" ? "\u6CA1\u62CD\u5B8C\u7684\u90E8\u5206" : "\u505C\u9760\u539F\u56E0";
2034
+ lines.push(`${label}\uFF1A${singleLine(order2.reason)}`);
2035
+ }
2036
+ for (const gap of order2.gaps ?? []) {
2037
+ const kind = gap.kind === "character" ? "\u89D2\u8272" : gap.kind === "scene" ? "\u573A\u666F" : "\u7D20\u6750";
2038
+ const scenes = gap.sceneNos.length > 0 ? `\uFF0C\u51FA\u73B0\u5728\u7B2C ${gap.sceneNos.join("\u3001")} \u955C` : "";
2039
+ lines.push(`\u7F3A\u53E3\uFF1A${kind}\u300C${singleLine(gap.label !== "" ? gap.label : gap.key)}\u300D\u8FD8\u6CA1\u7ED1\u5B9A${scenes}`);
2040
+ }
2041
+ const next = nextStepOf(order2);
2042
+ return next === null ? lines : [...lines, next];
2043
+ }
2044
+ function nextStepOf(order2) {
2045
+ switch (order2.status) {
2046
+ case "preparing":
2047
+ return `\u4E0B\u4E00\u6B65\uFF1A\u7B49\u5199\u7A3F\u4E0E\u6392\u7248\u8DD1\u5B8C\uFF0C\u91CD\u65B0\u67E5\u8BE2\uFF1Abuke drama status ${order2.id}`;
2048
+ case "needs_attention":
2049
+ 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}`;
2050
+ case "quotable":
2051
+ 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>`;
2052
+ case "producing":
2053
+ case "stitching":
2054
+ return `\u4E0B\u4E00\u6B65\uFF1A\u7B49\u5B83\u62CD\u5B8C\uFF1Abuke drama status ${order2.id} --wait`;
2055
+ case "partial":
2056
+ 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}`;
2057
+ case "delivered":
2058
+ return `\u4E0B\u8F7D\uFF1Abuke drama download ${order2.id}`;
2059
+ default:
2060
+ return null;
2061
+ }
2062
+ }
2063
+ function sufficientLine(sufficient) {
2064
+ if (sufficient === true) return "\u591F\u8FD9\u4E00\u5355";
2065
+ if (sufficient === false) return "\u4E0D\u591F\u8FD9\u4E00\u5355";
2066
+ return "\u6682\u65F6\u7B97\u4E0D\u51FA\u6765";
2067
+ }
2068
+ function quoteLines(orderId, quote3) {
2069
+ const price = [`\u603B\u4EF7 ${quote3.totalCredits} \u5E03`, `${quote3.shotCount} \u4E2A\u955C\u5934`];
2070
+ if (quote3.shotCreditCost !== void 0) price.push(`\u6BCF\u955C ${quote3.shotCreditCost} \u5E03`);
2071
+ const spec = [quote3.videoModel, quote3.resolution].filter((entry) => entry !== void 0);
2072
+ return [
2073
+ `\u62A5\u4EF7\uFF1A${price.join("\uFF0C")}`,
2074
+ ...spec.length > 0 ? [`\u6863\u4F4D\uFF1A${spec.join(" / ")}`] : [],
2075
+ `\u62A5\u4EF7\u54C8\u5E0C\uFF1A${quote3.quoteHash}`,
2076
+ `\u4F59\u989D\uFF1A${sufficientLine(quote3.sufficient)}`,
2077
+ `\u786E\u8BA4\u5F00\u62CD\uFF1Abuke drama confirm ${orderId} --quote-hash ${quote3.quoteHash}`
2078
+ ];
2079
+ }
2080
+ async function create(context) {
2081
+ const { io, out, values } = context;
2082
+ const productId = textOption(values, "product");
2083
+ if (productId === void 0) {
2084
+ throw usageError("\u8981\u7ED9 --product <\u4EA7\u54C1id>\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2085
+ }
2086
+ const genreId = textOption(values, "genre");
2087
+ const durationKey = textOption(values, "duration");
2088
+ if (durationKey !== void 0 && !DURATION_KEYS.includes(durationKey)) {
2089
+ throw usageError(`--duration \u53EA\u80FD\u662F ${DURATION_KEYS.join("\u3001")}\uFF0C\u5199\u7684\u662F ${durationKey}`);
2090
+ }
2091
+ const briefRef = textOption(values, "brief");
2092
+ const brief = briefRef === void 0 ? void 0 : await readPropsArgument(io, briefRef, "\u6545\u4E8B\u8BBE\u5B9A\uFF08--brief\uFF09");
2093
+ const idempotencyKey = idempotencyKeyOf(values);
2094
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2095
+ const created = await createDramaOrder(
2096
+ baseUrl,
2097
+ apiKey,
2098
+ {
2099
+ productId,
2100
+ ...genreId === void 0 ? {} : { genreId },
2101
+ ...durationKey === void 0 ? {} : { durationKey },
2102
+ ...brief === void 0 ? {} : { brief }
2103
+ },
2104
+ idempotencyKey,
2105
+ io
2106
+ );
2107
+ out.result(
2108
+ [...orderLines2(created.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`, `\u67E5\u8FDB\u5EA6\uFF1Abuke drama status ${created.view.id}`],
2109
+ // --json 按文档回服务端那份完整的 order;replayed 说明这次是新建还是同键重放。
2110
+ { order: created.raw.order, replayed: created.replayed, idempotencyKey }
2111
+ );
2112
+ }
2113
+ async function status2(context, rawId) {
2114
+ const { io, out, values } = context;
2115
+ const orderId = orderIdOf2(rawId);
2116
+ const shouldWait = values.wait === true;
2117
+ const controls = shouldWait ? waitControls2(io, values, (line) => out.notice(line)) : void 0;
2118
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2119
+ const current = await getDramaOrder(baseUrl, apiKey, orderId, io);
2120
+ const final = shouldWait ? settleOrder2(await awaitDramaOrder(baseUrl, apiKey, current, io, controls), baseUrl) : current;
2121
+ out.result([...orderLines2(final.view), ...guidanceLines(final.view)], {
2122
+ order: final.raw.order,
2123
+ ...shouldWait ? { waited: true } : {}
2124
+ });
2125
+ }
2126
+ async function quote2(context, rawId) {
2127
+ const { io, out } = context;
2128
+ const orderId = orderIdOf2(rawId);
2129
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2130
+ const { view, raw } = await getDramaQuote(baseUrl, apiKey, orderId, io);
2131
+ out.result(quoteLines(orderId, view), { quote: raw.quote, quoteHash: view.quoteHash, sufficient: view.sufficient });
2132
+ }
2133
+ async function confirm(context, rawId) {
2134
+ const { io, out, values } = context;
2135
+ const orderId = orderIdOf2(rawId);
2136
+ const quoteHash = textOption(values, "quote-hash");
2137
+ if (quoteHash === void 0) {
2138
+ throw usageError("\u8981\u7ED9 --quote-hash <\u62A5\u4EF7\u54C8\u5E0C>\uFF08buke drama quote \u56DE\u7684\u90A3\u4E2A\uFF09");
2139
+ }
2140
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2141
+ const current = await getDramaOrder(baseUrl, apiKey, orderId, io);
2142
+ const quoted = current.view.status === "quotable" ? await getDramaQuote(baseUrl, apiKey, orderId, io) : null;
2143
+ if (values.yes !== true) {
2144
+ if (!out.json) {
2145
+ out.result(
2146
+ 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),
2147
+ void 0
2148
+ );
2149
+ }
2150
+ throw new CliError({
2151
+ code: "CLI_USAGE",
2152
+ exitCode: EXIT.USAGE,
2153
+ 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`,
2154
+ hints: [`\u770B\u8FC7\u518D\u786E\u8BA4\uFF1Abuke drama status ${orderId}`]
2155
+ });
2156
+ }
2157
+ const idempotencyKey = idempotencyKeyOf(values);
2158
+ const confirmed = await confirmDramaOrder(baseUrl, apiKey, orderId, quoteHash, idempotencyKey, io);
2159
+ out.result(
2160
+ [
2161
+ ...orderLines2(confirmed.view),
2162
+ 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",
2163
+ ...guidanceLines(confirmed.view),
2164
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2165
+ ],
2166
+ {
2167
+ order: confirmed.raw.order,
2168
+ productionStarted: confirmed.productionStarted,
2169
+ // 计价的那一份:可报价时是刚取回的报价;续拍按原签字走,没有当前报价。
2170
+ confirmed: { totalCredits: quoted === null ? null : quoted.view.totalCredits, quoteHash },
2171
+ idempotencyKey
2172
+ }
2173
+ );
2174
+ }
2175
+ async function cancel2(context, rawId) {
2176
+ const { io, out, values } = context;
2177
+ const orderId = orderIdOf2(rawId);
2178
+ const idempotencyKey = idempotencyKeyOf(values);
2179
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2180
+ const canceled = await cancelDramaOrder(baseUrl, apiKey, orderId, idempotencyKey, io);
2181
+ out.result(
2182
+ [
2183
+ ...orderLines2(canceled.view),
2184
+ 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",
2185
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2186
+ ],
2187
+ { order: canceled.raw.order, canceled: canceled.canceled, idempotencyKey }
2188
+ );
2189
+ }
2190
+ async function download2(context, rawId) {
2191
+ const { io, out, values } = context;
2192
+ const orderId = orderIdOf2(rawId);
2193
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2194
+ const result = await downloadDramaFilm(baseUrl, apiKey, orderId, io);
2195
+ const defaultName = result.filename ?? `drama-${orderId}.mp4`;
2196
+ const target = textOption(values, "out") ?? defaultName;
2197
+ const saved = await saveDownload(target, result.bytes);
2198
+ out.result(
2199
+ [`\u5DF2\u4E0B\u8F7D\uFF1A${saved.path}`, `\u5927\u5C0F\uFF1A${saved.bytes} \u5B57\u8282`, `\u6821\u9A8C\uFF1Asha256 ${saved.sha256}`],
2200
+ { download: { ...saved, filename: defaultName } }
2201
+ );
2202
+ }
2203
+
868
2204
  // src/output.ts
869
2205
  function errorDocument(error) {
870
2206
  const document = {
@@ -872,13 +2208,27 @@ function errorDocument(error) {
872
2208
  };
873
2209
  if (error.retryAfterSeconds !== void 0) document.error.retryAfterSeconds = error.retryAfterSeconds;
874
2210
  if (error.httpStatus !== void 0) document.error.httpStatus = error.httpStatus;
2211
+ if (error.recovery !== void 0) document.recovery = error.recovery;
875
2212
  return document;
876
2213
  }
877
2214
  function errorLines(error) {
878
2215
  const details = [`\u9519\u8BEF\u7801 ${singleLine(error.code)}`];
879
2216
  if (error.traceId) details.push(`\u8BF7\u6C42\u7F16\u53F7 ${singleLine(error.traceId)}`);
880
2217
  if (error.retryAfterSeconds !== void 0) details.push(`${error.retryAfterSeconds} \u79D2\u540E\u53EF\u4EE5\u91CD\u8BD5`);
881
- return [`\u9519\u8BEF\uFF1A${singleLine(error.message)}`, `\uFF08${details.join("\uFF1B")}\uFF09`, ...error.hints];
2218
+ const recovery = error.recovery;
2219
+ return [
2220
+ `\u9519\u8BEF\uFF1A${singleLine(error.message)}`,
2221
+ `\uFF08${details.join("\uFF1B")}\uFF09`,
2222
+ ...error.hints,
2223
+ ...recovery === void 0 ? [] : [
2224
+ recovery.acceptance === "confirmed" ? `\u5DF2\u53D7\u7406\u4EFB\u52A1\uFF1A${singleLine(recovery.taskId ?? "")}` : "\u63D0\u4EA4\u7ED3\u679C\u5C1A\u672A\u786E\u8BA4\uFF0C\u8BF7\u5148\u5728 Studio \u6838\u5BF9\u539F\u8BF7\u6C42\uFF1B\u4E0D\u8981\u91CD\u65B0\u4E0B\u5355",
2225
+ ...recovery.baseUrl === void 0 ? [] : [`\u670D\u52A1\u5730\u5740\uFF1A${singleLine(recovery.baseUrl)}`],
2226
+ ...recovery.status === void 0 ? [] : [`\u6700\u540E\u72B6\u6001\uFF1A${singleLine(recovery.status)}`],
2227
+ ...recovery.failureCode === void 0 ? [] : [`\u4EFB\u52A1\u5931\u8D25\u7801\uFF1A${singleLine(recovery.failureCode)}`],
2228
+ ...recovery.idempotencyKey === void 0 ? [] : [`\u5E42\u7B49\u952E\uFF1A${singleLine(recovery.idempotencyKey)}`],
2229
+ ...recovery.statusCommand === void 0 ? [] : [`\u53EA\u67E5\u8BE2\u539F\u4EFB\u52A1\uFF1A${singleLine(recovery.statusCommand)}`]
2230
+ ]
2231
+ ];
882
2232
  }
883
2233
  function createOutput(streams, json) {
884
2234
  const secrets = [];
@@ -996,36 +2346,333 @@ async function logout({ io, out }) {
996
2346
  }
997
2347
  }
998
2348
 
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" };
2349
+ // src/commands/products.ts
2350
+ var PRODUCT_ANGLES = ["front", "side", "back", "detail"];
2351
+ async function products(context) {
2352
+ const { args, values } = context;
2353
+ const [subcommand] = args;
2354
+ if (subcommand === void 0) {
2355
+ throw usageError("products \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1Acreate\u3001set-images \u6216 confirm-lock");
1007
2356
  }
1008
- const stored = await readCredentials(io, (line) => out.notice(line));
1009
- if (!stored) {
2357
+ if (subcommand === "create") return create2(context, values);
2358
+ if (subcommand === "set-images") return setImages(context, values, args[1]);
2359
+ if (subcommand === "confirm-lock") return confirmLock(context, values, args[1]);
2360
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Aproducts \u7684\u5B50\u547D\u4EE4\u662F create\u3001set-images \u548C confirm-lock`);
2361
+ }
2362
+ function productIdOf(raw, what) {
2363
+ if (raw === void 0 || raw.trim() === "") {
2364
+ throw usageError(`\u8981\u7ED9${what}`);
2365
+ }
2366
+ return raw.trim();
2367
+ }
2368
+ async function create2(context, values) {
2369
+ const { io, out } = context;
2370
+ const name2 = textOption(values, "name");
2371
+ if (name2 === void 0) {
2372
+ throw usageError("\u8981\u7ED9 --name <\u540D\u79F0>");
2373
+ }
2374
+ const category = textOption(values, "category");
2375
+ const idempotencyKey = idempotencyKeyOf(values);
2376
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2377
+ const { view, raw, replayed } = await createProduct(baseUrl, apiKey, name2, category, idempotencyKey, io);
2378
+ out.result(
2379
+ [
2380
+ 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)}`,
2381
+ `\u540D\u79F0\uFF1A${singleLine(view.name)}`,
2382
+ `\u7C7B\u76EE\uFF1A${singleLine(view.category)}`,
2383
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2384
+ ],
2385
+ { product: raw.product, replayed, idempotencyKey }
2386
+ );
2387
+ }
2388
+ async function setImages(context, values, rawId) {
2389
+ const { io, out } = context;
2390
+ const productId = productIdOf(rawId, "\u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2391
+ const planned = [];
2392
+ for (const angle of PRODUCT_ANGLES) {
2393
+ const path = textOption(values, angle);
2394
+ if (path !== void 0) planned.push({ angle, path });
2395
+ }
2396
+ if (planned.length === 0) {
2397
+ throw usageError("\u81F3\u5C11\u7ED9\u4E00\u4E2A\u89D2\u5EA6\u7684\u56FE\uFF1A--front\u3001--side\u3001--back \u6216 --detail");
2398
+ }
2399
+ const images = await Promise.all(planned.map(async ({ angle, path }) => ({ angle, ...await readImageFile(path) })));
2400
+ const idempotencyKey = idempotencyKeyOf(values);
2401
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2402
+ let latest;
2403
+ const uploaded = [];
2404
+ for (const image of images) {
2405
+ const result = await putProductImage(
2406
+ baseUrl,
2407
+ apiKey,
2408
+ productId,
2409
+ image.angle,
2410
+ image.bytes,
2411
+ image.contentType,
2412
+ idempotencyKey,
2413
+ io
2414
+ );
2415
+ latest = result;
2416
+ uploaded.push({ angle: image.angle, replayed: result.replayed });
2417
+ }
2418
+ out.result(
2419
+ [
2420
+ ...uploaded.map(
2421
+ ({ angle, replayed }) => replayed ? `${angle}\uFF1A\u8FD9\u5F20\u5DF2\u7ECF\u4F20\u8FC7\u4E86\uFF08\u540C\u5B57\u8282\u91CD\u653E\uFF09` : `${angle}\uFF1A\u5DF2\u4E0A\u4F20`
2422
+ ),
2423
+ `\u4EA7\u54C1\uFF1A${singleLine(latest?.view.id ?? productId)}\uFF08\u73B0\u5728 ${latest?.view.images.length ?? 0} \u5F20\u56FE\uFF09`,
2424
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2425
+ ],
2426
+ { product: latest?.raw.product, uploaded, idempotencyKey }
2427
+ );
2428
+ }
2429
+ async function confirmLock(context, values, rawId) {
2430
+ const { io, out } = context;
2431
+ const productId = productIdOf(rawId, "\u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2432
+ const colorName = textOption(values, "color");
2433
+ const silhouette = textOption(values, "silhouette");
2434
+ const material = textOption(values, "material");
2435
+ for (const [option, value] of [
2436
+ ["--color", colorName],
2437
+ ["--silhouette", silhouette],
2438
+ ["--material", material]
2439
+ ]) {
2440
+ if (value === void 0) {
2441
+ throw usageError(`\u5916\u89C2\u9501\u8981\u7ED9\u5168\uFF1A\u7F3A ${option}`);
2442
+ }
2443
+ }
2444
+ const idempotencyKey = idempotencyKeyOf(values);
2445
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2446
+ const { view, raw } = await confirmProductLock(
2447
+ baseUrl,
2448
+ apiKey,
2449
+ productId,
2450
+ { colorName, silhouette, material },
2451
+ idempotencyKey,
2452
+ io
2453
+ );
2454
+ out.result(
2455
+ [
2456
+ `\u5916\u89C2\u9501\u5DF2\u786E\u8BA4\uFF1A\u4EA7\u54C1 ${singleLine(view.product.id)}`,
2457
+ `\u989C\u8272\uFF1A${singleLine(view.productLock.colorName)}`,
2458
+ `\u5ED3\u5F62\uFF1A${singleLine(view.productLock.silhouette)}`,
2459
+ `\u6750\u8D28\uFF1A${singleLine(view.productLock.material)}`,
2460
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2461
+ ],
2462
+ { productLock: raw.productLock, product: raw.product, idempotencyKey }
2463
+ );
2464
+ }
2465
+
2466
+ // src/commands/videos.ts
2467
+ var SUBCOMMANDS3 = "create\u3001status\u3001message\u3001confirm \u548C download";
2468
+ async function videos(context) {
2469
+ const { args } = context;
2470
+ const [subcommand] = args;
2471
+ if (subcommand === void 0) {
2472
+ throw usageError(`videos \u8981\u8DDF\u4E00\u4E2A\u5B50\u547D\u4EE4\uFF1A${SUBCOMMANDS3}`);
2473
+ }
2474
+ if (subcommand === "create") return create3(context);
2475
+ if (subcommand === "status") return status3(context, args[1]);
2476
+ if (subcommand === "message") return message(context, args[1]);
2477
+ if (subcommand === "confirm") return confirm2(context, args[1]);
2478
+ if (subcommand === "download") return download3(context, args[1]);
2479
+ throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u5B50\u547D\u4EE4 ${subcommand}\uFF1Avideos \u7684\u5B50\u547D\u4EE4\u662F ${SUBCOMMANDS3}`);
2480
+ }
2481
+ function runIdOf(raw) {
2482
+ if (raw === void 0 || raw.trim() === "") {
2483
+ throw usageError("\u8981\u7ED9\u89C6\u9891\u4EFB\u52A1 id\uFF08videos create \u56DE\u7684\u90A3\u4E2A\uFF09");
2484
+ }
2485
+ return raw.trim();
2486
+ }
2487
+ function waitControls3(io, values) {
2488
+ return {
2489
+ timeoutMs: timeoutSecondsOf(values, DEFAULT_WAIT_TIMEOUT_MS / 1e3) * 1e3,
2490
+ now: io.now ?? (() => /* @__PURE__ */ new Date()),
2491
+ sleep: io.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))
2492
+ };
2493
+ }
2494
+ function settleRun(outcome, baseUrl) {
2495
+ const run2 = outcome.value.view;
2496
+ const recovery = taskRecovery("videos", run2.rootRunId, run2.status, baseUrl, run2.failure?.code);
2497
+ if (outcome.timedOut) {
1010
2498
  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"
2499
+ code: "CLI_WAIT_TIMEOUT",
2500
+ exitCode: EXIT.WAIT_TIMEOUT,
2501
+ message: `\u7B49\u4E86\u8FD9\u4E48\u4E45\u8FD8\u5728 ${run2.status}\uFF08${run2.stage}\uFF09\uFF1A\u518D\u67E5\u7528 ${recovery.statusCommand}\uFF0C\u7ED9\u8BE5\u67E5\u8BE2\u52A0 --wait --timeout <\u79D2\u6570> \u53EF\u7EE7\u7EED\u7B49\u5F85`,
2502
+ recovery
1014
2503
  });
1015
2504
  }
1016
- out.protect(stored.apiKey);
1017
- if (explicit !== null && explicit !== stored.baseUrl) {
2505
+ if (run2.status === "failed" || run2.status === "canceled") {
1018
2506
  throw new CliError({
1019
- code: "CLI_BASE_URL_MISMATCH",
2507
+ code: "CLI_TASK_FAILED",
2508
+ exitCode: EXIT.TASK_FAILED,
2509
+ 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)}`),
2510
+ recovery
2511
+ });
2512
+ }
2513
+ return outcome.value;
2514
+ }
2515
+ function runLines(run2) {
2516
+ return [
2517
+ `\u89C6\u9891\u4EFB\u52A1\uFF1A${singleLine(run2.rootRunId)}`,
2518
+ `\u72B6\u6001\uFF1A${singleLine(run2.status)}\uFF08${singleLine(run2.stage)}\uFF09`,
2519
+ `\u4EA7\u54C1\uFF1A${singleLine(run2.product.name)}`,
2520
+ `\u66F4\u65B0\u65F6\u95F4\uFF1A${singleLine(run2.updatedAt)}`
2521
+ ];
2522
+ }
2523
+ function waitingLines(run2) {
2524
+ if (run2.status === "awaiting_confirmation") return [`\u7B49\u4F60\u786E\u8BA4\uFF1Abuke videos confirm ${run2.rootRunId}`];
2525
+ if (run2.status === "awaiting_reply" || run2.status === "needs_input") {
2526
+ return [`\u7B49\u4F60\u56DE\u7B54\uFF1Abuke videos message ${run2.rootRunId} --text <\u5185\u5BB9>`];
2527
+ }
2528
+ return [];
2529
+ }
2530
+ async function create3(context) {
2531
+ const { io, out, values } = context;
2532
+ const productId = textOption(values, "product");
2533
+ if (productId === void 0) {
2534
+ throw usageError("\u8981\u7ED9 --product <\u4EA7\u54C1id>\uFF08buke products create \u56DE\u7684\u90A3\u4E2A\uFF09");
2535
+ }
2536
+ const intent = textOption(values, "intent");
2537
+ const durationRaw = textOption(values, "duration");
2538
+ let durationSeconds;
2539
+ if (durationRaw !== void 0) {
2540
+ const parsed = Number(durationRaw);
2541
+ if (!Number.isInteger(parsed) || parsed <= 0) {
2542
+ throw usageError(`--duration \u8981\u662F\u6B63\u6574\u6570\u79D2\u6570\uFF0C\u5199\u7684\u662F ${durationRaw}`);
2543
+ }
2544
+ durationSeconds = parsed;
2545
+ }
2546
+ const preferences = {};
2547
+ if (durationSeconds !== void 0) preferences.durationSeconds = durationSeconds;
2548
+ for (const [option, field] of [
2549
+ ["resolution", "resolution"],
2550
+ ["speed", "speed"],
2551
+ ["aspect-ratio", "aspectRatio"],
2552
+ ["style-preset", "stylePreset"]
2553
+ ]) {
2554
+ const value = textOption(values, option);
2555
+ if (value !== void 0) preferences[field] = value;
2556
+ }
2557
+ const idempotencyKey = idempotencyKeyOf(values);
2558
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2559
+ const { view, raw } = await createVideoRun(
2560
+ baseUrl,
2561
+ apiKey,
2562
+ {
2563
+ productId,
2564
+ ...intent === void 0 ? {} : { intent },
2565
+ ...Object.keys(preferences).length > 0 ? { preferences } : {}
2566
+ },
2567
+ idempotencyKey,
2568
+ io
2569
+ );
2570
+ out.result(
2571
+ [
2572
+ ...runLines(view),
2573
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`,
2574
+ `\u67E5\u8FDB\u5EA6\uFF1Abuke videos status ${view.rootRunId}`
2575
+ ],
2576
+ // --json 按文档回服务端那份完整的 run 视图。
2577
+ { run: raw.run, idempotencyKey }
2578
+ );
2579
+ }
2580
+ async function status3(context, rawId) {
2581
+ const { io, out, values } = context;
2582
+ const runId = runIdOf(rawId);
2583
+ const shouldWait = values.wait === true;
2584
+ const controls = shouldWait ? waitControls3(io, values) : void 0;
2585
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2586
+ const current = await getVideoRun(baseUrl, apiKey, runId, io);
2587
+ const final = shouldWait ? settleRun(await awaitVideoRun(baseUrl, apiKey, current, io, controls), baseUrl) : current;
2588
+ const lastDirectorLine = [...final.view.messages].reverse().find((message2) => message2.role === "assistant" && message2.text.trim() !== "");
2589
+ out.result(
2590
+ [
2591
+ ...runLines(final.view),
2592
+ ...lastDirectorLine === void 0 ? [] : [`\u5BFC\u6F14\u8BF4\uFF1A${singleLine(lastDirectorLine.text)}`],
2593
+ ...waitingLines(final.view),
2594
+ ...final.view.status === "succeeded" ? [`\u4E0B\u8F7D\uFF1Abuke videos download ${final.view.rootRunId}`] : []
2595
+ ],
2596
+ { run: final.raw.run, ...shouldWait ? { waited: true } : {} }
2597
+ );
2598
+ }
2599
+ async function message(context, rawId) {
2600
+ const { io, out, values } = context;
2601
+ const runId = runIdOf(rawId);
2602
+ const text = textOption(values, "text");
2603
+ if (text === void 0) {
2604
+ throw usageError("\u8981\u7ED9 --text <\u5185\u5BB9>\uFF1A\u60F3\u5BF9\u5BFC\u6F14\u8BF4\u7684\u90A3\u53E5\u8BDD");
2605
+ }
2606
+ const idempotencyKey = idempotencyKeyOf(values);
2607
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2608
+ const { view, raw } = await postVideoMessage(baseUrl, apiKey, runId, text, idempotencyKey, io);
2609
+ const lastDirectorLine = [...view.messages].reverse().find((entry) => entry.role === "assistant" && entry.text.trim() !== "");
2610
+ out.result(
2611
+ [
2612
+ ...runLines(view),
2613
+ ...lastDirectorLine === void 0 ? [] : [`\u5BFC\u6F14\u8BF4\uFF1A${singleLine(lastDirectorLine.text)}`],
2614
+ ...waitingLines(view),
2615
+ `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`
2616
+ ],
2617
+ { run: raw.run, idempotencyKey }
2618
+ );
2619
+ }
2620
+ async function confirm2(context, rawId) {
2621
+ const { io, out, values } = context;
2622
+ const runId = runIdOf(rawId);
2623
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2624
+ const fetched = await getVideoRun(baseUrl, apiKey, runId, io);
2625
+ const run2 = fetched.view;
2626
+ const card = run2.nextActions.find((action) => action.type === "confirm_production");
2627
+ if (card === void 0) {
2628
+ throw new CliError({
2629
+ code: "CLI_USAGE",
1020
2630
  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}`
2631
+ 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
2632
  });
1023
2633
  }
1024
- return { apiKey: stored.apiKey, baseUrl: stored.baseUrl, source: "file", credentialsPath: credentialsPath(io) };
2634
+ if (card.confirmation === void 0) {
2635
+ throw new CliError({
2636
+ code: "CLI_USAGE",
2637
+ exitCode: EXIT.USAGE,
2638
+ 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"
2639
+ });
2640
+ }
2641
+ const credits = card.credits ?? 0;
2642
+ const kind = card.confirmKind === void 0 ? "\u786E\u8BA4" : `\u786E\u8BA4\uFF08${card.confirmKind}\uFF09`;
2643
+ if (values.yes !== true) {
2644
+ throw new CliError({
2645
+ code: "CLI_USAGE",
2646
+ exitCode: EXIT.USAGE,
2647
+ message: `\u8FD9\u5F20\u5361\u8981\u82B1 ${credits} \u5E03\u3002\u52A0 --yes \u786E\u8BA4\u8FD9\u7B14\u5E03\u652F\u51FA`,
2648
+ hints: [`${kind}\uFF1A${singleLine(run2.label)}`, `\u770B\u8FC7\u518D\u786E\u8BA4\uFF1Abuke videos status ${run2.rootRunId}`]
2649
+ });
2650
+ }
2651
+ const idempotencyKey = idempotencyKeyOf(values);
2652
+ const outcome = await confirmVideoProduction(baseUrl, apiKey, runId, run2.version, card.confirmation, idempotencyKey, io);
2653
+ out.result(
2654
+ [...runLines(outcome.view), `\u5DF2\u786E\u8BA4\uFF1A\u8FD9\u7B14\u6700\u591A ${credits} \u5E03`, ...waitingLines(outcome.view), `\u5E42\u7B49\u952E\uFF1A${idempotencyKey}`],
2655
+ { run: outcome.raw.run, confirmed: { credits, confirmKind: card.confirmKind ?? null }, idempotencyKey }
2656
+ );
1025
2657
  }
2658
+ async function download3(context, rawId) {
2659
+ const { io, out, values } = context;
2660
+ const runId = runIdOf(rawId);
2661
+ const { apiKey, baseUrl } = await resolveApiKey(context);
2662
+ const result = await downloadVideo(baseUrl, apiKey, runId, io);
2663
+ const defaultName = result.filename ?? `video-${runId}.mp4`;
2664
+ const target = textOption(values, "out") ?? defaultName;
2665
+ const saved = await saveDownload(target, result.bytes);
2666
+ out.result(
2667
+ [`\u5DF2\u4E0B\u8F7D\uFF1A${saved.path}`, `\u5927\u5C0F\uFF1A${saved.bytes} \u5B57\u8282`, `\u6821\u9A8C\uFF1Asha256 ${saved.sha256}`],
2668
+ { download: { ...saved, filename: defaultName } }
2669
+ );
2670
+ }
2671
+
2672
+ // src/commands/whoami.ts
1026
2673
  async function whoami(context) {
1027
2674
  const { io, out } = context;
1028
- const resolved = await resolve(context);
2675
+ const resolved = await resolveApiKey(context);
1029
2676
  const { apiKey, baseUrl } = resolved;
1030
2677
  let me;
1031
2678
  try {
@@ -1041,11 +2688,7 @@ async function whoami(context) {
1041
2688
  const displayPrefix = displayPrefixOf(apiKey, me.key.id);
1042
2689
  const identity = identityDocument(baseUrl, me, displayPrefix);
1043
2690
  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
- ],
2691
+ [`\u5F53\u524D\u767B\u5F55 ${baseUrl}`, ...identityLines(me, displayPrefix), keySourceLine(resolved)],
1049
2692
  resolved.source === "env" ? { ...identity, source: resolved.source } : { ...identity, source: resolved.source, credentialsPath: resolved.credentialsPath },
1050
2693
  [displayPrefix]
1051
2694
  );
@@ -1062,6 +2705,8 @@ var COMMANDS = {
1062
2705
  login: {
1063
2706
  run: login,
1064
2707
  acceptsBaseUrl: true,
2708
+ options: [],
2709
+ positionals: [],
1065
2710
  help: [
1066
2711
  "\u7528\u6CD5\uFF1Abuke login [--base-url <\u5730\u5740>] [--json]",
1067
2712
  "",
@@ -1078,6 +2723,8 @@ var COMMANDS = {
1078
2723
  whoami: {
1079
2724
  run: whoami,
1080
2725
  acceptsBaseUrl: true,
2726
+ options: [],
2727
+ positionals: [],
1081
2728
  help: [
1082
2729
  "\u7528\u6CD5\uFF1Abuke whoami [--base-url <\u5730\u5740>] [--json]",
1083
2730
  "",
@@ -1093,6 +2740,8 @@ var COMMANDS = {
1093
2740
  logout: {
1094
2741
  run: logout,
1095
2742
  acceptsBaseUrl: false,
2743
+ options: [],
2744
+ positionals: [],
1096
2745
  help: [
1097
2746
  "\u7528\u6CD5\uFF1Abuke logout [--json]",
1098
2747
  "",
@@ -1101,15 +2750,189 @@ var COMMANDS = {
1101
2750
  "\u9009\u9879\uFF1A",
1102
2751
  " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
1103
2752
  ].join("\n")
2753
+ },
2754
+ balance: {
2755
+ run: balance,
2756
+ acceptsBaseUrl: true,
2757
+ options: [],
2758
+ positionals: [],
2759
+ help: [
2760
+ "\u7528\u6CD5\uFF1Abuke balance [--json]",
2761
+ "",
2762
+ "\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",
2763
+ "",
2764
+ "\u9009\u9879\uFF1A",
2765
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2766
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2767
+ ].join("\n")
2768
+ },
2769
+ products: {
2770
+ run: products,
2771
+ acceptsBaseUrl: true,
2772
+ options: ["name", "category", "front", "side", "back", "detail", "color", "silhouette", "material", "idempotency-key"],
2773
+ positionals: ["\u5B50\u547D\u4EE4", "\u4EA7\u54C1id"],
2774
+ help: [
2775
+ "\u7528\u6CD5\uFF1A",
2776
+ " buke products create --name <\u540D\u79F0> [--category <\u7C7B\u76EE>] [--json]",
2777
+ " buke products set-images <\u4EA7\u54C1id> --front <\u56FE> [--side <\u56FE>] [--back <\u56FE>] [--detail <\u56FE>]",
2778
+ " buke products confirm-lock <\u4EA7\u54C1id> --color <\u8272> --silhouette <\u5ED3\u5F62> --material <\u6750\u8D28>",
2779
+ "",
2780
+ "\u4EA7\u54C1\u5EFA\u6863\uFF0C\u662F\u5546\u54C1\u89C6\u9891\u4E0B\u5355\u7684\u524D\u7F6E\uFF1A",
2781
+ " 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",
2782
+ " set-images \u4F20\u4EA7\u54C1\u56FE\uFF08JPEG/PNG/WebP \u7684\u6587\u4EF6\u8DEF\u5F84\uFF1B\u81F3\u5C11\u7ED9\u4E00\u4E2A\u89D2\u5EA6\uFF09",
2783
+ " confirm-lock \u786E\u8BA4\u5916\u89C2\u9501\uFF1A\u989C\u8272\u3001\u5ED3\u5F62\u3001\u6750\u8D28\u4E09\u6837\u90FD\u8981\u7ED9",
2784
+ "",
2785
+ "\u9009\u9879\uFF1A",
2786
+ " --name <\u540D\u79F0> \u4EA7\u54C1\u540D\u79F0",
2787
+ " --category <\u7C7B\u76EE> \u7C7B\u76EE\uFF1B\u4E0D\u7ED9\u65F6\u670D\u52A1\u7AEF\u6309\u540D\u79F0\u548C\u4ECB\u7ECD\u8BC6\u522B",
2788
+ " --front/--side/--back/--detail <\u56FE> \u4EA7\u54C1\u56FE\u7684\u89D2\u5EA6\u4E0E\u6587\u4EF6\u8DEF\u5F84",
2789
+ " --color <\u8272> \u5916\u89C2\u9501\uFF1A\u989C\u8272",
2790
+ " --silhouette <\u5ED3\u5F62> \u5916\u89C2\u9501\uFF1A\u5ED3\u5F62",
2791
+ " --material <\u6750\u8D28> \u5916\u89C2\u9501\uFF1A\u6750\u8D28",
2792
+ " --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",
2793
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2794
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2795
+ ].join("\n")
2796
+ },
2797
+ shorts: {
2798
+ run: shorts,
2799
+ acceptsBaseUrl: true,
2800
+ options: ["template", "props", "quote-hash", "wait", "timeout", "out", "idempotency-key"],
2801
+ positionals: ["\u5B50\u547D\u4EE4", "\u8BA2\u5355id"],
2802
+ help: [
2803
+ "\u7528\u6CD5\uFF1A",
2804
+ " buke shorts catalog [--json]",
2805
+ " buke shorts upload <\u56FE\u7247\u6587\u4EF6>",
2806
+ " buke shorts quote --template <\u6A21\u677Fid> --props <props.json|->",
2807
+ " buke shorts order --template <\u6A21\u677Fid> --props <props.json|-> [--quote-hash <hash>] [--wait] [--timeout <\u79D2>]",
2808
+ " buke shorts status <\u8BA2\u5355id> [--wait] [--timeout <\u79D2>]",
2809
+ " buke shorts download <\u8BA2\u5355id> [--out <\u8DEF\u5F84>]",
2810
+ " buke shorts cancel <\u8BA2\u5355id>",
2811
+ "",
2812
+ "\u6E32\u67D3\u77ED\u7247\uFF08\u6A21\u677F\u76F4\u51FA\u7684\u7AD6\u5C4F\u5546\u54C1\u77ED\u7247\uFF09\uFF1A",
2813
+ " catalog \u5217\u51FA\u5168\u90E8\u6A21\u677F\uFF08id\u3001\u65F6\u957F\u3001\u8981\u54EA\u4E9B\u7D20\u6750\uFF09",
2814
+ " 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",
2815
+ " quote \u53EA\u8BFB\u62A5\u4EF7\uFF1B\u6536\u8D39\u7684\u6A21\u677F\u4E0B\u5355\u524D\u5FC5\u987B\u5148\u62FF quoteHash",
2816
+ " order \u4E0B\u5355\uFF1B\u540C\u952E\u540C\u5185\u5BB9\u91CD\u8BD5\u56DE\u540C\u4E00\u5F20\u5355",
2817
+ " status \u67E5\u8FDB\u5EA6\uFF1B--wait \u4E00\u76F4\u7B49\u5230\u51FA\u7ED3\u679C",
2818
+ " download \u4E0B\u8F7D\u6E32\u67D3\u597D\u7684\u6210\u7247\uFF08mp4\uFF09",
2819
+ " cancel \u53D6\u6D88\u8FD8\u6CA1\u5F00\u59CB\u7684\u8BA2\u5355",
2820
+ "",
2821
+ "\u9009\u9879\uFF1A",
2822
+ " --template <\u6A21\u677Fid> \u6A21\u677F id\uFF08catalog \u91CC\u5217\u51FA\u7684\uFF09",
2823
+ " --props <props.json|-> \u6A21\u677F\u53C2\u6570\uFF1A\u4E00\u4E2A JSON \u6587\u4EF6\uFF0C\u6216 - \u8868\u793A\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB",
2824
+ " --quote-hash <hash> \u62A5\u4EF7\u54C8\u5E0C\uFF08quote \u547D\u4EE4\u56DE\u7684\uFF09",
2825
+ " --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",
2826
+ " --timeout <\u79D2> --wait \u7684\u65F6\u9650\uFF0C\u9ED8\u8BA4 600",
2827
+ " --out <\u8DEF\u5F84> \u4E0B\u8F7D\u4FDD\u5B58\u7684\u4F4D\u7F6E\uFF1B\u4E0D\u7ED9\u65F6\u5B58\u5230\u5F53\u524D\u76EE\u5F55",
2828
+ " --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",
2829
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2830
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2831
+ ].join("\n")
2832
+ },
2833
+ videos: {
2834
+ run: videos,
2835
+ acceptsBaseUrl: true,
2836
+ options: [
2837
+ "product",
2838
+ "intent",
2839
+ "duration",
2840
+ "resolution",
2841
+ "speed",
2842
+ "aspect-ratio",
2843
+ "style-preset",
2844
+ "text",
2845
+ "yes",
2846
+ "wait",
2847
+ "timeout",
2848
+ "out",
2849
+ "idempotency-key"
2850
+ ],
2851
+ positionals: ["\u5B50\u547D\u4EE4", "\u89C6\u9891id"],
2852
+ help: [
2853
+ "\u7528\u6CD5\uFF1A",
2854
+ " buke videos create --product <\u4EA7\u54C1id> [--intent <\u4E00\u53E5\u8BDD>] [--duration <\u79D2>] [--resolution <r>]",
2855
+ " [--speed <\u6863>] [--aspect-ratio <a>] [--style-preset <p>] [--json]",
2856
+ " buke videos status <\u89C6\u9891id> [--wait] [--timeout <\u79D2>]",
2857
+ " buke videos message <\u89C6\u9891id> --text <\u5185\u5BB9>",
2858
+ " buke videos confirm <\u89C6\u9891id> [--yes]",
2859
+ " buke videos download <\u89C6\u9891id> [--out <\u8DEF\u5F84>]",
2860
+ "",
2861
+ "\u5546\u54C1\u89C6\u9891\uFF08\u5BFC\u6F14\u5168\u7A0B\u7F16\u6392\u7684\u90A3\u6761\u4EA7\u7EBF\uFF09\uFF1A",
2862
+ " create \u4E0B\u5355\uFF1B\u4EA7\u54C1\u5148\u7528 buke products \u5EFA\u597D\u3002\u53D7\u7406\u540E\u8F6E\u8BE2 status \u53D6\u8FDB\u5EA6",
2863
+ " status \u67E5\u8FDB\u5EA6\uFF1B--wait \u4E00\u76F4\u7B49\u5230\u7EC8\u6001",
2864
+ " message \u7ED9\u5BFC\u6F14\u56DE\u4E00\u53E5\u8BDD\uFF08\u8865\u610F\u56FE\u3001\u56DE\u7B54\u63D0\u95EE\uFF09",
2865
+ " 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",
2866
+ " download \u4E0B\u8F7D\u6210\u7247\uFF08mp4\uFF09",
2867
+ "",
2868
+ "\u9009\u9879\uFF1A",
2869
+ " --product <\u4EA7\u54C1id> \u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\uFF09",
2870
+ " --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",
2871
+ " --duration <\u79D2> \u671F\u671B\u65F6\u957F",
2872
+ " --resolution <r> \u5206\u8FA8\u7387\u6863\u4F4D",
2873
+ " --speed <\u6863> \u751F\u6210\u901F\u5EA6\u6863\u4F4D",
2874
+ " --aspect-ratio <a> \u753B\u5E45\uFF08\u5982 9:16\uFF09",
2875
+ " --style-preset <p> \u753B\u9762\u98CE\u683C",
2876
+ " --text <\u5185\u5BB9> \u8981\u5E26\u7ED9\u5BFC\u6F14\u7684\u90A3\u53E5\u8BDD",
2877
+ " --yes confirm \u65F6\u771F\u7684\u63D0\u4EA4\uFF1B\u4E0D\u7ED9\u65F6\u53EA\u663E\u793A\u90A3\u5F20\u5361",
2878
+ " --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",
2879
+ " --timeout <\u79D2> --wait \u7684\u65F6\u9650\uFF0C\u9ED8\u8BA4 600",
2880
+ " --out <\u8DEF\u5F84> \u4E0B\u8F7D\u4FDD\u5B58\u7684\u4F4D\u7F6E\uFF1B\u4E0D\u7ED9\u65F6\u5B58\u5230\u5F53\u524D\u76EE\u5F55",
2881
+ " --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",
2882
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2883
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2884
+ ].join("\n")
2885
+ },
2886
+ drama: {
2887
+ run: drama,
2888
+ acceptsBaseUrl: true,
2889
+ options: ["product", "genre", "duration", "brief", "quote-hash", "yes", "wait", "timeout", "out", "idempotency-key"],
2890
+ positionals: ["\u5B50\u547D\u4EE4", "\u8BA2\u5355id"],
2891
+ help: [
2892
+ "\u7528\u6CD5\uFF1A",
2893
+ " buke drama create --product <\u4EA7\u54C1id> [--genre <\u9898\u6750id>] [--duration 30s|60s|90s] [--brief <JSON \u6587\u4EF6|->] [--json]",
2894
+ " buke drama status <\u8BA2\u5355id> [--wait] [--timeout <\u79D2>]",
2895
+ " buke drama quote <\u8BA2\u5355id>",
2896
+ " buke drama confirm <\u8BA2\u5355id> --quote-hash <\u62A5\u4EF7\u54C8\u5E0C> [--yes]",
2897
+ " buke drama cancel <\u8BA2\u5355id>",
2898
+ " buke drama download <\u8BA2\u5355id> [--out <\u8DEF\u5F84>]",
2899
+ "",
2900
+ "\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",
2901
+ " create \u4E0B\u5355\uFF1B\u4EA7\u54C1\u5148\u7528 buke products \u5EFA\u597D\u3002\u53D7\u7406\u540E\u8F6E\u8BE2 status \u53D6\u8FDB\u5EA6",
2902
+ " 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",
2903
+ " 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",
2904
+ " 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",
2905
+ " 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",
2906
+ " download \u4E0B\u8F7D\u76F4\u62FC\u597D\u7684\u6210\u7247\uFF08mp4\uFF09",
2907
+ "",
2908
+ "\u9009\u9879\uFF1A",
2909
+ " --product <\u4EA7\u54C1id> \u4EA7\u54C1 id\uFF08buke products create \u56DE\u7684\uFF09",
2910
+ " --genre <\u9898\u6750id> \u9898\u6750 id\uFF08\u5982 counterattack\u3001urban-ceo\u3001family\uFF09",
2911
+ " --duration <\u4F53\u91CF> \u6210\u7247\u4F53\u91CF\uFF1A30s\u300160s \u6216 90s\uFF1B\u4E0D\u7ED9\u65F6 30s",
2912
+ " --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",
2913
+ " --quote-hash <\u54C8\u5E0C> \u62A5\u4EF7\u54C8\u5E0C\uFF08buke drama quote \u56DE\u7684\uFF09\uFF0C\u786E\u8BA4\u5F00\u62CD\u65F6\u539F\u6837\u5E26\u56DE",
2914
+ " --yes confirm \u65F6\u771F\u7684\u63D0\u4EA4\u5E76\u5F00\u62CD\uFF1B\u4E0D\u7ED9\u65F6\u53EA\u770B\u5F53\u524D\u62A5\u4EF7",
2915
+ " --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",
2916
+ " --timeout <\u79D2> --wait \u7684\u65F6\u9650\uFF0C\u9ED8\u8BA4 600",
2917
+ " --out <\u8DEF\u5F84> \u4E0B\u8F7D\u4FDD\u5B58\u7684\u4F4D\u7F6E\uFF1B\u4E0D\u7ED9\u65F6\u5B58\u5230\u5F53\u524D\u76EE\u5F55",
2918
+ " --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",
2919
+ " --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
2920
+ " --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
2921
+ ].join("\n")
1104
2922
  }
1105
2923
  };
1106
2924
  var GENERAL_HELP = [
1107
2925
  "buke\uFF1A\u300A\u4E0D\u53EF\u601D\u8BAE\u300B\u7684\u547D\u4EE4\u884C\u5DE5\u5177",
1108
2926
  "",
1109
2927
  "\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",
2928
+ " buke login [--base-url <\u5730\u5740>] [--json] \u7528\u4E00\u628A API \u5BC6\u94A5\u767B\u5F55\uFF0C\u5E76\u628A\u5B83\u4FDD\u5B58\u5728\u672C\u673A",
2929
+ " 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",
2930
+ " buke logout [--json] \u9000\u51FA\u767B\u5F55\uFF0C\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5",
2931
+ " buke balance [--json] \u67E5\u8BE2\u8D26\u6237\u4F59\u989D",
2932
+ " buke products <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u4EA7\u54C1\u5EFA\u6863\uFF1Acreate\u3001set-images\u3001confirm-lock",
2933
+ " buke shorts <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u6E32\u67D3\u77ED\u7247\uFF1Acatalog\u3001upload\u3001quote\u3001order\u3001status\u3001download\u3001cancel",
2934
+ " buke videos <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u5546\u54C1\u89C6\u9891\uFF1Acreate\u3001status\u3001message\u3001confirm\u3001download",
2935
+ " buke drama <\u5B50\u547D\u4EE4> [\u53C2\u6570] \u77ED\u5267\uFF1Acreate\u3001status\u3001quote\u3001confirm\u3001cancel\u3001download",
1113
2936
  " buke --help | -h \u663E\u793A\u8FD9\u4EFD\u8BF4\u660E\uFF1Bbuke <\u547D\u4EE4> --help \u770B\u5355\u6761\u547D\u4EE4",
1114
2937
  " buke --version | -v \u663E\u793A\u7248\u672C\u53F7",
1115
2938
  "",
@@ -1122,7 +2945,7 @@ var GENERAL_HELP = [
1122
2945
  ...KEY_HOWTO,
1123
2946
  "",
1124
2947
  "\u73AF\u5883\u53D8\u91CF\uFF1A",
1125
- " BUKE_API_KEY API \u5BC6\u94A5\uFF1B\u8BBE\u4E86\u5B83\uFF0Clogin \u548C whoami \u90FD\u76F4\u63A5\u7528\u5B83",
2948
+ " BUKE_API_KEY API \u5BC6\u94A5\uFF1B\u8BBE\u4E86\u5B83\uFF0Clogin \u548C\u522B\u7684\u547D\u4EE4\u90FD\u76F4\u63A5\u7528\u5B83",
1126
2949
  " BUKE_BASE_URL \u670D\u52A1\u5730\u5740\uFF0C\u4F5C\u7528\u540C --base-url",
1127
2950
  " 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
2951
  " \u8FD9\u4E2A\u4E5F\u6CA1\u8BBE\u5C31\u662F ~/.config/buke\uFF08Windows \u4E0A\u662F %APPDATA%\\buke\uFF09",
@@ -1134,34 +2957,63 @@ var GENERAL_HELP = [
1134
2957
  " 4 \u4F59\u989D\u4E0D\u8DB3",
1135
2958
  " 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
2959
  " 6 \u62A5\u4EF7\u53D8\u4E86",
1137
- " 7 \u8BF7\u6C42\u592A\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
2960
+ " 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
2961
  " 8 \u7B49\u5F85\u8D85\u65F6",
1139
2962
  " 9 \u4EFB\u52A1\u5931\u8D25",
1140
2963
  " 10 \u8981\u627E\u7684\u4E1C\u897F\u4E0D\u5B58\u5728",
1141
2964
  " 130 \u88AB Ctrl-C \u6253\u65AD",
1142
2965
  "",
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"
2966
+ "\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
2967
  ].join("\n");
1145
2968
  var MAX_ECHO_LENGTH = 40;
1146
2969
  function echo(item) {
1147
2970
  return item.length > MAX_ECHO_LENGTH ? `${item.slice(0, MAX_ECHO_LENGTH)}\u2026` : item;
1148
2971
  }
2972
+ var GLOBAL_OPTIONS = /* @__PURE__ */ new Set(["help", "version", "json", "base-url"]);
2973
+ var STRING_OPTIONS = /* @__PURE__ */ new Set([
2974
+ "base-url",
2975
+ "idempotency-key",
2976
+ "name",
2977
+ "category",
2978
+ "front",
2979
+ "side",
2980
+ "back",
2981
+ "detail",
2982
+ "color",
2983
+ "silhouette",
2984
+ "material",
2985
+ "template",
2986
+ "props",
2987
+ "quote-hash",
2988
+ "timeout",
2989
+ "out",
2990
+ "product",
2991
+ "intent",
2992
+ "duration",
2993
+ "resolution",
2994
+ "speed",
2995
+ "aspect-ratio",
2996
+ "style-preset",
2997
+ "text",
2998
+ "genre",
2999
+ "brief"
3000
+ ]);
3001
+ var BOOLEAN_OPTIONS = /* @__PURE__ */ new Set(["help", "version", "json", "wait", "yes"]);
1149
3002
  function parse(argv) {
1150
3003
  try {
3004
+ const options = {};
3005
+ for (const name2 of STRING_OPTIONS) options[name2] = { type: "string" };
3006
+ for (const name2 of BOOLEAN_OPTIONS) options[name2] = { type: "boolean", ...name2 === "help" ? { short: "h" } : name2 === "version" ? { short: "v" } : {} };
1151
3007
  const { values, positionals } = parseArgs({
1152
3008
  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
- },
3009
+ options,
1159
3010
  strict: true,
1160
3011
  allowPositionals: true
1161
3012
  });
1162
3013
  return {
1163
3014
  command: positionals[0],
1164
- extraPositionals: Math.max(0, positionals.length - 1),
3015
+ positionals,
3016
+ values,
1165
3017
  help: values.help === true,
1166
3018
  version: values.version === true,
1167
3019
  baseUrl: values["base-url"]
@@ -1193,9 +3045,21 @@ async function dispatch(argv, io, out) {
1193
3045
  const parsed = parse(argv);
1194
3046
  const command = parsed.command !== void 0 && Object.hasOwn(COMMANDS, parsed.command) ? COMMANDS[parsed.command] : void 0;
1195
3047
  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`);
3048
+ const args = parsed.positionals.slice(1);
3049
+ if (command) {
3050
+ if (command.positionals.length === 0 && args.length > 0) {
3051
+ throw usageError(`${parsed.command} \u540E\u9762\u4E0D\u63A5\u522B\u7684\u53C2\u6570`);
3052
+ }
3053
+ if (args.length > command.positionals.length) {
3054
+ throw usageError(`${parsed.command} \u540E\u9762\u7684\u53C2\u6570\u591A\u4E86\uFF08${echo(args[command.positionals.length])}\uFF09`);
3055
+ }
3056
+ if (!command.acceptsBaseUrl && parsed.baseUrl !== void 0) {
3057
+ throw usageError(`${parsed.command} \u4E0D\u8BA4\u8BC6\u9009\u9879 --base-url`);
3058
+ }
3059
+ for (const [name2, value] of Object.entries(parsed.values)) {
3060
+ if (value === void 0 || GLOBAL_OPTIONS.has(name2)) continue;
3061
+ if (!command.options.includes(name2)) throw usageError(`${parsed.command} \u4E0D\u8BA4\u8BC6\u9009\u9879 --${name2}`);
3062
+ }
1199
3063
  }
1200
3064
  if (parsed.help || !command && !parsed.version) {
1201
3065
  const text = command ? command.help : GENERAL_HELP;
@@ -1206,7 +3070,7 @@ async function dispatch(argv, io, out) {
1206
3070
  out.result([`${PACKAGE_NAME}/${VERSION}`], { name: PACKAGE_NAME, version: VERSION });
1207
3071
  return;
1208
3072
  }
1209
- if (command) await command.run({ io, out, baseUrl: parsed.baseUrl });
3073
+ if (command) await command.run({ io, out, baseUrl: parsed.baseUrl, args, values: parsed.values });
1210
3074
  }
1211
3075
  async function run(argv, io) {
1212
3076
  let out;