focalapi-cli 0.5.0 → 0.6.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 +78 -65
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 - 2026-08-26
4
+
5
+ - Retired `gen omni-video` with a local redirect to `gen video -m gemini-omni-flash-preview`: the gateway rejects the Interactions endpoint for this model in every shape (video matrix evidence 2026-08-26). All four capabilities (t2v/first-frame/reference/edit) ride the video facade.
6
+ - Added task-parameter flags to `gen video`, mapped to the task DTO's top-level fields with local enum validation: `--omni-reference-task-type` (seedance 2.5 edit/extend semantics were unreachable — auto-inference conflicts with the default duration), `--voice`/`--avatar` (HeyGen, enums now published by the platform), `--operation` (Bria), `--enhance-model` (HitPaw), and `--upscaler-model` (Topaz).
7
+ - Added local constraint tables for the August video wave (wan2.7 ×4, pixverse-v5, kling-v3-omni, luma-ray-3-2, MiniMax-H3, HeyGen ×2, happyhorse ×4, topaz/hitpaw video, beeble, bria, veo 3.1 ×3): duration tiers and resolutions are validated locally, and source-length families (heygen, topaz/hitpaw video, beeble, bria, happyhorse video-edit) reject `--seconds` up front instead of round-tripping a guaranteed 400.
8
+
9
+ ## 0.5.1 - 2026-08-26
10
+
11
+ - 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.
12
+
3
13
  ## 0.5.0 - 2026-08-25
4
14
 
5
15
  - 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).
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.5.0" : "0.0.0-dev";
179
+ var VERSION = true ? "0.6.0" : "0.0.0-dev";
180
180
 
181
181
  // src/commands/auth.ts
182
182
  import { createInterface } from "readline/promises";
@@ -1321,6 +1321,42 @@ var VIDEO_CONSTRAINTS = {
1321
1321
  maxReferenceImages: 2,
1322
1322
  supportsGenerateAudio: true
1323
1323
  },
1324
+ "wan2.7-t2v": { resolutions: ["720p", "1080p"], minSeconds: 2, maxSeconds: 15 },
1325
+ "wan2.7-i2v": { resolutions: ["720p", "1080p"], minSeconds: 2, maxSeconds: 15 },
1326
+ "wan2.7-r2v": { resolutions: ["720p", "1080p"], minSeconds: 2, maxSeconds: 15 },
1327
+ "wan2.7-videoedit": { resolutions: ["720p", "1080p"], minSeconds: 2, maxSeconds: 10 },
1328
+ "pixverse-v5": { resolutions: ["360p", "540p", "720p", "1080p"], minSeconds: 5, maxSeconds: 8, allowedSeconds: [5, 8] },
1329
+ "kling-v3-omni": { resolutions: ["720p", "1080p", "4k"], minSeconds: 3, maxSeconds: 15 },
1330
+ "luma-ray-3-2": { resolutions: ["540p", "720p", "1080p"], minSeconds: 5, maxSeconds: 10, allowedSeconds: [5, 10] },
1331
+ "MiniMax-H3": { resolutions: ["768p", "2k"], minSeconds: 5, maxSeconds: 15 },
1332
+ "heygen-talking-photo": { resolutions: ["720p", "1080p"], noDuration: true },
1333
+ "heygen-avatar": { resolutions: ["720p", "1080p"], noDuration: true },
1334
+ "happyhorse-1.1-t2v": { resolutions: ["720p", "1080p"], minSeconds: 3, maxSeconds: 15 },
1335
+ "happyhorse-1.1-i2v": { resolutions: ["720p", "1080p"], minSeconds: 3, maxSeconds: 15 },
1336
+ "happyhorse-1.1-r2v": { resolutions: ["720p", "1080p"], minSeconds: 3, maxSeconds: 15 },
1337
+ "happyhorse-1.0-video-edit": { resolutions: ["720p", "1080p"], noDuration: true },
1338
+ "topaz-video-enhance": { resolutions: ["1080p", "2160p"], noDuration: true },
1339
+ "hitpaw-video-enhance": { resolutions: ["original", "720p", "1080p", "2k/qhd", "4k/uhd", "8k"], noDuration: true },
1340
+ "beeble-switchx-video": { resolutions: ["720p", "1080p"], noDuration: true },
1341
+ "bria-video-edit": { noDuration: true },
1342
+ "veo-3.1-generate-preview": {
1343
+ resolutions: ["720p", "1080p"],
1344
+ minSeconds: 4,
1345
+ maxSeconds: 8,
1346
+ allowedSeconds: [4, 6, 8]
1347
+ },
1348
+ "veo-3.1-fast-generate-preview": {
1349
+ resolutions: ["720p", "1080p"],
1350
+ minSeconds: 4,
1351
+ maxSeconds: 8,
1352
+ allowedSeconds: [4, 6, 8]
1353
+ },
1354
+ "veo-3.1-lite-generate-preview": {
1355
+ resolutions: ["720p", "1080p"],
1356
+ minSeconds: 4,
1357
+ maxSeconds: 8,
1358
+ allowedSeconds: [4, 6, 8]
1359
+ },
1324
1360
  "flux-3": {
1325
1361
  resolutions: ["hd", "fhd"],
1326
1362
  ratios: FLUX_VIDEO_RATIOS,
@@ -1523,6 +1559,9 @@ function validateGeminiImageGeneration(model, input) {
1523
1559
  function validateVideoGeneration(model, input) {
1524
1560
  const constraint = VIDEO_CONSTRAINTS[model.trim()];
1525
1561
  if (!constraint) return;
1562
+ if (constraint.noDuration && input.seconds !== void 0) {
1563
+ throw new ApiError("invalid_request", `${model} does not accept a duration; the output length follows the source media`);
1564
+ }
1526
1565
  if (constraint.maxPromptRunes !== void 0 && input.promptRunes !== void 0) {
1527
1566
  const referenceCount = input.imageCount ?? input.firstFrameCount ?? 0;
1528
1567
  if (input.promptRunes + 500 * referenceCount > constraint.maxPromptRunes) {
@@ -1547,13 +1586,13 @@ function validateVideoGeneration(model, input) {
1547
1586
  if (input.watermark !== void 0 && constraint.supportsWatermark === false) {
1548
1587
  throw new ApiError("invalid_request", `${model} does not support watermark`);
1549
1588
  }
1550
- if (input.seconds !== void 0 && (input.seconds < constraint.minSeconds || input.seconds > constraint.maxSeconds)) {
1589
+ if (input.seconds !== void 0 && constraint.minSeconds !== void 0 && constraint.maxSeconds !== void 0 && (input.seconds < constraint.minSeconds || input.seconds > constraint.maxSeconds)) {
1551
1590
  throw new ApiError("invalid_request", `${model} seconds must be ${constraint.minSeconds}-${constraint.maxSeconds} (received: ${input.seconds})`);
1552
1591
  }
1553
1592
  if (input.seconds !== void 0 && constraint.allowedSeconds && !constraint.allowedSeconds.includes(input.seconds)) {
1554
1593
  throw new ApiError("invalid_request", `${model} seconds must be one of ${constraint.allowedSeconds.join(", ")} (received: ${input.seconds})`);
1555
1594
  }
1556
- if (input.resolution && !constraint.resolutions.includes(input.resolution.toLowerCase())) {
1595
+ if (input.resolution && constraint.resolutions && !constraint.resolutions.includes(input.resolution.toLowerCase())) {
1557
1596
  throw new ApiError("invalid_request", `${model} resolution must be one of ${constraint.resolutions.join(", ")} (received: ${input.resolution})`);
1558
1597
  }
1559
1598
  if (input.resolution && (input.imageCount ?? 0) > 0 && constraint.referenceResolutions && !constraint.referenceResolutions.includes(input.resolution.toLowerCase())) {
@@ -1943,6 +1982,15 @@ function parseJsonArray(raw, option) {
1943
1982
  throw new ApiError("invalid_request", `--${option} must be a JSON array`);
1944
1983
  }
1945
1984
  }
1985
+ async function readJsonArrayArgument(raw, option) {
1986
+ if (!raw.startsWith("@")) return parseJsonArray(raw, option);
1987
+ try {
1988
+ return parseJsonArray((await readFile(normalizeHomePath(raw.slice(1)), "utf8")).trim(), option);
1989
+ } catch (error) {
1990
+ if (error instanceof ApiError) throw error;
1991
+ throw new ApiError("invalid_request", `--${option} @file \u65E0\u6CD5\u8BFB\u53D6\uFF1A${raw.slice(1, 120)}`);
1992
+ }
1993
+ }
1946
1994
  var MAX_CONTENT_FILE_BYTES = 64 * 1024 * 1024;
1947
1995
  async function readContentArgument(raw) {
1948
1996
  if (raw === "-") {
@@ -2046,24 +2094,6 @@ function geminiImagePart(source) {
2046
2094
  }
2047
2095
  return { fileData: { fileUri: source } };
2048
2096
  }
2049
- function geminiOmniImageInput(source) {
2050
- const dataUri = /^data:([^;,]+);base64,([a-z0-9+/=\r\n]+)$/i.exec(source.trim());
2051
- if (!dataUri) {
2052
- throw new ApiError("invalid_request", "--image for Gemini Omni must be a base64 data URI");
2053
- }
2054
- const [, mimeType = "", data = ""] = dataUri;
2055
- return { type: "image", mime_type: mimeType, data: data.replace(/[\r\n]/g, "") };
2056
- }
2057
- function extractGeminiOmniVideo(response) {
2058
- for (const step of response.steps ?? []) {
2059
- for (const content of step.content ?? []) {
2060
- if (content.type === "video" && content.data) {
2061
- return { data: content.data, mimeType: content.mime_type };
2062
- }
2063
- }
2064
- }
2065
- return void 0;
2066
- }
2067
2097
  function extractGeminiImageItems(response) {
2068
2098
  return (response.candidates ?? []).flatMap(
2069
2099
  (candidate) => (candidate.content?.parts ?? []).flatMap(
@@ -2079,8 +2109,8 @@ function registerGen(program) {
2079
2109
  const model = opts.model ?? (await resolveCreativeModel(auth, "image")).model.id;
2080
2110
  if (!opts.model && !g.json) info(`\u5DF2\u81EA\u52A8\u9009\u62E9\u56FE\u50CF\u6A21\u578B\uFF1A${model}`);
2081
2111
  const n = clampInt(opts.n, 1, MAX_IMAGE_N, "n");
2082
- const styleReferences = opts.styleReferences ? parseJsonArray(opts.styleReferences, "style-references") : void 0;
2083
- const moodboards = opts.moodboards ? parseJsonArray(opts.moodboards, "moodboards") : void 0;
2112
+ const styleReferences = opts.styleReferences ? await readJsonArrayArgument(opts.styleReferences, "style-references") : void 0;
2113
+ const moodboards = opts.moodboards ? await readJsonArrayArgument(opts.moodboards, "moodboards") : void 0;
2084
2114
  const referenceImages = await resolveMediaInputs(opts.image ?? []);
2085
2115
  const maskImage = (await resolveMediaInputs(opts.mask ? [opts.mask] : []))[0];
2086
2116
  validateImageGeneration(model, {
@@ -2232,49 +2262,10 @@ function registerGen(program) {
2232
2262
  for (const file of files) info(`\u2713 ${file}`);
2233
2263
  }
2234
2264
  });
2235
- gen.command("omni-video").description("\u4F7F\u7528 Gemini Omni Flash \u539F\u751F Interactions API \u751F\u6210\u6216\u7F16\u8F91\u89C6\u9891").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("--image <data-uri...>", "\u56FE\u751F\u89C6\u9891\u53C2\u8003\u56FE\uFF1B\u4EC5\u652F\u6301 base64 data URI\uFF0C\u53EF\u591A\u4E2A").option("--previous-interaction-id <id>", "\u4E0A\u4E00\u6B21\u4EA4\u4E92 ID\uFF0C\u7528\u4E8E\u8FDE\u7EED\u89C6\u9891\u7F16\u8F91").option("--aspect-ratio <ratio>", "\u753B\u9762\u6BD4\u4F8B\uFF1A16:9 \u6216 9:16").option("--task <task>", "\u89C6\u9891\u4EFB\u52A1\uFF1Atext_to_video\u3001image_to_video\u3001reference_to_video \u6216 edit").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).action(async (promptParts, opts, cmd) => {
2236
- const g = cmd.optsWithGlobals();
2237
- const auth = resolveAuth(g);
2238
- if (opts.aspectRatio && !["16:9", "9:16"].includes(opts.aspectRatio)) {
2239
- throw new ApiError("invalid_request", "--aspect-ratio must be 16:9 or 9:16");
2240
- }
2241
- if (opts.task && !["text_to_video", "image_to_video", "reference_to_video", "edit"].includes(opts.task)) {
2242
- throw new ApiError("invalid_request", "--task must be text_to_video, image_to_video, reference_to_video, or edit");
2243
- }
2244
- const prompt = promptParts.join(" ");
2245
- const imageInputs = (opts.image ?? []).map(geminiOmniImageInput);
2246
- const input = imageInputs.length === 0 ? prompt : [...imageInputs, { type: "text", text: prompt }];
2247
- const res = await withProgress("\u6B63\u5728\u751F\u6210 Gemini Omni \u89C6\u9891", () => request({
2248
- baseUrl: auth.baseUrl,
2249
- path: "/v1beta/interactions",
2250
- apiKey: auth.apiKey,
2251
- body: {
2252
- model: "gemini-omni-flash-preview",
2253
- input,
2254
- ...opts.previousInteractionId ? { previous_interaction_id: opts.previousInteractionId } : {},
2255
- ...opts.aspectRatio ? { response_format: { type: "video", aspect_ratio: opts.aspectRatio } } : {},
2256
- ...opts.task ? { generation_config: { video_config: { task: opts.task } } } : {}
2257
- },
2258
- timeoutMs: 6e5
2259
- }));
2260
- const video = extractGeminiOmniVideo(res);
2261
- if (!video) {
2262
- throw new ApiError("bad_response", "Gemini Omni \u54CD\u5E94\u4E2D\u672A\u627E\u5230\u89C6\u9891\u6570\u636E", { body: res });
2263
- }
2264
- const dir = resolve2(opts.out);
2265
- await mkdir2(dir, { recursive: true });
2266
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2267
- const extension = video.mimeType?.includes("webm") ? ".webm" : ".mp4";
2268
- const file = join3(dir, `gemini-omni-${timestamp}${extension}`);
2269
- await writeFile(file, Buffer.from(video.data, "base64"));
2270
- if (g.json) {
2271
- printJson({ interaction_id: res.id, file });
2272
- } else {
2273
- info(`\u2713 ${file}`);
2274
- if (res.id) info(`\u4EA4\u4E92 ID\uFF1A${res.id}`);
2275
- }
2265
+ gen.command("omni-video").description("\u5DF2\u4E0B\u7EBF\uFF1AGemini Omni Flash \u5168\u90E8\u80FD\u529B\u6539\u8D70 gen video \u95E8\u9762").allowUnknownOption().action(async () => {
2266
+ throw new ApiError("invalid_request", "gen omni-video \u5DF2\u4E0B\u7EBF\uFF1Agemini-omni-flash-preview \u7684\u6587\u751F\u89C6\u9891/\u9996\u5E27/\u53C2\u8003/\u7F16\u8F91\u80FD\u529B\u5168\u90E8\u6539\u8D70 `focalapi gen video -m gemini-omni-flash-preview`\uFF08\u652F\u6301 --first-frame/--image/--content\uFF09");
2276
2267
  });
2277
- 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--duration \u4E3A\u540C\u4E49\u522B\u540D\u3002\u7CBE\u786E\u8303\u56F4\u8FD0\u884C focalapi models get <model> \u67E5\u770B", (v) => Number.parseInt(v, 10)).option("--duration <n>", "\u65F6\u957F\u79D2\u6570\uFF08--seconds \u7684\u522B\u540D\uFF0C\u4E0E API \u5951\u7EA6\u5B57\u6BB5\u540C\u540D\uFF09", (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 \u6216\u672C\u5730\u8DEF\u5F84\uFF08@C:/path/x.jpg\uFF09\uFF0CGrok \u89C6\u9891\u4E3A reference-to-video \u6A21\u5F0F").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("--idempotency-key <key>", "\u5E42\u7B49\u952E\uFF088\u2013128 \u4E2A\u53EF\u6253\u5370 ASCII \u5B57\u7B26\uFF09\u3002\u540C\u4E00 key \u7684\u91CD\u590D\u63D0\u4EA4\u62FF\u56DE\u539F\u4EFB\u52A1\u800C\u4E0D\u91CD\u590D\u8BA1\u8D39\uFF1B\u7701\u7565\u65F6\u81EA\u52A8\u751F\u6210\u3002\u91CD\u8BD5\u4E0D\u786E\u5B9A\u7684\u63D0\u4EA4\u65F6\u52A1\u5FC5\u590D\u7528\u539F key").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 content JSON \u6570\u7EC4\uFF1B\u652F\u6301\u5185\u8054 JSON\u3001@\u6587\u4EF6\u8DEF\u5F84\uFF08\u591A\u56FE base64 \u5FC5\u987B\u8D70\u6587\u4EF6\uFF09\u6216 - \uFF08stdin\uFF09\uFF1B\u8986\u76D6 prompt/image \u95E8\u9762\u5B57\u6BB5").action(
2268
+ 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--duration \u4E3A\u540C\u4E49\u522B\u540D\u3002\u7CBE\u786E\u8303\u56F4\u8FD0\u884C focalapi models get <model> \u67E5\u770B", (v) => Number.parseInt(v, 10)).option("--duration <n>", "\u65F6\u957F\u79D2\u6570\uFF08--seconds \u7684\u522B\u540D\uFF0C\u4E0E API \u5951\u7EA6\u5B57\u6BB5\u540C\u540D\uFF09", (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 \u6216\u672C\u5730\u8DEF\u5F84\uFF08@C:/path/x.jpg\uFF09\uFF0CGrok \u89C6\u9891\u4E3A reference-to-video \u6A21\u5F0F").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("--omni-reference-task-type <type>", "Seedance 2.5 \u591A\u6A21\u6001\u4EFB\u52A1\u7C7B\u578B\uFF1Aauto\u3001reference\u3001edit \u6216 extend\uFF08\u7F16\u8F91/\u5EF6\u957F\u53C2\u8003\u89C6\u9891\u65F6\u5FC5\u586B\uFF0Cauto \u4F1A\u4E0E\u9ED8\u8BA4\u65F6\u957F\u51B2\u7A81\uFF09").option("--voice <label>", "HeyGen \u8BED\u97F3\u6807\u7B7E\uFF08talking-photo \u5267\u672C\u5FC5\u586B\uFF1B\u5408\u6CD5\u503C\u89C1 models get heygen-talking-photo \u7684 voice \u679A\u4E3E\uFF09").option("--avatar <label>", "HeyGen Avatar \u5F62\u8C61\u6807\u7B7E\uFF08heygen-avatar \u5FC5\u586B\uFF1B\u5408\u6CD5\u503C\u89C1 models get heygen-avatar \u7684 avatar \u679A\u4E3E\uFF09").option("--operation <op>", "Bria \u89C6\u9891\u64CD\u4F5C\uFF1Aremove_background\u3001green_screen \u6216 replace_background").option("--enhance-model <id>", "HitPaw \u89C6\u9891\u589E\u5F3A\u6A21\u578B\uFF08\u5982 general_restore_2x\uFF1B\u5B8C\u6574\u679A\u4E3E\u89C1 models get hitpaw-video-enhance\uFF09").option("--upscaler-model <family>", "Topaz \u89C6\u9891\u653E\u5927\u6A21\u578B\uFF1Astarlight_fast\u3001starlight_creative\u3001starlight_precise_25 \u6216 astra_2").option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u5B8C\u6210").option("--idempotency-key <key>", "\u5E42\u7B49\u952E\uFF088\u2013128 \u4E2A\u53EF\u6253\u5370 ASCII \u5B57\u7B26\uFF09\u3002\u540C\u4E00 key \u7684\u91CD\u590D\u63D0\u4EA4\u62FF\u56DE\u539F\u4EFB\u52A1\u800C\u4E0D\u91CD\u590D\u8BA1\u8D39\uFF1B\u7701\u7565\u65F6\u81EA\u52A8\u751F\u6210\u3002\u91CD\u8BD5\u4E0D\u786E\u5B9A\u7684\u63D0\u4EA4\u65F6\u52A1\u5FC5\u590D\u7528\u539F key").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 content JSON \u6570\u7EC4\uFF1B\u652F\u6301\u5185\u8054 JSON\u3001@\u6587\u4EF6\u8DEF\u5F84\uFF08\u591A\u56FE base64 \u5FC5\u987B\u8D70\u6587\u4EF6\uFF09\u6216 - \uFF08stdin\uFF09\uFF1B\u8986\u76D6 prompt/image \u95E8\u9762\u5B57\u6BB5").action(
2278
2269
  async (promptParts, opts, cmd) => {
2279
2270
  const g = cmd.optsWithGlobals();
2280
2271
  const auth = resolveAuth(g);
@@ -2309,6 +2300,28 @@ function registerGen(program) {
2309
2300
  if (opts.returnLastFrame !== void 0) metadata.return_last_frame = opts.returnLastFrame;
2310
2301
  if (opts.executionExpiresAfter !== void 0) metadata.execution_expires_after = opts.executionExpiresAfter;
2311
2302
  if (opts.safetyIdentifier) metadata.safety_identifier = opts.safetyIdentifier;
2303
+ if (opts.omniReferenceTaskType) {
2304
+ const taskType = opts.omniReferenceTaskType.toLowerCase();
2305
+ if (!["auto", "reference", "edit", "extend"].includes(taskType)) {
2306
+ throw new ApiError("invalid_request", "--omni-reference-task-type must be auto, reference, edit, or extend");
2307
+ }
2308
+ body.omni_reference_task_type = taskType;
2309
+ }
2310
+ if (opts.voice) body.voice = opts.voice;
2311
+ if (opts.avatar) body.avatar = opts.avatar;
2312
+ if (opts.operation) {
2313
+ if (!["remove_background", "green_screen", "replace_background"].includes(opts.operation)) {
2314
+ throw new ApiError("invalid_request", "--operation must be remove_background, green_screen, or replace_background");
2315
+ }
2316
+ body.operation = opts.operation;
2317
+ }
2318
+ if (opts.enhanceModel) body.enhance_model = opts.enhanceModel;
2319
+ if (opts.upscalerModel) {
2320
+ if (!["starlight_fast", "starlight_creative", "starlight_precise_25", "astra_2"].includes(opts.upscalerModel)) {
2321
+ throw new ApiError("invalid_request", "--upscaler-model must be starlight_fast, starlight_creative, starlight_precise_25, or astra_2");
2322
+ }
2323
+ body.upscaler_model = opts.upscalerModel;
2324
+ }
2312
2325
  if (opts.content) metadata.content = parseJsonArray(await readContentArgument(opts.content), "content");
2313
2326
  validateVideoGeneration(model, {
2314
2327
  promptRunes: [...promptParts.join(" ")].length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "focalapi-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "让任意 AI Agent 直接调用 focalapi 创作模型的命令行工具",
5
5
  "type": "module",
6
6
  "bin": {