focalapi-cli 0.4.1 → 0.5.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.
- package/CHANGELOG.md +14 -0
- package/dist/cli.js +148 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 - 2026-08-26
|
|
4
|
+
|
|
5
|
+
- Added `@file` support to `--style-references` and `--moodboards`: the JSON array can now be read from a file instead of inline argv. Windows `cmd` mangles embedded double quotes in JSON arguments (matrix-run evidence 2026-08-25), and the file source sidesteps shell quoting entirely.
|
|
6
|
+
|
|
7
|
+
## 0.5.0 - 2026-08-25
|
|
8
|
+
|
|
9
|
+
- 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).
|
|
10
|
+
- 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`.
|
|
11
|
+
- Enforce the exact-one-input-image requirement locally for the enhance/upscale/vectorize/switch families instead of surfacing the server 400.
|
|
12
|
+
- Map `--resolution` to the `target_resolution` wire field for WaveSpeed upscales (the generic `resolution` field is silently ignored by that family).
|
|
13
|
+
- Send Topaz `--creativity` as an integer 1-9 (the platform contract takes a number; the Krea string modes are unchanged).
|
|
14
|
+
- 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).
|
|
15
|
+
- 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).
|
|
16
|
+
|
|
3
17
|
## 0.4.1 - 2026-08-18
|
|
4
18
|
|
|
5
19
|
- 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.
|
|
179
|
+
var VERSION = true ? "0.5.1" : "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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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})`);
|
|
@@ -1817,6 +1943,15 @@ function parseJsonArray(raw, option) {
|
|
|
1817
1943
|
throw new ApiError("invalid_request", `--${option} must be a JSON array`);
|
|
1818
1944
|
}
|
|
1819
1945
|
}
|
|
1946
|
+
async function readJsonArrayArgument(raw, option) {
|
|
1947
|
+
if (!raw.startsWith("@")) return parseJsonArray(raw, option);
|
|
1948
|
+
try {
|
|
1949
|
+
return parseJsonArray((await readFile(normalizeHomePath(raw.slice(1)), "utf8")).trim(), option);
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
if (error instanceof ApiError) throw error;
|
|
1952
|
+
throw new ApiError("invalid_request", `--${option} @file \u65E0\u6CD5\u8BFB\u53D6\uFF1A${raw.slice(1, 120)}`);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1820
1955
|
var MAX_CONTENT_FILE_BYTES = 64 * 1024 * 1024;
|
|
1821
1956
|
async function readContentArgument(raw) {
|
|
1822
1957
|
if (raw === "-") {
|
|
@@ -1953,8 +2088,8 @@ function registerGen(program) {
|
|
|
1953
2088
|
const model = opts.model ?? (await resolveCreativeModel(auth, "image")).model.id;
|
|
1954
2089
|
if (!opts.model && !g.json) info(`\u5DF2\u81EA\u52A8\u9009\u62E9\u56FE\u50CF\u6A21\u578B\uFF1A${model}`);
|
|
1955
2090
|
const n = clampInt(opts.n, 1, MAX_IMAGE_N, "n");
|
|
1956
|
-
const styleReferences = opts.styleReferences ?
|
|
1957
|
-
const moodboards = opts.moodboards ?
|
|
2091
|
+
const styleReferences = opts.styleReferences ? await readJsonArrayArgument(opts.styleReferences, "style-references") : void 0;
|
|
2092
|
+
const moodboards = opts.moodboards ? await readJsonArrayArgument(opts.moodboards, "moodboards") : void 0;
|
|
1958
2093
|
const referenceImages = await resolveMediaInputs(opts.image ?? []);
|
|
1959
2094
|
const maskImage = (await resolveMediaInputs(opts.mask ? [opts.mask] : []))[0];
|
|
1960
2095
|
validateImageGeneration(model, {
|
|
@@ -1981,14 +2116,17 @@ function registerGen(program) {
|
|
|
1981
2116
|
throw new ApiError("invalid_request", "--response-format b64_json cannot be used with --no-wait; use url");
|
|
1982
2117
|
}
|
|
1983
2118
|
const body = { model, prompt: promptParts.join(" "), n };
|
|
2119
|
+
const constraint = getImageConstraint(model);
|
|
1984
2120
|
if (opts.size) body.size = opts.size;
|
|
1985
2121
|
if (opts.aspectRatio) body.aspect_ratio = opts.aspectRatio;
|
|
1986
|
-
if (opts.resolution) body
|
|
2122
|
+
if (opts.resolution) body[constraint?.resolutionParam ?? "resolution"] = opts.resolution.toLowerCase();
|
|
1987
2123
|
if (opts.seed !== void 0) body.seed = opts.seed;
|
|
1988
2124
|
if (opts.quality) body.quality = opts.quality;
|
|
1989
2125
|
if (opts.background) body.background = opts.background;
|
|
1990
2126
|
if (opts.negativePrompt) body.negative_prompt = opts.negativePrompt;
|
|
1991
|
-
if (opts.creativity)
|
|
2127
|
+
if (opts.creativity) {
|
|
2128
|
+
body.creativity = constraint?.integerCreativity ? Number(opts.creativity) : opts.creativity.toLowerCase();
|
|
2129
|
+
}
|
|
1992
2130
|
if (opts.promptExtend !== void 0) body.prompt_extend = opts.promptExtend;
|
|
1993
2131
|
if (styleReferences) body.image_style_references = styleReferences;
|
|
1994
2132
|
if (moodboards) body.moodboards = moodboards;
|