focalapi-cli 0.4.0 → 0.4.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 +8 -0
- package/dist/cli.js +169 -28
- package/package.json +1 -1
- package/skills/focalapi-gen/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1 - 2026-08-18
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- Bare local paths (e.g., `C:/...` without `@`) are intercepted locally with actionable guidance to add the `@` prefix or host the file, instead of round-tripping to the server's 400.
|
|
7
|
+
- Added `--content @file.json` and `--content -` (stdin) for gen video, bypassing the Windows ~32KB argv limit for multi-image content arrays; capped at 64MB.
|
|
8
|
+
- Added `task download --direct`: fetches the task's upstream artifact URL directly (CDN/edge typically beats the cross-border gateway hop; measured 328KB/s client-side vs 26MB/s server-local) and falls back to the gateway proxy automatically.
|
|
9
|
+
- Added grok video prompt budget validation: the upstream 4096-character limit applies to the composite of text plus ~500 characters per reference image — the CLI mirrors the gateway's calibrated gate and rejects locally before submission.
|
|
10
|
+
|
|
3
11
|
## 0.4.0 - 2026-08-18
|
|
4
12
|
|
|
5
13
|
- Added `task status --wait [--download]`: built-in bounded polling with elapsed-time reporting and an optional auto-download, so agents never need to write their own poller (the audited agent session's custom script crashed on the Windows `.cmd` shim and stalled blind).
|
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.
|
|
179
|
+
var VERSION = true ? "0.4.1" : "0.0.0-dev";
|
|
180
180
|
|
|
181
181
|
// src/commands/auth.ts
|
|
182
182
|
import { createInterface } from "readline/promises";
|
|
@@ -912,8 +912,8 @@ function registerDoctor(program) {
|
|
|
912
912
|
|
|
913
913
|
// src/commands/gen.ts
|
|
914
914
|
import { createWriteStream as createWriteStream2 } from "fs";
|
|
915
|
-
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
916
|
-
import { join as join3, resolve as resolve2 } from "path";
|
|
915
|
+
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
916
|
+
import { extname as extname2, join as join3, resolve as resolve2 } from "path";
|
|
917
917
|
import { pipeline as pipeline2 } from "stream/promises";
|
|
918
918
|
import { Readable as Readable2 } from "stream";
|
|
919
919
|
import { randomUUID } from "crypto";
|
|
@@ -1158,7 +1158,8 @@ var VIDEO_CONSTRAINTS = {
|
|
|
1158
1158
|
disallowReferences: true,
|
|
1159
1159
|
maxFirstFrameImages: 1,
|
|
1160
1160
|
supportsGenerateAudio: false,
|
|
1161
|
-
supportsWatermark: false
|
|
1161
|
+
supportsWatermark: false,
|
|
1162
|
+
maxPromptRunes: 4096
|
|
1162
1163
|
},
|
|
1163
1164
|
"grok-imagine-video-1.5": {
|
|
1164
1165
|
resolutions: ["480p", "720p", "1080p"],
|
|
@@ -1169,7 +1170,8 @@ var VIDEO_CONSTRAINTS = {
|
|
|
1169
1170
|
referenceResolutions: ["480p", "720p"],
|
|
1170
1171
|
maxFirstFrameImages: 1,
|
|
1171
1172
|
supportsGenerateAudio: false,
|
|
1172
|
-
supportsWatermark: false
|
|
1173
|
+
supportsWatermark: false,
|
|
1174
|
+
maxPromptRunes: 4096
|
|
1173
1175
|
},
|
|
1174
1176
|
"kling-3.0": {
|
|
1175
1177
|
resolutions: ["720p", "1080p", "4k"],
|
|
@@ -1395,6 +1397,12 @@ function validateGeminiImageGeneration(model, input) {
|
|
|
1395
1397
|
function validateVideoGeneration(model, input) {
|
|
1396
1398
|
const constraint = VIDEO_CONSTRAINTS[model.trim()];
|
|
1397
1399
|
if (!constraint) return;
|
|
1400
|
+
if (constraint.maxPromptRunes !== void 0 && input.promptRunes !== void 0) {
|
|
1401
|
+
const referenceCount = input.imageCount ?? input.firstFrameCount ?? 0;
|
|
1402
|
+
if (input.promptRunes + 500 * referenceCount > constraint.maxPromptRunes) {
|
|
1403
|
+
throw new ApiError("invalid_request", `${model} prompt budget exceeded: upstream counts prompt text plus ~500 characters per reference image against its ${constraint.maxPromptRunes}-character limit (received ${input.promptRunes} characters of text + ${referenceCount} reference images)`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1398
1406
|
if ((input.imageCount ?? 0) > 0 && (input.firstFrameCount ?? 0) > 0) {
|
|
1399
1407
|
throw new ApiError("invalid_request", `${model}: --image and --first-frame are mutually exclusive (reference-to-video vs image-to-video)`);
|
|
1400
1408
|
}
|
|
@@ -1659,6 +1667,46 @@ var EXT_BY_CONTENT_TYPE = {
|
|
|
1659
1667
|
"audio/mpeg": ".mp3",
|
|
1660
1668
|
"audio/wav": ".wav"
|
|
1661
1669
|
};
|
|
1670
|
+
function extractTaskArtifactURL(raw) {
|
|
1671
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
1672
|
+
const obj = raw;
|
|
1673
|
+
const data = obj.data;
|
|
1674
|
+
const candidates = [obj.url, data?.url, data?.data?.url, data?.data?.video_url];
|
|
1675
|
+
for (const candidate of candidates) {
|
|
1676
|
+
if (typeof candidate === "string" && /^https?:\/\//.test(candidate)) return candidate;
|
|
1677
|
+
}
|
|
1678
|
+
return void 0;
|
|
1679
|
+
}
|
|
1680
|
+
async function downloadTaskArtifact(baseUrl, apiKey, taskId, outDir, opts) {
|
|
1681
|
+
let upstreamURL;
|
|
1682
|
+
if (opts?.direct) {
|
|
1683
|
+
const info2 = await fetchTask(baseUrl, apiKey, taskId);
|
|
1684
|
+
upstreamURL = extractTaskArtifactURL(info2.raw);
|
|
1685
|
+
if (upstreamURL) {
|
|
1686
|
+
try {
|
|
1687
|
+
const res = await fetch(upstreamURL, {
|
|
1688
|
+
signal: AbortSignal.timeout(6e5)
|
|
1689
|
+
});
|
|
1690
|
+
if (res.ok && res.body) {
|
|
1691
|
+
const contentType = res.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
|
|
1692
|
+
const ext = EXT_BY_CONTENT_TYPE[contentType] ?? extFromURL(upstreamURL) ?? ".bin";
|
|
1693
|
+
const dir = resolve(outDir);
|
|
1694
|
+
await mkdir(dir, { recursive: true });
|
|
1695
|
+
const filePath = join2(dir, `${opts?.filenameBase ?? `task-${taskId}`}${ext}`);
|
|
1696
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(filePath));
|
|
1697
|
+
return { file: filePath, source: "upstream" };
|
|
1698
|
+
}
|
|
1699
|
+
} catch {
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
const file = await downloadTaskContent(baseUrl, apiKey, taskId, outDir, opts?.filenameBase);
|
|
1704
|
+
return { file, source: "proxy" };
|
|
1705
|
+
}
|
|
1706
|
+
function extFromURL(url) {
|
|
1707
|
+
const match = /\.(mp4|webm|png|jpe?g|webp|mp3|wav)(?:[?#]|$)/i.exec(url);
|
|
1708
|
+
return match ? match[1].toLowerCase() === "jpeg" ? ".jpg" : `.${match[1].toLowerCase()}` : void 0;
|
|
1709
|
+
}
|
|
1662
1710
|
async function downloadTaskContent(baseUrl, apiKey, taskId, outDir, filenameBase) {
|
|
1663
1711
|
const res = await rawRequest({
|
|
1664
1712
|
baseUrl,
|
|
@@ -1769,6 +1817,93 @@ function parseJsonArray(raw, option) {
|
|
|
1769
1817
|
throw new ApiError("invalid_request", `--${option} must be a JSON array`);
|
|
1770
1818
|
}
|
|
1771
1819
|
}
|
|
1820
|
+
var MAX_CONTENT_FILE_BYTES = 64 * 1024 * 1024;
|
|
1821
|
+
async function readContentArgument(raw) {
|
|
1822
|
+
if (raw === "-") {
|
|
1823
|
+
const chunks = [];
|
|
1824
|
+
for await (const chunk of process.stdin) {
|
|
1825
|
+
chunks.push(Buffer.from(chunk));
|
|
1826
|
+
if (Buffer.concat(chunks).byteLength > MAX_CONTENT_FILE_BYTES) {
|
|
1827
|
+
throw new ApiError("invalid_request", "--content stdin \u8F93\u5165\u8D85\u8FC7 64MB \u4E0A\u9650");
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1831
|
+
}
|
|
1832
|
+
if (raw.startsWith("@")) {
|
|
1833
|
+
const path = raw.slice(1);
|
|
1834
|
+
let content;
|
|
1835
|
+
try {
|
|
1836
|
+
content = await readFile(path);
|
|
1837
|
+
} catch (err) {
|
|
1838
|
+
throw new ApiError("invalid_request", `\u8BFB\u53D6 --content \u6587\u4EF6\u5931\u8D25 @${path}\uFF1A${err.message}`);
|
|
1839
|
+
}
|
|
1840
|
+
if (content.byteLength > MAX_CONTENT_FILE_BYTES) {
|
|
1841
|
+
throw new ApiError("invalid_request", `--content \u6587\u4EF6 ${(content.byteLength / 1048576).toFixed(1)}MB \u8D85\u8FC7 64MB \u4E0A\u9650`);
|
|
1842
|
+
}
|
|
1843
|
+
return content.toString("utf8");
|
|
1844
|
+
}
|
|
1845
|
+
return raw;
|
|
1846
|
+
}
|
|
1847
|
+
var MAX_LOCAL_MEDIA_BYTES = 8 * 1024 * 1024;
|
|
1848
|
+
var MAX_TOTAL_LOCAL_MEDIA_BYTES = 12 * 1024 * 1024;
|
|
1849
|
+
var MEDIA_MIME_BY_EXT = {
|
|
1850
|
+
".jpg": "image/jpeg",
|
|
1851
|
+
".jpeg": "image/jpeg",
|
|
1852
|
+
".png": "image/png",
|
|
1853
|
+
".webp": "image/webp",
|
|
1854
|
+
".gif": "image/gif",
|
|
1855
|
+
".mp4": "video/mp4",
|
|
1856
|
+
".webm": "video/webm",
|
|
1857
|
+
".mov": "video/quicktime",
|
|
1858
|
+
".mp3": "audio/mpeg",
|
|
1859
|
+
".wav": "audio/wav"
|
|
1860
|
+
};
|
|
1861
|
+
async function resolveMediaInputs(sources) {
|
|
1862
|
+
let totalLocalBytes = 0;
|
|
1863
|
+
const resolved = [];
|
|
1864
|
+
for (const source of sources) {
|
|
1865
|
+
if (!source) continue;
|
|
1866
|
+
if (!source.startsWith("@")) {
|
|
1867
|
+
const lowered = source.toLowerCase();
|
|
1868
|
+
if (!source.startsWith("data:") && !lowered.startsWith("http://") && !lowered.startsWith("https://")) {
|
|
1869
|
+
throw new ApiError("invalid_request", `\u53C2\u8003\u5A92\u4F53\u5FC5\u987B\u662F http(s) URL\u3001data URI \u6216 @\u672C\u5730\u8DEF\u5F84\uFF08\u6536\u5230\uFF1A${source.slice(0, 80)}\uFF09`, {
|
|
1870
|
+
hint: "\u672C\u5730\u6587\u4EF6\u8BF7\u52A0 @ \u524D\u7F00\u5185\u8054\uFF08\u5982 --image @C:/imgs/ref.png\uFF0C\u5355\u6587\u4EF6 \u22648MB\uFF09\uFF1B\u5927\u56FE/\u591A\u56FE\u8BF7\u5148\u6258\u7BA1\u4E3A URL \u518D\u4F20\u5165\u3002"
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
resolved.push(source);
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
const path = source.slice(1);
|
|
1877
|
+
let content;
|
|
1878
|
+
try {
|
|
1879
|
+
content = await readFile(path);
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
throw new ApiError("invalid_request", `\u8BFB\u53D6\u672C\u5730\u5A92\u4F53\u5931\u8D25 @${path}\uFF1A${err.message}`, {
|
|
1882
|
+
hint: "@ \u540E\u9762\u662F\u6587\u4EF6\u8DEF\u5F84\uFF08\u5982 --image @C:/imgs/ref.png\uFF09\u3002\u82E5\u6587\u4EF6\u5728\u522B\u7684\u4E3B\u673A\u4E0A\uFF0C\u8BF7\u5148\u6258\u7BA1\u5E76\u4F20 http(s) URL\u3002"
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
const ext = extname2(path).toLowerCase();
|
|
1886
|
+
const mime = MEDIA_MIME_BY_EXT[ext];
|
|
1887
|
+
if (!mime) {
|
|
1888
|
+
throw new ApiError("invalid_request", `\u4E0D\u652F\u6301\u7684\u672C\u5730\u5A92\u4F53\u7C7B\u578B ${ext}\uFF08@${path}\uFF09`, {
|
|
1889
|
+
hint: `\u652F\u6301\uFF1A${Object.keys(MEDIA_MIME_BY_EXT).join(" ")}`
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
if (content.byteLength > MAX_LOCAL_MEDIA_BYTES) {
|
|
1893
|
+
throw new ApiError("invalid_request", `\u672C\u5730\u5A92\u4F53 @${path} \u4E3A ${(content.byteLength / 1048576).toFixed(1)}MB\uFF0C\u8D85\u8FC7\u5355\u6587\u4EF6 ${MAX_LOCAL_MEDIA_BYTES / 1048576}MB \u4E0A\u9650`, {
|
|
1894
|
+
hint: "\u5927\u6587\u4EF6\u8BF7\u5148\u538B\u7F29\u6216\u6258\u7BA1\u4E3A URL \u518D\u4F20\u5165\uFF1B\u547D\u4EE4\u884C\u5185\u8054\u4EC5\u9002\u5408\u4E2D\u5C0F\u56FE\u3002"
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
totalLocalBytes += content.byteLength;
|
|
1898
|
+
if (totalLocalBytes > MAX_TOTAL_LOCAL_MEDIA_BYTES) {
|
|
1899
|
+
throw new ApiError("invalid_request", `\u672C\u5730\u5A92\u4F53\u603B\u91CF\u8D85\u8FC7 ${MAX_TOTAL_LOCAL_MEDIA_BYTES / 1048576}MB \u4E0A\u9650`, {
|
|
1900
|
+
hint: "\u591A\u5F20\u5927\u56FE\u8BF7\u5148\u6258\u7BA1\u4E3A URL\uFF08\u751F\u6210\u7C7B\u63A5\u53E3\u8FD4\u56DE\u7684\u4EA7\u7269 URL \u53EF\u76F4\u63A5\u590D\u7528\uFF09\u3002"
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
resolved.push(`data:${mime};base64,${content.toString("base64")}`);
|
|
1904
|
+
}
|
|
1905
|
+
return resolved;
|
|
1906
|
+
}
|
|
1772
1907
|
function parseGeminiResponseModalities(raw) {
|
|
1773
1908
|
const modalities = raw.split(",").map((value) => value.trim().toUpperCase()).filter(Boolean);
|
|
1774
1909
|
const unique = new Set(modalities);
|
|
@@ -1812,7 +1947,7 @@ function extractGeminiImageItems(response) {
|
|
|
1812
1947
|
}
|
|
1813
1948
|
function registerGen(program) {
|
|
1814
1949
|
const gen = program.command("gen").description("\u56FE\u50CF / \u89C6\u9891\u751F\u6210");
|
|
1815
|
-
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
|
|
1950
|
+
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/\u7F16\u8F91\u56FE URL \u6216\u672C\u5730\u8DEF\u5F84\uFF08@C:/path/x.jpg \u81EA\u52A8\u5185\u8054\uFF09\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("--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("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).action(async (promptParts, opts, cmd) => {
|
|
1816
1951
|
const g = cmd.optsWithGlobals();
|
|
1817
1952
|
const auth = resolveAuth(g);
|
|
1818
1953
|
const model = opts.model ?? (await resolveCreativeModel(auth, "image")).model.id;
|
|
@@ -1820,6 +1955,8 @@ function registerGen(program) {
|
|
|
1820
1955
|
const n = clampInt(opts.n, 1, MAX_IMAGE_N, "n");
|
|
1821
1956
|
const styleReferences = opts.styleReferences ? parseJsonArray(opts.styleReferences, "style-references") : void 0;
|
|
1822
1957
|
const moodboards = opts.moodboards ? parseJsonArray(opts.moodboards, "moodboards") : void 0;
|
|
1958
|
+
const referenceImages = await resolveMediaInputs(opts.image ?? []);
|
|
1959
|
+
const maskImage = (await resolveMediaInputs(opts.mask ? [opts.mask] : []))[0];
|
|
1823
1960
|
validateImageGeneration(model, {
|
|
1824
1961
|
n,
|
|
1825
1962
|
size: opts.size,
|
|
@@ -1837,8 +1974,8 @@ function registerGen(program) {
|
|
|
1837
1974
|
styleReferenceCount: styleReferences?.length,
|
|
1838
1975
|
moodboardCount: moodboards?.length,
|
|
1839
1976
|
responseFormat: opts.responseFormat,
|
|
1840
|
-
imageCount:
|
|
1841
|
-
hasMask: Boolean(
|
|
1977
|
+
imageCount: referenceImages.length,
|
|
1978
|
+
hasMask: Boolean(maskImage)
|
|
1842
1979
|
});
|
|
1843
1980
|
if (opts.wait === false && opts.responseFormat === "b64_json") {
|
|
1844
1981
|
throw new ApiError("invalid_request", "--response-format b64_json cannot be used with --no-wait; use url");
|
|
@@ -1858,11 +1995,11 @@ function registerGen(program) {
|
|
|
1858
1995
|
if (opts.watermark !== void 0) body.watermark = opts.watermark;
|
|
1859
1996
|
if (opts.outputFormat) body.output_format = opts.outputFormat.toLowerCase();
|
|
1860
1997
|
if (opts.optimizePrompt) body.optimize_prompt_options = { thinking: opts.optimizePrompt.toLowerCase() };
|
|
1861
|
-
if (
|
|
1862
|
-
if (
|
|
1998
|
+
if (referenceImages.length > 0) body.image = referenceImages;
|
|
1999
|
+
if (maskImage) body.mask = maskImage;
|
|
1863
2000
|
if (opts.responseFormat) body.response_format = opts.responseFormat;
|
|
1864
2001
|
const idempotencyKey = opts.wait === false ? resolveIdempotencyKey(opts.idempotencyKey) : void 0;
|
|
1865
|
-
warnDoubleEncodedReferenceURLs("--image",
|
|
2002
|
+
warnDoubleEncodedReferenceURLs("--image", referenceImages);
|
|
1866
2003
|
const res = await withProgress(opts.wait === false ? "\u6B63\u5728\u63D0\u4EA4\u56FE\u50CF\u4EFB\u52A1" : "\u6B63\u5728\u751F\u6210\u56FE\u50CF", () => request({
|
|
1867
2004
|
baseUrl: auth.baseUrl,
|
|
1868
2005
|
path: "/v1/images/generations",
|
|
@@ -1906,18 +2043,19 @@ function registerGen(program) {
|
|
|
1906
2043
|
for (const f of files) info(`\u2713 ${f}`);
|
|
1907
2044
|
}
|
|
1908
2045
|
});
|
|
1909
|
-
gen.command("gemini-image").description("\u4F7F\u7528 Gemini \u539F\u751F generateContent \u63A5\u53E3\u751F\u6210\u56FE\u50CF").argument("<prompt...>", "\u63D0\u793A\u8BCD").requiredOption("-m, --model <model>", "Gemini \u56FE\u50CF\u6A21\u578B ID\uFF1B\u5148\u7528 focalapi models get \u786E\u8BA4").option("--aspect-ratio <ratio>", "\u753B\u9762\u6BD4\u4F8B\uFF0C\u4F8B\u5982 1:1\u300116:9\u3001auto").option("--image-size <size>", "\u8F93\u51FA\u5C3A\u5BF8\uFF0C\u4F8B\u5982 1K\u30012K\u30014K").option("--response-modalities <modalities>", "\u8F93\u51FA\u7C7B\u578B\uFF1AIMAGE \u6216 IMAGE,TEXT\uFF1B\u672A\u4F20\u65F6\u7531\u670D\u52A1\u7AEF\u9ED8\u8BA4 IMAGE,TEXT").option("--config <json>", "\u9644\u52A0 Gemini generationConfig JSON\uFF1B\u547D\u4EE4\u56FA\u5B9A responseFormat.image \u548C\u5355\u5019\u9009").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).option("--image <url...>", "Gemini reference image URL or
|
|
2046
|
+
gen.command("gemini-image").description("\u4F7F\u7528 Gemini \u539F\u751F generateContent \u63A5\u53E3\u751F\u6210\u56FE\u50CF").argument("<prompt...>", "\u63D0\u793A\u8BCD").requiredOption("-m, --model <model>", "Gemini \u56FE\u50CF\u6A21\u578B ID\uFF1B\u5148\u7528 focalapi models get \u786E\u8BA4").option("--aspect-ratio <ratio>", "\u753B\u9762\u6BD4\u4F8B\uFF0C\u4F8B\u5982 1:1\u300116:9\u3001auto").option("--image-size <size>", "\u8F93\u51FA\u5C3A\u5BF8\uFF0C\u4F8B\u5982 1K\u30012K\u30014K").option("--response-modalities <modalities>", "\u8F93\u51FA\u7C7B\u578B\uFF1AIMAGE \u6216 IMAGE,TEXT\uFF1B\u672A\u4F20\u65F6\u7531\u670D\u52A1\u7AEF\u9ED8\u8BA4 IMAGE,TEXT").option("--config <json>", "\u9644\u52A0 Gemini generationConfig JSON\uFF1B\u547D\u4EE4\u56FA\u5B9A responseFormat.image \u548C\u5355\u5019\u9009").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).option("--image <url...>", "Gemini reference image URL, data URI, or local path (@C:/path/x.jpg); repeatable").option("--system <text>", "Gemini systemInstruction text").option("--seed <n>", "Non-negative Gemini generation seed", (v) => Number.parseInt(v, 10)).option("--thinking-level <level>", "Nano Banana 2 Lite: MINIMAL or HIGH").option("--temperature <n>", "Nano Banana 2 Lite: 0 through 2", (v) => Number.parseFloat(v)).option("--top-p <n>", "Nano Banana 2 Lite: 0 through 1", (v) => Number.parseFloat(v)).action(async (promptParts, opts, cmd) => {
|
|
1910
2047
|
const g = cmd.optsWithGlobals();
|
|
1911
2048
|
const auth = resolveAuth(g);
|
|
2049
|
+
const geminiReferenceImages = await resolveMediaInputs(opts.image ?? []);
|
|
1912
2050
|
validateGeminiImageGeneration(opts.model, {
|
|
2051
|
+
referenceImageCount: geminiReferenceImages.length,
|
|
2052
|
+
nonDataUriReferenceCount: geminiReferenceImages.filter((source) => !source.startsWith("data:")).length,
|
|
1913
2053
|
aspectRatio: opts.aspectRatio,
|
|
1914
2054
|
imageSize: opts.imageSize,
|
|
1915
2055
|
seed: opts.seed,
|
|
1916
2056
|
thinkingLevel: opts.thinkingLevel,
|
|
1917
2057
|
temperature: opts.temperature,
|
|
1918
|
-
topP: opts.topP
|
|
1919
|
-
referenceImageCount: opts.image?.length,
|
|
1920
|
-
nonDataUriReferenceCount: opts.image?.filter((source) => !source.trim().startsWith("data:")).length
|
|
2058
|
+
topP: opts.topP
|
|
1921
2059
|
});
|
|
1922
2060
|
const suppliedConfig = parseGenerationConfig(opts.config);
|
|
1923
2061
|
const suppliedResponseFormat = suppliedConfig.responseFormat;
|
|
@@ -1942,7 +2080,7 @@ function registerGen(program) {
|
|
|
1942
2080
|
path: `/v1beta/models/${encodeURIComponent(opts.model)}:generateContent`,
|
|
1943
2081
|
apiKey: auth.apiKey,
|
|
1944
2082
|
body: {
|
|
1945
|
-
contents: [{ role: "user", parts: [{ text: promptParts.join(" ") }, ...
|
|
2083
|
+
contents: [{ role: "user", parts: [{ text: promptParts.join(" ") }, ...geminiReferenceImages.map(geminiImagePart)] }],
|
|
1946
2084
|
...opts.system ? { systemInstruction: { parts: [{ text: opts.system }] } } : {},
|
|
1947
2085
|
generationConfig
|
|
1948
2086
|
},
|
|
@@ -2007,7 +2145,7 @@ function registerGen(program) {
|
|
|
2007
2145
|
if (res.id) info(`\u4EA4\u4E92 ID\uFF1A${res.id}`);
|
|
2008
2146
|
}
|
|
2009
2147
|
});
|
|
2010
|
-
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\
|
|
2148
|
+
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(
|
|
2011
2149
|
async (promptParts, opts, cmd) => {
|
|
2012
2150
|
const g = cmd.optsWithGlobals();
|
|
2013
2151
|
const auth = resolveAuth(g);
|
|
@@ -2024,8 +2162,10 @@ function registerGen(program) {
|
|
|
2024
2162
|
body.duration = secondsValue;
|
|
2025
2163
|
}
|
|
2026
2164
|
if (opts.size) body.size = opts.size;
|
|
2027
|
-
|
|
2028
|
-
|
|
2165
|
+
const videoReferenceImages = await resolveMediaInputs(opts.image ?? []);
|
|
2166
|
+
const firstFrameImage = (await resolveMediaInputs(opts.firstFrame ? [opts.firstFrame] : []))[0];
|
|
2167
|
+
if (videoReferenceImages.length > 0) body.images = videoReferenceImages;
|
|
2168
|
+
if (firstFrameImage) body.image = firstFrameImage;
|
|
2029
2169
|
if (opts.resolution) metadata.resolution = opts.resolution.toLowerCase();
|
|
2030
2170
|
if (opts.ratio) metadata.ratio = opts.ratio;
|
|
2031
2171
|
if (opts.aspectRatio) metadata.ratio = opts.aspectRatio;
|
|
@@ -2040,8 +2180,9 @@ function registerGen(program) {
|
|
|
2040
2180
|
if (opts.returnLastFrame !== void 0) metadata.return_last_frame = opts.returnLastFrame;
|
|
2041
2181
|
if (opts.executionExpiresAfter !== void 0) metadata.execution_expires_after = opts.executionExpiresAfter;
|
|
2042
2182
|
if (opts.safetyIdentifier) metadata.safety_identifier = opts.safetyIdentifier;
|
|
2043
|
-
if (opts.content) metadata.content = parseJsonArray(opts.content, "content");
|
|
2183
|
+
if (opts.content) metadata.content = parseJsonArray(await readContentArgument(opts.content), "content");
|
|
2044
2184
|
validateVideoGeneration(model, {
|
|
2185
|
+
promptRunes: [...promptParts.join(" ")].length,
|
|
2045
2186
|
seconds,
|
|
2046
2187
|
resolution: opts.resolution,
|
|
2047
2188
|
ratio: opts.ratio,
|
|
@@ -2053,14 +2194,14 @@ function registerGen(program) {
|
|
|
2053
2194
|
priority: opts.priority,
|
|
2054
2195
|
executionExpiresAfter: opts.executionExpiresAfter,
|
|
2055
2196
|
safetyIdentifier: opts.safetyIdentifier,
|
|
2056
|
-
imageCount:
|
|
2057
|
-
firstFrameCount:
|
|
2197
|
+
imageCount: videoReferenceImages.length,
|
|
2198
|
+
firstFrameCount: firstFrameImage ? 1 : 0,
|
|
2058
2199
|
generateAudio: opts.generateAudio,
|
|
2059
2200
|
watermark: opts.watermark
|
|
2060
2201
|
});
|
|
2061
2202
|
if (Object.keys(metadata).length > 0) body.metadata = metadata;
|
|
2062
2203
|
const idempotencyKey = resolveIdempotencyKey(opts.idempotencyKey);
|
|
2063
|
-
warnDoubleEncodedReferenceURLs("--image/--first-frame", [...
|
|
2204
|
+
warnDoubleEncodedReferenceURLs("--image/--first-frame", [...videoReferenceImages, firstFrameImage]);
|
|
2064
2205
|
const created = await withProgress("\u6B63\u5728\u63D0\u4EA4\u89C6\u9891\u4EFB\u52A1", () => request({
|
|
2065
2206
|
baseUrl: auth.baseUrl,
|
|
2066
2207
|
path: "/v1/video/generations",
|
|
@@ -2144,7 +2285,7 @@ function registerTask(program) {
|
|
|
2144
2285
|
const elapsed = taskAgeSeconds(info_.createdAt);
|
|
2145
2286
|
let file;
|
|
2146
2287
|
if (opts.download && info_.status === "success") {
|
|
2147
|
-
file = await
|
|
2288
|
+
file = (await downloadTaskArtifact(auth.baseUrl, auth.apiKey, taskId, opts.out)).file;
|
|
2148
2289
|
}
|
|
2149
2290
|
if (g.json) {
|
|
2150
2291
|
printJson({
|
|
@@ -2212,14 +2353,14 @@ function registerTask(program) {
|
|
|
2212
2353
|
info(`\u2713 \u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88`);
|
|
2213
2354
|
}
|
|
2214
2355
|
});
|
|
2215
|
-
task.command("download").description("\u4E0B\u8F7D\u4EFB\u52A1\u4EA7\u7269\uFF08\u7ECF focalapi \u5185\u5BB9\u4EE3\u7406\
|
|
2356
|
+
task.command("download").description("\u4E0B\u8F7D\u4EFB\u52A1\u4EA7\u7269\uFF08\u9ED8\u8BA4\u7ECF focalapi \u5185\u5BB9\u4EE3\u7406\uFF1B--direct \u4F18\u5148\u76F4\u8FDE\u4EA7\u7269 URL\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u9000\u4EE3\u7406\uFF09").argument("<task_id>", "\u4EFB\u52A1 ID").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", "focalapi-out").option("--direct", "\u4F18\u5148\u76F4\u8FDE\u4EFB\u52A1\u7684\u4EA7\u7269 URL \u4E0B\u8F7D\uFF08\u8DE8\u5883\u94FE\u8DEF\u4E0B\u901A\u5E38\u663E\u8457\u5FEB\u4E8E\u4EE3\u7406\uFF09\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u9000").action(async (taskId, opts, cmd) => {
|
|
2216
2357
|
const g = cmd.optsWithGlobals();
|
|
2217
2358
|
const auth = resolveAuth(g);
|
|
2218
|
-
const filePath = await
|
|
2359
|
+
const { file: filePath, source } = await downloadTaskArtifact(auth.baseUrl, auth.apiKey, taskId, opts.out, { direct: opts.direct });
|
|
2219
2360
|
if (g.json) {
|
|
2220
|
-
printJson({ task_id: taskId, file: filePath, files: [filePath] });
|
|
2361
|
+
printJson({ task_id: taskId, file: filePath, files: [filePath], source });
|
|
2221
2362
|
} else {
|
|
2222
|
-
info(`\u2713 ${filePath}`);
|
|
2363
|
+
info(`\u2713 ${filePath}${source === "upstream" ? "\uFF08\u76F4\u8FDE\u4EA7\u7269 URL\uFF09" : ""}`);
|
|
2223
2364
|
}
|
|
2224
2365
|
});
|
|
2225
2366
|
}
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ focalapi gen image "<prompt>" -m <model-id> [contract-supported options] -o ./fo
|
|
|
31
31
|
focalapi gen video "<prompt>" -m <model-id> [contract-supported options] --no-wait -o ./focalapi-out --json
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
- Use `--image <url...>` for image editing and reference images. Pass `--mask` only when the contract lists it.
|
|
34
|
+
- Use `--image <url...>` for image editing and reference images. Local files work directly with the `@` prefix (`--image @C:/path/ref.jpg`, inlined as a data URI; per-file ≤8MB, per-command ≤12MB — larger sets must be hosted as URLs first). Pass `--mask` only when the contract lists it.
|
|
35
35
|
- Use `--negative-prompt`, `--creativity`, `--prompt-extend`, `--style-references`, and `--moodboards` only when the image contract lists the corresponding field.
|
|
36
36
|
- For video inputs, `--image <url...>` means reference images (Grok 1.5 reference-to-video, capped at 720p and 7 images) and `--first-frame <url>` means image-to-video from a single starting frame. The two flags are mutually exclusive and both are validated against the live contract before submission.
|
|
37
37
|
- Pass duration, resolution, aspect ratio, and audio options only as allowed by `supported_params`.
|