@lingjingai/scriptctl 0.61.3 → 0.62.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/README.md +20 -0
- package/changes/0.61.4.md +13 -0
- package/changes/0.61.5.md +5 -0
- package/changes/0.62.0.md +13 -0
- package/changes/0.62.1.md +15 -0
- package/dist/bin.js +152 -23
- package/dist/bin.js.map +4 -4
- package/dist/cli/register-script.js +12 -0
- package/dist/cli/register-script.js.map +1 -1
- package/dist/cli/register-workflows.js +3 -2
- package/dist/cli/register-workflows.js.map +1 -1
- package/dist/infra/llm/doubao-video-model.js +2 -2
- package/dist/infra/llm/doubao-video-model.js.map +1 -1
- package/dist/infra/llm/gemini-video-model.js +2 -2
- package/dist/infra/llm/gemini-video-model.js.map +1 -1
- package/dist/infra/llm/openai-compatible-model.js +4 -4
- package/dist/infra/llm/openai-compatible-model.js.map +1 -1
- package/dist/llm/router.d.ts +2 -1
- package/dist/llm/router.js +71 -17
- package/dist/llm/router.js.map +1 -1
- package/dist/llm/types.d.ts +5 -0
- package/dist/llm/types.js.map +1 -1
- package/dist/review-agent.js +102 -13
- package/dist/review-agent.js.map +4 -4
- package/dist/usecases/ingest/command.js +10 -1
- package/dist/usecases/ingest/command.js.map +1 -1
- package/package.json +1 -1
- package/skills/scriptctl/SKILL.md +10 -1
package/README.md
CHANGED
|
@@ -117,3 +117,23 @@ pnpm publish # access=public,会自动跑 prepublishOnly
|
|
|
117
117
|
## License
|
|
118
118
|
|
|
119
119
|
MIT
|
|
120
|
+
|
|
121
|
+
### 指定视频理解服务
|
|
122
|
+
|
|
123
|
+
默认视频 ingest 沿用 Gemini 主流程,失败后由豆包兜底。显式选择服务时只调用所选服务和模型:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# 豆包:默认读取 ARK_API_KEY;模型也可通过 ARK_MODEL 设置
|
|
127
|
+
scriptctl ingest --source-path ./video.mp4 --video-provider doubao --provider-model ep-your-endpoint
|
|
128
|
+
|
|
129
|
+
# 使用自定义凭据来源和服务地址
|
|
130
|
+
scriptctl ingest --source-path ./video.mp4 --video-provider doubao \
|
|
131
|
+
--provider-model ep-your-endpoint --provider-api-key-env MY_ARK_API_KEY \
|
|
132
|
+
--provider-base-url https://ark.cn-beijing.volces.com/api/v3
|
|
133
|
+
|
|
134
|
+
# Gemini:模型省略时使用配置中的视频模型
|
|
135
|
+
scriptctl ingest --source-path ./video.mp4 --video-provider gemini
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`--provider-api-key` 与 `--provider-api-key-env` 互斥。豆包仅支持默认的 `--upload-mode file-api`;
|
|
139
|
+
直接选择豆包必须提供模型,且不需要 Gemini key。这些参数只影响视频理解阶段。
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# 0.61.5
|
|
2
|
+
|
|
3
|
+
## Fixed
|
|
4
|
+
- Review frame requests now describe even single timestamps as JSON arrays. Framework argument errors are returned to the agent as text so it can correct the request, instead of crashing while treating the error as image results.
|
|
5
|
+
- Valid frame images keep their existing model output format.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 0.62.0
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
- `scriptctl ingest --video-provider gemini|doubao` supports explicit video provider selection with existing model, API key, and base URL overrides. Direct Doubao requires `--provider-model` or `ARK_MODEL` and file-api upload mode. Explicit selection disables fallback; the default Gemini flow with Doubao fallback is unchanged.
|
|
5
|
+
|
|
6
|
+
## Changed
|
|
7
|
+
- None.
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
- None.
|
|
11
|
+
|
|
12
|
+
## Breaking
|
|
13
|
+
- None.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 0.62.1
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
- `scriptctl refs` and `sc refs` locate asset and appearance references with scene/action addresses, surrounding text, and pagination. Explicit scene bindings, speakers, and textual mentions are reported separately; mentions do not imply presence or a particular appearance.
|
|
5
|
+
- The screenplay SDK exports read-only `references` and `formatReferences`, shared by the CLI and review agents.
|
|
6
|
+
- The Mastra screenplay review agent exposes `refs` during both episode review and whole-series convergence, querying the current in-memory document after edits.
|
|
7
|
+
|
|
8
|
+
## Changed
|
|
9
|
+
- Review instructions and the packaged scriptctl skill document reference lookup and distinguish read-only `refs` from the binding mutation `ref`.
|
|
10
|
+
|
|
11
|
+
## Fixed
|
|
12
|
+
- None.
|
|
13
|
+
|
|
14
|
+
## Breaking
|
|
15
|
+
- None.
|
package/dist/bin.js
CHANGED
|
@@ -1648,7 +1648,7 @@ var GeminiVideoModel = class {
|
|
|
1648
1648
|
return file;
|
|
1649
1649
|
}
|
|
1650
1650
|
async generate(request2) {
|
|
1651
|
-
if (request2.media.kind === "local-fallback") {
|
|
1651
|
+
if (request2.media.kind === "local-fallback" || request2.media.kind === "local") {
|
|
1652
1652
|
throw new LlmTransportError("invalid_response", "A local fallback video cannot be sent to Gemini.");
|
|
1653
1653
|
}
|
|
1654
1654
|
const config = {
|
|
@@ -1761,7 +1761,7 @@ var GeminiVideoModel = class {
|
|
|
1761
1761
|
return { text: value.text, finishReason: value.finishReason, attempts, usage: addTokenUsage(failedUsage, value.usage), raw: value.raw };
|
|
1762
1762
|
}
|
|
1763
1763
|
async release(media, signal) {
|
|
1764
|
-
if (media.kind === "base64" || media.kind === "local-fallback") return;
|
|
1764
|
+
if (media.kind === "base64" || media.kind === "local-fallback" || media.kind === "local") return;
|
|
1765
1765
|
try {
|
|
1766
1766
|
await this.client.files.delete({ name: media.file.name });
|
|
1767
1767
|
} catch (error) {
|
|
@@ -1930,7 +1930,7 @@ var OpenAiCompatibleModel = class {
|
|
|
1930
1930
|
const choice = firstChoice(payload);
|
|
1931
1931
|
const reason = finishReason(choice);
|
|
1932
1932
|
if (reason === "length") throw new LlmTransportError("truncated", "Provider output was truncated.");
|
|
1933
|
-
if (reason === "content_filter") throw new LlmTransportError("content_filtered",
|
|
1933
|
+
if (reason === "content_filter" || reason.startsWith("content_filter:")) throw new LlmTransportError("content_filtered", `Provider filtered the response (${reason}).`);
|
|
1934
1934
|
const text2 = messageText(choice);
|
|
1935
1935
|
if (!text2) throw new LlmTransportError("empty_response", "Provider returned an empty text response.");
|
|
1936
1936
|
return { text: text2, finishReason: reason, attempts, usage, raw: payload };
|
|
@@ -1957,7 +1957,7 @@ var OpenAiCompatibleModel = class {
|
|
|
1957
1957
|
const choice = firstChoice(payload);
|
|
1958
1958
|
const reason = finishReason(choice);
|
|
1959
1959
|
if (reason === "length") throw new LlmTransportError("truncated", "Provider tool output was truncated.");
|
|
1960
|
-
if (reason === "content_filter") throw new LlmTransportError("content_filtered",
|
|
1960
|
+
if (reason === "content_filter" || reason.startsWith("content_filter:")) throw new LlmTransportError("content_filtered", `Provider filtered the response (${reason}).`);
|
|
1961
1961
|
const calls = message(choice)?.["tool_calls"];
|
|
1962
1962
|
if (!Array.isArray(calls)) throw new LlmTransportError("invalid_response", "Provider did not return a tool call.");
|
|
1963
1963
|
for (const call of calls) {
|
|
@@ -2065,6 +2065,11 @@ var ModelRouter = class {
|
|
|
2065
2065
|
const cached2 = this.resolvedRoutes.get(task);
|
|
2066
2066
|
if (cached2) return cached2;
|
|
2067
2067
|
const spec2 = LLM_TASK_REGISTRY[task];
|
|
2068
|
+
if (spec2.mode === "video" && this.options.videoProvider?.provider === "doubao") {
|
|
2069
|
+
const resolved2 = this.resolveDoubao(task, true);
|
|
2070
|
+
this.resolvedRoutes.set(task, resolved2);
|
|
2071
|
+
return resolved2;
|
|
2072
|
+
}
|
|
2068
2073
|
const profile = this.config.tasks[task] ?? spec2.defaultProfile;
|
|
2069
2074
|
const modelConfig = this.config.models[profile];
|
|
2070
2075
|
if (!modelConfig) throw configRuntimeError("The task route references a missing model profile.", [profile], [`task: ${task}`]);
|
|
@@ -2257,25 +2262,37 @@ var ModelRouter = class {
|
|
|
2257
2262
|
};
|
|
2258
2263
|
});
|
|
2259
2264
|
}
|
|
2260
|
-
|
|
2261
|
-
const
|
|
2265
|
+
resolveDoubao(task, explicit) {
|
|
2266
|
+
const overrides = explicit ? this.options.videoProvider : void 0;
|
|
2267
|
+
const modelName = explicit ? (overrides?.model ?? process.env["ARK_MODEL"] ?? "").trim() : this.doubaoModel();
|
|
2268
|
+
if (!modelName) {
|
|
2269
|
+
throw configRuntimeError("Doubao requires --provider-model or ARK_MODEL.", ["--provider-model <id> or ARK_MODEL"], ["model: empty"]);
|
|
2270
|
+
}
|
|
2271
|
+
const providerConfig = {
|
|
2262
2272
|
protocol: "openai-compatible",
|
|
2263
|
-
baseUrl: DOUBAO_FALLBACK.baseUrl,
|
|
2273
|
+
baseUrl: overrides?.baseUrl ? normalizeProviderBaseUrl(overrides.baseUrl, "--provider-base-url") : DOUBAO_FALLBACK.baseUrl,
|
|
2264
2274
|
apiKeyEnv: DOUBAO_FALLBACK.apiKeyEnv,
|
|
2265
2275
|
timeoutMs: DOUBAO_FALLBACK.timeoutMs,
|
|
2266
2276
|
maxAttempts: DOUBAO_FALLBACK.maxAttempts
|
|
2267
2277
|
};
|
|
2268
|
-
const
|
|
2278
|
+
const modelConfig = { provider: "doubao", model: modelName };
|
|
2269
2279
|
return {
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2280
|
+
info: {
|
|
2281
|
+
task,
|
|
2282
|
+
profile: explicit ? "doubao" : "doubao-fallback",
|
|
2283
|
+
provider: "doubao",
|
|
2284
|
+
protocol: providerConfig.protocol,
|
|
2285
|
+
baseUrl: providerConfig.baseUrl,
|
|
2286
|
+
model: modelName,
|
|
2287
|
+
fingerprint: routeFingerprint(task, providerConfig, modelConfig)
|
|
2288
|
+
},
|
|
2289
|
+
providerConfig,
|
|
2290
|
+
modelConfig
|
|
2277
2291
|
};
|
|
2278
2292
|
}
|
|
2293
|
+
doubaoRoute(task) {
|
|
2294
|
+
return this.resolveDoubao(task, false).info;
|
|
2295
|
+
}
|
|
2279
2296
|
adapter(route) {
|
|
2280
2297
|
const key = `${route.info.provider}\0${route.info.model}\0${route.modelConfig.thinkingBudgetTokens ?? ""}`;
|
|
2281
2298
|
const cached2 = this.adapters.get(key);
|
|
@@ -2317,6 +2334,38 @@ var ModelRouter = class {
|
|
|
2317
2334
|
const spec2 = LLM_TASK_REGISTRY[task];
|
|
2318
2335
|
if (spec2.mode !== "video") throw configRuntimeError("The task does not support video generation.", ["video task"], [`task: ${task}`]);
|
|
2319
2336
|
const route = this.resolve(task);
|
|
2337
|
+
if (this.options.videoProvider?.provider === "doubao") {
|
|
2338
|
+
const adapter = this.doubaoAdapter(route);
|
|
2339
|
+
if (!adapter) throw credentialError("doubao");
|
|
2340
|
+
return {
|
|
2341
|
+
prepare: async (localPath, mimeType, mode, signal) => {
|
|
2342
|
+
if (mode !== "file-api") {
|
|
2343
|
+
throw configRuntimeError("Doubao requires file-api upload mode.", ["file-api"], [mode]);
|
|
2344
|
+
}
|
|
2345
|
+
signal?.throwIfAborted();
|
|
2346
|
+
return { kind: "local", path: localPath, mimeType };
|
|
2347
|
+
},
|
|
2348
|
+
generate: (request2) => {
|
|
2349
|
+
if (request2.media.kind !== "local") {
|
|
2350
|
+
throw configRuntimeError("Doubao requires locally prepared video.", ["local media"], [request2.media.kind]);
|
|
2351
|
+
}
|
|
2352
|
+
request2.signal?.throwIfAborted();
|
|
2353
|
+
const { path: localPath, mimeType: localMime } = request2.media;
|
|
2354
|
+
return this.tracked(route.info, () => adapter.run({
|
|
2355
|
+
localPath,
|
|
2356
|
+
localMime,
|
|
2357
|
+
user: request2.user,
|
|
2358
|
+
fps: request2.fps,
|
|
2359
|
+
maxOutputTokens: spec2.maxOutputTokens,
|
|
2360
|
+
...request2.system ? { system: request2.system } : {},
|
|
2361
|
+
...request2.responseJsonSchema ? { responseJsonSchema: request2.responseJsonSchema } : {},
|
|
2362
|
+
...request2.signal ? { signal: request2.signal } : {}
|
|
2363
|
+
}));
|
|
2364
|
+
},
|
|
2365
|
+
release: async () => {
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2320
2369
|
if (route.providerConfig.protocol !== "google-gemini") {
|
|
2321
2370
|
throw configRuntimeError(
|
|
2322
2371
|
"Video tasks require a google-gemini provider.",
|
|
@@ -2405,16 +2454,17 @@ var ModelRouter = class {
|
|
|
2405
2454
|
}
|
|
2406
2455
|
};
|
|
2407
2456
|
}
|
|
2408
|
-
//
|
|
2457
|
+
// Resolve an explicit Doubao route or the default fallback; absent fallback credentials skip it.
|
|
2409
2458
|
// Overridable in tests to inject a fake Ark client.
|
|
2410
|
-
doubaoAdapter() {
|
|
2411
|
-
const apiKey = (process.env[DOUBAO_FALLBACK.apiKeyEnv] ?? "").trim();
|
|
2459
|
+
doubaoAdapter(route) {
|
|
2460
|
+
const apiKey = route ? this.videoCredential(route) : (process.env[DOUBAO_FALLBACK.apiKeyEnv] ?? "").trim();
|
|
2412
2461
|
if (!apiKey) return null;
|
|
2413
|
-
const model = this.doubaoModel();
|
|
2414
|
-
const
|
|
2462
|
+
const model = route?.info.model ?? this.doubaoModel();
|
|
2463
|
+
const baseUrl = route?.providerConfig.baseUrl ?? DOUBAO_FALLBACK.baseUrl;
|
|
2464
|
+
const key = `doubao ${baseUrl} ${model}`;
|
|
2415
2465
|
const cached2 = this.doubaoAdapters.get(key);
|
|
2416
2466
|
if (cached2) return cached2;
|
|
2417
|
-
const adapter = new DoubaoVideoModel(model, DOUBAO_FALLBACK.timeoutMs, DOUBAO_FALLBACK.maxAttempts, apiKey,
|
|
2467
|
+
const adapter = new DoubaoVideoModel(model, DOUBAO_FALLBACK.timeoutMs, DOUBAO_FALLBACK.maxAttempts, apiKey, baseUrl);
|
|
2418
2468
|
this.doubaoAdapters.set(key, adapter);
|
|
2419
2469
|
return adapter;
|
|
2420
2470
|
}
|
|
@@ -4502,6 +4552,67 @@ function spanAddresses(doc2, actions) {
|
|
|
4502
4552
|
});
|
|
4503
4553
|
}
|
|
4504
4554
|
|
|
4555
|
+
// packages/screenplay/dist/references.js
|
|
4556
|
+
function references(doc2, address, opts = {}) {
|
|
4557
|
+
const offset = opts.offset ?? 0, limit = opts.limit ?? 20, context = opts.context ?? 3;
|
|
4558
|
+
for (const [name, value, minimum] of [["offset", offset, 0], ["limit", limit, 1], ["context", context, 0]]) {
|
|
4559
|
+
if (!Number.isSafeInteger(value) || value < minimum)
|
|
4560
|
+
throw new Error(`${name} must be an integer >= ${minimum}`);
|
|
4561
|
+
}
|
|
4562
|
+
const node = resolve6(doc2, address);
|
|
4563
|
+
if (node.t !== "asset" && node.t !== "state")
|
|
4564
|
+
throw new Error("references requires an asset or asset/state address; use read for episodes/scenes/actions");
|
|
4565
|
+
const asset2 = node.asset;
|
|
4566
|
+
const state2 = node.t === "state" ? node.state.id : null;
|
|
4567
|
+
const names = [.../* @__PURE__ */ new Set([asset2.id, asset2.name, ...asset2.aliases])].filter(Boolean);
|
|
4568
|
+
const hits = [];
|
|
4569
|
+
for (const ep of doc2.episodes)
|
|
4570
|
+
for (const scene of ep.scenes) {
|
|
4571
|
+
const bindings = [...scene.location ? [scene.location] : [], ...scene.actors, ...scene.props].filter((ref2) => ref2.id === asset2.id && (state2 === null || ref2.states.includes(state2)));
|
|
4572
|
+
if (bindings.length)
|
|
4573
|
+
hits.push({ kind: "scene_binding", scene, matched: bindings.flatMap((ref2) => ref2.states) });
|
|
4574
|
+
for (const action of scene.actions) {
|
|
4575
|
+
if (action.speaker?.id === asset2.id && (state2 === null || action.speaker.state === state2)) {
|
|
4576
|
+
hits.push({ kind: "speaker", scene, action, matched: [asset2.id] });
|
|
4577
|
+
}
|
|
4578
|
+
if (state2 === null) {
|
|
4579
|
+
const matched = names.filter((name) => action.content.includes(name));
|
|
4580
|
+
if (matched.length)
|
|
4581
|
+
hits.push({ kind: "text_mention", scene, action, matched });
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
const end = offset + limit;
|
|
4586
|
+
return {
|
|
4587
|
+
asset_id: asset2.id,
|
|
4588
|
+
state_id: state2,
|
|
4589
|
+
total: hits.length,
|
|
4590
|
+
next_offset: end < hits.length ? end : null,
|
|
4591
|
+
items: hits.slice(offset, end).map(({ kind, scene, action, matched }) => {
|
|
4592
|
+
const index = action ? scene.actions.indexOf(action) : -1;
|
|
4593
|
+
const nearby = action ? scene.actions.slice(Math.max(0, index - context), index + context + 1) : [];
|
|
4594
|
+
const gutter = Math.max(0, ...nearby.map((row) => row.anchor.length));
|
|
4595
|
+
return {
|
|
4596
|
+
kind,
|
|
4597
|
+
address: action?.anchor ?? scene.anchor,
|
|
4598
|
+
scene_address: scene.anchor,
|
|
4599
|
+
episode: scene.ep.no,
|
|
4600
|
+
matched,
|
|
4601
|
+
text: [formatSceneHead(doc2, scene), ...nearby.map((row) => (row === action ? "> " : " ") + formatAction(doc2, row, gutter))].join("\n")
|
|
4602
|
+
};
|
|
4603
|
+
})
|
|
4604
|
+
};
|
|
4605
|
+
}
|
|
4606
|
+
function formatReferences(result) {
|
|
4607
|
+
return [
|
|
4608
|
+
`${result.asset_id}${result.state_id === null ? "" : "/" + result.state_id} ${result.total} references \xB7 showing ${result.items.length}`,
|
|
4609
|
+
"scene_binding=explicit scene presence; speaker=explicit speaker; text_mention=name/alias mention only (not proof of presence).",
|
|
4610
|
+
...result.items.map((hit) => `${hit.address} ${hit.kind} ep=${hit.episode}
|
|
4611
|
+
${hit.text}`),
|
|
4612
|
+
result.next_offset === null ? "end" : `Continue: --offset ${result.next_offset}`
|
|
4613
|
+
].join("\n\n");
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4505
4616
|
// packages/screenplay/dist/tools.js
|
|
4506
4617
|
var NON_ACTOR_NAMES = ["\u65C1\u767D", "\u5E7F\u64AD", "\u753B\u5916", "\u7FA4\u58F0"];
|
|
4507
4618
|
var NON_ACTOR_KIND = {
|
|
@@ -5629,6 +5740,15 @@ ${issues(d, {})}`
|
|
|
5629
5740
|
...opts["full"] ? { full: true } : {}
|
|
5630
5741
|
}), "find"), state2));
|
|
5631
5742
|
describeArguments(find2, ["\u6B63\u5219\uFF1B\u53EA\u6309 speaker/type \u8FC7\u6EE4\u65F6\u53EF\u7701\u7565"]);
|
|
5743
|
+
const refs = withScriptOpts(program.command("refs <addr>"), "read").description("\u53EA\u8BFB\u5B9A\u4F4D\u8D44\u4EA7/\u9020\u578B\u7684\u573A\u666F\u7ED1\u5B9A\u3001\u8BF4\u8BDD\u5F15\u7528\u548C\u6B63\u6587\u63D0\u53CA\uFF0C\u8FD4\u56DE\u53EF read \u7684\u5730\u5740\u53CA\u4E0A\u4E0B\u6587").option("--offset <n>", "\u4ECE\u7B2C\u51E0\u6761\u5F15\u7528\u7EE7\u7EED\uFF08\u4ECE 0 \u5F00\u59CB\uFF09").option("--limit <n>", "\u672C\u6279\u5F15\u7528\u6570\uFF08\u9ED8\u8BA4 20\uFF09").option("--context <n>", "\u6B63\u6587\u547D\u4E2D\u524D\u540E\u5404\u51E0\u6761 action\uFF08\u9ED8\u8BA4 3\uFF09").action(dispatch((opts) => {
|
|
5744
|
+
const result = references(doc(opts), arg(opts), {
|
|
5745
|
+
offset: num(opts, "offset"),
|
|
5746
|
+
limit: num(opts, "limit"),
|
|
5747
|
+
context: num(opts, "context")
|
|
5748
|
+
});
|
|
5749
|
+
return [{ text: formatReferences(result), data: { op: "refs", changed: false, ...result } }, EXIT_OK];
|
|
5750
|
+
}, state2));
|
|
5751
|
+
describeArguments(refs, ["\u4EBA\u7269/\u573A\u666F/\u9053\u5177 ID \u6216\u540D\u79F0\uFF0C\u6216 <\u8D44\u4EA7>/<\u9020\u578B>\uFF1B\u6B63\u6587\u63D0\u53CA\u4E0D\u7B49\u4E8E\u5B9E\u9645\u51FA\u573A"]);
|
|
5632
5752
|
const span2 = withScriptOpts(program.command("span <range>"), "read").description("\u628A\u4E00\u6BB5 action \u6362\u7B97\u6210\u96C6\u5185\u89C6\u9891\u65F6\u95F4\u7A97\u53E3\uFF08\u534A\u5F00\u533A\u95F4\uFF09").action(dispatch((opts) => text(() => span(doc(opts), arg(opts)), "span"), state2));
|
|
5633
5753
|
describeArguments(span2, [`${ADDR}\uFF0C\u6216 \`\u8D77ID..\u6B62ID\``]);
|
|
5634
5754
|
withScriptOpts(program.command("validate"), "read").description("\u8DD1\u6574\u672C\u6821\u9A8C").action(dispatch((opts) => text(() => validate(doc(opts)), "validate"), state2));
|
|
@@ -12097,6 +12217,7 @@ var intOf = (v, fallback) => {
|
|
|
12097
12217
|
return Number.isInteger(n) && n > 0 ? n : fallback;
|
|
12098
12218
|
};
|
|
12099
12219
|
var VIDEO_OPTION_KEYS = [
|
|
12220
|
+
"video_provider",
|
|
12100
12221
|
"upload_mode",
|
|
12101
12222
|
"provider_base_url",
|
|
12102
12223
|
"provider_api_key",
|
|
@@ -12129,6 +12250,10 @@ function uploadModeOf(opts) {
|
|
|
12129
12250
|
return mode;
|
|
12130
12251
|
}
|
|
12131
12252
|
function providerOverridesOf(opts) {
|
|
12253
|
+
const provider = requiredOptionValue(opts, "video_provider", "--video-provider");
|
|
12254
|
+
if (provider !== void 0 && provider !== "gemini" && provider !== "doubao") {
|
|
12255
|
+
throw invalidVideoOption("Video provider must be gemini or doubao.", ["--video-provider gemini|doubao"]);
|
|
12256
|
+
}
|
|
12132
12257
|
const literalKeyProvided = optionProvided(opts, "provider_api_key");
|
|
12133
12258
|
const keyEnvProvided = optionProvided(opts, "provider_api_key_env");
|
|
12134
12259
|
if (literalKeyProvided && keyEnvProvided) {
|
|
@@ -12147,8 +12272,9 @@ function providerOverridesOf(opts) {
|
|
|
12147
12272
|
let apiKey;
|
|
12148
12273
|
if (literalKey !== void 0) apiKey = { kind: "literal", value: literalKey };
|
|
12149
12274
|
else if (keyEnv !== void 0) apiKey = { kind: "env", name: keyEnv };
|
|
12150
|
-
if (!baseUrl && !apiKey && !model) return void 0;
|
|
12275
|
+
if (!provider && !baseUrl && !apiKey && !model) return void 0;
|
|
12151
12276
|
return {
|
|
12277
|
+
...provider ? { provider } : {},
|
|
12152
12278
|
...baseUrl ? { baseUrl } : {},
|
|
12153
12279
|
...apiKey ? { apiKey } : {},
|
|
12154
12280
|
...model ? { model } : {}
|
|
@@ -12176,6 +12302,9 @@ async function commandIngest(opts, composition = PRODUCTION_LLM_COMPOSITION) {
|
|
|
12176
12302
|
if (resolved.kind !== "text") {
|
|
12177
12303
|
const uploadMode = uploadModeOf(opts);
|
|
12178
12304
|
const videoProvider = providerOverridesOf(opts);
|
|
12305
|
+
if (videoProvider?.provider === "doubao" && uploadMode !== "file-api") {
|
|
12306
|
+
throw invalidVideoOption("Doubao requires --upload-mode file-api.", ["--upload-mode file-api"]);
|
|
12307
|
+
}
|
|
12179
12308
|
const executorOptions = videoProvider ? { videoProvider } : void 0;
|
|
12180
12309
|
const executor2 = composition.createExecutor(strOf3(opts["llm_config"]).trim() || void 0, executorOptions);
|
|
12181
12310
|
const configuredRoute = executor2.route("ingest.video-serial");
|
|
@@ -15962,7 +16091,7 @@ function registerWorkflowCommands(program, state2, llm2) {
|
|
|
15962
16091
|
"after",
|
|
15963
16092
|
"\nExample:\n scriptctl migrate -p ./old-script.json -o ./script-v4.json --to 4\n"
|
|
15964
16093
|
).action(dispatch(commandMigrate, state2));
|
|
15965
|
-
const ingest = program.command("ingest").description("Convert source files into a Script v4 workspace").option("-s, --source-path <path>", "Source file or directory").option("-w, --workspace-path <path>", "Workspace directory", "workspace").option("--llm-config <path>", "LLM configuration file").option("--concurrency <n>", "Maximum concurrent model calls").option("--fps <n>", "Video sampling rate").option("--upload-mode <mode>", "Video transport: file-api (default) or base64").option("--provider-base-url <url>", "
|
|
16094
|
+
const ingest = program.command("ingest").description("Convert source files into a Script v4 workspace").option("-s, --source-path <path>", "Source file or directory").option("-w, --workspace-path <path>", "Workspace directory", "workspace").option("--llm-config <path>", "LLM configuration file").option("--concurrency <n>", "Maximum concurrent model calls").option("--fps <n>", "Video sampling rate").option("--upload-mode <mode>", "Video transport: file-api (default) or base64").option("--video-provider <provider>", "Video provider: gemini or doubao (explicit selection disables fallback)").option("--provider-base-url <url>", "Selected video provider service root").option("--provider-api-key <key>", "Provider API key (visible in process arguments; prefer --provider-api-key-env)").option("--provider-api-key-env <name>", "Environment variable containing the provider API key").option("--provider-model <model>", "Selected video provider model (Doubao defaults to ARK_MODEL)").option("--step", "Process only the next unfinished video episode").option("--force", "Rebuild completed stages").addHelpText("after", "\nExample:\n scriptctl ingest --source-path ./draft.md --workspace-path ./workspace\n").action(dispatch((options) => commandIngest(options, llm2), state2));
|
|
15966
16095
|
ingest.command("status").description("Show ingest workspace progress").option("-w, --workspace-path <path>", "Workspace directory").action(dispatch(commandIngestStatus, state2));
|
|
15967
16096
|
ingest.command("skip").description("Manually accept the next failed video episode as empty").option("-w, --workspace-path <path>", "Workspace directory").requiredOption("--episode <n>", "Failed episode number").requiredOption("--reason <text>", "Why this episode is being skipped").addHelpText("after", '\nExample:\n scriptctl ingest skip -w ./workspace --episode 7 --reason "provider policy block"\n').action(dispatch(commandSkipVideoEpisode, state2));
|
|
15968
16097
|
program.command("evidence").description("Generate local original-video reference frames from final Script v4 timestamps").option("-p, --script-path <path>", "Final Script v4 JSON file").requiredOption("-s, --source-path <path>", "Linked source-video file or episode directory").requiredOption("-w, --workspace-path <path>", "Project workspace directory").option("--force", "Re-extract frames even when deterministic output files already exist").addHelpText("after", "\nExample:\n scriptctl evidence -p ./script.json -s /Volumes/NAS/videos -w ./workspace --force\n").action(dispatch(commandGenerateVideoStateEvidence, state2));
|