focalapi-cli 0.3.0 → 0.3.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 +7 -0
- package/dist/cli.js +46 -23
- package/package.json +1 -1
- package/skills/focalapi/SKILL.md +1 -1
- package/skills/focalapi-gen/SKILL.md +2 -0
- package/skills/focalapi-task/SKILL.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 - 2026-08-18
|
|
4
|
+
|
|
5
|
+
- Added `--duration` as an alias of `--seconds` on `gen video` so the API contract field name works directly; conflicting values are rejected locally.
|
|
6
|
+
- Emitted a stderr breadcrumb (`task_id=...`) for every `--no-wait` submission so callers that fail to parse stdout JSON can recover the task instead of resubmitting and double-charging.
|
|
7
|
+
- Added case-sensitive ID transcription guidance to `task status`/`task cancel` 404 errors (`l`/`1`/`I`, `O`/`0`) with an explicit warning against blind resubmission.
|
|
8
|
+
- Updated the bundled Agent Skills with resubmission discipline, the Windows `.cmd` shim invocation requirement, and exact task-ID copying guidance.
|
|
9
|
+
|
|
3
10
|
## 0.3.0 - 2026-08-18
|
|
4
11
|
|
|
5
12
|
- Added `task cancel` for queued tasks through `DELETE /v1/video/generations/{task_id}`, including the 409 `task_already_running` / `task_already_finished` and 502 `task_cancel_failed` contract with actionable hints.
|
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.3.
|
|
179
|
+
var VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
180
180
|
|
|
181
181
|
// src/commands/auth.ts
|
|
182
182
|
import { createInterface } from "readline/promises";
|
|
@@ -1549,6 +1549,12 @@ async function cancelTask(baseUrl, apiKey, taskId) {
|
|
|
1549
1549
|
hint: "\u8FD0\u884C focalapi task status " + taskId + " \u67E5\u770B\u7ED3\u679C\uFF1B\u6210\u529F\u540E\u53EF\u4E0B\u8F7D\u4EA7\u7269\u3002"
|
|
1550
1550
|
});
|
|
1551
1551
|
}
|
|
1552
|
+
if (code === "task_not_found") {
|
|
1553
|
+
throw new ApiError("task_not_found", `\u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D Key`, {
|
|
1554
|
+
status: err.status,
|
|
1555
|
+
hint: "task_id \u533A\u5206\u5927\u5C0F\u5199\u4E14\u6DF7\u6709\u5C0F\u5199 l / \u6570\u5B57 1 / \u5927\u5199 I\uFF0C\u5FC5\u987B\u9010\u5B57\u590D\u5236\uFF1B\u8BF7\u56DE\u67E5\u63D0\u4EA4\u8F93\u51FA\u540E\u518D\u8BD5\u3002"
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1552
1558
|
if (code === "task_cancel_incomplete") {
|
|
1553
1559
|
throw new ApiError("task_cancel_incomplete", `\u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88\u4F46\u6E05\u7406\u672A\u5B8C\u6210`, {
|
|
1554
1560
|
status: err.status,
|
|
@@ -1825,6 +1831,7 @@ function registerGen(program) {
|
|
|
1825
1831
|
if (!taskId) {
|
|
1826
1832
|
throw new ApiError("bad_response", "\u5F02\u6B65\u56FE\u50CF\u4EFB\u52A1\u54CD\u5E94\u4E2D\u672A\u627E\u5230 task_id", { body: res });
|
|
1827
1833
|
}
|
|
1834
|
+
info(`task_id=${taskId}`);
|
|
1828
1835
|
if (g.json) {
|
|
1829
1836
|
printJson({ model, task_id: taskId, status: res.status ?? "queued", submitted: true, next_command: `focalapi task status ${taskId} --json` });
|
|
1830
1837
|
} else {
|
|
@@ -1951,17 +1958,21 @@ function registerGen(program) {
|
|
|
1951
1958
|
if (res.id) info(`\u4EA4\u4E92 ID\uFF1A${res.id}`);
|
|
1952
1959
|
}
|
|
1953
1960
|
});
|
|
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(
|
|
1961
|
+
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\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(
|
|
1955
1962
|
async (promptParts, opts, cmd) => {
|
|
1956
1963
|
const g = cmd.optsWithGlobals();
|
|
1957
1964
|
const auth = resolveAuth(g);
|
|
1958
1965
|
const model = opts.model ?? (await resolveCreativeModel(auth, "video")).model.id;
|
|
1959
1966
|
if (!opts.model && !g.json) info(`\u5DF2\u81EA\u52A8\u9009\u62E9\u89C6\u9891\u6A21\u578B\uFF1A${model}`);
|
|
1967
|
+
if (opts.seconds !== void 0 && opts.duration !== void 0 && opts.seconds !== opts.duration) {
|
|
1968
|
+
throw new ApiError("invalid_request", `--seconds \u4E0E --duration \u662F\u540C\u4E00\u53C2\u6570\uFF0C\u53EA\u9700\u4F20\u4E00\u4E2A\uFF08\u6536\u5230 ${opts.seconds} \u548C ${opts.duration}\uFF09`);
|
|
1969
|
+
}
|
|
1970
|
+
const seconds = opts.seconds ?? opts.duration;
|
|
1960
1971
|
const body = { model, prompt: promptParts.join(" ") };
|
|
1961
1972
|
const metadata = {};
|
|
1962
|
-
if (
|
|
1963
|
-
const
|
|
1964
|
-
body.duration =
|
|
1973
|
+
if (seconds !== void 0) {
|
|
1974
|
+
const secondsValue = clampInt(seconds, 1, MAX_TASK_DURATION_SECONDS, "seconds");
|
|
1975
|
+
body.duration = secondsValue;
|
|
1965
1976
|
}
|
|
1966
1977
|
if (opts.size) body.size = opts.size;
|
|
1967
1978
|
if (opts.image) body.images = opts.image;
|
|
@@ -1982,7 +1993,7 @@ function registerGen(program) {
|
|
|
1982
1993
|
if (opts.safetyIdentifier) metadata.safety_identifier = opts.safetyIdentifier;
|
|
1983
1994
|
if (opts.content) metadata.content = parseJsonArray(opts.content, "content");
|
|
1984
1995
|
validateVideoGeneration(model, {
|
|
1985
|
-
seconds
|
|
1996
|
+
seconds,
|
|
1986
1997
|
resolution: opts.resolution,
|
|
1987
1998
|
ratio: opts.ratio,
|
|
1988
1999
|
aspectRatio: opts.aspectRatio,
|
|
@@ -2011,6 +2022,7 @@ function registerGen(program) {
|
|
|
2011
2022
|
throw new ApiError("bad_response", "\u89C6\u9891\u4EFB\u52A1\u54CD\u5E94\u4E2D\u672A\u627E\u5230 task_id", { body: created });
|
|
2012
2023
|
}
|
|
2013
2024
|
if (opts.wait === false) {
|
|
2025
|
+
info(`task_id=${taskId}`);
|
|
2014
2026
|
if (g.json) {
|
|
2015
2027
|
printJson({ model, task_id: taskId, submitted: true, next_command: `focalapi task status ${taskId} --json` });
|
|
2016
2028
|
} else {
|
|
@@ -2040,29 +2052,40 @@ function registerGen(program) {
|
|
|
2040
2052
|
}
|
|
2041
2053
|
|
|
2042
2054
|
// src/commands/task.ts
|
|
2055
|
+
var TASK_ID_HINT = "task_id \u533A\u5206\u5927\u5C0F\u5199\u4E14\u6DF7\u6709\u5C0F\u5199 l / \u6570\u5B57 1 / \u5927\u5199 I\uFF08O \u4E0E 0 \u540C\u7406\uFF09\uFF0C\u5FC5\u987B\u9010\u5B57\u590D\u5236\u3002\u82E5\u63D0\u4EA4\u65F6\u7684\u8F93\u51FA\u5DF2\u4E22\u5931\uFF0C\u5148\u56DE\u67E5\u5F53\u65F6\u7684 stderr \u9762\u5305\u5C51\uFF08task_id=...\uFF09\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u65B0\u63D0\u4EA4\u2014\u2014\u91CD\u590D\u63D0\u4EA4\u4F1A\u91CD\u590D\u6263\u8D39\u3002";
|
|
2056
|
+
function withTaskIdHint(err) {
|
|
2057
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
2058
|
+
return new ApiError(err.code, err.message, { status: err.status, hint: TASK_ID_HINT, body: err.body, upstreamCode: err.upstreamCode, requestId: err.requestId });
|
|
2059
|
+
}
|
|
2060
|
+
return err;
|
|
2061
|
+
}
|
|
2043
2062
|
function registerTask(program) {
|
|
2044
2063
|
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");
|
|
2045
2064
|
task.command("status").description("\u67E5\u8BE2\u4EFB\u52A1\u72B6\u6001").argument("<task_id>", "\u4EFB\u52A1 ID").action(async (taskId, _opts, cmd) => {
|
|
2046
2065
|
const g = cmd.optsWithGlobals();
|
|
2047
2066
|
const auth = resolveAuth(g);
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
[
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2067
|
+
try {
|
|
2068
|
+
const info_ = await fetchTask(auth.baseUrl, auth.apiKey, taskId);
|
|
2069
|
+
if (g.json) {
|
|
2070
|
+
printJson({ task_id: taskId, status: info_.status, raw_status: info_.rawStatus, progress: info_.progress, raw: info_.raw });
|
|
2071
|
+
} else {
|
|
2072
|
+
printTable(
|
|
2073
|
+
["\u5B57\u6BB5", "\u503C"],
|
|
2074
|
+
[
|
|
2075
|
+
["\u4EFB\u52A1 ID", taskId],
|
|
2076
|
+
["\u72B6\u6001", `${info_.status}${info_.rawStatus && info_.rawStatus !== info_.status ? `\uFF08\u4E0A\u6E38\uFF1A${info_.rawStatus}\uFF09` : ""}`],
|
|
2077
|
+
["\u8FDB\u5EA6", info_.progress !== void 0 ? `${info_.progress}%` : "-"]
|
|
2078
|
+
]
|
|
2079
|
+
);
|
|
2080
|
+
if (info_.status === "success") {
|
|
2081
|
+
info(`\u4EA7\u7269\u4E0B\u8F7D\uFF1Afocalapi task download ${taskId}`);
|
|
2082
|
+
}
|
|
2083
|
+
if (info_.status === "pending" || info_.status === "running") {
|
|
2084
|
+
info(`\u5982\u9700\u505C\u6B62\u6392\u961F\u4E2D\u7684\u4EFB\u52A1\uFF1Afocalapi task cancel ${taskId}`);
|
|
2085
|
+
}
|
|
2065
2086
|
}
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
throw withTaskIdHint(err);
|
|
2066
2089
|
}
|
|
2067
2090
|
});
|
|
2068
2091
|
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) => {
|
package/package.json
CHANGED
package/skills/focalapi/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ FocalAPI is a creative-model gateway for the current Agent to call. It is not th
|
|
|
17
17
|
1. If the user does not specify a model, omit `--model`. The CLI selects a FocalAPI default from the live model pool and detailed contracts available to the current key. Do not generate a test sample first.
|
|
18
18
|
2. If the user specifies a model or provider, run `focalapi models get <model-id> --json` first. If the model ID is incomplete, run `models search` once to find the exact ID, then read its details.
|
|
19
19
|
3. Use only parameters and values listed in the detailed `supported_params`. Never infer support from a similar model.
|
|
20
|
-
4. Add `--json` for Agent and script calls. stdout is the only machine-readable result; diagnostics go to stderr.
|
|
20
|
+
4. Add `--json` for Agent and script calls. stdout is the only machine-readable result; diagnostics go to stderr. Do not merge the two streams (`2>&1`) before parsing. Programmatic callers on Windows must invoke the CLI through a shell (`cmd /c focalapi ...` or `shell=True`) because npm installs a `.cmd` shim.
|
|
21
21
|
5. After successful generation, return local absolute file paths to the user. If a video response contains `task_id`, follow `next_command`; never submit the same task again.
|
|
22
22
|
|
|
23
23
|
```bash
|
|
@@ -50,6 +50,8 @@ focalapi task download <task-id> -o ./focalapi-out --json
|
|
|
50
50
|
|
|
51
51
|
`pending` and `running` are not failures. Keep checking the same `task_id` and never resubmit generation. On failure, read the structured `error.code` and `hint`, and fix only the explicit problem instead of rotating models blindly.
|
|
52
52
|
|
|
53
|
+
Never resubmit after an output-parsing failure on your side. If your own pipeline fails to parse a submission command's stdout JSON, the task was almost certainly created and charged: recover the `task_id` from the command's stderr breadcrumb line (`task_id=...`) and continue with `task status`. Resubmitting a request whose outcome is unknown duplicates the charge.
|
|
54
|
+
|
|
53
55
|
Two transient outcomes need no parameter changes:
|
|
54
56
|
|
|
55
57
|
- `capacity_exhausted` (HTTP 503): the platform queue is full. Retry the same command after roughly 10 seconds; do not switch models or shrink the request.
|
|
@@ -24,6 +24,8 @@ focalapi task cancel <task-id> --json
|
|
|
24
24
|
- `cancelled`: the task was stopped; a cancelled queued task is refunded. Do not download or resubmit unless the user asks for a new attempt.
|
|
25
25
|
- `unknown`: preserve the raw response and run `focalapi doctor --json`; never fabricate a success state.
|
|
26
26
|
|
|
27
|
+
Task IDs are case-sensitive and mix `l`/`1`/`I` and `O`/`0`. Copy them exactly from the submission output — stdout JSON or the stderr `task_id=` breadcrumb — instead of retyping.
|
|
28
|
+
|
|
27
29
|
`task cancel` only works while a task is still queued (`pending`). A 409 `task_already_running` means generation already started and cannot be stopped — keep tracking with `task status`. Cancellation failures return explicit codes (`task_already_finished`, `task_cancel_failed`); follow `error.hint` instead of retrying blindly.
|
|
28
30
|
|
|
29
31
|
Polling must be bounded. When the user does not ask for blocking wait behavior, report the current status and `task_id`.
|