@lingjingai/scriptctl 0.73.8 → 0.74.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.
package/README.md CHANGED
@@ -54,7 +54,7 @@ Script v4 的新节点使用类型前缀加 6 位 Bitcoin Base58 的 NanoID。
54
54
  | 仅 `-p script.json` | 内部结构、引用、人物与状态的一致性 | `review-agent` |
55
55
  | `-s` 文本 ingest 工作区或对应 txt/md/docx | 原文行窗口、遗漏、说话人、心声类型和语义忠实度 | `review-text` |
56
56
  | `-s` 漫画 ingest 工作区或对应 PDF | 原页、画格、气泡与正文对照 | `review-comic` |
57
- | `-s` 视频/分集目录或完整 `--video` 列表 | 原声、画面、ASR 与时间轴 | `review-agent` |
57
+ | `-s` 视频/分集目录或完整 `--video` 列表 | 原声、画面与时间轴 | `review-agent` |
58
58
 
59
59
  纯剧本使用独立 prompt 和工具,不要求时间戳,不生成缺失剧情;遇到无法判定哪一版正确的设定矛盾时保留原文并报告未解决项。需要事实依据的警告不靠猜测补齐。
60
60
 
@@ -68,29 +68,23 @@ Script v4 的新节点使用类型前缀加 6 位 Bitcoin Base58 的 NanoID。
68
68
 
69
69
  既有 v4 产物通过 `v4_identity_map.json` 和 `pass1/body_start.json` 恢复来源,按稳定 ID 对应,不按当前行序或正文相似度猜测。保留这些文件以及 `manifest.json`、`pass1/source.txt`。缺失来源或摘要不符时停止,不转为纯剧本审核。
70
70
 
71
- `.review/text/episode-N.json` 与 `global.json` 保存 findings、用量和已读/未读行窗口;读取记录不代表内容正确。未读完本集原文会留下 unresolved。文本审核使用 `--episodes`、`--converge-only`、`--max-steps` 和 `--episode-timeout-ms`,不执行 ASR 或视频校时。
71
+ `.review/text/episode-N.json` 与 `global.json` 保存 findings、用量和已读/未读行窗口;读取记录不代表内容正确。未读完本集原文会留下 unresolved。文本审核使用 `--episodes`、`--converge-only`、`--max-steps` 和 `--episode-timeout-ms`。
72
72
 
73
- ## 原视频审核的全剧 ASR 校时与补漏
73
+ ## 原视频审核
74
74
 
75
- `scriptctl review -p <script.json> -s <视频目录>`(或按集排序的完整 `--video` 列表)逐集执行「豆包 Flash ASR → Gemini 对比 → 校时落盘 → Mastra 审核 → 落盘」。`--concurrency` 控制同时进行的整集流程,默认 **5 集并发**。需要 `ffmpeg` 和 `SD_AUDIO_APIKEY`;ASR 协议与 Redraw `subtitle_realign/asr.py` 一致,使用每集完整音轨,以本集 00:00 为原点。
75
+ `scriptctl review -p <script.json> -s <视频目录>`(或按集排序的完整 `--video` 列表)逐集执行「Mastra 审核 → 落盘」。`--concurrency` 控制同时审核的集数,默认 **5 集并发**。需要 `ffmpeg`。
76
76
 
77
- Gemini 接收左侧带时间戳、行锚点的亚洲格式剧本正文,以及有句/词编号、起止时间的 ASR 文本,不接收剧本 JSON。按内容与上下文做多对多对应,容许 ASR 同音字、专名、标点及合理拆句差异。
78
-
79
- - **时间不齐或缺时间**:Gemini 为每条可明确对应的台词选择 ASR 句/词起点。程序取该编号的真实毫秒时间,经 screenplay SDK 批量更新本集时间戳,保留原始文字、说话人、类型及其他字段。本集校时落盘后立即启动本集 Agent,不等待其他集;纯画面动作不按 ASR 猜时间,只有后半句证据时保留整条起点。
80
- - **漏台词**:记录 ASR 原话、时间范围、邻近位置和理由,不直接插入 ASR 文本。随后对应集 Agent 收到清单和切好的看片窗口,逐项 `watch` 核对原片后修复并记录 finding。每处线索按窗口数增加看片预算,长句拆成不超过 15 秒的窗口。
81
- - **无法可靠对应**:保留原样,把具体说明交给 Agent。服务请求失败、无效响应或非法修正目标记为本集失败,继续后续集;失败集不应用对比方案、不启动本集 Agent。已完成集的修改保留,失败清单在 `episode` 事件及最终统计中体现。
82
-
83
- 剧本旁 `.review/asr/episode-N/` 保存按音频 SHA-256 复用的成功 ASR 响应,以及每次对比的 `script.txt`、`asr.txt`、`prompt.txt`、逐模型逐次响应和 `report.json`(含前后时间、台词、缺漏线索)。当前剧本每次重新对比,不缓存对比结论。
77
+ 审核不做语音识别,也不自动校时:时间戳保留 ingest 的精度,Agent 在看片预算内用 `watch`、`frames` 按疑点核对原片,只修时间轴校验报出的问题和有原片证据的错位。默认每集看片预算为 1 次,时间轴问题按看片窗口补充,`--media-budget` 可调。单集失败不影响其他集,已完成集的修改保留,失败清单在 `episode` 事件及最终统计中体现。
84
78
 
85
79
  视频和纯剧本审核使用 `review-agent`,文本和漫画使用 `review-text`、`review-comic`;主 Agent 每次模型请求按 MSUICODE Vertex → GCP → T3 → 兔子(`TUZI_API_KEY`,`https://api.tu-zi.com`)→ 豆包 Seed 2.1 Lite(`ARK_API_KEY`)→ Turbo(`DOUBAO_TURBO_API_KEY`)的顺序使用,Gemini 渠道均为 `gemini-3.8-flash`。NNYYAI 不参与 Agent 工具调用:它会把并行函数调用拆成两段,并丢失工具循环的函数结果历史。Grok 也不在默认链中:长审核历史下它只回复计划而不调用工具。MSUICODE 地址为 `http://15.204.107.123:3000`,三个来源分别读取 `MSUICODE_VERTEX_API_KEY`、`MSUICODE_GCP_API_KEY`、`MSUICODE_T3_API_KEY`。主渠道密钥必需,额外渠道仅在配置对应密钥时参与。普通服务错误按顺序切换;Gemini 返回明确的内容拦截时,当前这一步跳过剩余 Gemini 渠道交给豆包,下一步重新从 Vertex 开始。各模型沿用 provider 的 `maxAttempts`,内置最多三次。模型切换发生在当前调用内,保留成功的工具操作;用量记录实际渠道和模型,Gemini 思考签名只在生成它的渠道内复用。逐集与全剧审核都要求成功调用 validate 后才结束;模型只回复计划而没有收口时会被要求继续,全部渠道都失败时直接结束。`--no-fallback` 或 `SCRIPTCTL_REVIEW_SINGLE_MODEL=1` 仅使用选定模型。
86
80
 
87
- 独立的 ASR JSON 比对和原片视频核验使用 `review-media`:NNYYAI Gemini 3.8 → 3.7 → 3.6 Flash → 豆包 Seed 2.1 Lite → Turbo,凭据分别为 `NNYYAI_API_KEY`、`ARK_API_KEY`、`DOUBAO_TURBO_API_KEY`;`DOUBAO_LITE_MODEL`、`DOUBAO_TURBO_MODEL` 可覆盖对应豆包端点。显式 `--model` 或自定义配置仍决定媒体主渠道。豆包 Flash ASR 继续读取 `SD_AUDIO_APIKEY`。
81
+ 原片视频核验使用 `review-media`:NNYYAI Gemini 3.8 → 3.7 → 3.6 Flash → 豆包 Seed 2.1 Lite → Turbo,凭据分别为 `NNYYAI_API_KEY`、`ARK_API_KEY`、`DOUBAO_TURBO_API_KEY`;`DOUBAO_LITE_MODEL`、`DOUBAO_TURBO_MODEL` 可覆盖对应豆包端点。显式 `--model` 或自定义配置仍决定媒体主渠道。
88
82
 
89
83
  全剧收敛审核必须成功执行 `validate` 才能正常结束;未完成时在同一会话内继续,保留已有工具操作,并共用原步数和总时间预算。整集默认 15 分钟总时限,耗尽后结束本轮审核。
90
84
 
91
- `asr-started` / `asr-transcribed` / `asr-compared` NDJSON 按集报告进度与证据;`asr-compared` 不代表修改已落盘,带 `episode_number` 的 `asr-applied` 表示本集校时已经保存。之后的 `episode` 报告本集完成或失败,已有修改已保存;`done.statistics.fixed` 包含自动校时条数,`usage_total` 汇总对比与审核 Gemini 用量。
85
+ `episode` NDJSON 报告本集完成或失败,已有修改已保存;`usage_total` 汇总审核用量。
92
86
 
93
- `--episodes` 只对所选集执行 ASR、校时和审核,适合失败集重试;`--converge-only` 只做全剧收敛。纯剧本和原文对照审核不执行 ASR;`--check` 返回所选 `review_kind`,只检查配置与 bundle,不发 ASR/Gemini 请求。
87
+ `--episodes` 只审核所选集,适合失败集重试;`--converge-only` 只做全剧收敛。`--check` 返回所选 `review_kind`,只检查配置与 bundle,不发模型请求。
94
88
 
95
89
  ## 原漫画审核
96
90
 
@@ -100,7 +94,7 @@ Gemini 接收左侧带时间戳、行锚点的亚洲格式剧本正文,以及
100
94
 
101
95
  漫画 Agent 逐页看原稿并反向核对 action,使用 `source` 定位、`pages` 看完整原页、`region` 看清晰局部,核对遗漏、重复、文字、人物、心声类型及阅读顺序。需要 Poppler(`pdftoppm`);局部图按显示页的 CropBox/旋转归一化坐标渲染。正文修改保留来源;插入遗漏内容必须同时提供 `extend.scriptctl.source` 页码及区域。跨页内容使用同一条 action 的多个来源区域,不添加时间戳。
102
96
 
103
- `.review/comic/episode-N.json` 保存逐话 findings、用量和原页加载记录,`global.json` 保存最后的全剧收敛结果。`viewed_pages` 仅表示整页图片已加载,不代表内容正确或小字已看清;未加载完全部页时报告未解决项。图片证据保存在剧本旁的 `review-pages/`。漫画不执行 ASR、校时或视频媒体预算;`--max-steps` 和 `--episode-timeout-ms` 限制审核运行。逐话结果在剧本与报告落盘后才发出,未提供 `-s` 仍是纯剧本审核。
97
+ `.review/comic/episode-N.json` 保存逐话 findings、用量和原页加载记录,`global.json` 保存最后的全剧收敛结果。`viewed_pages` 仅表示整页图片已加载,不代表内容正确或小字已看清;未加载完全部页时报告未解决项。图片证据保存在剧本旁的 `review-pages/`。漫画没有校时和视频媒体预算;`--max-steps` 和 `--episode-timeout-ms` 限制审核运行。逐话结果在剧本与报告落盘后才发出,未提供 `-s` 仍是纯剧本审核。
104
98
 
105
99
  ## 审核失败单元的类型
106
100
 
@@ -108,12 +102,8 @@ Gemini 接收左侧带时间戳、行锚点的亚洲格式剧本正文,以及
108
102
 
109
103
  | `failure_kind` | `retry` | 含义 |
110
104
  |---|---|---|
111
- | `dialogue_cross_language` | `ours` | 剧本台词与原声语言不同,且按原声自动改写失败(对应不到一半台词) |
112
- | `dialogue_mismatch` | `caller` | 台词与原声差异超过 50%,多为视频与集对应错误 |
113
- | `asr_config` | `caller` | 缺少 `SD_AUDIO_APIKEY` |
114
105
  | `video_missing` | `caller` | 该集没有传视频 |
115
106
  | `credentials` | `caller` | 模型渠道拒绝凭据(401/403) |
116
- | `asr_failed` | `retry` | ASR 或比对模型暂时不可用 |
117
107
  | `timeout` | `retry` | 单元超时 |
118
108
  | `model_unavailable` | `retry` | 所有模型渠道都失败 |
119
109
  | `timeline_unresolved` | `retry_once` | 审核后时间轴仍有冲突 |
@@ -141,7 +131,7 @@ Gemini 接收左侧带时间戳、行锚点的亚洲格式剧本正文,以及
141
131
  | `PROJECT_LITELLM_GATEWAY_BASE_URL` | 内置配置的 gateway endpoint;OpenAI-compatible synopsis 必须配置 |
142
132
  | `SCRIPTCTL_LLM_CONFIG` | LLM 路由 YAML;优先级低于 `--llm-config` |
143
133
  | `NNYYAI_CI_API_KEY` | 内置视频 ingest 首选 NNYYAI 次卡 `gemini-3.8-flash-anci` 的密钥 |
144
- | `NNYYAI_API_KEY` | 视频 ingest 普通 NNYYAI 后备渠道,以及漫画拉片、ASR 比对与独立媒体核验的密钥 |
134
+ | `NNYYAI_API_KEY` | 视频 ingest 普通 NNYYAI 后备渠道,以及漫画拉片与独立媒体核验的密钥 |
145
135
  | `TUZI_API_KEY` | 审核 Agent 的兔子 Gemini 渠道密钥 |
146
136
  | `MSUICODE_VERTEX_API_KEY` | 默认审核 Agent 的 MSUICODE Vertex 主渠道密钥 |
147
137
  | `MSUICODE_GCP_API_KEY` / `MSUICODE_T3_API_KEY` | 审核 Agent 的 MSUICODE GCP / T3 后续渠道密钥 |
@@ -265,7 +255,3 @@ scriptctl ingest --source-path ./video.mp4 --video-provider gemini
265
255
  ### Official Gemini via Vertex AI
266
256
 
267
257
  The built-in official Gemini provider uses `google-vertex` with Google Application Default Credentials (ADC). Set `GOOGLE_CLOUD_PROJECT` and configure ADC on each execution machine; tokens refresh automatically. Video requests send inline media; text requests use the Vertex OpenAI-compatible API. A custom provider can declare `protocol: google-vertex`, `timeoutMs` and `maxAttempts` without an `apiKey` field. To use a Vertex API key, add `apiKey: { env: GOOGLE_API_KEY }` to that provider and set the named environment variable. Select the provider through the task profile in your LLM configuration. The project reads `VERTEX_PROJECT_ID`, falling back to `GOOGLE_CLOUD_PROJECT`; the location reads `VERTEX_LOCATION`, then `GOOGLE_CLOUD_LOCATION`, defaulting to `global`. Custom Gemini gateways retain the `google-gemini` protocol and their own credentials.
268
-
269
- ### 台词与原声门禁
270
-
271
- 有原视频的审核先比对整集剧本台词与 ASR 文字。剧本与 ASR 文字体系不同(例如英文原声配中文字幕被当成台词)时,以原声为准自动改写:模型只把每条台词对应到原声的 ASR 起止编号,台词原文全部取自 ASR(只补标点与大小写),说话人和类型不变,时间戳校准到原声起音,每条改写记一条 `fixed` 结论,NDJSON 发出 `asr-rewritten` 事件,之后照常审核。改写不到一半台词或改写后仍与原声明显不符时,本集失败(`dialogue_cross_language`)。同一种语言但字符差异超过 50%(视频绑错或台词凭空编造)时本集失败(`dialogue_mismatch`);10%–50% 的差异照常审核,并作为待核对线索交给 Agent。被拦的集不影响其余集的全剧收敛。仅在已核实并授权的任务中设置 `SCRIPTCTL_ALLOW_DIALOGUE_MISMATCH=1`,放行被拦的集且不做自动改写,仍写入原始差异报告。
@@ -0,0 +1,14 @@
1
+ # 0.73.9
2
+
3
+ ## Added
4
+ - None.
5
+
6
+ ## Changed
7
+ - None.
8
+
9
+ ## Fixed
10
+ - 换壳新剧的场景、道具造型名由方案写:只有一套样子的,造型就是资产的新外观,方案没给新名字时叫“默认”;有几套样子的,方案为每一套写新名字和样子。原剧的造型名不再留在新剧里。
11
+ - 换壳新剧的资产别名只用方案登记的,沿用原名的人物也不带原剧的别名(如“红安全帽工头”不再带原剧的“张队”)。
12
+
13
+ ## Breaking
14
+ - None.
@@ -0,0 +1,13 @@
1
+ # 0.74.0
2
+
3
+ ## Added
4
+ - None.
5
+
6
+ ## Changed
7
+ - 视频审核不再做语音识别和自动校时:去掉豆包 ASR、Gemini 对比、台词差异门禁和按原声自动改写,不再需要 `SD_AUDIO_APIKEY`。台词由 Agent 在看片预算内按疑点核对,时间戳保留 ingest 的精度。
8
+
9
+ ## Fixed
10
+ - None.
11
+
12
+ ## Breaking
13
+ - 移除 NDJSON 事件 `asr-started`、`asr-transcribed`、`asr-skipped`、`asr-compared`、`asr-applied`、`asr-rewritten`,失败类型 `dialogue_cross_language`、`dialogue_mismatch`、`asr_config`、`asr_failed`,工具 `asr_window` 和环境变量 `SCRIPTCTL_ALLOW_DIALOGUE_MISMATCH`。`done.statistics.fixed` 不再包含校时条数。
package/dist/bin.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{b as s}from"./chunk-7QE6NHW5.js";import{Console as r}from"node:console";var o=new r({stdout:process.stderr,stderr:process.stderr});console.debug=o.debug.bind(o);console.info=o.info.bind(o);console.log=o.log.bind(o);s(process.argv.slice(2)).then(e=>{process.exitCode=e}).catch(e=>{console.error(e),process.exitCode=70});
2
+ import{b as s}from"./chunk-QQ7KIXWT.js";import{Console as r}from"node:console";var o=new r({stdout:process.stderr,stderr:process.stderr});console.debug=o.debug.bind(o);console.info=o.info.bind(o);console.log=o.log.bind(o);s(process.argv.slice(2)).then(e=>{process.exitCode=e}).catch(e=>{console.error(e),process.exitCode=70});
@@ -219,7 +219,7 @@ ${s}`);continue}let c=i.length>1?i[1]??"":"";c||(c=n),n=c;let a=[];s&&a.push(`${
219
219
  `),{line:p}=qe.object({line:qe.number().int().nonnegative()}).parse(r("pass1/body_start.json"));p>=d.length&&Kt("Text body start is outside the source snapshot.");let l=_S.parse(r("v4_identity_map.json")),f=Mo(JSON.parse(Xn.readFileSync(t,"utf8")));f.version!==4&&Kt("Text source review requires the ingested Script v4.");let u=wa(s.source.sha256),h=Object.entries(l.episode).map(([y,_])=>({id:_,...Lp(y,p)})).sort((y,_)=>y.start_line-_.start_line);h.some(y=>y.end_line>d.length)&&Kt("An episode's ingest window is outside the original source.");let m=f.episodes.filter(y=>!n?.length||n.includes(y.no)).map(y=>{let _=h.filter(A=>A.id===y.id);_.length!==1&&Kt(`Episode ${y.no} has no unambiguous text source identity.`);let x=_[0],E=h[h.indexOf(x)+1];return{episode:y.no,start_line:h.indexOf(x)===0?p+1:x.start_line,end_line:E?E.end_line:d.length}});m.length||Kt("No selected episodes exist in this script.");let g=new Map,w=new Map(Object.entries(l.action).map(([y,_])=>[_,Np(y,u,p)]));for(let y of bn(f)){if(!m.some(E=>E.episode===y.scene.ep.no))continue;let _=qe.object({scriptctl:qe.object({source:Op.optional()}).optional()}).safeParse(y.raw.extend??{});_.success||Kt(`Invalid text source metadata on ${y.anchor}.`);let x=_.data.scriptctl?.source??w.get(y.anchor);x&&(x.document_id!==u&&Kt(`Action ${y.anchor} belongs to a different text document.`),x.ranges.some(E=>E.start_line>E.end_line||E.end_line>d.length)&&Kt(`Invalid source lines on ${y.anchor}.`),g.set(y.anchor,x))}return{document_id:u,title:jt.basename(s.source.path),path:c,lines:d,episodes:m,references:g}}var sg="review-agent",SS=["minimal","low","medium","high"];function ka(e,t){let n=t||sg,o=e.models[n];if(!o)throw new S(`LLM profile "${n}" is not defined.`,{exitCode:b,errorCode:"REVIEW_PROFILE_UNKNOWN",hint:`Define it under models, or pick one of: ${Object.keys(e.models).join(", ")}.`,data:{expected:["a defined models entry"],actual:[n]}});let i=e.providers[o.provider];if(!i)throw new S(`Provider "${o.provider}" is not defined.`,{exitCode:b,errorCode:"REVIEW_PROVIDER_UNKNOWN",hint:`Available providers: ${Object.keys(e.providers).join(", ")}.`,data:{expected:["a defined providers entry"],actual:[o.provider]}});let r=(process.env[i.apiKeyEnv]??"").trim();if(!r&&i.protocol!=="google-vertex")throw new S(`${i.apiKeyEnv} is not set.`,{exitCode:b,errorCode:"REVIEW_API_KEY_MISSING",hint:`Export ${i.apiKeyEnv} for provider ${o.provider}.`,data:{expected:[i.apiKeyEnv],actual:["<empty>"]}});let s=i.protocol.startsWith("google")&&SS.includes(o.reasoning??"")?o.reasoning:void 0;return{provider:o.provider,model:o.model,baseUrl:i.baseUrl,apiKey:r,maxAttempts:i.maxAttempts,protocol:i.protocol,responseSchemaField:i.videoResponseSchemaField,...s?{thinking:s}:{}}}function rg(e){let{baseUrl:t,...n}=e;return{...n,...t?{baseUrl:t}:{}}}function Up(e,t,n=!1){let o=rg(ka(e,t));if(n||process.env.SCRIPTCTL_REVIEW_SINGLE_MODEL==="1")return{...o,singleModel:!0};let i=(e.models[t]?.fallbacks??[]).flatMap(r=>{let s=e.models[r],c=s&&e.providers[s.provider];return c&&c.protocol!=="google-vertex"&&!process.env[c.apiKeyEnv]?.trim()?[]:[rg(ka(e,r))]});return{...o,fallbacks:i}}async function nn(){let e=ba.dirname(ES(import.meta.url)),t=ba.join(e,"review-agent.js"),n=ui.existsSync(t)?xS(t).href:"@lingjingai/review-agent";try{return await import(n)}catch(o){throw new S("The review agent is not installed.",{exitCode:X,errorCode:"REVIEW_AGENT_MISSING",hint:"Reinstall scriptctl without --omit=optional, or install @mastra/core and @ai-sdk/google.",data:{expected:["@lingjingai/review-agent"],actual:[String(o.message).slice(0,200)]}})}}function es(e){let t=e.trim();if(!t)return;let n=t.split(",").map(o=>Number(o.trim()));if(n.some(o=>!Number.isInteger(o)||o<1))throw new S("--episodes takes a comma-separated list of 1-based episode numbers.",{exitCode:b,errorCode:"REVIEW_EPISODES_INVALID",hint:"For example: --episodes 1,2,5",data:{expected:["1-based episode numbers"],actual:[t]}});return n}function at(e,t,n){let o=e[t];if(o===void 0)return n;let i=Number(o);if(!Number.isFinite(i)||i<=0)throw new S(`--${t.replace(/_/g,"-")} must be a positive number.`,{exitCode:b,errorCode:"REVIEW_OPTION_INVALID",hint:`Pass a positive number for --${t.replace(/_/g,"-")}.`,data:{expected:["a positive number"],actual:[String(o)]}});return i}async function ag(e){let t=bo(O(e.llm_config).trim()||void 0),n=O(e.model).trim(),o=O(e.source_path).trim(),i=(Array.isArray(e.video)?e.video:[]).map(m=>m.trim()).filter(m=>m.length>0),r=Fm(o,i.length>0),c=Up(t,n||(r==="comic"?"review-comic":r==="text"?"review-text":sg),e.fallback===!1),a=O(e.media_model).trim(),d=r==="video"?a?Up(t,a,e.fallback===!1):t.source==="builtin"&&!n?Up(t,"review-media",e.fallback===!1):c:void 0,p=O(e.assets).split(",").map(m=>m.trim()).filter(Boolean);if(p.length&&(r!=="video"||e.converge_only!==!0||e.converge===!1))throw new S("--assets \u7528\u4E8E\u89C6\u9891\u8D44\u4EA7\u5B9A\u70B9\u590D\u6838\uFF0C\u8BF7\u540C\u65F6\u63D0\u4F9B --converge-only \u4E0E\u89C6\u9891\u6765\u6E90\u3002",{exitCode:b,errorCode:"REVIEW_OPTION_INVALID",hint:"\u7528 --assets actXXXXXX --converge-only\uFF1B--episodes \u53EF\u9650\u5B9A\u4EBA\u7269\u9636\u6BB5\u590D\u6838\u96C6\u6570\u3002",data:{expected:["video review with --converge-only"],actual:[r]}});if(e.check){let m=await nn(),w=typeof{script:m.reviewPlainScript,text:m.reviewTextScript,comic:m.reviewComicScript,video:m.reviewVideoScript}[r]=="function";return[{text:w?`ready: ${c.provider}/${c.model}${d?`
220
220
  media: ${d.provider}/${d.model}`:""}`:"the review agent bundle is present but exports no reviewScript",data:{ready:w,review_kind:r,provider:c.provider,model:c.model,...d?{media_provider:d.provider,media_model:d.model}:{},llm_config:t.sourcePath??"builtin"}},w?G:X]}let l=me(e);if(!ui.existsSync(l))throw new S("Local script file not found.",{exitCode:b,errorCode:"SCRIPT_NOT_FOUND",hint:"Run `scriptctl checkout`, `scriptctl create`, or pass --script-path to an existing script.",data:{expected:["existing local script JSON"],actual:[l]}});let f=i.filter(m=>!ui.existsSync(m));if(f.length>0)throw new S("A --video path does not exist.",{exitCode:b,errorCode:"REVIEW_SOURCE_NOT_FOUND",hint:"Pass --video once per episode, in episode order.",data:{expected:["existing video files"],actual:f.slice(0,5)}});if(o&&!ui.existsSync(o))throw new S("Review source not found.",{exitCode:b,errorCode:"REVIEW_SOURCE_NOT_FOUND",hint:"Pass an existing source or ingest workspace, or omit -s for script-only review.",data:{expected:["existing source or ingest workspace"],actual:[o]}});let u=await nn(),h=m=>process.stdout.write(JSON.stringify(m)+`
221
221
  `);try{let m={scriptPath:l,...es(O(e.episodes))?{episodes:es(O(e.episodes))}:{},concurrency:at(e,"concurrency",5),converge:e.converge!==!1,convergeOnly:e.converge_only===!0,resumeUnits:(Array.isArray(e.resume_log)?e.resume_log:[]).flatMap(g=>ui.readFileSync(g,"utf8").split(`
222
- `).flatMap(w=>{let y;try{y=JSON.parse(w)}catch{return[]}return y&&(y.type==="episode"||y.type==="global")?[y]:[]})),...c,maxSteps:at(e,"max_steps",300),episodeTimeoutMs:at(e,"episode_timeout_ms",9e5),onEvent:h};if(r==="comic"){let g=await zm(o,l,m.converge?void 0:m.episodes);await u.reviewComicScript({...m,comic:g})}else if(r==="text"){let g=await ig(o,l,m.converge?void 0:m.episodes);await u.reviewTextScript({...m,text:g})}else r==="video"?await u.reviewVideoScript({...m,source:o,videos:i,mediaConfig:d,...p.length?{convergenceAssets:p}:{},strategy:e.review_strategy==="batch"?"batch":"legacy",mediaModel:d.model,...e.media_budget===void 0?{}:{mediaBudget:at(e,"media_budget",1)}}):await u.reviewPlainScript(m)}catch(m){throw m instanceof S?m:new S("Review failed.",{exitCode:X,errorCode:"REVIEW_FAILED",hint:r==="video"?"Read the error details, NDJSON asr-compared/episode reports and .review/asr/ evidence before retrying.":`Read the error details and episode reports${r==="script"?"":` in .review/${r}/`} before retrying.`,data:{expected:["a completed review run"],actual:[m instanceof Error?m.message:String(m)]}})}return[{text:"",data:{}},G]}import*as Rn from"node:fs";import*as ge from"node:fs";import*as Ee from"node:path";import{createHash as IS}from"node:crypto";var ts=["analyze","direction","plan","write","review"],AS={analyze:"reskin",direction:"reskin-claude",plan:"reskin-claude",write:"reskin",review:"reskin-claude"},kS={"claude-opus-5-5":{input:4,cached_input:.2,output:20}};function Pp(e,t){let n=Ee.join(e,t);return{root:n,source:Ee.join(n,"0_input","script.json"),analysis:Ee.join(n,"1_src","\u62C6\u89E3.json"),direction:Ee.join(n,"2_new","\u6362\u58F3\u65B9\u5411.json"),brief:Ee.join(n,"2_new","\u6362\u58F3\u65B9\u5411.md"),plan:Ee.join(n,"2_new","\u6362\u58F3\u65B9\u6848.json"),script:Ee.join(n,"4_output","script.json"),log:o=>Ee.join(n,"logs",`${o}.ndjson`),status:Ee.join(n,"status.json")}}function va(e,t){ge.mkdirSync(Ee.dirname(e),{recursive:!0}),ge.writeFileSync(`${e}.tmp`,JSON.stringify(t,null,2)+`
222
+ `).flatMap(w=>{let y;try{y=JSON.parse(w)}catch{return[]}return y&&(y.type==="episode"||y.type==="global")?[y]:[]})),...c,maxSteps:at(e,"max_steps",300),episodeTimeoutMs:at(e,"episode_timeout_ms",9e5),onEvent:h};if(r==="comic"){let g=await zm(o,l,m.converge?void 0:m.episodes);await u.reviewComicScript({...m,comic:g})}else if(r==="text"){let g=await ig(o,l,m.converge?void 0:m.episodes);await u.reviewTextScript({...m,text:g})}else r==="video"?await u.reviewVideoScript({...m,source:o,videos:i,mediaConfig:d,...p.length?{convergenceAssets:p}:{},strategy:e.review_strategy==="batch"?"batch":"legacy",mediaModel:d.model,...e.media_budget===void 0?{}:{mediaBudget:at(e,"media_budget",1)}}):await u.reviewPlainScript(m)}catch(m){throw m instanceof S?m:new S("Review failed.",{exitCode:X,errorCode:"REVIEW_FAILED",hint:r==="video"?"Read the error details and NDJSON episode reports before retrying.":`Read the error details and episode reports${r==="script"?"":` in .review/${r}/`} before retrying.`,data:{expected:["a completed review run"],actual:[m instanceof Error?m.message:String(m)]}})}return[{text:"",data:{}},G]}import*as Rn from"node:fs";import*as ge from"node:fs";import*as Ee from"node:path";import{createHash as IS}from"node:crypto";var ts=["analyze","direction","plan","write","review"],AS={analyze:"reskin",direction:"reskin-claude",plan:"reskin-claude",write:"reskin",review:"reskin-claude"},kS={"claude-opus-5-5":{input:4,cached_input:.2,output:20}};function Pp(e,t){let n=Ee.join(e,t);return{root:n,source:Ee.join(n,"0_input","script.json"),analysis:Ee.join(n,"1_src","\u62C6\u89E3.json"),direction:Ee.join(n,"2_new","\u6362\u58F3\u65B9\u5411.json"),brief:Ee.join(n,"2_new","\u6362\u58F3\u65B9\u5411.md"),plan:Ee.join(n,"2_new","\u6362\u58F3\u65B9\u6848.json"),script:Ee.join(n,"4_output","script.json"),log:o=>Ee.join(n,"logs",`${o}.ndjson`),status:Ee.join(n,"status.json")}}function va(e,t){ge.mkdirSync(Ee.dirname(e),{recursive:!0}),ge.writeFileSync(`${e}.tmp`,JSON.stringify(t,null,2)+`
223
223
  `),ge.renameSync(`${e}.tmp`,e)}function Cp(e){return ge.existsSync(e)?JSON.parse(ge.readFileSync(e,"utf8")):null}function bS(e,t){let n=Object.keys(e).filter(o=>t.startsWith(o)).sort((o,i)=>i.length-o.length)[0];return n?e[n]:null}function cg(e,t){let n=0,o=[];for(let[i,r]of Object.entries(e)){let s=bS(t,i);if(!s){o.push(i);continue}n+=((r.input-r.cached_input)*s.input+r.cached_input*s.cached_input+r.output*s.output)/1e6}return{usd:n,unpriced:o}}function vS(e,t){for(let n of t){let o=e[n.model]??={calls:0,input:0,cached_input:0,output:0};o.calls+=1,o.input+=n.input_tokens,o.cached_input+=n.cached_input_tokens,o.output+=n.output_tokens}}var OS=new Set(["unit-done","episode","global","direction-done"]),RS=["\u987A","\u8981\u6539\u505A\u6CD5","\u7275\u5F3A","\u6491\u4E0D\u4F4F"];function MS(e){if(!e)return"\u672A\u5BA1\u8BFB";let t=[...e.stages,...e.points];return RS.map(n=>[n,t.filter(o=>o.grade===n).length]).filter(([,n])=>n).map(([n,o])=>`${n} ${o}`).join(" \xB7 ")}function dg(e){return e?[...e.stages.filter(t=>t.grade!=="\u987A").map(t=>`${t.stage}\uFF08${t.grade}\uFF09\uFF1A${t.note}`),...e.points.filter(t=>t.grade!=="\u987A").map(t=>`${t.point}\uFF08${t.grade}\uFF09\uFF1A${t.note}`)]:[]}function LS(e,t){return IS("sha256").update(JSON.stringify([ge.readFileSync(e,"utf8"),t.notes??"",t.custom??"",t.market??"",t.rating??"",t.avoid??[]])).digest("hex")}function NS(e,t,n){let o=n.market?.trim();return[`# ${e.title}`,`\u76EE\u6807\u5E02\u573A\uFF1A${o||"\u540C\u539F\u5267"}`,o&&n.language?.trim()?`\u53F0\u8BCD\u8BED\u8A00\uFF1A${n.language.trim()}`:"",`\u5C3A\u5EA6\uFF1A${n.rating?.trim()||"\u540C\u539F\u5267"}`,`\u6362\u58F3\uFF1A${e.shell}`,`\u6362\u6CD5\uFF1A${e.approach}`,e.keeps?`\u6CBF\u7528\u539F\u5267\uFF1A${e.keeps}`:"",e.changes?`\u5FC5\u987B\u6539\u52A8\uFF1A${e.changes}`:"",e.risks?`\u98CE\u9669\uFF1A${e.risks}`:"",dg(e.review).length?`\u72EC\u7ACB\u5BA1\u8BFB\u6307\u51FA\u7684\u627F\u63A5\u95EE\u9898\uFF0C\u65B9\u6848\u91CC\u8981\u89E3\u51B3\uFF1A
224
224
  ${dg(e.review).map(i=>`- ${i}`).join(`
225
225
  `)}`:"",e.research?`\u65B0\u58F3\u5728\u5F53\u5730\u7684\u73B0\u5B9E\uFF08\u8054\u7F51\u8C03\u7814\uFF0C\u5177\u4F53\u505A\u6CD5\u4F9D\u636E\u5B83\u8BBE\u8BA1\uFF09\uFF1A
package/dist/cli.js CHANGED
@@ -1 +1 @@
1
- import{a,b}from"./chunk-7QE6NHW5.js";export{a as buildProgram,b as main};
1
+ import{a,b}from"./chunk-QQ7KIXWT.js";export{a as buildProgram,b as main};