@lingjingai/scriptctl 0.65.5 → 0.65.8

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/dist/cli.js CHANGED
@@ -1 +1 @@
1
- import{a,b}from"./chunk-AMHE5YHV.js";export{a as buildProgram,b as main};
1
+ import{a,b}from"./chunk-T7ZYKPXV.js";export{a as buildProgram,b as main};
@@ -140,7 +140,7 @@ ${t}
140
140
  ${o||"\uFF08\u6CA1\u6709\u8BC6\u522B\u5230\u6709\u6548\u8BED\u53E5\uFF09"}
141
141
  </\u8C46\u5305ASR>`}function nU(e,t,o,n){let s=new Map(o.map(c=>[c.id,c])),i=c=>{let d=s.get(c);if(!d)throw new Error(`\u7B2C ${t} \u96C6\u5BF9\u6BD4\u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 ASR \u7F16\u53F7\uFF1A${c}`);return d},r=new Set,l=n.timestamp_corrections.map(c=>{let d=me(e,c.action);if(d.t!=="action"||d.action.scene.ep.no!==t||!["dialogue","inner_thought"].includes(d.action.type))throw new Error(`\u7B2C ${t} \u96C6\u6821\u65F6\u76EE\u6807\u4E0D\u662F\u672C\u96C6\u53F0\u8BCD\uFF1A${c.action}`);if(r.has(d.action.anchor))throw new Error(`\u7B2C ${t} \u96C6\u91CD\u590D\u6821\u65F6\u76EE\u6807\uFF1A${c.action}`);r.add(d.action.anchor);let p=i(c.start_ref);return{action:d.action.anchor,before:d.action.ts??null,after:go(zt(p.start_time)).canonical,content:d.action.content,asr_text:p.text,reason:c.reason}}).filter(c=>c.before!==c.after),a=n.missing_dialogue.map(c=>{let d=i(c.asr_ref);return{asr_ref:d.id,start_ms:d.start_time,end_ms:d.end_time,text:d.text,reason:c.reason}});return{summary:n.summary,timestamp_corrections:l,missing_dialogue:a,notes:n.notes}}async function td(e,t,o,n,s,i){let r=ot.join(ot.dirname(n),".review","asr",`episode-${t}`);if(await mt.mkdir(r,{recursive:!0}),!await ju(o)){let y={summary:"\u539F\u7247\u6CA1\u6709\u97F3\u8F68\uFF0C\u65E0\u6CD5\u6267\u884C ASR \u6821\u65F6\uFF1B\u7EE7\u7EED\u5BA1\u6838\u753B\u9762\u4E0E\u5267\u672C\u3002",timestamp_corrections:[],missing_dialogue:[],notes:["ffprobe \u786E\u8BA4\u539F\u7247\u6CA1\u6709\u97F3\u9891\u6D41\u3002\u4FDD\u7559\u73B0\u6709\u53F0\u8BCD\u53CA\u65F6\u95F4\uFF0C\u4E0D\u6839\u636E\u65E0\u97F3\u8F68\u63A8\u65AD\u53F0\u8BCD\u4E0D\u5B58\u5728\uFF1B\u770B\u7247\u6838\u5BF9\u753B\u9762\u4E0E\u5B57\u5E55\uFF0C\u65E0\u6CD5\u4ECE\u753B\u9762\u786E\u8BA4\u7684\u53D1\u58F0\u5185\u5BB9\u660E\u786E\u6807\u4E3A\u8BC1\u636E\u4E0D\u8DB3\u3002"]},M=await mt.mkdtemp(ot.join(r,"no-audio-"));return await mt.writeFile(ot.join(M,"report.json"),JSON.stringify(y,null,2)),{status:"source_no_audio",report:y,reportDir:M,usage:[]}}let l=await Ku(o,r);if(i?.(),l.length===0){let y={summary:"ASR \u6CA1\u6709\u8BC6\u522B\u5230\u6709\u6548\u8BED\u53E5\uFF0C\u65E0\u6CD5\u6267\u884C\u8BED\u97F3\u6821\u65F6\uFF1B\u7EE7\u7EED\u6838\u5BF9\u539F\u7247\u4E0E\u5267\u672C\u3002",timestamp_corrections:[],missing_dialogue:[],notes:["\u6CA1\u6709 ASR \u8BED\u53E5\u4E0D\u7B49\u4E8E\u539F\u7247\u6CA1\u6709\u5BF9\u767D\u3002\u4FDD\u7559\u73B0\u6709\u53F0\u8BCD\u548C\u65F6\u95F4\uFF0C\u9010\u6761\u6838\u5BF9\u539F\u7247\u4E2D\u7684\u77ED\u53E5\u3001\u611F\u53F9\u3001\u753B\u5916\u97F3\u53CA\u5B57\u5E55\uFF1B\u65E0\u8BED\u97F3\u8BC1\u636E\u65F6\u4E0D\u5220\u9664\u53F0\u8BCD\u3001\u4E0D\u63A8\u7B97\u53D1\u58F0\u65F6\u95F4\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7684\u5185\u5BB9\u6807\u4E3A\u8BC1\u636E\u4E0D\u8DB3\u3002"]},M=await mt.mkdtemp(ot.join(r,"no-speech-"));return await mt.writeFile(ot.join(M,"report.json"),JSON.stringify(y,null,2)),{status:"no_speech",report:y,reportDir:M,usage:[]}}let a=Xu(ed(e,t).map(y=>y.content).join(`
142
142
  `),l.map(y=>y.text).join(`
143
- `)),c=await mt.mkdtemp(ot.join(r,"dialogue-check-")),d=ot.join(c,"report.json");if(await mt.writeFile(d,JSON.stringify(a,null,2)),a.blocked)throw new ns(t,a,d);let p=e.episodes.find(y=>y.no===t).scenes.flatMap(y=>y.actions),f=new Set;p.forEach((y,M)=>{if(!(y.type!=="dialogue"&&y.type!=="inner_thought"))for(let _=Math.max(0,M-1);_<=Math.min(p.length-1,M+1);_+=1)f.add(_)});let u=p.filter((y,M)=>f.has(M)).map(y=>`[${y.type}] ${Te(e,y.anchor,{timestamps:!0,context:0})}`).join(`
143
+ `)),c=await mt.mkdtemp(ot.join(r,"dialogue-check-")),d=ot.join(c,"report.json");if(await mt.writeFile(d,JSON.stringify(a,null,2)),a.blocked&&process.env.SCRIPTCTL_ALLOW_DIALOGUE_MISMATCH!=="1")throw new ns(t,a,d);let p=e.episodes.find(y=>y.no===t).scenes.flatMap(y=>y.actions),f=new Set;p.forEach((y,M)=>{if(!(y.type!=="dialogue"&&y.type!=="inner_thought"))for(let _=Math.max(0,M-1);_<=Math.min(p.length-1,M+1);_+=1)f.add(_)});let u=p.filter((y,M)=>f.has(M)).map(y=>`[${y.type}] ${Te(e,y.anchor,{timestamps:!0,context:0})}`).join(`
144
144
  `),g=Bu(l),h=oU(t,u,g),w=pa(l),U=tU(e,t,w),A=await mt.mkdtemp(ot.join(r,"comparison-"));await Promise.all([mt.writeFile(ot.join(A,"script.txt"),u),mt.writeFile(ot.join(A,"asr.txt"),g),mt.writeFile(ot.join(A,"prompt.txt"),h)]);let O=await xi({config:s,prompt:h,schema:U,timeoutMs:s.timeoutMs,callKey:`review:asr:ep${t}:${ot.basename(A)}`,onResponse:(y,M,_)=>mt.writeFile(ot.join(A,`${y.provider}-${y.model.replaceAll("/","_")}-${M}.json`),_)}),E=nU(e,t,w,O.value);return await mt.writeFile(ot.join(A,"report.json"),JSON.stringify(E,null,2)),{status:"compared",report:E,reportDir:A,usage:O.usage}}import*as bo from"node:fs";import*as ln from"node:path";function Ko(e){return{apiKey:e.apiKey,model:e.model??"gemini-3.8-flash",baseUrl:e.baseUrl,maxAttempts:e.maxAttempts??3,maxSteps:e.maxSteps??300,timeoutMs:e.episodeTimeoutMs??8*6e4}}var sU=["wardrobe","actor-identity","asset"];function ki(e){let t=new Map;for(let o of e.resumeUnits??[])t.set(o.episode_number?`episode-${o.episode_number}`:o.asset??"identity",o);return t}function od(e){return ln.join(ln.dirname(e),".review",ln.basename(e,".json"),"findings")}function Vs(e,t){if(!t.episode_number)return;let o=od(e);bo.mkdirSync(o,{recursive:!0});let n=ln.join(o,`episode-${t.episode_number}.json`);bo.writeFileSync(`${n}.tmp`,JSON.stringify(t.findings,null,2)+`
145
145
  `),bo.renameSync(`${n}.tmp`,n)}function iU(e,t){let o=od(t);return e.episodes.flatMap(n=>{let s=ln.join(o,`episode-${n.no}.json`);return bo.existsSync(s)?JSON.parse(bo.readFileSync(s,"utf8")):[]}).filter(n=>sU.includes(n.category))}async function ss(e,t,o,n){let s=Date.now(),i=ki(t),r=t.convergeOnly?[]:e.episodes.filter(p=>(!t.episodes?.length||t.episodes.includes(p.no))&&i.get(`episode-${p.no}`)?.state!=="completed").map(p=>p.no),l=[],a=[],c=p=>{l.push(p),t.onEvent?.(p)};async function d(p,f,u){c({type:"unit-started",episode_number:p,...u?{asset:u}:{}});let g=await o(p,f,u);return gt(e,t.scriptPath),Vs(t.scriptPath,g),n?.(g),a.push(g),c({type:p?"episode":"global",...g}),g}return await at(r,t.concurrency??5,p=>d(p)),(t.convergeOnly||t.converge!==!1)&&await wa(e,t,(p,f)=>d(0,p,f)),c({type:"done",statistics:Ts(a),usage_total:a.flatMap(p=>p.usage),wall_ms:Date.now()-s}),l}async function wa(e,t,o){let n=iU(e,t.scriptPath),s=ki(t),i=new Map(e.assets.map(p=>[p.id,Te(e,p.id)])),r=[...n],l=[...s.values()].filter(p=>p.episode_number===0),a=JSON.stringify(l.flatMap(p=>p.findings.filter(f=>f.outcome!=="unresolved"))),c=n.filter(p=>p.outcome==="unresolved"&&!a.includes(p.id));c.push(...l.flatMap(p=>p.findings.filter(f=>f.outcome==="unresolved"))),r.push(...l.flatMap(p=>p.findings));for(let p=0;p<3&&(p>0||s.get("identity")?.state!=="completed"||c.length>0);p++){let f=await o(c);if(r.push(...f.findings),f.state==="failed")return;let u=JSON.stringify(f.findings),g=c.filter(h=>!u.includes(h.id));if(!g.length||g.length===c.length)break;c=g}let d=e.assets.filter(p=>{let f=s.get(p.id);return!f||f.state!=="completed"||f.findings.some(u=>u.outcome==="unresolved")||i.get(p.id)!==Te(e,p.id)});await at(d.map(p=>p.id),t.concurrency??5,p=>o(r.filter(f=>f.subject?.id?.split("/")[0]===p||f.subject?.name===p),p))}function Gi(e){return e.asset?`-${e.asset}`:""}function Ts(e){let t=e.flatMap(o=>o.findings);return{fixed:t.filter(o=>o.outcome==="fixed").length,unchanged:t.filter(o=>o.outcome==="unchanged").length,unresolved:t.filter(o=>o.outcome==="unresolved").length,failed_episodes:e.filter(o=>o.state==="failed"&&o.episode_number>0).length}}async function at(e,t,o){let n=[...e],s=Array.from({length:Math.max(1,Math.min(t,n.length))},async()=>{for(;;){let l=n.shift();if(l===void 0)return;await o(l)}}),r=(await Promise.allSettled(s)).filter(l=>l.status==="rejected");if(r.length)throw new Error(r.map(l=>{let a=l.reason;return a instanceof Error?a.message:String(a)}).join(`
146
146
  `))}function sd(e,t){if(!e)return Array.from({length:t},()=>{});if(is.statSync(e).isFile())return Array.from({length:t},()=>e);let n=is.readdirSync(e).filter(s=>/\.(mp4|mov|mkv|m4v)$/i.test(s)).sort().map(s=>nd.join(e,s));if(n.length<t)throw new Error(`${e} holds ${n.length} videos for ${t} episodes`);return n.slice(0,t)}async function Ea(e){let t=Date.now(),o=Re(JSON.parse(is.readFileSync(e.scriptPath,"utf8"))),n={apiKey:e.apiKey,model:e.model??"gemini-3.8-flash",mediaModel:e.mediaModel??e.model??"gemini-3.8-flash",...e.baseUrl?{baseUrl:e.baseUrl}:{},maxAttempts:e.maxAttempts??3,maxSteps:e.maxSteps??300,mediaBudget:e.mediaBudget??1,timeoutMs:e.episodeTimeoutMs??8*6e4},s=o.episodes.map(U=>U.no),i=ki(e),r=(e.convergeOnly?[]:e.episodes?.length?e.episodes.filter(U=>s.includes(U)):s).filter(U=>i.get(`episode-${U}`)?.state!=="completed"),l=e.videos?.length?e.videos:sd(e.source,o.episodes.length),a=[],c=U=>{a.push(U),e.onEvent?.(U)},d=new Map,p=0,f=!1,u=[],g=(U,A)=>{u.push(U),gt(o,e.scriptPath),Vs(e.scriptPath,U),c({type:A,...U})};await at(r,e.concurrency??5,async U=>{let A=Date.now(),O=l[U-1],E;if(e.source||e.videos?.length){c({type:"asr-started",episode_number:U});try{if(!O)throw new Error(`\u7B2C ${U} \u96C6\u7F3A\u5C11\u539F\u89C6\u9891`);E=await td(o,U,O,e.scriptPath,n,()=>c({type:"asr-transcribed",episode_number:U}))}catch(M){M instanceof ns&&(f=!0),g({episode_number:U,state:"failed",message:"ASR \u5BF9\u6BD4\u5931\u8D25",error:M instanceof Error?M.message:String(M),findings:[],wall_ms:Date.now()-A,steps:0,media_calls:0,usage:M instanceof vn?M.usage:[]},"episode");return}if(d.set(U,E),E.status!=="compared")c({type:"asr-skipped",episode_number:U,reason:E.status,...E});else{c({type:"asr-compared",episode_number:U,...E});let M=E.report.timestamp_corrections;bl(o,M.map(_=>({at:_.action,ts:_.after}))),gt(o,e.scriptPath),p+=M.length,c({type:"asr-applied",episode_number:U,timestamp_corrections:M.length})}}c({type:"unit-started",episode_number:U});let y=await ha(o,U,O,n,E?.report);E?.status==="no_speech"&&(y.message=`ASR \u672A\u8BC6\u522B\u5230\u6709\u6548\u8BED\u53E5\uFF0C\u672A\u6267\u884C\u8BED\u97F3\u6821\u65F6\uFF1B${y.message}`),E?.status==="source_no_audio"&&(y.message=`\u539F\u7247\u65E0\u97F3\u8F68\uFF0C\u5DF2\u8DF3\u8FC7 ASR \u6821\u65F6\uFF1B${y.message}`,y.findings.push(jt(o,{category:"timeline",at:String(U),title:"\u539F\u7247\u65E0\u97F3\u8F68\uFF0CASR \u6821\u65F6\u672A\u6267\u884C",issue:"ffprobe \u786E\u8BA4\u6E90\u89C6\u9891\u6CA1\u6709\u97F3\u9891\u6D41\u3002",impact:"\u53F0\u8BCD\u53EA\u80FD\u6309\u753B\u9762\u5B57\u5E55\u6838\u5BF9\uFF0C\u65E0\u6CD5\u6838\u9A8C\u5B9E\u9645\u53D1\u58F0\u65F6\u95F4\u3001\u6F0F\u8BCD\u6216\u65E0\u5B57\u5E55\u753B\u5916\u97F3\u3002",resolution:"\u4FDD\u7559\u73B0\u6709\u53F0\u8BCD\u548C\u65F6\u95F4\uFF0C\u7EE7\u7EED\u753B\u9762\u5BA1\u6838\uFF1B\u672C\u96C6\u672A\u901A\u8FC7\u8BED\u97F3\u6821\u65F6\u3002",outcome:"unresolved",confidence:"high",unresolved_reason:"\u6E90\u89C6\u9891\u7F3A\u5C11\u97F3\u8F68",evidence:[`video|\u65E0\u97F3\u8F68\u63A2\u6D4B\u8BB0\u5F55|${E.reportDir}/report.json`]},U,0))),g(y,"episode")}),!f&&(e.convergeOnly||e.converge!==!1)&&await wa(o,e,async(U,A)=>{c({type:"unit-started",episode_number:0,...A?{asset:A}:{}});let O=await ha(o,0,void 0,n,void 0,U,l,A);return g(O,"global"),O});let h=[...d.values()].flatMap(U=>U.usage).concat(u.flatMap(U=>U.usage)),w=Ts(u);return w.fixed+=p,c({type:"done",statistics:w,usage_total:h,wall_ms:Date.now()-t}),a}import*as ad from"node:fs";function Ua(){var e=["zxbPC29Kzq","cUwfQowjP+IUVUwUMU+8MGO","cUwfQowXGoMxRUMIMos6PoE7MEAuTUAvM++8JoAVJ+AjUEs/RUAuUEIVU+wBNUw5TIb2ywXPzgf0zEoaGG","ioMBHUEAHowgHEMdQos4GoIhToAaP+oaGUwfIcbZy2fU77Ym6ycq5P2H5Qoa5P+L5Q2J5PAh5zkm5BYv55sO77Ym5B+f6kAb5PE2ihjLywqGlYbYzwzZioAGUowVUEs4IUs4I+AwH+oaGUAYOEACIEwKLUMdQowoN+EOV++8Jos4JEIdVEwiPoAwREA8J+AkHoAiLUAtHEIhQUwINUIHPEAvHEs6I+oaGGRNM7JPGRVPM4BMOPFMPOlVViJKU4xKVzZKUiRKUiVMLOFVViNVVjOk","5A6H5Qc456YSia","BMvPz2HIB3vYCW","zg9J","C2vYAwvZq2fYza","zxbPC29KzuzPBMrPBMDZ","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","yxnZzxq"];return Ua=function(){return e},Ua()}function Ct(e,t){e=e-0;var o=Ua(),n=o[e];if(Ct.BzIofa===void 0){var s=function(l){for(var a="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=",c="",d="",p=0,f,u,g=0;u=l.charAt(g++);~u&&(f=p%4?f*64+u:u,p++%4)?c+=String.fromCharCode(255&f>>(-2*p&6)):0)u=a.indexOf(u);for(var h=0,w=c.length;h<w;h++)d+="%"+("00"+c.charCodeAt(h).toString(16)).slice(-2);return decodeURIComponent(d)};Ct.SVwJFN=s,Ct.qaIhuU={},Ct.BzIofa=!0}var i=o[0];Ct.bpgCcV!==i&&(Ct.qaIhuU={},Ct.bpgCcV=i);var r=Ct.qaIhuU[e];return r===void 0?(n=Ct.SVwJFN(n),Ct.qaIhuU[e]=n):n=r,n}function id(e){var t=Ct;return t(9)+rn+t(1)+e[t(7)]}function rd(e){var t=Ct;return e[t(0)]===0?an(e[t(6)],e[t(10)],e[t(8)]??[]):t(4)+e[t(0)]+t(3)+e[t(5)]+t(2)}async function ld(e){let t=Re(JSON.parse(ad.readFileSync(e.scriptPath,"utf8"))),o=Ko(e);return ss(t,e,async(n,s,i)=>{let r={doc:t,episode:n,phase:n?"episode":"converge",asset:i,findings:[],usage:[],mediaCalls:0},l={episode:n,episodeFindings:s,seriesCard:Io(t),neighbours:os(t,n),doc:t,asset:i};return nn(r,o,id(l),rd(l),ao(r))})}import*as cn from"node:fs";import*as $s from"node:path";import{createTool as rs}from"@mastra/core/tools";import{z as Ue}from"zod";import{z as Tt}from"zod";var rU=Tt.string().min(1),aU=Tt.array(Tt.number().int().min(1)).min(1).describe("1-based PDF \u7269\u7406\u9875\u7801\uFF1B\u8DE8\u9875\u753B\u683C\u6216\u540C\u4E00\u6C14\u6CE1\u586B\u5199\u76F8\u5173\u7684\u5168\u90E8\u9875\u7801\uFF1B\u540C\u4E00\u4E2A\u8DE8\u9875\u6C14\u6CE1\u5408\u4E3A\u4E00\u6761\u5B8C\u6574\u5BF9\u767D\uFF0C\u6309\u5347\u5E8F\u6392\u5217"),Si=Tt.number().min(0).max(1e3),ya=Tt.object({page:Tt.number().int().min(1),kind:Tt.enum(["panel","bubble","text"]),box:Tt.object({top:Si,left:Si,bottom:Si,right:Si}).describe("\u76F8\u5BF9\u4E8E\u5B8C\u6574 PDF \u7269\u7406\u9875\u7684\u77E9\u5F62\uFF0C\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\uFF0C\u6A2A\u7EB5\u5404\u5F52\u4E00\u5316\u5230 0\u20131000\uFF1B\u4E0D\u662F\u88C1\u526A\u56FE\u6216\u753B\u683C\u5185\u7684\u5750\u6807")}),Aa=Tt.array(ya).min(1).describe("\u672C\u6761\u6B63\u6587\u5B9E\u9645\u5BF9\u5E94\u7684\u753B\u683C(panel)\u3001\u6C14\u6CE1(bubble)\u6216\u72EC\u7ACB\u6587\u5B57\u5757(text)\uFF1B\u6309\u9605\u8BFB\u987A\u5E8F\u5217\u51FA\u5168\u90E8\u533A\u57DF\u3002\u8DE8\u9875\u533A\u57DF\u5404\u9875\u5206\u522B\u7ED9\u6846\uFF0C\u4E0D\u7528\u6574\u9875\u4EE3\u66FF\u5177\u4F53\u5185\u5BB9");function Ma(e,t){let o=[];for(let n of t){e.includes(n.page)||o.push(`Region page ${n.page} is absent from action pages.`);let{top:s,left:i,bottom:r,right:l}=n.box;(r<=s||l<=i)&&o.push(`Region on page ${n.page} has an empty or inverted box.`)}for(let n of e)t.some(s=>s.page===n)||o.push(`Page ${n} has no source region.`);return o}var Ia=Tt.object({kind:Tt.literal("comic"),document_id:rU,pages:aU,regions:Aa.optional()}),cd=Tt.object({scriptctl:Tt.object({source:Ia})});var Oa=Ue.object({text:Ue.string(),images:Ue.array(Ue.object({path:Ue.string(),label:Ue.string(),data:Ue.string()}))});function ud(e){let t=Ue.object({error:Ue.literal(!0),message:Ue.string()}).safeParse(e);if(t.success)return{type:"text",value:t.data.message};let o=Oa.parse(e);return{type:"content",value:[{type:"text",text:o.text},...o.images.flatMap(n=>[{type:"text",text:`${n.label}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingjingai/scriptctl",
3
- "version": "0.65.5",
3
+ "version": "0.65.8",
4
4
  "description": "剧本阶段统一 CLI:素材转 Script v4,并兼容 Script v3 的本地写作、精修、校验和发布。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,20 +22,6 @@
22
22
  "engines": {
23
23
  "node": ">=22.22.0"
24
24
  },
25
- "scripts": {
26
- "build": "pnpm run clean && pnpm --filter @lingjingai/screenplay build && pnpm --filter @lingjingai/review-agent build && tsc && node scripts/bundle.mjs",
27
- "clean": "rm -rf dist packages/screenplay/dist packages/review-agent/dist",
28
- "postinstall": "node scripts/install-skill.mjs",
29
- "install-skill": "node scripts/install-skill.mjs",
30
- "prepublishOnly": "pnpm run build",
31
- "pretest": "pnpm run build",
32
- "test": "vitest run",
33
- "pretest:coverage": "pnpm run build",
34
- "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov --report-dir=coverage vitest run",
35
- "test:watch": "vitest",
36
- "typecheck": "tsc --noEmit",
37
- "surface:snapshot": "node scripts/scriptctl-surface-snapshot.mjs"
38
- },
39
25
  "dependencies": {
40
26
  "@anthropic-ai/sdk": "^0.111.0",
41
27
  "@google/genai": "^2.11.0",
@@ -53,8 +39,8 @@
53
39
  "zod": "^4.5.4"
54
40
  },
55
41
  "devDependencies": {
56
- "@lingjingai/review-agent": "workspace:*",
57
- "@lingjingai/screenplay": "workspace:*",
42
+ "@lingjingai/review-agent": "0.1.0",
43
+ "@lingjingai/screenplay": "0.1.0",
58
44
  "@types/jsdom": "^30.0.0",
59
45
  "@types/node": "^20.14.0",
60
46
  "@types/proper-lockfile": "^4.1.4",
@@ -82,5 +68,18 @@
82
68
  "@ai-sdk/openai-compatible": "^3.0.48",
83
69
  "@ai-sdk/provider": "^4.0.4",
84
70
  "@mastra/core": "^1.64.0"
71
+ },
72
+ "scripts": {
73
+ "build": "pnpm run clean && pnpm --filter @lingjingai/screenplay build && pnpm --filter @lingjingai/review-agent build && tsc && node scripts/bundle.mjs",
74
+ "clean": "rm -rf dist packages/screenplay/dist packages/review-agent/dist",
75
+ "postinstall": "node scripts/install-skill.mjs",
76
+ "install-skill": "node scripts/install-skill.mjs",
77
+ "pretest": "pnpm run build",
78
+ "test": "vitest run",
79
+ "pretest:coverage": "pnpm run build",
80
+ "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov --report-dir=coverage vitest run",
81
+ "test:watch": "vitest",
82
+ "typecheck": "tsc --noEmit",
83
+ "surface:snapshot": "node scripts/scriptctl-surface-snapshot.mjs"
85
84
  }
86
- }
85
+ }
@@ -3,7 +3,7 @@ name: scriptctl
3
3
  description: "剧本 script.json 的本地阅读、定位、原子修改、整场整集回写、素材 ingest 与 revision publish CLI。处理 Script v3/v4、剧本资产与 state、Action、场景引用或视频证据核对时使用。"
4
4
  metadata:
5
5
  version: "0.32.1"
6
- scriptctl_version: "0.65.5"
6
+ scriptctl_version: "0.65.7"
7
7
  author: "official"
8
8
  ---
9
9
 
@@ -222,8 +222,11 @@ scriptctl checkout / status / publish # 远端工作副本
222
222
  单个 PDF 或分话 PDF 目录自动进入漫画 ingest;`--step` 完成下一话,重跑续接已接受进度。漫画正文通过 `extend.scriptctl.source` 保存原 PDF 页码及画格/气泡/文字区域,`view` 可点击正文定位并高亮原页区域。文档模型通过 `ingest.comic-serial` 配置,内置路由为 NNYYAI `gemini-3.8-flash`,使用 `NNYYAI_API_KEY`、base64 PDF 与 `responseSchema`。
223
223
 
224
224
  `ingest` 的 Gemini 文本、结构化工具、视频和 PDF 文档请求先用配置的主模型,再按 3.8 Flash →
225
- 3.7 Flash → 3.6 Flash 补齐未试模型,全部失败后使用豆包。Gemini 按 provider 的
226
- `maxAttempts` 重试暂时性错误,豆包最多尝试 3 次;明确拒绝时换模型,用户取消时停止。
225
+ 3.7 Flash → 3.6 Flash 在同渠道补齐未试模型。视频随后使用 GrsAI
226
+ (`https://grsai.dakka.com.cn`)的 3.8 / 3.7 / 3.6 Flash,再进入豆包;其他请求在主渠道耗尽后进入豆包。
227
+ GrsAI 使用 `GRSAI_API_KEY` 和 base64 视频,每个型号最多尝试 3 次;未配置密钥或主渠道已为该 GrsAI 地址时跳过此段。
228
+ 主渠道视频上传失败时,也从 GrsAI 开始使用本地视频继续请求。
229
+ 主渠道 Gemini 按 provider 的 `maxAttempts` 重试暂时性错误,豆包最多尝试 3 次;明确拒绝时换模型,用户取消时停止。
227
230
  豆包需要 `ARK_API_KEY`,`ARK_MODEL` 可指定端点。
228
231
  已完成集和跨集 context 保留,同 source、同 workspace 重跑会从失败集续接。
229
232
 
@@ -98,10 +98,13 @@ scriptctl ingest -s uploads/正片/ -w workspaces/正片 \
98
98
  --provider-model gemini-3.7-flash
99
99
  ```
100
100
 
101
- - `--upload-mode file-api|base64`:默认 `file-api`;`base64` 把自动转码后的本地视频放入 Gemini `inlineData`,文件必须小于 100 MB。
101
+ - `--upload-mode file-api|base64`:默认采用 provider 的 `videoUploadMode`,内置 NNYYAI 为 `base64`,未配置时为 `file-api`;`base64` 把自动转码后的本地视频放入 Gemini `inlineData`,文件必须小于 100 MB。
102
102
  - `--provider-base-url`、`--provider-api-key` / `--provider-api-key-env`、`--provider-model`:只覆盖视频任务对应字段,其余字段继承 `--llm-config` 或内置配置。
103
103
  - provider 参数必须提供非空值;`--provider-api-key` 与 `--provider-api-key-env` 互斥。明文参数会暴露在进程参数和 shell history 中,常规调用使用环境变量方式;密钥不会写入 workspace、错误或命令输出。
104
- - Gemini 文本、结构化工具和视频请求先使用配置的主模型,再按 `gemini-3.8-flash → gemini-3.7-flash → gemini-3.6-flash` 尝试尚未调用的模型,全部失败后才进入豆包。默认路由与显式 provider/model 覆盖均使用此顺序;Gemini 保留原服务地址、凭据和模型名前缀。每个 Gemini 模型按 provider 的 `maxAttempts` 重试暂时性错误,豆包最多尝试 3 次;明确拒绝时直接换模型,用户取消时立即停止。豆包使用 `ARK_API_KEY`;`ARK_MODEL` 可指定端点,默认 `ep-20260721150136-98dj9`。视频的 file-api 和 base64 在 Gemini 模型间复用同一媒体;公共 Files API 上传重试耗尽时交由豆包上传本地视频。直接选择豆包时只调用豆包。
104
+ - Gemini 请求先使用配置的主模型,再按 `gemini-3.8-flash → gemini-3.7-flash → gemini-3.6-flash` 在同渠道尝试尚未调用的模型,保留主渠道的服务地址、凭据和模型名前缀。每个模型按 provider 的 `maxAttempts` 重试暂时性错误;明确拒绝时直接换模型,用户取消时立即停止。
105
+ - 视频的主渠道模型耗尽后,依次尝试 GrsAI(`https://grsai.dakka.com.cn`)的 `gemini-3.8-flash → gemini-3.7-flash → gemini-3.6-flash`,再进入豆包。GrsAI 使用独立的 `GRSAI_API_KEY`、base64 视频和 `responseSchema`,每个型号最多尝试 3 次;未配置密钥或主渠道已为该 GrsAI 地址时跳过此段。视频 CLI 的地址、凭据和模型覆盖只作用于主渠道。
106
+ - 视频在主渠道的模型间复用媒体;切到 GrsAI 时复用 base64 内容,或从本地转码文件读取内容。主渠道 Files API 上传失败时,从 GrsAI 开始继续请求。豆包重新上传同一本地视频。默认路由与显式 Gemini provider/model 覆盖均使用该链;直接选择豆包时只调用豆包。
107
+ - 豆包使用火山方舟 `https://ark.cn-beijing.volces.com/api/v3` 和 `ARK_API_KEY`,最多尝试 3 次;`ARK_MODEL` 可指定端点,默认 `ep-20260721150136-98dj9`。文本、结构化工具和 PDF 请求在主渠道 Gemini 型号耗尽后进入豆包。
105
108
  - `--provider-base-url` 指向 Gemini Developer API 兼容的服务根地址;`file-api` 模式要求该服务同时兼容 Files API。
106
109
 
107
110
  常用 flag:
@@ -109,7 +112,7 @@ scriptctl ingest -s uploads/正片/ -w workspaces/正片 \
109
112
  - `--concurrency <n>`:文本 fanout 并发,默认 30。视频逐集串行。
110
113
  - `--fps <n>`:视频采样帧率(>1 解锁亚秒级时间码)。默认 3。
111
114
  - `--step`:视频每次只处理下一个未完成集。
112
- - `--upload-mode <file-api|base64>`:视频媒体传输方式,默认 `file-api`。
115
+ - `--upload-mode <file-api|base64>`:视频媒体传输方式,默认采用 provider 配置,内置 NNYYAI 为 `base64`。
113
116
  - `--video-output <dsl|json>`:视频模型输出格式,默认 `dsl`(逐行记录,输出 token 约为 JSON 的一半);`json` 使用 Schema 约束的 JSON。两种格式解析为同一结构,产物一致。模型原文逐集保存:`dsl` 为 `ep/ep_NNN.ep`,`json` 为 `ep/ep_NNN.md`。
114
117
  - `--force`:当前视频 pipeline 的**同源**工作区可清掉产物和人工跳过记录重跑(**不能换源,也不能升级旧 pipeline workspace**)。
115
118
 
@@ -163,7 +166,7 @@ scriptctl read -p workspace/script.json # 抽完当普通剧本读
163
166
  失败时 `status -j` 的 `errors[]` 按 `failure_type` / `failure_code` 给出稳定分类,并显式给出
164
167
  `retryable`。网络错误可含 `system_code`,HTTP 错误可含 `http_status`;Gemini 合规拦截可含
165
168
  `provider_source`、`provider_reason` 和 `safety_ratings`。Gemini 请求在当前路由内执行有限的传输
166
- 重试,失败后继续下一 Gemini 模型,最后由已配置的豆包重试兜底;完整模型链都失败时,`attempts[]` 保留各自的 provider、model 和
169
+ 重试,失败后继续下一 Gemini 模型;视频还会尝试已配置密钥的 GrsAI 三个型号,最后由已配置的豆包重试兜底。完整模型链都失败时,`attempts[]` 保留各自的 provider、model 和
167
170
  失败分类。`usage.json` 记录实际调用的模型和用量。`failure_summary` 汇总 retryable / terminal
168
171
  数量和 `by_type`。`state` 仍只表示管线生命周期,合规拦截表现为
169
172
  `state=failed` + `failure_type=policy`。