focalapi-cli 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cli.js +137 -8
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-08-25
4
+
5
+ - Fixed `krea-2-medium/-turbo/large` reference-image validation: the local table said 0 while the platform contract accepts up to 10 reference images (each maps to a Krea style reference), so every `gen image --image` edit was rejected locally without reaching the server (2026-08-25 live audit P01).
6
+ - Added local constraints for the August image wave so invalid parameters are intercepted before submission: `topaz-image-reimagine`/`-bloom-2`/`-wonder-3-5`, `wavespeed-seedvr2-upscale`/`-ultimate-upscale`, `quiver-text-to-svg`/`-image-to-svg`, `hitpaw-image-enhance`/`-portrait-enhance`, `beeble-switchx-image-720p`/`-1080p`, `recraft-v4`/`-v4-pro`.
7
+ - Enforce the exact-one-input-image requirement locally for the enhance/upscale/vectorize/switch families instead of surfacing the server 400.
8
+ - Map `--resolution` to the `target_resolution` wire field for WaveSpeed upscales (the generic `resolution` field is silently ignored by that family).
9
+ - Send Topaz `--creativity` as an integer 1-9 (the platform contract takes a number; the Krea string modes are unchanged).
10
+ - Validate Recraft sizes against the official V4 tables (non-pro caps at ~1.5K, pro at ~3K) and reject reference images locally (the channel is text-to-image only).
11
+ - Intercept `gen image` on Gemini image models with a direct pointer to `gen gemini-image` (native generateContent endpoint) instead of an opaque upstream failure (audit P07).
12
+
3
13
  ## 0.4.1 - 2026-08-18
4
14
 
5
15
  - Added `@file` syntax for local reference media on all gen paths (`--image @C:/path/ref.jpg`): the file is read and inlined as a data URI with per-file 8MB and per-command 12MB guards — local files no longer require manual hosting.
package/dist/cli.js CHANGED
@@ -176,7 +176,7 @@ function displayWidth(s) {
176
176
  }
177
177
 
178
178
  // src/lib/version.ts
179
- var VERSION = true ? "0.4.1" : "0.0.0-dev";
179
+ var VERSION = true ? "0.5.0" : "0.0.0-dev";
180
180
 
181
181
  // src/commands/auth.ts
182
182
  import { createInterface } from "readline/promises";
@@ -1050,7 +1050,7 @@ var IMAGE_CONSTRAINTS = {
1050
1050
  },
1051
1051
  "krea-2-medium": {
1052
1052
  maxN: 1,
1053
- maxReferenceImages: 0,
1053
+ maxReferenceImages: 10,
1054
1054
  aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1055
1055
  resolutions: ["1k"],
1056
1056
  supportsSeed: true,
@@ -1061,7 +1061,7 @@ var IMAGE_CONSTRAINTS = {
1061
1061
  },
1062
1062
  "krea-2-medium-turbo": {
1063
1063
  maxN: 1,
1064
- maxReferenceImages: 0,
1064
+ maxReferenceImages: 10,
1065
1065
  aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1066
1066
  resolutions: ["1k"],
1067
1067
  supportsSeed: true,
@@ -1072,7 +1072,7 @@ var IMAGE_CONSTRAINTS = {
1072
1072
  },
1073
1073
  "krea-2-large": {
1074
1074
  maxN: 1,
1075
- maxReferenceImages: 0,
1075
+ maxReferenceImages: 10,
1076
1076
  aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1077
1077
  resolutions: ["1k"],
1078
1078
  supportsSeed: true,
@@ -1106,8 +1106,112 @@ var IMAGE_CONSTRAINTS = {
1106
1106
  supportsNegativePrompt: true,
1107
1107
  supportsPromptExtend: true,
1108
1108
  supportsWatermark: true
1109
+ },
1110
+ "topaz-image-reimagine": {
1111
+ maxN: 1,
1112
+ minImageCount: 1,
1113
+ maxReferenceImages: 1,
1114
+ integerCreativity: [1, 9]
1115
+ },
1116
+ "topaz-image-bloom-2": {
1117
+ maxN: 1,
1118
+ minImageCount: 1,
1119
+ maxReferenceImages: 1
1120
+ },
1121
+ "topaz-image-wonder-3-5": {
1122
+ maxN: 1,
1123
+ minImageCount: 1,
1124
+ maxReferenceImages: 1
1125
+ },
1126
+ "wavespeed-seedvr2-upscale": {
1127
+ maxN: 1,
1128
+ minImageCount: 1,
1129
+ maxReferenceImages: 1,
1130
+ resolutions: ["2k", "4k", "8k"],
1131
+ resolutionParam: "target_resolution"
1132
+ },
1133
+ "wavespeed-ultimate-upscale": {
1134
+ maxN: 1,
1135
+ minImageCount: 1,
1136
+ maxReferenceImages: 1,
1137
+ resolutions: ["2k", "4k", "8k"],
1138
+ resolutionParam: "target_resolution"
1139
+ },
1140
+ "quiver-text-to-svg": {
1141
+ maxN: 1,
1142
+ maxReferenceImages: 4
1143
+ },
1144
+ "quiver-image-to-svg": {
1145
+ maxN: 1,
1146
+ minImageCount: 1,
1147
+ maxReferenceImages: 1
1148
+ },
1149
+ "hitpaw-image-enhance": {
1150
+ maxN: 1,
1151
+ minImageCount: 1,
1152
+ maxReferenceImages: 1
1153
+ },
1154
+ "hitpaw-image-portrait-enhance": {
1155
+ maxN: 1,
1156
+ minImageCount: 1,
1157
+ maxReferenceImages: 1
1158
+ },
1159
+ "beeble-switchx-image-720p": {
1160
+ maxN: 1,
1161
+ minImageCount: 1,
1162
+ maxReferenceImages: 2
1163
+ },
1164
+ "beeble-switchx-image-1080p": {
1165
+ maxN: 1,
1166
+ minImageCount: 1,
1167
+ maxReferenceImages: 2
1168
+ },
1169
+ "recraft-v4": {
1170
+ defaultSize: "1024x1024",
1171
+ maxN: 6,
1172
+ maxReferenceImages: 0,
1173
+ allowedSizes: [
1174
+ "1024x1024",
1175
+ "1280x832",
1176
+ "832x1280",
1177
+ "1216x896",
1178
+ "896x1216",
1179
+ "1152x896",
1180
+ "896x1152",
1181
+ "1536x768",
1182
+ "768x1536",
1183
+ "1344x768",
1184
+ "768x1344",
1185
+ "832x1344",
1186
+ "1280x896",
1187
+ "896x1280"
1188
+ ]
1189
+ },
1190
+ "recraft-v4-pro": {
1191
+ defaultSize: "2048x2048",
1192
+ maxN: 6,
1193
+ maxReferenceImages: 0,
1194
+ allowedSizes: [
1195
+ "2048x2048",
1196
+ "2560x1664",
1197
+ "1664x2560",
1198
+ "2432x1792",
1199
+ "1792x2432",
1200
+ "2304x1792",
1201
+ "1792x2304",
1202
+ "3072x1536",
1203
+ "1536x3072",
1204
+ "2688x1536",
1205
+ "1536x2688",
1206
+ "1664x2688",
1207
+ "2560x1792",
1208
+ "1792x2560"
1209
+ ]
1109
1210
  }
1110
1211
  };
1212
+ function getImageConstraint(model) {
1213
+ return IMAGE_CONSTRAINTS[model.trim()];
1214
+ }
1111
1215
  var SEEDANCE_RATIOS = ["adaptive", "16:9", "4:3", "1:1", "3:4", "9:16", "21:9"];
1112
1216
  var GROK_VIDEO_ASPECT_RATIOS = ["auto", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16"];
1113
1217
  var KLING_VIDEO_ASPECT_RATIOS = ["16:9", "9:16", "1:1"];
@@ -1285,11 +1389,18 @@ function validateImageGeneration(model, input) {
1285
1389
  if (input.responseFormat && input.responseFormat !== "url" && input.responseFormat !== "b64_json") {
1286
1390
  throw new ApiError("invalid_request", `response_format must be url or b64_json (received: ${input.responseFormat})`);
1287
1391
  }
1288
- const constraint = IMAGE_CONSTRAINTS[model.trim()];
1392
+ const trimmedModel = model.trim();
1393
+ if (GEMINI_IMAGE_CONSTRAINTS[trimmedModel]) {
1394
+ throw new ApiError("invalid_request", `${model} is a Gemini image model; use \`focalapi gen gemini-image\` (native generateContent endpoint) instead of \`gen image\``);
1395
+ }
1396
+ const constraint = IMAGE_CONSTRAINTS[trimmedModel];
1289
1397
  if (!constraint) return;
1290
1398
  if (input.n < 1 || input.n > constraint.maxN) {
1291
1399
  throw new ApiError("invalid_request", `${model} n must be 1-${constraint.maxN} (received: ${input.n})`);
1292
1400
  }
1401
+ if (input.imageCount !== void 0 && constraint.minImageCount !== void 0 && input.imageCount < constraint.minImageCount) {
1402
+ throw new ApiError("invalid_request", `${model} requires at least ${constraint.minImageCount} input image${constraint.minImageCount === 1 ? "" : "s"}`);
1403
+ }
1293
1404
  if (input.imageCount !== void 0 && constraint.maxReferenceImages !== void 0 && input.imageCount > constraint.maxReferenceImages) {
1294
1405
  throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxReferenceImages} reference images`);
1295
1406
  }
@@ -1305,7 +1416,16 @@ function validateImageGeneration(model, input) {
1305
1416
  validateOptionalChoice(model, "resolution", input.resolution, constraint.resolutions);
1306
1417
  validateOptionalChoice(model, "output_format", input.outputFormat, constraint.outputFormats);
1307
1418
  validateOptionalChoice(model, "optimize_prompt", input.optimizePrompt, constraint.optimizePromptModes);
1308
- validateOptionalChoice(model, "creativity", input.creativity, constraint.creativityModes);
1419
+ if (!constraint.integerCreativity) {
1420
+ validateOptionalChoice(model, "creativity", input.creativity, constraint.creativityModes);
1421
+ }
1422
+ if (input.creativity !== void 0 && constraint.integerCreativity) {
1423
+ const [minimum, maximum] = constraint.integerCreativity;
1424
+ const creativity = Number(input.creativity);
1425
+ if (!Number.isInteger(creativity) || creativity < minimum || creativity > maximum) {
1426
+ throw new ApiError("invalid_request", `${model} creativity must be an integer ${minimum}-${maximum} (received: ${input.creativity})`);
1427
+ }
1428
+ }
1309
1429
  if (input.negativePrompt !== void 0 && !constraint.supportsNegativePrompt) {
1310
1430
  throw new ApiError("invalid_request", `${model} does not support negative_prompt`);
1311
1431
  }
@@ -1340,6 +1460,12 @@ function validateImageGeneration(model, input) {
1340
1460
  return;
1341
1461
  }
1342
1462
  const suppliedSize = input.size ?? constraint.defaultSize;
1463
+ if (constraint.allowedSizes) {
1464
+ if (!constraint.allowedSizes.includes(suppliedSize.toLowerCase())) {
1465
+ throw new ApiError("invalid_request", `${model} size must be one of ${constraint.allowedSizes.join(", ")} (received: ${suppliedSize})`);
1466
+ }
1467
+ return;
1468
+ }
1343
1469
  if (constraint.sizeTiers?.includes(suppliedSize.toLowerCase())) return;
1344
1470
  if (constraint.sizeTiers && !/^\d+x\d+$/i.test(suppliedSize)) {
1345
1471
  throw new ApiError("invalid_request", `${model} size must be one of ${constraint.sizeTiers.join(", ")} or WIDTHxHEIGHT (received: ${suppliedSize})`);
@@ -1981,14 +2107,17 @@ function registerGen(program) {
1981
2107
  throw new ApiError("invalid_request", "--response-format b64_json cannot be used with --no-wait; use url");
1982
2108
  }
1983
2109
  const body = { model, prompt: promptParts.join(" "), n };
2110
+ const constraint = getImageConstraint(model);
1984
2111
  if (opts.size) body.size = opts.size;
1985
2112
  if (opts.aspectRatio) body.aspect_ratio = opts.aspectRatio;
1986
- if (opts.resolution) body.resolution = opts.resolution.toLowerCase();
2113
+ if (opts.resolution) body[constraint?.resolutionParam ?? "resolution"] = opts.resolution.toLowerCase();
1987
2114
  if (opts.seed !== void 0) body.seed = opts.seed;
1988
2115
  if (opts.quality) body.quality = opts.quality;
1989
2116
  if (opts.background) body.background = opts.background;
1990
2117
  if (opts.negativePrompt) body.negative_prompt = opts.negativePrompt;
1991
- if (opts.creativity) body.creativity = opts.creativity.toLowerCase();
2118
+ if (opts.creativity) {
2119
+ body.creativity = constraint?.integerCreativity ? Number(opts.creativity) : opts.creativity.toLowerCase();
2120
+ }
1992
2121
  if (opts.promptExtend !== void 0) body.prompt_extend = opts.promptExtend;
1993
2122
  if (styleReferences) body.image_style_references = styleReferences;
1994
2123
  if (moodboards) body.moodboards = moodboards;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "focalapi-cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "让任意 AI Agent 直接调用 focalapi 创作模型的命令行工具",
5
5
  "type": "module",
6
6
  "bin": {