focalapi-cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -37,6 +37,20 @@ var ApiError = class extends Error {
37
37
  };
38
38
  }
39
39
  };
40
+ var ERROR_HINTS = {
41
+ missing_api_key: "\u672A\u914D\u7F6E API Key\u3002\u8FD0\u884C focalapi auth login --key <sk-...>\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF FOCALAPI_API_KEY\u3002",
42
+ invalid_api_key: "API Key \u65E0\u6548\u3001\u5DF2\u8FC7\u671F\u6216\u88AB\u5220\u9664\u3002\u524D\u5F80 https://focalapi.com/console/token \u68C0\u67E5\u6216\u65B0\u5EFA Key\uFF0C\u7136\u540E\u91CD\u65B0 focalapi auth login\u3002",
43
+ insufficient_quota: "\u8D26\u6237\u989D\u5EA6\u4E0D\u8DB3\u3002\u524D\u5F80 https://focalapi.com/console/topup \u5145\u503C\uFF1B\u53EF\u8FD0\u884C focalapi usage \u67E5\u770B\u5F53\u524D\u989D\u5EA6\u3002",
44
+ rate_limited: "\u8BF7\u6C42\u89E6\u53D1\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
45
+ capacity_exhausted: "\u5E73\u53F0\u521B\u4F5C\u5BB9\u91CF\u5DF2\u6EE1\uFF08\u6392\u961F\u51C6\u5165\u8FBE\u5230\u4E0A\u9650\uFF0CHTTP 503\uFF09\u3002\u670D\u52A1\u7AEF\u5EFA\u8BAE\u7EA6 10 \u79D2\u540E\u91CD\u8BD5\uFF1A\u76F4\u63A5\u91CD\u8DD1\u540C\u4E00\u6761\u547D\u4EE4\u5373\u53EF\uFF0C\u65E0\u9700\u4FEE\u6539\u53C2\u6570\u6216\u66F4\u6362\u6A21\u578B\u3002",
46
+ model_not_found: "\u6A21\u578B\u4E0D\u5B58\u5728\u6216\u672A\u5BF9\u4F60\u7684 Key \u5F00\u653E\u3002\u8FD0\u884C focalapi models list \u67E5\u770B\u53EF\u7528\u6A21\u578B\u3002",
47
+ network_error: "\u65E0\u6CD5\u8FDE\u63A5 focalapi \u670D\u52A1\u3002\u68C0\u67E5\u7F51\u7EDC\u4EE3\u7406\u4E0E FOCALAPI_BASE_URL \u914D\u7F6E\uFF1B\u53EF\u8FD0\u884C focalapi doctor \u505A\u94FE\u8DEF\u8BCA\u65AD\u3002",
48
+ timeout: "\u8BF7\u6C42\u8D85\u65F6\u3002\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u8FD0\u884C focalapi doctor \u68C0\u67E5\u94FE\u8DEF\u8D28\u91CF\u3002",
49
+ server_error: "focalapi \u670D\u52A1\u7AEF\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002\u82E5\u6301\u7EED\u51FA\u73B0\uFF0C\u8BF7\u643A\u5E26\u9519\u8BEF\u4FE1\u606F\u53CD\u9988\u7ED9\u670D\u52A1\u65B9\u3002",
50
+ invalid_request: "\u8BF7\u6C42\u53C2\u6570\u6709\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u547D\u4EE4\u53C2\u6570\u3002",
51
+ authentication_failed: "\u9274\u6743\u5931\u8D25\u3002\u5148\u8FD0\u884C focalapi auth status \u9A8C\u8BC1\u672C\u7AD9 Key\uFF1B\u82E5\u672C\u7AD9 Key \u6709\u6548\uFF0C\u5219\u8BF7\u5C06\u8BF7\u6C42 ID \u63D0\u4F9B\u7ED9\u670D\u52A1\u65B9\u6392\u67E5\u6E20\u9053\u6743\u9650\u3002",
52
+ upstream_auth_failed: "\u4E0A\u6E38\u6E20\u9053\u9274\u6743\u5931\u8D25\uFF0C\u5E76\u4E0D\u8868\u793A\u4F60\u7684 FocalAPI Key \u65E0\u6548\u3002\u5148\u8FD0\u884C focalapi auth status\uFF1B\u82E5\u901A\u8FC7\uFF0C\u8BF7\u5C06\u8BF7\u6C42 ID \u63D0\u4F9B\u7ED9\u670D\u52A1\u65B9\u6392\u67E5\u6E20\u9053\u914D\u7F6E\u3002"
53
+ };
40
54
  function refineErrorCode(status, message, opts) {
41
55
  const m = message.toLowerCase();
42
56
  if (m.includes("quota") || m.includes("\u989D\u5EA6") || m.includes("insufficient")) {
@@ -104,8 +118,15 @@ function printJson(data) {
104
118
  function info(message) {
105
119
  process.stderr.write(message + "\n");
106
120
  }
107
- function printError(err, opts) {
108
- if (err instanceof ApiError) {
121
+ function printError(errInput, opts) {
122
+ if (errInput instanceof ApiError) {
123
+ const err = !errInput.hint && ERROR_HINTS[errInput.code] ? new ApiError(errInput.code, errInput.message, {
124
+ status: errInput.status,
125
+ hint: ERROR_HINTS[errInput.code],
126
+ body: errInput.body,
127
+ upstreamCode: errInput.upstreamCode,
128
+ requestId: errInput.requestId
129
+ }) : errInput;
109
130
  if (opts?.json) {
110
131
  printJson(err.toJSON());
111
132
  } else {
@@ -127,7 +148,7 @@ function printError(err, opts) {
127
148
  }
128
149
  return;
129
150
  }
130
- const message = err instanceof Error ? err.message : String(err);
151
+ const message = errInput instanceof Error ? errInput.message : String(errInput);
131
152
  if (opts?.json) {
132
153
  printJson({ error: { code: "internal_error", message } });
133
154
  } else {
@@ -155,7 +176,7 @@ function displayWidth(s) {
155
176
  }
156
177
 
157
178
  // src/lib/version.ts
158
- var VERSION = true ? "0.2.1" : "0.0.0-dev";
179
+ var VERSION = true ? "0.3.0" : "0.0.0-dev";
159
180
 
160
181
  // src/commands/auth.ts
161
182
  import { createInterface } from "readline/promises";
@@ -319,7 +340,10 @@ async function rawRequest(opts) {
319
340
  if (!res.ok) {
320
341
  const text = await res.text().catch(() => "");
321
342
  const { message, body, upstreamCode } = extractErrorMessage(text);
322
- const code = refineErrorCode(res.status, message, { authFailureIsInvalidApiKey: opts.authFailureIsInvalidApiKey });
343
+ let code = refineErrorCode(res.status, message, { authFailureIsInvalidApiKey: opts.authFailureIsInvalidApiKey });
344
+ if (upstreamCode === "capacity_exhausted" || res.status === 503 && /capacity/i.test(message)) {
345
+ code = "capacity_exhausted";
346
+ }
323
347
  const requestId = res.headers.get("x-request-id") ?? res.headers.get("request-id") ?? res.headers.get("x-requestid") ?? void 0;
324
348
  throw new ApiError(code, message, { status: res.status, body, upstreamCode, requestId });
325
349
  }
@@ -471,15 +495,21 @@ var RECOMMENDED_MODELS = {
471
495
  "seedream-5-0-260128",
472
496
  "gpt-image-2",
473
497
  "gemini-3.1-flash-image",
474
- "grok-imagine-image-quality",
498
+ "grok-imagine-image-2.0",
499
+ "kling-v3",
500
+ "qwen-image-3.0-pro",
501
+ "krea-2-large",
475
502
  "seedream-4-5-251128"
476
503
  ],
477
504
  video: [
478
505
  "dreamina-seedance-2-5-260628",
479
- "veo-3.1-generate-preview",
506
+ "kling-3.0",
507
+ "viduq3-pro",
508
+ "gemini-omni-flash-preview",
480
509
  "grok-imagine-video-1.5",
481
- "dreamina-seedance-2-0-260128",
482
- "veo-3.1-fast-generate-preview"
510
+ "ltx-2-5-fast",
511
+ "flux-3",
512
+ "dreamina-seedance-2-0-260128"
483
513
  ]
484
514
  };
485
515
  function parseCreativeCapability(value) {
@@ -888,6 +918,7 @@ import { pipeline as pipeline2 } from "stream/promises";
888
918
  import { Readable as Readable2 } from "stream";
889
919
 
890
920
  // src/lib/model-capabilities.ts
921
+ var GEMINI_IMAGE_MAX_SEED = 9007199254740991;
891
922
  var SEEDREAM_OUTPUT_FORMATS = ["png", "jpeg"];
892
923
  var SEEDREAM_OPTIMIZE_PROMPT_MODES = ["auto", "enabled", "disabled"];
893
924
  var GROK_IMAGE_ASPECT_RATIOS = [
@@ -906,6 +937,24 @@ var GROK_IMAGE_ASPECT_RATIOS = [
906
937
  "1:3",
907
938
  "3:1"
908
939
  ];
940
+ var GROK_IMAGE_20_ASPECT_RATIOS = [
941
+ "auto",
942
+ "1:1",
943
+ "2:3",
944
+ "3:2",
945
+ "3:4",
946
+ "4:3",
947
+ "9:16",
948
+ "16:9",
949
+ "9:19.5",
950
+ "19.5:9",
951
+ "9:20",
952
+ "20:9",
953
+ "1:2",
954
+ "2:1"
955
+ ];
956
+ var KLING_IMAGE_ASPECT_RATIOS = ["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9"];
957
+ var KREA_IMAGE_ASPECT_RATIOS = ["1:1", "4:3", "3:2", "16:9", "2.35:1", "4:5", "2:3", "9:16"];
909
958
  var IMAGE_CONSTRAINTS = {
910
959
  "gpt-image-2": {
911
960
  defaultSize: "1024x1024",
@@ -921,7 +970,7 @@ var IMAGE_CONSTRAINTS = {
921
970
  backgrounds: ["auto", "opaque"]
922
971
  },
923
972
  "seedream-4-0-250828": {
924
- defaultSize: "1k",
973
+ defaultSize: "2k",
925
974
  sizeTiers: ["1k", "2k", "4k"],
926
975
  maxN: 10,
927
976
  maxReferenceImages: 10,
@@ -968,103 +1017,243 @@ var IMAGE_CONSTRAINTS = {
968
1017
  optimizePromptModes: SEEDREAM_OPTIMIZE_PROMPT_MODES
969
1018
  },
970
1019
  "grok-imagine-image-quality": {
971
- defaultSize: "1024x1024",
972
1020
  maxN: 10,
973
1021
  maxReferenceImages: 3,
974
1022
  aspectRatios: GROK_IMAGE_ASPECT_RATIOS,
975
1023
  resolutions: ["1k", "2k"],
976
- supportsSeed: true
1024
+ supportsSeed: true,
1025
+ maxSeed: 2147483647
977
1026
  },
978
1027
  "grok-imagine-image": {
979
- defaultSize: "1024x1024",
980
1028
  maxN: 10,
981
1029
  maxReferenceImages: 3,
982
1030
  aspectRatios: GROK_IMAGE_ASPECT_RATIOS,
983
1031
  resolutions: ["1k", "2k"],
984
- supportsSeed: true
1032
+ supportsSeed: true,
1033
+ maxSeed: 2147483647
1034
+ },
1035
+ "grok-imagine-image-2.0": {
1036
+ maxN: 10,
1037
+ maxReferenceImages: 3,
1038
+ aspectRatios: GROK_IMAGE_20_ASPECT_RATIOS,
1039
+ resolutions: ["1k", "2k"],
1040
+ qualities: ["low", "medium"],
1041
+ supportsSeed: true,
1042
+ maxSeed: 2147483647
1043
+ },
1044
+ "kling-v3": {
1045
+ maxN: 9,
1046
+ maxReferenceImages: 1,
1047
+ aspectRatios: KLING_IMAGE_ASPECT_RATIOS,
1048
+ supportsNegativePrompt: true
1049
+ },
1050
+ "krea-2-medium": {
1051
+ maxN: 1,
1052
+ maxReferenceImages: 0,
1053
+ aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1054
+ resolutions: ["1k"],
1055
+ supportsSeed: true,
1056
+ maxSeed: 2147483647,
1057
+ creativityModes: ["raw", "low", "medium", "high"],
1058
+ maxStyleReferences: 10,
1059
+ maxMoodboards: 1
1060
+ },
1061
+ "krea-2-medium-turbo": {
1062
+ maxN: 1,
1063
+ maxReferenceImages: 0,
1064
+ aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1065
+ resolutions: ["1k"],
1066
+ supportsSeed: true,
1067
+ maxSeed: 2147483647,
1068
+ creativityModes: ["raw", "low", "medium", "high"],
1069
+ maxStyleReferences: 10,
1070
+ maxMoodboards: 1
1071
+ },
1072
+ "krea-2-large": {
1073
+ maxN: 1,
1074
+ maxReferenceImages: 0,
1075
+ aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1076
+ resolutions: ["1k"],
1077
+ supportsSeed: true,
1078
+ maxSeed: 2147483647,
1079
+ creativityModes: ["raw", "low", "medium", "high"],
1080
+ maxStyleReferences: 10,
1081
+ maxMoodboards: 1
1082
+ },
1083
+ "qwen-image-3.0": {
1084
+ defaultSize: "1024x1024",
1085
+ maxN: 6,
1086
+ maxReferenceImages: 3,
1087
+ minMegapixels: 0.262144,
1088
+ maxMegapixels: 4.194304,
1089
+ maxAspectRatio: 8,
1090
+ supportsSeed: true,
1091
+ maxSeed: 2147483647,
1092
+ supportsNegativePrompt: true,
1093
+ supportsPromptExtend: true,
1094
+ supportsWatermark: true
1095
+ },
1096
+ "qwen-image-3.0-pro": {
1097
+ defaultSize: "1024x1024",
1098
+ maxN: 6,
1099
+ maxReferenceImages: 3,
1100
+ minMegapixels: 0.262144,
1101
+ maxMegapixels: 4.194304,
1102
+ maxAspectRatio: 8,
1103
+ supportsSeed: true,
1104
+ maxSeed: 2147483647,
1105
+ supportsNegativePrompt: true,
1106
+ supportsPromptExtend: true,
1107
+ supportsWatermark: true
985
1108
  }
986
1109
  };
987
1110
  var SEEDANCE_RATIOS = ["adaptive", "16:9", "4:3", "1:1", "3:4", "9:16", "21:9"];
988
1111
  var GROK_VIDEO_ASPECT_RATIOS = ["auto", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16"];
1112
+ var KLING_VIDEO_ASPECT_RATIOS = ["16:9", "9:16", "1:1"];
1113
+ var VIDU_VIDEO_ASPECT_RATIOS = ["16:9", "9:16", "3:4", "4:3", "1:1"];
1114
+ var FLUX_VIDEO_RATIOS = ["auto", "21:9", "2:1", "16:9", "4:3", "1:1", "3:4", "9:16"];
989
1115
  var VIDEO_CONSTRAINTS = {
990
- "veo-3.1-generate-preview": {
991
- resolutions: ["720p", "1080p", "4k"],
992
- ratios: ["16:9", "9:16"],
993
- minSeconds: 4,
994
- maxSeconds: 8,
995
- allowedSeconds: [4, 6, 8],
996
- requiredSecondsByResolution: { "1080p": 8, "4k": 8 },
997
- supportsSeed: true
998
- },
999
- "veo-3.1-fast-generate-preview": {
1000
- resolutions: ["720p", "1080p", "4k"],
1001
- ratios: ["16:9", "9:16"],
1002
- minSeconds: 4,
1003
- maxSeconds: 8,
1004
- allowedSeconds: [4, 6, 8],
1005
- requiredSecondsByResolution: { "1080p": 8, "4k": 8 },
1006
- supportsSeed: true
1007
- },
1008
- "veo-3.1-lite-generate-preview": {
1009
- resolutions: ["720p", "1080p"],
1010
- ratios: ["16:9", "9:16"],
1011
- minSeconds: 4,
1012
- maxSeconds: 8,
1013
- allowedSeconds: [4, 6, 8],
1014
- requiredSecondsByResolution: { "1080p": 8 },
1015
- supportsSeed: true
1016
- },
1017
1116
  "dreamina-seedance-2-0-260128": {
1018
1117
  resolutions: ["480p", "720p", "1080p", "4k"],
1019
1118
  ratios: SEEDANCE_RATIOS,
1020
1119
  minSeconds: 4,
1021
1120
  maxSeconds: 15,
1022
- supportsPriority: true
1121
+ maxReferenceImages: 9
1023
1122
  },
1024
1123
  "dreamina-seedance-2-0-fast-260128": {
1025
1124
  resolutions: ["480p", "720p"],
1026
1125
  ratios: SEEDANCE_RATIOS,
1027
1126
  minSeconds: 4,
1028
1127
  maxSeconds: 15,
1029
- supportsPriority: true
1128
+ maxReferenceImages: 9
1030
1129
  },
1031
- "dreamina-seedance-2-0-mini-260615": {
1130
+ "seed-2-0-mini-260428": {
1032
1131
  resolutions: ["480p", "720p"],
1033
1132
  ratios: SEEDANCE_RATIOS,
1034
1133
  minSeconds: 4,
1035
1134
  maxSeconds: 15,
1036
- supportsPriority: true
1135
+ maxReferenceImages: 9
1037
1136
  },
1038
1137
  "dreamina-seedance-2-5-260628": {
1039
- resolutions: ["480p", "720p"],
1138
+ resolutions: ["480p", "720p", "1080p"],
1040
1139
  ratios: SEEDANCE_RATIOS,
1041
1140
  minSeconds: 4,
1042
- maxSeconds: 30
1141
+ maxSeconds: 30,
1142
+ maxReferenceImages: 30
1143
+ },
1144
+ "gemini-omni-flash-preview": {
1145
+ resolutions: [],
1146
+ ratios: ["16:9", "9:16"],
1147
+ minSeconds: 3,
1148
+ maxSeconds: 10,
1149
+ maxReferenceImages: 14,
1150
+ supportsGenerateAudio: false
1043
1151
  },
1044
1152
  "grok-imagine-video": {
1045
- resolutions: ["480p", "720p", "1080p"],
1153
+ resolutions: ["480p", "720p"],
1046
1154
  aspectRatios: GROK_VIDEO_ASPECT_RATIOS,
1047
1155
  minSeconds: 1,
1048
1156
  maxSeconds: 15,
1049
- supportsSeed: true
1157
+ disallowReferences: true,
1158
+ maxFirstFrameImages: 1,
1159
+ supportsGenerateAudio: false,
1160
+ supportsWatermark: false
1050
1161
  },
1051
1162
  "grok-imagine-video-1.5": {
1052
1163
  resolutions: ["480p", "720p", "1080p"],
1053
1164
  aspectRatios: GROK_VIDEO_ASPECT_RATIOS,
1054
1165
  minSeconds: 1,
1055
1166
  maxSeconds: 15,
1056
- supportsSeed: true
1167
+ maxReferenceImages: 7,
1168
+ referenceResolutions: ["480p", "720p"],
1169
+ maxFirstFrameImages: 1,
1170
+ supportsGenerateAudio: false,
1171
+ supportsWatermark: false
1172
+ },
1173
+ "kling-3.0": {
1174
+ resolutions: ["720p", "1080p", "4k"],
1175
+ aspectRatios: KLING_VIDEO_ASPECT_RATIOS,
1176
+ minSeconds: 3,
1177
+ maxSeconds: 15,
1178
+ maxReferenceImages: 2,
1179
+ supportsGenerateAudio: true
1180
+ },
1181
+ "viduq3-pro": {
1182
+ resolutions: ["720p", "1080p"],
1183
+ aspectRatios: VIDU_VIDEO_ASPECT_RATIOS,
1184
+ minSeconds: 1,
1185
+ maxSeconds: 16,
1186
+ supportsSeed: true,
1187
+ maxSeed: 2147483647,
1188
+ maxReferenceImages: 2
1189
+ },
1190
+ "viduq3-turbo": {
1191
+ resolutions: ["720p", "1080p"],
1192
+ aspectRatios: VIDU_VIDEO_ASPECT_RATIOS,
1193
+ minSeconds: 1,
1194
+ maxSeconds: 16,
1195
+ supportsSeed: true,
1196
+ maxSeed: 2147483647,
1197
+ maxReferenceImages: 2
1198
+ },
1199
+ "ltx-2-5-fast": {
1200
+ resolutions: ["1280x720", "720x1280", "1920x1080", "1080x1920", "2560x1440", "1440x2560", "3840x2160", "2160x3840"],
1201
+ minSeconds: 6,
1202
+ maxSeconds: 20,
1203
+ allowedSeconds: [6, 8, 10, 12, 14, 16, 18, 20],
1204
+ allowedFps: [24, 25, 48, 50],
1205
+ maxReferenceImages: 2,
1206
+ supportsGenerateAudio: true
1207
+ },
1208
+ "ltx-2-5-pro": {
1209
+ resolutions: ["1280x720", "720x1280", "1920x1080", "1080x1920"],
1210
+ minSeconds: 6,
1211
+ maxSeconds: 10,
1212
+ allowedSeconds: [6, 8, 10],
1213
+ allowedFps: [24, 25, 50],
1214
+ maxReferenceImages: 2,
1215
+ supportsGenerateAudio: true
1216
+ },
1217
+ "flux-3": {
1218
+ resolutions: ["hd", "fhd"],
1219
+ ratios: FLUX_VIDEO_RATIOS,
1220
+ minSeconds: 5,
1221
+ maxSeconds: 20,
1222
+ safetyTolerance: { minimum: 0, maximum: 4 },
1223
+ safetyToleranceMaxWithImages: 2,
1224
+ maxReferenceImages: 10
1057
1225
  }
1058
1226
  };
1059
1227
  var COMMON_GEMINI_RATIOS = ["auto", "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
1228
+ var EXTENDED_GEMINI_RATIOS = [...COMMON_GEMINI_RATIOS, "1:4", "4:1", "1:8", "8:1"];
1060
1229
  var GEMINI_IMAGE_CONSTRAINTS = {
1061
- "gemini-2.5-flash-image": { aspectRatios: COMMON_GEMINI_RATIOS, supportsSampling: false },
1062
- "gemini-3-pro-image": { aspectRatios: COMMON_GEMINI_RATIOS, imageSizes: ["1K", "2K", "4K"], supportsSampling: false },
1063
- "gemini-3.1-flash-image": { aspectRatios: COMMON_GEMINI_RATIOS, imageSizes: ["1K", "2K", "4K"], supportsSampling: false },
1230
+ "gemini-2.5-flash-image": {
1231
+ aspectRatios: COMMON_GEMINI_RATIOS,
1232
+ supportsSampling: false,
1233
+ maxReferenceImages: 1,
1234
+ referenceImagesInlineDataOnly: true,
1235
+ maxSeed: GEMINI_IMAGE_MAX_SEED
1236
+ },
1237
+ "gemini-3-pro-image": {
1238
+ aspectRatios: COMMON_GEMINI_RATIOS,
1239
+ imageSizes: ["1K", "2K", "4K"],
1240
+ supportsSampling: false,
1241
+ maxReferenceImages: 14,
1242
+ maxSeed: GEMINI_IMAGE_MAX_SEED
1243
+ },
1244
+ "gemini-3.1-flash-image": {
1245
+ aspectRatios: EXTENDED_GEMINI_RATIOS,
1246
+ imageSizes: ["1K", "2K", "4K"],
1247
+ supportsSampling: true,
1248
+ maxReferenceImages: 14,
1249
+ maxSeed: GEMINI_IMAGE_MAX_SEED
1250
+ },
1064
1251
  "gemini-3.1-flash-lite-image": {
1065
- aspectRatios: [...COMMON_GEMINI_RATIOS, "1:4", "4:1", "1:8", "8:1"],
1252
+ aspectRatios: EXTENDED_GEMINI_RATIOS,
1066
1253
  imageSizes: ["1K"],
1067
- supportsSampling: true
1254
+ supportsSampling: true,
1255
+ maxReferenceImages: 14,
1256
+ maxSeed: GEMINI_IMAGE_MAX_SEED
1068
1257
  }
1069
1258
  };
1070
1259
  function parseSize(size, model) {
@@ -1113,17 +1302,40 @@ function validateImageGeneration(model, input) {
1113
1302
  validateOptionalChoice(model, "resolution", input.resolution, constraint.resolutions);
1114
1303
  validateOptionalChoice(model, "output_format", input.outputFormat, constraint.outputFormats);
1115
1304
  validateOptionalChoice(model, "optimize_prompt", input.optimizePrompt, constraint.optimizePromptModes);
1305
+ validateOptionalChoice(model, "creativity", input.creativity, constraint.creativityModes);
1306
+ if (input.negativePrompt !== void 0 && !constraint.supportsNegativePrompt) {
1307
+ throw new ApiError("invalid_request", `${model} does not support negative_prompt`);
1308
+ }
1309
+ if (input.negativePrompt !== void 0 && model === "kling-v3" && (input.imageCount ?? 0) > 0) {
1310
+ throw new ApiError("invalid_request", "kling-v3 negative_prompt cannot be combined with an image input");
1311
+ }
1312
+ if (input.promptExtend !== void 0 && !constraint.supportsPromptExtend) {
1313
+ throw new ApiError("invalid_request", `${model} does not support prompt_extend`);
1314
+ }
1315
+ if (input.styleReferenceCount !== void 0 && (constraint.maxStyleReferences === void 0 || input.styleReferenceCount > constraint.maxStyleReferences)) {
1316
+ throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxStyleReferences ?? 0} image style references`);
1317
+ }
1318
+ if (input.moodboardCount !== void 0 && (constraint.maxMoodboards === void 0 || input.moodboardCount > constraint.maxMoodboards)) {
1319
+ throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxMoodboards ?? 0} moodboards`);
1320
+ }
1116
1321
  if (input.seed !== void 0) {
1117
1322
  if (!constraint.supportsSeed) {
1118
1323
  throw new ApiError("invalid_request", `${model} does not support seed`);
1119
1324
  }
1120
- if (!Number.isInteger(input.seed) || input.seed < 0) {
1121
- throw new ApiError("invalid_request", "seed must be a non-negative integer");
1325
+ if (!Number.isInteger(input.seed) || input.seed < 0 || constraint.maxSeed !== void 0 && input.seed > constraint.maxSeed) {
1326
+ const maximum = constraint.maxSeed === void 0 ? "" : ` no greater than ${constraint.maxSeed}`;
1327
+ throw new ApiError("invalid_request", `seed must be a non-negative integer${maximum}`);
1122
1328
  }
1123
1329
  }
1124
1330
  if (input.watermark !== void 0 && !constraint.supportsWatermark) {
1125
1331
  throw new ApiError("invalid_request", `${model} does not support watermark`);
1126
1332
  }
1333
+ if (!constraint.defaultSize) {
1334
+ if (input.size) {
1335
+ throw new ApiError("invalid_request", `${model} does not support size; use its aspect_ratio or resolution parameter`);
1336
+ }
1337
+ return;
1338
+ }
1127
1339
  const suppliedSize = input.size ?? constraint.defaultSize;
1128
1340
  if (constraint.sizeTiers?.includes(suppliedSize.toLowerCase())) return;
1129
1341
  if (constraint.sizeTiers && !/^\d+x\d+$/i.test(suppliedSize)) {
@@ -1156,11 +1368,18 @@ function validateGeminiImageGeneration(model, input) {
1156
1368
  const supported = constraint.imageSizes?.join(", ") ?? "none";
1157
1369
  throw new ApiError("invalid_request", `${model} imageSize must be one of ${supported} (received: ${input.imageSize})`);
1158
1370
  }
1159
- if (input.seed !== void 0 && (!Number.isInteger(input.seed) || input.seed < 0)) {
1160
- throw new ApiError("invalid_request", "seed must be a non-negative integer");
1371
+ if (input.seed !== void 0 && (!Number.isInteger(input.seed) || input.seed < 0 || constraint.maxSeed !== void 0 && input.seed > constraint.maxSeed)) {
1372
+ const maximum = constraint.maxSeed === void 0 ? "" : ` no greater than ${constraint.maxSeed}`;
1373
+ throw new ApiError("invalid_request", `seed must be a non-negative integer${maximum}`);
1374
+ }
1375
+ if (input.referenceImageCount !== void 0 && constraint.maxReferenceImages !== void 0 && input.referenceImageCount > constraint.maxReferenceImages) {
1376
+ throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxReferenceImages} reference image${constraint.maxReferenceImages === 1 ? "" : "s"}`);
1377
+ }
1378
+ if (constraint.referenceImagesInlineDataOnly && input.nonDataUriReferenceCount) {
1379
+ throw new ApiError("invalid_request", `${model} reference images must be base64 data URIs (inlineData); fileUri inputs are rejected by this model`);
1161
1380
  }
1162
1381
  if (!constraint.supportsSampling && (input.thinkingLevel || input.temperature !== void 0 || input.topP !== void 0)) {
1163
- throw new ApiError("invalid_request", `${model} supports thinkingLevel, temperature, and topP only on gemini-3.1-flash-lite-image`);
1382
+ throw new ApiError("invalid_request", `${model} supports thinkingLevel, temperature, and topP only on gemini-3.1-flash-image and gemini-3.1-flash-lite-image`);
1164
1383
  }
1165
1384
  if (input.thinkingLevel && !["MINIMAL", "HIGH"].includes(input.thinkingLevel.toUpperCase())) {
1166
1385
  throw new ApiError("invalid_request", "thinkingLevel must be MINIMAL or HIGH");
@@ -1175,6 +1394,24 @@ function validateGeminiImageGeneration(model, input) {
1175
1394
  function validateVideoGeneration(model, input) {
1176
1395
  const constraint = VIDEO_CONSTRAINTS[model.trim()];
1177
1396
  if (!constraint) return;
1397
+ if ((input.imageCount ?? 0) > 0 && (input.firstFrameCount ?? 0) > 0) {
1398
+ throw new ApiError("invalid_request", `${model}: --image and --first-frame are mutually exclusive (reference-to-video vs image-to-video)`);
1399
+ }
1400
+ if (constraint.disallowReferences && (input.imageCount ?? 0) > 0) {
1401
+ throw new ApiError("invalid_request", `${model} supports image-to-video only (single --first-frame image); reference images require grok-imagine-video-1.5`);
1402
+ }
1403
+ if (input.imageCount !== void 0 && constraint.maxReferenceImages !== void 0 && input.imageCount > constraint.maxReferenceImages) {
1404
+ throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxReferenceImages} reference image${constraint.maxReferenceImages === 1 ? "" : "s"}`);
1405
+ }
1406
+ if (input.firstFrameCount !== void 0 && constraint.maxFirstFrameImages !== void 0 && input.firstFrameCount > constraint.maxFirstFrameImages) {
1407
+ throw new ApiError("invalid_request", `${model} image-to-video supports exactly ${constraint.maxFirstFrameImages} starting image${constraint.maxFirstFrameImages === 1 ? "" : "s"} (--first-frame)`);
1408
+ }
1409
+ if (input.generateAudio !== void 0 && constraint.supportsGenerateAudio === false) {
1410
+ throw new ApiError("invalid_request", `${model} does not support generate_audio`);
1411
+ }
1412
+ if (input.watermark !== void 0 && constraint.supportsWatermark === false) {
1413
+ throw new ApiError("invalid_request", `${model} does not support watermark`);
1414
+ }
1178
1415
  if (input.seconds !== void 0 && (input.seconds < constraint.minSeconds || input.seconds > constraint.maxSeconds)) {
1179
1416
  throw new ApiError("invalid_request", `${model} seconds must be ${constraint.minSeconds}-${constraint.maxSeconds} (received: ${input.seconds})`);
1180
1417
  }
@@ -1184,6 +1421,9 @@ function validateVideoGeneration(model, input) {
1184
1421
  if (input.resolution && !constraint.resolutions.includes(input.resolution.toLowerCase())) {
1185
1422
  throw new ApiError("invalid_request", `${model} resolution must be one of ${constraint.resolutions.join(", ")} (received: ${input.resolution})`);
1186
1423
  }
1424
+ if (input.resolution && (input.imageCount ?? 0) > 0 && constraint.referenceResolutions && !constraint.referenceResolutions.includes(input.resolution.toLowerCase())) {
1425
+ throw new ApiError("invalid_request", `${model} reference-to-video mode supports only ${constraint.referenceResolutions.join(" and ")} (received: ${input.resolution}); ${input.resolution} requires text-to-video or a single --first-frame image`);
1426
+ }
1187
1427
  if (input.seconds !== void 0 && input.resolution) {
1188
1428
  const required = constraint.requiredSecondsByResolution?.[input.resolution.toLowerCase()];
1189
1429
  if (required !== void 0 && input.seconds !== required) {
@@ -1210,8 +1450,19 @@ function validateVideoGeneration(model, input) {
1210
1450
  if (!constraint.supportsSeed) {
1211
1451
  throw new ApiError("invalid_request", `${model} does not support seed`);
1212
1452
  }
1213
- if (!Number.isInteger(input.seed) || input.seed < 0) {
1214
- throw new ApiError("invalid_request", "seed must be a non-negative integer");
1453
+ if (!Number.isInteger(input.seed) || input.seed < 0 || constraint.maxSeed !== void 0 && input.seed > constraint.maxSeed) {
1454
+ const maximum = constraint.maxSeed === void 0 ? "" : ` no greater than ${constraint.maxSeed}`;
1455
+ throw new ApiError("invalid_request", `seed must be a non-negative integer${maximum}`);
1456
+ }
1457
+ }
1458
+ if (input.fps !== void 0 && (!constraint.allowedFps || !constraint.allowedFps.includes(input.fps))) {
1459
+ throw new ApiError("invalid_request", `${model} fps must be one of ${constraint.allowedFps?.join(", ") ?? "none"} (received: ${input.fps})`);
1460
+ }
1461
+ if (input.safetyTolerance !== void 0 && constraint.safetyTolerance) {
1462
+ const ceiling = (input.imageCount ?? 0) > 0 && constraint.safetyToleranceMaxWithImages !== void 0 ? Math.min(constraint.safetyTolerance.maximum, constraint.safetyToleranceMaxWithImages) : constraint.safetyTolerance.maximum;
1463
+ if (!Number.isInteger(input.safetyTolerance) || input.safetyTolerance < constraint.safetyTolerance.minimum || input.safetyTolerance > ceiling) {
1464
+ const withImagesNote = constraint.safetyToleranceMaxWithImages !== void 0 && ceiling !== constraint.safetyTolerance.maximum ? ` while images are attached (text-only allows up to ${constraint.safetyTolerance.maximum})` : "";
1465
+ throw new ApiError("invalid_request", `${model} safety_tolerance must be ${constraint.safetyTolerance.minimum}-${ceiling}${withImagesNote} (received: ${input.safetyTolerance})`);
1215
1466
  }
1216
1467
  }
1217
1468
  if (input.priority !== void 0 && !constraint.supportsPriority) {
@@ -1238,15 +1489,17 @@ import { join as join2, resolve } from "path";
1238
1489
  import { pipeline } from "stream/promises";
1239
1490
  import { Readable } from "stream";
1240
1491
  var SUCCESS_STATES = /* @__PURE__ */ new Set(["success", "succeeded", "completed", "done", "finish", "finished"]);
1241
- var FAILED_STATES = /* @__PURE__ */ new Set(["failed", "failure", "error", "cancelled", "canceled"]);
1492
+ var FAILED_STATES = /* @__PURE__ */ new Set(["failed", "failure", "error", "expired"]);
1493
+ var CANCELLED_STATES = /* @__PURE__ */ new Set(["cancelled", "canceled"]);
1242
1494
  var RUNNING_STATES = /* @__PURE__ */ new Set(["running", "processing", "in_progress", "generating"]);
1243
1495
  var PENDING_STATES = /* @__PURE__ */ new Set(["pending", "queued", "submitted", "waiting", "not_start"]);
1244
1496
  function normalizeTaskStatus(raw) {
1245
1497
  const s = String(raw ?? "").toLowerCase();
1246
1498
  if (SUCCESS_STATES.has(s)) return "success";
1499
+ if (CANCELLED_STATES.has(s)) return "cancelled";
1247
1500
  if (FAILED_STATES.has(s)) return "failed";
1248
1501
  if (RUNNING_STATES.has(s)) return "running";
1249
- if (PENDING_STATES.has(s)) return "pending";
1502
+ if (PENDING_STATES.has(s) || s.startsWith("queued_")) return "pending";
1250
1503
  return "unknown";
1251
1504
  }
1252
1505
  function extractTaskId(body) {
@@ -1273,6 +1526,39 @@ function extractProgress(body) {
1273
1526
  }
1274
1527
  return void 0;
1275
1528
  }
1529
+ async function cancelTask(baseUrl, apiKey, taskId) {
1530
+ try {
1531
+ await request({
1532
+ baseUrl,
1533
+ path: `/v1/video/generations/${encodeURIComponent(taskId)}`,
1534
+ method: "DELETE",
1535
+ apiKey
1536
+ });
1537
+ } catch (err) {
1538
+ if (err instanceof ApiError) {
1539
+ const code = err.upstreamCode;
1540
+ if (code === "task_already_running") {
1541
+ throw new ApiError("task_already_running", `\u4EFB\u52A1 ${taskId} \u5DF2\u5F00\u59CB\u8FD0\u884C\uFF0C\u65E0\u6CD5\u53D6\u6D88`, {
1542
+ status: err.status,
1543
+ hint: "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4E0D\u53EF\u53D6\u6D88\uFF1B\u7528 focalapi task status " + taskId + " \u8DDF\u8E2A\u5230\u5B8C\u6210\u540E\u4E0B\u8F7D\u4EA7\u7269\u3002"
1544
+ });
1545
+ }
1546
+ if (code === "task_already_finished") {
1547
+ throw new ApiError("task_already_finished", `\u4EFB\u52A1 ${taskId} \u5DF2\u7ED3\u675F\uFF0C\u65E0\u9700\u53D6\u6D88`, {
1548
+ status: err.status,
1549
+ hint: "\u8FD0\u884C focalapi task status " + taskId + " \u67E5\u770B\u7ED3\u679C\uFF1B\u6210\u529F\u540E\u53EF\u4E0B\u8F7D\u4EA7\u7269\u3002"
1550
+ });
1551
+ }
1552
+ if (code === "task_cancel_incomplete") {
1553
+ throw new ApiError("task_cancel_incomplete", `\u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88\u4F46\u6E05\u7406\u672A\u5B8C\u6210`, {
1554
+ status: err.status,
1555
+ hint: "\u8BF7\u91CD\u8BD5\u540C\u4E00\u6761 cancel \u547D\u4EE4\u5B8C\u6210\u6E05\u7406\u3002"
1556
+ });
1557
+ }
1558
+ }
1559
+ throw err;
1560
+ }
1561
+ }
1276
1562
  async function fetchTask(baseUrl, apiKey, taskId) {
1277
1563
  let raw;
1278
1564
  try {
@@ -1308,10 +1594,17 @@ async function pollTask(baseUrl, apiKey, taskId, opts) {
1308
1594
  last = await fetchTask(baseUrl, apiKey, taskId);
1309
1595
  opts?.onUpdate?.(last);
1310
1596
  if (last.status === "success") return last;
1597
+ if (last.status === "cancelled") {
1598
+ throw new ApiError("task_cancelled", `\u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88\uFF08\u4E0A\u6E38\u72B6\u6001\uFF1A${last.rawStatus || "cancelled"}\uFF09`, {
1599
+ body: last.raw,
1600
+ hint: "\u4EFB\u52A1\u5DF2\u505C\u6B62\u4E14\u4E0D\u518D\u4EA7\u751F\u8D39\u7528\uFF1B\u5982\u9700\u91CD\u65B0\u751F\u6210\u8BF7\u63D0\u4EA4\u65B0\u4EFB\u52A1\u3002"
1601
+ });
1602
+ }
1311
1603
  if (last.status === "failed") {
1312
- throw new ApiError("task_failed", `\u4EFB\u52A1 ${taskId} \u5931\u8D25\uFF08\u4E0A\u6E38\u72B6\u6001\uFF1A${last.rawStatus || "unknown"}\uFF09`, {
1604
+ const expired = last.rawStatus?.toLowerCase() === "expired";
1605
+ throw new ApiError("task_failed", expired ? `\u4EFB\u52A1 ${taskId} \u8D85\u8FC7\u6267\u884C\u671F\u9650\uFF08expired\uFF09\uFF0C\u8D39\u7528\u5DF2\u81EA\u52A8\u9000\u8FD8` : `\u4EFB\u52A1 ${taskId} \u5931\u8D25\uFF08\u4E0A\u6E38\u72B6\u6001\uFF1A${last.rawStatus || "unknown"}\uFF09`, {
1313
1606
  body: last.raw,
1314
- hint: "\u8FD0\u884C focalapi task status " + taskId + " --json \u67E5\u770B\u4E0A\u6E38\u8FD4\u56DE\u8BE6\u60C5\uFF1B\u82E5\u662F\u63D0\u793A\u8BCD\u6216\u53C2\u6570\u95EE\u9898\u8BF7\u8C03\u6574\u540E\u91CD\u8BD5\u3002"
1607
+ hint: expired ? "\u63D0\u4EA4\u72B6\u6001\u672A\u77E5\u4E14\u8D85\u8FC7 10 \u5206\u949F\u5BF9\u8D26\u671F\u9650\u7684\u4EFB\u52A1\u4F1A\u4EE5 expired \u7EC8\u6B62\u5E76\u9000\u6B3E\u3002\u53EF\u67E5\u770B focalapi task status " + taskId + " --json \u540E\u91CD\u65B0\u63D0\u4EA4\u3002" : "\u8FD0\u884C focalapi task status " + taskId + " --json \u67E5\u770B\u4E0A\u6E38\u8FD4\u56DE\u8BE6\u60C5\uFF1B\u82E5\u662F\u63D0\u793A\u8BCD\u6216\u53C2\u6570\u95EE\u9898\u8BF7\u8C03\u6574\u540E\u91CD\u8BD5\u3002"
1315
1608
  });
1316
1609
  }
1317
1610
  if (Date.now() > deadline) {
@@ -1470,12 +1763,14 @@ function extractGeminiImageItems(response) {
1470
1763
  }
1471
1764
  function registerGen(program) {
1472
1765
  const gen = program.command("gen").description("\u56FE\u50CF / \u89C6\u9891\u751F\u6210");
1473
- gen.command("image").description("\u751F\u6210\u56FE\u50CF\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u56FE\u50CF\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--size <size>", "\u5C3A\u5BF8\uFF0C\u5982 1024x1024").option("--aspect-ratio <ratio>", "\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF08\u4EC5 Grok \u56FE\u50CF\u6A21\u578B\uFF09\uFF0C\u5982 16:9").option("--resolution <resolution>", "\u539F\u751F\u8F93\u51FA\u6863\u4F4D\uFF08\u4EC5 Grok \u56FE\u50CF\u6A21\u578B\uFF09\uFF0C\u5982 1k\u30012k").option("--seed <n>", "\u968F\u673A\u79CD\u5B50\uFF08\u4EC5 Grok \u56FE\u50CF\u6A21\u578B\uFF0C\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--quality <quality>", "\u56FE\u50CF\u8D28\u91CF\u6863\u4F4D\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09").option("--background <background>", "\u80CC\u666F\u6A21\u5F0F\uFF08\u4EC5 gpt-image-2 \u652F\u6301 auto/opaque\uFF09").option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u4EC5 Seedream\uFF0Ctrue \u6216 false\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--output-format <format>", "\u8F93\u51FA\u683C\u5F0F\uFF08\u4EC5 Seedream\uFF1Apng \u6216 jpeg\uFF09").option("--optimize-prompt <mode>", "\u63D0\u793A\u8BCD\u4F18\u5316\uFF08\u4EC5 Seedream\uFF1Aauto\u3001enabled \u6216 disabled\uFF09").option("--image <url...>", "\u53C2\u8003\u56FE\u6216\u7F16\u8F91\u56FE URL\uFF0C\u53EF\u591A\u4E2A").option("--mask <url>", "\u7F16\u8F91 mask URL\uFF08gpt-image-2 \u9700\u8981\u5355\u5F20\u53C2\u8003\u56FE\uFF09").option("--response-format <format>", "\u56FE\u50CF\u54CD\u5E94\u683C\u5F0F\uFF1Aurl \u6216 b64_json").option("--n <count>", "\u5F20\u6570\uFF081\u2013128\uFF09", (v) => Number.parseInt(v, 10), 1).option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u56FE\u50CF\u751F\u6210\u5B8C\u6210").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).action(async (promptParts, opts, cmd) => {
1766
+ gen.command("image").description("\u751F\u6210\u56FE\u50CF\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u56FE\u50CF\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--size <size>", "\u5C3A\u5BF8\uFF0C\u5982 1024x1024").option("--aspect-ratio <ratio>", "\u6A21\u578B\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF0C\u5982 16:9").option("--resolution <resolution>", "\u6A21\u578B\u539F\u751F\u8F93\u51FA\u6863\u4F4D\uFF0C\u5982 1k\u30012k").option("--seed <n>", "\u6A21\u578B\u968F\u673A\u79CD\u5B50\uFF08\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--quality <quality>", "\u56FE\u50CF\u8D28\u91CF\u6863\u4F4D\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09").option("--background <background>", "\u80CC\u666F\u6A21\u5F0F\uFF08\u4EC5 gpt-image-2 \u652F\u6301 auto/opaque\uFF09").option("--negative-prompt <text>", "\u8D1F\u9762\u63D0\u793A\u8BCD\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09").option("--creativity <level>", "\u63D0\u793A\u8BCD\u6269\u5C55\u5F3A\u5EA6\uFF0C\u5982 raw\u3001low\u3001medium\u3001high").option("--prompt-extend <boolean>", "\u662F\u5426\u6269\u5C55\u63D0\u793A\u8BCD\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "prompt-extend")).option("--style-references <json>", "Krea image_style_references JSON \u6570\u7EC4").option("--moodboards <json>", "Krea moodboards JSON \u6570\u7EC4").option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--output-format <format>", "\u8F93\u51FA\u683C\u5F0F\uFF08\u4EC5 Seedream\uFF1Apng \u6216 jpeg\uFF09").option("--optimize-prompt <mode>", "\u63D0\u793A\u8BCD\u4F18\u5316\uFF08\u4EC5 Seedream\uFF1Aauto\u3001enabled \u6216 disabled\uFF09").option("--image <url...>", "\u53C2\u8003\u56FE\u6216\u7F16\u8F91\u56FE URL\uFF0C\u53EF\u591A\u4E2A").option("--mask <url>", "\u7F16\u8F91 mask URL\uFF08gpt-image-2 \u9700\u8981\u5355\u5F20\u53C2\u8003\u56FE\uFF09").option("--response-format <format>", "\u56FE\u50CF\u54CD\u5E94\u683C\u5F0F\uFF1Aurl \u6216 b64_json").option("--n <count>", "\u5F20\u6570\uFF081\u2013128\uFF09", (v) => Number.parseInt(v, 10), 1).option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u56FE\u50CF\u751F\u6210\u5B8C\u6210").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).action(async (promptParts, opts, cmd) => {
1474
1767
  const g = cmd.optsWithGlobals();
1475
1768
  const auth = resolveAuth(g);
1476
1769
  const model = opts.model ?? (await resolveCreativeModel(auth, "image")).model.id;
1477
1770
  if (!opts.model && !g.json) info(`\u5DF2\u81EA\u52A8\u9009\u62E9\u56FE\u50CF\u6A21\u578B\uFF1A${model}`);
1478
1771
  const n = clampInt(opts.n, 1, MAX_IMAGE_N, "n");
1772
+ const styleReferences = opts.styleReferences ? parseJsonArray(opts.styleReferences, "style-references") : void 0;
1773
+ const moodboards = opts.moodboards ? parseJsonArray(opts.moodboards, "moodboards") : void 0;
1479
1774
  validateImageGeneration(model, {
1480
1775
  n,
1481
1776
  size: opts.size,
@@ -1487,6 +1782,11 @@ function registerGen(program) {
1487
1782
  watermark: opts.watermark,
1488
1783
  outputFormat: opts.outputFormat,
1489
1784
  optimizePrompt: opts.optimizePrompt,
1785
+ negativePrompt: opts.negativePrompt,
1786
+ creativity: opts.creativity,
1787
+ promptExtend: opts.promptExtend,
1788
+ styleReferenceCount: styleReferences?.length,
1789
+ moodboardCount: moodboards?.length,
1490
1790
  responseFormat: opts.responseFormat,
1491
1791
  imageCount: opts.image?.length,
1492
1792
  hasMask: Boolean(opts.mask)
@@ -1501,6 +1801,11 @@ function registerGen(program) {
1501
1801
  if (opts.seed !== void 0) body.seed = opts.seed;
1502
1802
  if (opts.quality) body.quality = opts.quality;
1503
1803
  if (opts.background) body.background = opts.background;
1804
+ if (opts.negativePrompt) body.negative_prompt = opts.negativePrompt;
1805
+ if (opts.creativity) body.creativity = opts.creativity.toLowerCase();
1806
+ if (opts.promptExtend !== void 0) body.prompt_extend = opts.promptExtend;
1807
+ if (styleReferences) body.image_style_references = styleReferences;
1808
+ if (moodboards) body.moodboards = moodboards;
1504
1809
  if (opts.watermark !== void 0) body.watermark = opts.watermark;
1505
1810
  if (opts.outputFormat) body.output_format = opts.outputFormat.toLowerCase();
1506
1811
  if (opts.optimizePrompt) body.optimize_prompt_options = { thinking: opts.optimizePrompt.toLowerCase() };
@@ -1554,7 +1859,9 @@ function registerGen(program) {
1554
1859
  seed: opts.seed,
1555
1860
  thinkingLevel: opts.thinkingLevel,
1556
1861
  temperature: opts.temperature,
1557
- topP: opts.topP
1862
+ topP: opts.topP,
1863
+ referenceImageCount: opts.image?.length,
1864
+ nonDataUriReferenceCount: opts.image?.filter((source) => !source.trim().startsWith("data:")).length
1558
1865
  });
1559
1866
  const suppliedConfig = parseGenerationConfig(opts.config);
1560
1867
  const suppliedResponseFormat = suppliedConfig.responseFormat;
@@ -1644,7 +1951,7 @@ function registerGen(program) {
1644
1951
  if (res.id) info(`\u4EA4\u4E92 ID\uFF1A${res.id}`);
1645
1952
  }
1646
1953
  });
1647
- gen.command("video").description("\u751F\u6210\u89C6\u9891\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u89C6\u9891\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--seconds <n>", "\u65F6\u957F\u79D2\u6570\uFF1B\u7CBE\u786E\u8303\u56F4\u8FD0\u884C focalapi models get <model> \u67E5\u770B", (v) => Number.parseInt(v, 10)).option("--size <size>", "\u5206\u8FA8\u7387\uFF0C\u5982 1280x720").option("--resolution <resolution>", "\u539F\u751F\u8F93\u51FA\u5206\u8FA8\u7387\uFF0C\u5982 480p\u3001720p\u30011080p\u30014k").option("--ratio <ratio>", "\u539F\u751F\u5BBD\u9AD8\u6BD4\uFF0C\u5982 16:9\u30019:16\u3001adaptive").option("--aspect-ratio <ratio>", "Grok \u89C6\u9891\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF0C\u5982 16:9\u30019:16\u3001auto").option("--seed <n>", "Grok \u89C6\u9891\u968F\u673A\u79CD\u5B50\uFF08\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--image <url...>", "\u56FE\u751F\u89C6\u9891\u7684\u6E90\u56FE\u50CF URL\uFF0C\u53EF\u591A\u4E2A").option("--generate-audio <boolean>", "\u662F\u5426\u751F\u6210\u97F3\u9891\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "generate-audio")).option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--service-tier <tier>", "\u670D\u52A1\u5C42\u7EA7\uFF08Seedance 2.0 \u9ED8\u8BA4 default\uFF09").option("--priority <n>", "\u4EFB\u52A1\u4F18\u5148\u7EA7\uFF08\u4EC5 Seedance 2.0 \u7CFB\u5217\uFF09", (v) => Number.parseInt(v, 10)).option("--callback-url <url>", "\u4EFB\u52A1\u5B8C\u6210\u56DE\u8C03 URL").option("--return-last-frame <boolean>", "\u662F\u5426\u8FD4\u56DE\u6700\u540E\u4E00\u5E27\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "return-last-frame")).option("--execution-expires-after <seconds>", "\u4EFB\u52A1\u8FC7\u671F\u79D2\u6570\uFF083600\u2013259200\uFF09", (v) => Number.parseInt(v, 10)).option("--safety-identifier <identifier>", "Seedance \u5B89\u5168\u6807\u8BC6\u7B26\uFF081\u201364 \u4E2A\u53EF\u6253\u5370 ASCII \u5B57\u7B26\uFF09").option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u5B8C\u6210").option("--poll-interval <ms>", "\u8F6E\u8BE2\u95F4\u9694\u6BEB\u79D2", (v) => Number.parseInt(v, 10), 5e3).option("--timeout <minutes>", "\u6700\u957F\u7B49\u5F85\u5206\u949F", (v) => Number.parseInt(v, 10), 30).option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).option("--content <json>", "Ark-compatible content JSON array; overrides prompt/image facade fields").action(
1954
+ gen.command("video").description("\u751F\u6210\u89C6\u9891\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u89C6\u9891\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--seconds <n>", "\u65F6\u957F\u79D2\u6570\uFF1B\u7CBE\u786E\u8303\u56F4\u8FD0\u884C focalapi models get <model> \u67E5\u770B", (v) => Number.parseInt(v, 10)).option("--size <size>", "\u5206\u8FA8\u7387\uFF0C\u5982 1280x720").option("--resolution <resolution>", "\u539F\u751F\u8F93\u51FA\u5206\u8FA8\u7387\uFF0C\u5982 480p\u3001720p\u30011080p\u30014k").option("--ratio <ratio>", "\u539F\u751F\u5BBD\u9AD8\u6BD4\uFF0C\u5982 16:9\u30019:16\u3001adaptive").option("--aspect-ratio <ratio>", "\u6A21\u578B\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF0C\u5982 16:9\u30019:16\u3001auto").option("--seed <n>", "\u6A21\u578B\u968F\u673A\u79CD\u5B50\uFF08\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--fps <n>", "\u8F93\u51FA\u5E27\u7387\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09", (v) => Number.parseInt(v, 10)).option("--safety-tolerance <n>", "\u5B89\u5168\u5BB9\u5FCD\u5EA6\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09", (v) => Number.parseInt(v, 10)).option("--image <url...>", "\u53C2\u8003\u56FE URL\uFF08Grok \u89C6\u9891\u4E3A reference-to-video \u6A21\u5F0F\uFF0C\u53EF\u591A\u4E2A\uFF09").option("--first-frame <url>", "\u56FE\u751F\u89C6\u9891\u9996\u5E27\u56FE URL\uFF08image-to-video \u6A21\u5F0F\uFF1B\u4E0E --image \u4E92\u65A5\uFF09").option("--generate-audio <boolean>", "\u662F\u5426\u751F\u6210\u97F3\u9891\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "generate-audio")).option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--service-tier <tier>", "\u670D\u52A1\u5C42\u7EA7\uFF08Seedance 2.0 \u9ED8\u8BA4 default\uFF09").option("--priority <n>", "\u4EFB\u52A1\u4F18\u5148\u7EA7\uFF08\u4EC5 Seedance 2.0 \u7CFB\u5217\uFF09", (v) => Number.parseInt(v, 10)).option("--callback-url <url>", "\u4EFB\u52A1\u5B8C\u6210\u56DE\u8C03 URL").option("--return-last-frame <boolean>", "\u662F\u5426\u8FD4\u56DE\u6700\u540E\u4E00\u5E27\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "return-last-frame")).option("--execution-expires-after <seconds>", "\u4EFB\u52A1\u8FC7\u671F\u79D2\u6570\uFF083600\u2013259200\uFF09", (v) => Number.parseInt(v, 10)).option("--safety-identifier <identifier>", "Seedance \u5B89\u5168\u6807\u8BC6\u7B26\uFF081\u201364 \u4E2A\u53EF\u6253\u5370 ASCII \u5B57\u7B26\uFF09").option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u5B8C\u6210").option("--poll-interval <ms>", "\u8F6E\u8BE2\u95F4\u9694\u6BEB\u79D2", (v) => Number.parseInt(v, 10), 5e3).option("--timeout <minutes>", "\u6700\u957F\u7B49\u5F85\u5206\u949F", (v) => Number.parseInt(v, 10), 30).option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).option("--content <json>", "Ark-compatible content JSON array; overrides prompt/image facade fields").action(
1648
1955
  async (promptParts, opts, cmd) => {
1649
1956
  const g = cmd.optsWithGlobals();
1650
1957
  const auth = resolveAuth(g);
@@ -1658,10 +1965,13 @@ function registerGen(program) {
1658
1965
  }
1659
1966
  if (opts.size) body.size = opts.size;
1660
1967
  if (opts.image) body.images = opts.image;
1968
+ if (opts.firstFrame) body.image = opts.firstFrame;
1661
1969
  if (opts.resolution) metadata.resolution = opts.resolution.toLowerCase();
1662
1970
  if (opts.ratio) metadata.ratio = opts.ratio;
1663
1971
  if (opts.aspectRatio) metadata.ratio = opts.aspectRatio;
1664
1972
  if (opts.seed !== void 0) metadata.seed = opts.seed;
1973
+ if (opts.fps !== void 0) metadata.fps = opts.fps;
1974
+ if (opts.safetyTolerance !== void 0) metadata.safety_tolerance = opts.safetyTolerance;
1665
1975
  if (opts.generateAudio !== void 0) metadata.generate_audio = opts.generateAudio;
1666
1976
  if (opts.watermark !== void 0) metadata.watermark = opts.watermark;
1667
1977
  if (opts.serviceTier) metadata.service_tier = opts.serviceTier;
@@ -1677,10 +1987,16 @@ function registerGen(program) {
1677
1987
  ratio: opts.ratio,
1678
1988
  aspectRatio: opts.aspectRatio,
1679
1989
  seed: opts.seed,
1990
+ fps: opts.fps,
1991
+ safetyTolerance: opts.safetyTolerance,
1680
1992
  serviceTier: opts.serviceTier,
1681
1993
  priority: opts.priority,
1682
1994
  executionExpiresAfter: opts.executionExpiresAfter,
1683
- safetyIdentifier: opts.safetyIdentifier
1995
+ safetyIdentifier: opts.safetyIdentifier,
1996
+ imageCount: opts.image?.length,
1997
+ firstFrameCount: opts.firstFrame ? 1 : 0,
1998
+ generateAudio: opts.generateAudio,
1999
+ watermark: opts.watermark
1684
2000
  });
1685
2001
  if (Object.keys(metadata).length > 0) body.metadata = metadata;
1686
2002
  const created = await withProgress("\u6B63\u5728\u63D0\u4EA4\u89C6\u9891\u4EFB\u52A1", () => request({
@@ -1699,7 +2015,7 @@ function registerGen(program) {
1699
2015
  printJson({ model, task_id: taskId, submitted: true, next_command: `focalapi task status ${taskId} --json` });
1700
2016
  } else {
1701
2017
  process.stdout.write(taskId + "\n");
1702
- info(`\u4EFB\u52A1\u5DF2\u63D0\u4EA4\u3002\u7EED\u53D6\uFF1Afocalapi task status ${taskId} / focalapi task download ${taskId}`);
2018
+ info(`\u4EFB\u52A1\u5DF2\u63D0\u4EA4\u3002\u7EED\u53D6\uFF1Afocalapi task status ${taskId} / focalapi task download ${taskId}\uFF1B\u6392\u961F\u4E2D\u53EF\u53D6\u6D88\uFF1Afocalapi task cancel ${taskId}`);
1703
2019
  }
1704
2020
  return;
1705
2021
  }
@@ -1725,7 +2041,7 @@ function registerGen(program) {
1725
2041
 
1726
2042
  // src/commands/task.ts
1727
2043
  function registerTask(program) {
1728
- const task = program.command("task").description("\u4EFB\u52A1\u67E5\u8BE2\u4E0E\u4EA7\u7269\u4E0B\u8F7D\uFF08\u89C6\u9891\u7B49\u4EFB\u52A1\u5236\u80FD\u529B\uFF09");
2044
+ const task = program.command("task").description("\u4EFB\u52A1\u67E5\u8BE2\u3001\u53D6\u6D88\u4E0E\u4EA7\u7269\u4E0B\u8F7D\uFF08\u89C6\u9891\u7B49\u4EFB\u52A1\u5236\u80FD\u529B\uFF09");
1729
2045
  task.command("status").description("\u67E5\u8BE2\u4EFB\u52A1\u72B6\u6001").argument("<task_id>", "\u4EFB\u52A1 ID").action(async (taskId, _opts, cmd) => {
1730
2046
  const g = cmd.optsWithGlobals();
1731
2047
  const auth = resolveAuth(g);
@@ -1744,6 +2060,19 @@ function registerTask(program) {
1744
2060
  if (info_.status === "success") {
1745
2061
  info(`\u4EA7\u7269\u4E0B\u8F7D\uFF1Afocalapi task download ${taskId}`);
1746
2062
  }
2063
+ if (info_.status === "pending" || info_.status === "running") {
2064
+ info(`\u5982\u9700\u505C\u6B62\u6392\u961F\u4E2D\u7684\u4EFB\u52A1\uFF1Afocalapi task cancel ${taskId}`);
2065
+ }
2066
+ }
2067
+ });
2068
+ task.command("cancel").description("\u53D6\u6D88\u6392\u961F\u4E2D\u7684\u4EFB\u52A1\uFF08\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4E0D\u53EF\u53D6\u6D88\uFF1B\u53D6\u6D88\u540E\u8D39\u7528\u81EA\u52A8\u9000\u8FD8\uFF09").argument("<task_id>", "\u4EFB\u52A1 ID").action(async (taskId, _opts, cmd) => {
2069
+ const g = cmd.optsWithGlobals();
2070
+ const auth = resolveAuth(g);
2071
+ await cancelTask(auth.baseUrl, auth.apiKey, taskId);
2072
+ if (g.json) {
2073
+ printJson({ task_id: taskId, status: "cancelled", cancelled: true });
2074
+ } else {
2075
+ info(`\u2713 \u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88`);
1747
2076
  }
1748
2077
  });
1749
2078
  task.command("download").description("\u4E0B\u8F7D\u4EFB\u52A1\u4EA7\u7269\uFF08\u7ECF focalapi \u5185\u5BB9\u4EE3\u7406\uFF0C\u65E0\u9700\u4E0A\u6E38\u7B7E\u540D URL\uFF09").argument("<task_id>", "\u4EFB\u52A1 ID").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", "focalapi-out").action(async (taskId, opts, cmd) => {