allyhub 0.2.0-next.4 → 0.3.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.
Files changed (106) hide show
  1. package/dist/cli-version.js +2 -0
  2. package/dist/commands/add.js +5 -5
  3. package/dist/commands/cloud-tools.js +9 -10
  4. package/dist/commands/dev.js +2 -2
  5. package/dist/commands/mcp.js +1 -1
  6. package/dist/commands/model.js +1 -1
  7. package/dist/commands/orgs.js +2 -2
  8. package/dist/commands/render.js +1 -1
  9. package/dist/commands/skills.js +3 -3
  10. package/dist/commands/voice.js +2 -2
  11. package/dist/main.js +7 -7
  12. package/dist/works.js +1 -1
  13. package/editor-app/view.html +4 -4
  14. package/editor-web/assets/chevron-left-W6sP7nB1.js +1 -0
  15. package/editor-web/assets/{chevron-up-B2IfPYC4.js → chevron-up-566GQpAm.js} +1 -1
  16. package/editor-web/assets/{dist-VsUk3S-a.js → dist-BKDr73BR.js} +4 -4
  17. package/editor-web/assets/dist-CPCNTYVu.js +8 -0
  18. package/editor-web/assets/{dist-ChU-0IAY.js → dist-C_NajYp0.js} +1 -1
  19. package/editor-web/assets/{dist-DCgVauDD.js → dist-mA6NrAKh.js} +34 -34
  20. package/editor-web/assets/{floating-ui.react-dom-C6Aeuo3L.js → floating-ui.react-dom-BUxFRF6t.js} +1 -1
  21. package/editor-web/assets/index-CFZhjTQW.js +56 -0
  22. package/editor-web/assets/index-DgU-lyX6.css +2 -0
  23. package/editor-web/assets/{react-CSPssXRH.js → react-Ff_buMj8.js} +3 -3
  24. package/editor-web/assets/{undo-2-D6qCS-P0.js → undo-2-BKFItM3X.js} +1 -1
  25. package/editor-web/index.html +4 -4
  26. package/node_modules/@allyhub-next/audio/package.json +1 -1
  27. package/node_modules/@allyhub-next/browser/package.json +1 -1
  28. package/node_modules/@allyhub-next/build/dist/scan.js +1 -1
  29. package/node_modules/@allyhub-next/build/package.json +1 -1
  30. package/node_modules/@allyhub-next/check/dist/check.js +3 -3
  31. package/node_modules/@allyhub-next/check/package.json +1 -1
  32. package/node_modules/@allyhub-next/cloud/dist/call.js +2 -2
  33. package/node_modules/@allyhub-next/cloud/dist/index.js +1 -1
  34. package/node_modules/@allyhub-next/cloud/dist/signin.js +2 -2
  35. package/node_modules/@allyhub-next/cloud/dist/token.js +1 -1
  36. package/node_modules/@allyhub-next/cloud/dist/voice.js +1 -1
  37. package/node_modules/@allyhub-next/cloud/package.json +1 -1
  38. package/node_modules/@allyhub-next/components/package.json +1 -1
  39. package/node_modules/@allyhub-next/components/r/callout.json +5 -1
  40. package/node_modules/@allyhub-next/components/r/caption-line.json +5 -1
  41. package/node_modules/@allyhub-next/components/r/caption-subtitle.json +5 -1
  42. package/node_modules/@allyhub-next/components/r/caption-word.json +5 -1
  43. package/node_modules/@allyhub-next/components/r/chart.json +5 -1
  44. package/node_modules/@allyhub-next/components/r/code-panel.json +5 -1
  45. package/node_modules/@allyhub-next/components/r/screen-demo.json +5 -1
  46. package/node_modules/@allyhub-next/dev-server/dist/live.js +1 -1
  47. package/node_modules/@allyhub-next/dev-server/dist/tools.js +1 -1
  48. package/node_modules/@allyhub-next/dev-server/package.json +1 -1
  49. package/node_modules/@allyhub-next/doc/package.json +1 -1
  50. package/node_modules/@allyhub-next/export/dist/export.d.ts +5 -2
  51. package/node_modules/@allyhub-next/export/dist/export.js +2 -2
  52. package/node_modules/@allyhub-next/export/package.json +1 -1
  53. package/node_modules/@allyhub-next/formats/dist/items.d.ts +5 -0
  54. package/node_modules/@allyhub-next/formats/dist/items.js +1 -1
  55. package/node_modules/@allyhub-next/formats/dist/layout.d.ts +2 -0
  56. package/node_modules/@allyhub-next/formats/dist/layout.js +1 -1
  57. package/node_modules/@allyhub-next/formats/package.json +1 -1
  58. package/node_modules/@allyhub-next/measure/package.json +1 -1
  59. package/node_modules/@allyhub-next/media/package.json +1 -1
  60. package/node_modules/@allyhub-next/model/dist/report.d.ts +1 -1
  61. package/node_modules/@allyhub-next/model/package.json +1 -1
  62. package/node_modules/@allyhub-next/project/package.json +1 -1
  63. package/node_modules/@allyhub-next/protocol/dist/app.d.ts +3 -0
  64. package/node_modules/@allyhub-next/protocol/dist/auth.d.ts +17 -0
  65. package/node_modules/@allyhub-next/protocol/dist/auth.js +1 -1
  66. package/node_modules/@allyhub-next/protocol/dist/cli.d.ts +1 -1
  67. package/node_modules/@allyhub-next/protocol/dist/cli.js +1 -1
  68. package/node_modules/@allyhub-next/protocol/dist/cloud.d.ts +23 -168
  69. package/node_modules/@allyhub-next/protocol/dist/cloud.js +1 -1
  70. package/node_modules/@allyhub-next/protocol/dist/editor.d.ts +4 -0
  71. package/node_modules/@allyhub-next/protocol/dist/index.d.ts +1 -0
  72. package/node_modules/@allyhub-next/protocol/dist/index.js +1 -1
  73. package/node_modules/@allyhub-next/protocol/dist/registry.d.ts +5 -1
  74. package/node_modules/@allyhub-next/protocol/dist/registry.js +1 -1
  75. package/node_modules/@allyhub-next/protocol/dist/routes.d.ts +14 -0
  76. package/node_modules/@allyhub-next/protocol/dist/routes.js +2 -0
  77. package/node_modules/@allyhub-next/protocol/dist/teams.d.ts +1 -7
  78. package/node_modules/@allyhub-next/protocol/dist/teams.js +1 -1
  79. package/node_modules/@allyhub-next/protocol/dist/tools.d.ts +1 -0
  80. package/node_modules/@allyhub-next/protocol/package.json +1 -1
  81. package/node_modules/@allyhub-next/registry/package.json +1 -1
  82. package/node_modules/@allyhub-next/render/package.json +1 -1
  83. package/node_modules/@allyhub-next/runtime/dist/page.js +1 -1
  84. package/node_modules/@allyhub-next/runtime/package.json +1 -1
  85. package/node_modules/@allyhub-next/runtimes/package.json +1 -1
  86. package/node_modules/@allyhub-next/scaffold/dist/index.js +5 -5
  87. package/node_modules/@allyhub-next/scaffold/package.json +1 -1
  88. package/node_modules/@allyhub-next/scenery/package.json +1 -1
  89. package/node_modules/@allyhub-next/scenery-host/dist/bundle.d.ts +1 -0
  90. package/node_modules/@allyhub-next/scenery-host/dist/bundle.js +3 -3
  91. package/node_modules/@allyhub-next/scenery-host/dist/model-stage.d.ts +32 -0
  92. package/node_modules/@allyhub-next/scenery-host/dist/model-stage.js +3 -0
  93. package/node_modules/@allyhub-next/scenery-host/package.json +1 -1
  94. package/node_modules/@allyhub-next/sheet/package.json +1 -1
  95. package/node_modules/@allyhub-next/site/package.json +1 -1
  96. package/node_modules/@allyhub-next/stage/package.json +1 -1
  97. package/node_modules/@allyhub-next/video-core/dist/problem.d.ts +4 -0
  98. package/node_modules/@allyhub-next/video-core/dist/problem.js +1 -1
  99. package/node_modules/@allyhub-next/video-core/package.json +1 -1
  100. package/package.json +28 -28
  101. package/dist/charge.js +0 -2
  102. package/editor-web/assets/chevron-left-B1KTa28S.js +0 -1
  103. package/editor-web/assets/dist-CuyNR3bn.js +0 -8
  104. package/editor-web/assets/index-BoYpZLq7.js +0 -56
  105. package/editor-web/assets/index-DRbemuuR.css +0 -2
  106. package/node_modules/@allyhub-next/cloud/dist/price.js +0 -2
@@ -1,4 +1,4 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{createHash as te}from"node:crypto";import{uniformInt as se}from"pure-rand/distribution/uniformInt";import{xoroshiro128plus as de}from"pure-rand/generator/xoroshiro128plus";import{ffmpegOf as he,ffmpegRuns as ge,postPlaces as we,postProblems as xe,readPng as ne}from"@allyhub-next/render";import{comboIdOf as z,comboKey as B,comboMatches as be,countLevels as ye,coverCombos as ke,cutThroughout as oe,pieceNamed as $e,problem as U,CONSOLE_LIMIT as Se}from"@allyhub-next/video-core";import{OutputCache as Me,cacheKey as Fe,checkerId as ve}from"./cache.js";import{consoleOf as Le,baseDigest as Ce,outputDigest as ie,dedupe as ae,StuckError as re,CheckSession as De,runtimeDigest as Pe,extraCombos as Ee}from"@allyhub-next/project";import{matrixFacts as Ae,measuredFacts as _e,screenFidelity as Ie,flatFrames as Oe,cutSeams as Te,flashFrames as Ke,stillPicture as Re}from"@allyhub-next/measure";import{generatedFact as je,generatedPagedFact as He,mismatchFacts as Be}from"./generated.js";import{StretchLoudness as ce,estimatePlan as Ne,loadedPlan as ze,planProblems as Ue}from"./plans.js";import{sceneryLayerFacts as Ge,withBakes as Ye}from"./scenery.js";export function checkStats(t,e,n,s=0){const i=e.length;return{loaded:e,reused:n,outputs:{total:t,checked:i,reused:Math.max(0,t-i-s),covered:s}}}const qe=new Set(["text-time","words-on-screen","text-covered","flat-frames","still-picture"]);export const plural=(t,e)=>`${String(t)} ${e}${t===1?"":"s"}`;const me=t=>de(te("sha256").update(t).digest().readInt32BE(0));export function samplesFor(t,e,n,s){const i=new Set(t.slice(0,s)),r=me(n),c=Math.floor(e*30);for(let m=0;i.size<Math.min(s,c)&&m<s*20;m++)i.add(se(r,0,c-1)/30);return[...i]}export function shuffled(t,e){const n=[...t],s=me(e);for(let i=n.length-1;i>0;i--){const r=se(s,0,i),c=n[i],m=n[r];c===void 0||m===void 0||(n[i]=m,n[r]=c)}return n}export const RASTER_NOISE=3,RASTER_SPECKS=8;export function frameDifference(t,e){const n=ne(t),s=ne(e);if(n.width!==s.width||n.height!==s.height)return{max:255,pixels:Math.max(n.width*n.height,1)};let i=0,r=0;for(let c=0;c<n.data.length;c+=4){let m=0;for(let f=0;f<4;f++)m=Math.max(m,Math.abs((n.data[c+f]??0)-(s.data[c+f]??0)));m>RASTER_NOISE&&r++,m>i&&(i=m)}return{max:i,pixels:r}}export const CUT_RECHECK={most:48,every:2};export async function recheckCuts(t,e,n,s){const i=oe(e);if(i.length===0)return e;const r=CUT_RECHECK.every/n,c=new Set;for(const f of i){const a=s.find(h=>h.key===f.part);if(!a)continue;const d=e.moments.filter(h=>h.part===f.part).map(h=>h.t).sort((h,v)=>h-v),y=f.moments[0]??f.t,x=f.moments.at(-1)??f.t,F=Math.max(a.start,d.filter(h=>h<y).at(-1)??a.start),I=Math.min(a.end-1/n,d.find(h=>h>x)??a.end),D=Math.min(CUT_RECHECK.most,Math.floor((I-F)/r)+1),R=D>1?(I-F)/(D-1):0;for(let h=0;h<D;h++){const v=Math.round((F+R*h)*n)/n;d.includes(v)||c.add(v)}}if(c.size===0)return e;const m=await t.measure([...c].sort((f,a)=>f-a));return{...e,moments:[...e.moments,...m.moments].sort((f,a)=>f.t-a.t)}}export function offFrameProblems(t,e){const n=s=>`${s.toFixed(2)} s`;return oe(t).map(s=>{const i=s.moments[0]??0,r=s.moments.at(-1)??i,c=s.moments.length===1?`at ${n(i)}, the one moment measured that shows it`:`at all ${String(s.moments.length)} moments measured that show it (${i.toFixed(2)}–${n(r)})`,m=s.worst.edges.map(f=>`${String(f.px)} pixels past the ${f.side} edge`).join(" and ");return U("AK-L002",`Text goes off the frame: the frame cuts "${s.text.text.length>40?`${s.text.text.slice(0,40)}…`:s.text.text}" ${c}; its letters reach ${m} at ${e.format} (${String(Math.round(s.worst.share*100))}% of them outside). Move it inside the frame or make it smaller (fitText in a component); text that is part of a picture the frame crops on purpose (a mocked interface) goes inside an element with data-ak-picture. allyhub inspect --kind text-off-frame has each moment.`,{piece:e.piece,output:e.output,combo:e.combo,format:e.format,part:s.part,t:s.t,where:{bbox:s.worst.glyphs}})})}const fe=t=>te("sha256").update(t).digest("hex");export async function determinism(t,e,n){const s=t.timeline,i=samplesFor(s.parts.map(a=>a.still),s.duration,e,n),r=new Map;for(const a of[...i].sort((d,y)=>d-y))r.set(a,await t.shot(a));const c=[];let m=0;for(const a of shuffled(i,`${e}:shuffle`)){const d=r.get(a),y=await t.shot(a);if(!d||fe(y)===fe(d))continue;const x=frameDifference(d,y);x.pixels>RASTER_SPECKS&&(c.push(a),m=Math.max(m,x.max))}if(c.length===0)return[];c.sort((a,d)=>a-d);const f=[...new Set(c.flatMap(a=>s.parts.filter(d=>a>=d.start&&a<d.end).map(d=>d.key)))];return[U("AK-D002",`${plural(c.length,"moment")} drew differently in order and out of order (${c.map(a=>`${a.toFixed(2)}s`).join(", ")}; pixels differ by up to ${String(m)} levels). render must depend only on t, not on state left by an earlier frame.`,{piece:t.target.piece,output:t.target.output,combo:t.target.combo,format:t.target.format,t:c[0]??0,...f.length===1&&f[0]?{part:f[0]}:{}})]}export function summarize(t){const e=ye(t);return{status:e.errors>0?"failed":e.warnings>0?"warnings":"passed",...e}}function Je(t,e){const n=r=>[r.piece??"",r.output??"",r.combo?B(r.combo):"",r.format??"",r.part??"",r.where?.file??"",String(r.where?.line??0).padStart(6,"0"),r.code].join("\0"),s=n(t),i=n(e);return s<i?-1:s>i?1:0}class Qe{spent=new Map;on=process.env.ALLYHUB_CHECK_TIMINGS==="1";async time(e,n){if(!this.on)return n();const s=performance.now();try{return await n()}finally{this.spent.set(e,(this.spent.get(e)??0)+(performance.now()-s)/1e3)}}report(){if(!this.on)return;const e=[...this.spent].sort((n,s)=>s[1]-n[1]);process.stderr.write(`Check timings (s): ${e.map(([n,s])=>`${n} ${s.toFixed(2)}`).join(", ")}
3
- `)}}function Ve(t){const e=t.options,n=e.post,s=typeof n=="object"&&n!==null?n.filter:void 0;return{transparent:e.transparent===!0,deliverable:e.deliverable!==!1,...typeof e.maxDuration=="number"?{maxDuration:e.maxDuration}:{},...typeof s=="string"?{post:s}:{}}}async function We(t){let e;try{e=await he(t===void 0?{}:{ffmpeg:t})}catch(n){return{missing:`the pinned ffmpeg could not be had: ${(n instanceof Error?n.message:String(n)).split(`
4
- `)[0]??""}`}}return await ge(e)?{ffmpeg:e}:{missing:`${e} does not run`}}export async function runCheck(t){const e=new Qe,n=t.session??await e.time("prepare (discover, metadata, bundle)",()=>De.open({root:t.dir,...t.buildDir===void 0?{}:{buildDir:t.buildDir},...t.gpu===void 0?{}:{gpu:t.gpu},...t.consoleLimit===void 0?{}:{consoleLimit:t.consoleLimit}}));try{const s=n.prepared,i=t.samples??12,r=n.consoleLimit,c=await Pe(),m=await Me.open(s.root,await ve({runtimeSha:c,samples:i,gpu:n.gpu,...r===Se?{}:{consoleLimit:r}}));let f;const a=()=>f??=We(t.ffmpeg),d=[],y=[...s.problems],x=[],F=[],I=[],D=[];let R=0,h=0;const v=[];let L;const le=s.pieces.filter(o=>t.piece===void 0||$e({id:o.id,path:o.place},t.piece));for(const o of le){if(!s.bundle?.chunks.entries[o.id.replaceAll("/","__")])continue;const P=Ve(o),E=o.kind,O=o.format,Y=await Ce(s,o,c);x.push(...Ae(o));const q=new Set,j=[...o.combos.filter(u=>!t.combos?.length||t.combos.some(p=>be(u,p,z(o.matrix,u)))),...Ee(o,t.combos)],pe=new Set(o.matrix?ke(o.matrix.space,o.dims,j):j.keys()),A=[],J=u=>{I.push(u);for(const p of u.reads?.media??[])q.add(p.path);u.timeline&&A.push({...u,timeline:u.timeline})};let N=!1;const ue=(u,p,_)=>{if(t.fresh===!0)return;const l=m.get(u),$=l?.result.reads,M=l?.result.timeline;return!l||!$||!M||_&&!l.sampled||l.factless===!0&&t.facts!==!1?void 0:ie(s,Y,o,p,$,M.parts.map(g=>g.key))===l.digest?l:void 0};for(const[u,p]of j.entries()){const _=z(o.matrix,p),l={piece:o.id,output:E,combo:p,format:O},$=B(p);if(F.push({...l,..._?{id:_}:{},key:$}),!pe.has(u)){h++;continue}const M=o.kind==="video"&&!N,C=Fe(o.id,E,$,O),g=ue(C,p,M);if(g){R++,g.sampled&&(N=!0),y.push(...g.problems),x.push(...g.facts),d.push({version:l,log:g.console}),J({...g.result,digest:g.digest}),m.set(C,g);continue}D.push(`${o.id}/${E}/${$}/${O}`);const w=await e.time("load (mount, stills)",()=>n.open(o,l));try{const k=w.summary,T=[],S=[...k?.facts??[]];let Q=!1;try{if(k){const b=k.timeline;try{const K=await e.time("layout measure",()=>w.measure());T.push(...offFrameProblems(await e.time("recheck cut text",()=>recheckCuts(w,K,b.fps,b.parts)),l));const ee=await a();S.push(...await e.time("layout facts",()=>_e(K,b,l,{root:s.root,media:s.media,ffmpeg:"ffmpeg"in ee?ee.ffmpeg:"ffmpeg"},b.profile?.label)))}catch(K){if(K instanceof re)throw K}t.facts!==!1&&S.push(...await e.time("scenery facts",()=>Ge(w)))}k&&M&&(N=!0,Q=!0,T.push(...await e.time("determinism",()=>determinism(w,`${o.id}:${E}:${O}`,i))),t.facts!==!1&&!P.transparent&&S.push(...await e.time("flat frames",()=>Oe(w))),t.facts!==!1&&S.push(...await e.time("still picture",()=>Re(w))),t.facts!==!1&&(S.push(...await e.time("cut seams",()=>Te(w))),P.transparent||S.push(...await e.time("flash",()=>Ke(w))))),k&&t.facts!==!1&&S.push(...(await e.time("scenery screen fidelity",()=>Ie(w))).facts)}catch(b){if(!(b instanceof re))throw b;b.kept||T.push(U("AK-P003",b.message,{piece:l.piece,output:l.output,combo:l.combo,format:l.format}))}T.push(...await w.problems());const V=ae(T);y.push(...V);const W=P.deliverable?S:S.filter(b=>!qe.has(b.kind));x.push(...W);const X=await w.console();d.push({version:l,log:X});const H=k?ie(s,Y,o,p,k.reads,k.timeline.parts.map(b=>b.key)):void 0,Z={...l,...k?{timeline:k.timeline,reads:k.reads,text:k.text}:{}};J({...Z,...H?{digest:H}:{}}),H&&m.set(C,{digest:H,result:Z,problems:V,facts:W,sampled:Q,...t.facts===!1?{factless:!0}:{},console:X})}finally{await w.close()}}if(o.kind==="video"){const u=we(P.post,A.map(p=>p.timeline));if(u.length>0){const p=await a();"ffmpeg"in p&&y.push(...await e.time("post chains",()=>xe(u,{piece:o.id,output:E},p.ffmpeg)))}}if(o.kind!=="video")for(const u of A){const p=He(u,s.media);p&&x.push(p)}else for(const u of j){const p=z(o.matrix,u),_=B(u),l=A.find(g=>B(g.combo)===_),$=l?ze(l,p):Ne(o,u,p,E,O,A);if(!$)continue;if(v.push($),o.matrix&&L===void 0){const g=await a();if("ffmpeg"in g){const w=new ce(s.root,g.ffmpeg);await w.open(),L=w}else L={missing:"ffmpeg",why:`${g.missing} (set ALLYHUB_FFMPEG to an ffmpeg, or let the runtime manager fetch its pinned one)`}}const M=await e.time("plans (loudness, music)",()=>Ue($,{...P.maxDuration===void 0?{}:{maxDuration:P.maxDuration},fps:l?.timeline.fps??A[0]?.timeline.fps??30,media:s.media,local:g=>n.mediaPath(g),...o.matrix&&L?{loudness:L}:{}}));y.push(...M.problems),x.push(...M.facts);const C=je($,s.media);C&&x.push(C)}x.push(...Be({piece:o.id},q,s.media))}L instanceof ce&&await L.save(),await m.save(new Set(s.pieces.map(o=>o.id)));const G=ae(y).sort(Je);return e.report(),{targets:F,check:summarize(G),problems:G,facts:await Ye(x,s.root),results:I,plans:v,stats:checkStats(F.length,D,R,h),console:Le(r,d)}}finally{t.session||await n.close()}}
2
+ import{createHash as te}from"node:crypto";import{join as se}from"node:path";import{uniformInt as ne}from"pure-rand/distribution/uniformInt";import{xoroshiro128plus as ge}from"pure-rand/generator/xoroshiro128plus";import{CACHE_DIR as we}from"@allyhub-next/formats";import{buildModule as xe}from"@allyhub-next/model/build";import{ffmpegOf as be,ffmpegRuns as ye,postPlaces as $e,postProblems as ke,readPng as oe}from"@allyhub-next/render";import{comboIdOf as U,comboKey as B,comboMatches as Se,countLevels as Me,coverCombos as Fe,cutThroughout as ie,pieceNamed as ae,problem as N,CONSOLE_LIMIT as ve}from"@allyhub-next/video-core";import{OutputCache as Ce,cacheKey as Le,checkerId as De}from"./cache.js";import{consoleOf as Ee,baseDigest as Ae,outputDigest as re,dedupe as ce,StuckError as me,CheckSession as Pe,runtimeDigest as _e,extraCombos as Ie}from"@allyhub-next/project";import{matrixFacts as Ke,measuredFacts as Oe,screenFidelity as Te,flatFrames as Re,cutSeams as je,flashFrames as He,stillPicture as Be}from"@allyhub-next/measure";import{generatedFact as Ne,generatedPagedFact as ze,mismatchFacts as Ue}from"./generated.js";import{StretchLoudness as fe,estimatePlan as Ge,loadedPlan as Ye,planProblems as qe}from"./plans.js";import{sceneryLayerFacts as Je,withBakes as Qe}from"./scenery.js";export function checkStats(s,t,n,e=0){const i=t.length;return{loaded:t,reused:n,outputs:{total:s,checked:i,reused:Math.max(0,s-i-e),covered:e}}}const Ve=new Set(["text-time","words-on-screen","text-covered","flat-frames","still-picture"]);export const plural=(s,t)=>`${String(s)} ${t}${s===1?"":"s"}`;const le=s=>ge(te("sha256").update(s).digest().readInt32BE(0));export function samplesFor(s,t,n,e){const i=new Set(s.slice(0,e)),r=le(n),c=Math.floor(t*30);for(let m=0;i.size<Math.min(e,c)&&m<e*20;m++)i.add(ne(r,0,c-1)/30);return[...i]}export function shuffled(s,t){const n=[...s],e=le(t);for(let i=n.length-1;i>0;i--){const r=ne(e,0,i),c=n[i],m=n[r];c===void 0||m===void 0||(n[i]=m,n[r]=c)}return n}export const RASTER_NOISE=3,RASTER_SPECKS=8;export function frameDifference(s,t){const n=oe(s),e=oe(t);if(n.width!==e.width||n.height!==e.height)return{max:255,pixels:Math.max(n.width*n.height,1)};let i=0,r=0;for(let c=0;c<n.data.length;c+=4){let m=0;for(let f=0;f<4;f++)m=Math.max(m,Math.abs((n.data[c+f]??0)-(e.data[c+f]??0)));m>RASTER_NOISE&&r++,m>i&&(i=m)}return{max:i,pixels:r}}export const CUT_RECHECK={most:48,every:2};export async function recheckCuts(s,t,n,e){const i=ie(t);if(i.length===0)return t;const r=CUT_RECHECK.every/n,c=new Set;for(const f of i){const a=e.find(h=>h.key===f.part);if(!a)continue;const p=t.moments.filter(h=>h.part===f.part).map(h=>h.t).sort((h,C)=>h-C),x=f.moments[0]??f.t,b=f.moments.at(-1)??f.t,v=Math.max(a.start,p.filter(h=>h<x).at(-1)??a.start),I=Math.min(a.end-1/n,p.find(h=>h>b)??a.end),E=Math.min(CUT_RECHECK.most,Math.floor((I-v)/r)+1),R=E>1?(I-v)/(E-1):0;for(let h=0;h<E;h++){const C=Math.round((v+R*h)*n)/n;p.includes(C)||c.add(C)}}if(c.size===0)return t;const m=await s.measure([...c].sort((f,a)=>f-a));return{...t,moments:[...t.moments,...m.moments].sort((f,a)=>f.t-a.t)}}export function offFrameProblems(s,t){const n=e=>`${e.toFixed(2)} s`;return ie(s).map(e=>{const i=e.moments[0]??0,r=e.moments.at(-1)??i,c=e.moments.length===1?`at ${n(i)}, the one moment measured that shows it`:`at all ${String(e.moments.length)} moments measured that show it (${i.toFixed(2)}–${n(r)})`,m=e.worst.edges.map(f=>`${String(f.px)} pixels past the ${f.side} edge`).join(" and ");return N("AK-L002",`Text goes off the frame: the frame cuts "${e.text.text.length>40?`${e.text.text.slice(0,40)}…`:e.text.text}" ${c}; its letters reach ${m} at ${t.format} (${String(Math.round(e.worst.share*100))}% of them outside). Move it inside the frame or make it smaller (fitText in a component); text that is part of a picture the frame crops on purpose (a mocked interface) goes inside an element with data-ak-picture. allyhub inspect --kind text-off-frame has each moment.`,{piece:t.piece,output:t.output,combo:t.combo,format:t.format,part:e.part,t:e.t,where:{bbox:e.worst.glyphs}})})}const de=s=>te("sha256").update(s).digest("hex");export async function determinism(s,t,n){const e=s.timeline,i=samplesFor(e.parts.map(a=>a.still),e.duration,t,n),r=new Map;for(const a of[...i].sort((p,x)=>p-x))r.set(a,await s.shot(a));const c=[];let m=0;for(const a of shuffled(i,`${t}:shuffle`)){const p=r.get(a),x=await s.shot(a);if(!p||de(x)===de(p))continue;const b=frameDifference(p,x);b.pixels>RASTER_SPECKS&&(c.push(a),m=Math.max(m,b.max))}if(c.length===0)return[];c.sort((a,p)=>a-p);const f=[...new Set(c.flatMap(a=>e.parts.filter(p=>a>=p.start&&a<p.end).map(p=>p.key)))];return[N("AK-D002",`${plural(c.length,"moment")} drew differently in order and out of order (${c.map(a=>`${a.toFixed(2)}s`).join(", ")}; pixels differ by up to ${String(m)} levels). render must depend only on t, not on state left by an earlier frame.`,{piece:s.target.piece,output:s.target.output,combo:s.target.combo,format:s.target.format,t:c[0]??0,...f.length===1&&f[0]?{part:f[0]}:{}})]}export function summarize(s){const t=Me(s);return{status:t.errors>0?"failed":t.warnings>0?"warnings":"passed",...t}}function We(s,t){const n=r=>[r.piece??"",r.output??"",r.combo?B(r.combo):"",r.format??"",r.part??"",r.where?.file??"",String(r.where?.line??0).padStart(6,"0"),r.code].join("\0"),e=n(s),i=n(t);return e<i?-1:e>i?1:0}class Xe{spent=new Map;on=process.env.ALLYHUB_CHECK_TIMINGS==="1";async time(t,n){if(!this.on)return n();const e=performance.now();try{return await n()}finally{this.spent.set(t,(this.spent.get(t)??0)+(performance.now()-e)/1e3)}}report(){if(!this.on)return;const t=[...this.spent].sort((n,e)=>e[1]-n[1]);process.stderr.write(`Check timings (s): ${t.map(([n,e])=>`${n} ${e.toFixed(2)}`).join(", ")}
3
+ `)}}function Ze(s){const t=s.options,n=t.post,e=typeof n=="object"&&n!==null?n.filter:void 0;return{transparent:t.transparent===!0,deliverable:t.deliverable!==!1,...typeof t.maxDuration=="number"?{maxDuration:t.maxDuration}:{},...typeof e=="string"?{post:e}:{}}}async function et(s){let t;try{t=await be(s===void 0?{}:{ffmpeg:s})}catch(n){return{missing:`the pinned ffmpeg could not be had: ${(n instanceof Error?n.message:String(n)).split(`
4
+ `)[0]??""}`}}return await ye(t)?{ffmpeg:t}:{missing:`${t} does not run`}}export async function runCheck(s){const t=new Xe,n=s.session??await t.time("prepare (discover, metadata, bundle)",()=>Pe.open({root:s.dir,...s.buildDir===void 0?{}:{buildDir:s.buildDir},...s.gpu===void 0?{}:{gpu:s.gpu},...s.consoleLimit===void 0?{}:{consoleLimit:s.consoleLimit}}));try{const e=n.prepared,i=s.samples??12,r=n.consoleLimit,c=await _e(),m=await Ce.open(e.root,await De({runtimeSha:c,samples:i,gpu:n.gpu,...r===ve?{}:{consoleLimit:r}}));let f;const a=()=>f??=et(s.ffmpeg),p=[],x=[...e.problems],b=[],v=[],I=[],E=[];let R=0,h=0;const C=[];let L;const ue=e.pieces.filter(o=>s.piece===void 0||ae({id:o.id,path:o.place},s.piece));for(const o of ue){if(!e.bundle?.chunks.entries[o.id.replaceAll("/","__")])continue;const S=Ze(o),A=o.kind,K=o.format,Y=await Ae(e,o,c);b.push(...Ke(o));const q=new Set,j=[...o.combos.filter(u=>!s.combos?.length||s.combos.some(d=>Se(u,d,U(o.matrix,u)))),...Ie(o,s.combos)],pe=new Set(o.matrix?Fe(o.matrix.space,o.dims,j):j.keys()),P=[],J=u=>{I.push(u);for(const d of u.reads?.media??[])q.add(d.path);u.timeline&&P.push({...u,timeline:u.timeline})};let z=!1;const he=(u,d,_)=>{if(s.fresh===!0)return;const l=m.get(u),k=l?.result.reads,F=l?.result.timeline;return!l||!k||!F||_&&!l.sampled||l.factless===!0&&s.facts!==!1?void 0:re(e,Y,o,d,k,F.parts.map(g=>g.key))===l.digest?l:void 0};for(const[u,d]of j.entries()){const _=U(o.matrix,d),l={piece:o.id,output:A,combo:d,format:K},k=B(d);if(v.push({...l,..._?{id:_}:{},key:k}),!pe.has(u)){h++;continue}const F=o.kind==="video"&&!z,D=Le(o.id,A,k,K),g=he(D,d,F);if(g){R++,g.sampled&&(z=!0),x.push(...g.problems),b.push(...g.facts),p.push({version:l,log:g.console}),J({...g.result,digest:g.digest}),m.set(D,g);continue}E.push(`${o.id}/${A}/${k}/${K}`);const w=await t.time("load (mount, stills)",()=>n.open(o,l));try{const $=w.summary,O=[],M=[...$?.facts??[]];let Q=!1;try{if($){const y=$.timeline;try{const T=await t.time("layout measure",()=>w.measure());O.push(...offFrameProblems(await t.time("recheck cut text",()=>recheckCuts(w,T,y.fps,y.parts)),l));const ee=await a();M.push(...await t.time("layout facts",()=>Oe(T,y,l,{root:e.root,media:e.media,ffmpeg:"ffmpeg"in ee?ee.ffmpeg:"ffmpeg"},y.profile?.label)))}catch(T){if(T instanceof me)throw T}s.facts!==!1&&M.push(...await t.time("scenery facts",()=>Je(w)))}$&&F&&(z=!0,Q=!0,O.push(...await t.time("determinism",()=>determinism(w,`${o.id}:${A}:${K}`,i))),s.facts!==!1&&!S.transparent&&M.push(...await t.time("flat frames",()=>Re(w))),s.facts!==!1&&M.push(...await t.time("still picture",()=>Be(w))),s.facts!==!1&&(M.push(...await t.time("cut seams",()=>je(w))),S.transparent||M.push(...await t.time("flash",()=>He(w))))),$&&s.facts!==!1&&M.push(...(await t.time("scenery screen fidelity",()=>Te(w))).facts)}catch(y){if(!(y instanceof me))throw y;y.kept||O.push(N("AK-P003",y.message,{piece:l.piece,output:l.output,combo:l.combo,format:l.format}))}O.push(...await w.problems());const V=ce(O);x.push(...V);const W=S.deliverable?M:M.filter(y=>!Ve.has(y.kind));b.push(...W);const X=await w.console();p.push({version:l,log:X});const H=$?re(e,Y,o,d,$.reads,$.timeline.parts.map(y=>y.key)):void 0,Z={...l,...$?{timeline:$.timeline,reads:$.reads,text:$.text}:{}};J({...Z,...H?{digest:H}:{}}),H&&m.set(D,{digest:H,result:Z,problems:V,facts:W,sampled:Q,...s.facts===!1?{factless:!0}:{},console:X})}finally{await w.close()}}if(o.kind==="video"){const u=$e(S.post,P.map(d=>d.timeline));if(u.length>0){const d=await a();"ffmpeg"in d&&x.push(...await t.time("post chains",()=>ke(u,{piece:o.id,output:A},d.ffmpeg)))}}if(o.kind!=="video")for(const u of P){const d=ze(u,e.media);d&&b.push(d)}else for(const u of j){const d=U(o.matrix,u),_=B(u),l=P.find(g=>B(g.combo)===_),k=l?Ye(l,d):Ge(o,u,d,A,K,P);if(!k)continue;if(C.push(k),o.matrix&&L===void 0){const g=await a();if("ffmpeg"in g){const w=new fe(e.root,g.ffmpeg);await w.open(),L=w}else L={missing:"ffmpeg",why:`${g.missing} (set ALLYHUB_FFMPEG to an ffmpeg, or let the runtime manager fetch its pinned one)`}}const F=await t.time("plans (loudness, music)",()=>qe(k,{...S.maxDuration===void 0?{}:{maxDuration:S.maxDuration},fps:l?.timeline.fps??P[0]?.timeline.fps??30,media:e.media,local:g=>n.mediaPath(g),...o.matrix&&L?{loudness:L}:{}}));x.push(...F.problems),b.push(...F.facts);const D=Ne(k,e.media);D&&b.push(D)}b.push(...Ue({piece:o.id},q,e.media))}for(const o of e.items)if(o.kind==="model"&&!(s.piece!==void 0&&!ae({id:o.dir,path:o.dir},s.piece)))try{await t.time("models",()=>xe(se(e.root,o.entry),{root:e.root,cache:se(e.root,we,"model"),log:()=>{}}))}catch(S){x.push(N("AK-B010",`${o.entry} does not build: ${S instanceof Error?S.message:String(S)}`,{piece:o.dir,where:{file:o.entry}}))}L instanceof fe&&await L.save(),await m.save(new Set(e.pieces.map(o=>o.id)));const G=ce(x).sort(We);return t.report(),{targets:v,check:summarize(G),problems:G,facts:await Qe(b,e.root),results:I,plans:C,stats:checkStats(v.length,E,R,h),console:Ee(r,p)}}finally{s.session||await n.close()}}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/check",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,3 +1,3 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{mkdir as A,readdir as I,readFile as C,rm as M,writeFile as E}from"node:fs/promises";import{basename as T,dirname as F,extname as N,join as b}from"node:path";import{EXIT as w,FileContentResponseSchema as W,ToolCallSchema as O,callPath as L,fileContentPath as B,parseAllyFilePath as G,toolCallsPath as z}from"@allyhub-next/protocol";import{genFileFor as P}from"@allyhub-next/formats";import{CloudError as f}from"./errors.js";import{genRecord as K,inputHash as U,namedFile as X,readGen as Y,toolWords as Z,writeGen as q,writeWords as Q}from"./records.js";import{withKnownUploads as V}from"./uploads.js";export const declined=t=>new f("declined",`The person did not allow the credits for ${t}: nothing was called or charged.`,{hint:"Ask them what to do instead, or call it again to ask again."}),WAIT_SECONDS=900,POLL_SECONDS={least:1,most:60},RETRY_SECONDS={least:1,most:30},READ_SECONDS=120;const tt=new Set([408,429,500,502,503,504]),et=t=>t instanceof f&&(t.code==="network"||t.status!==void 0&&tt.has(t.status)),x=t=>b(t.config.cacheDir,"jobs"),y=(t,n)=>b(x(t),`${n.replace(/[^A-Za-z0-9_-]/gu,"_")}.json`);async function nt(t,n,i){await A(F(y(t,n)),{recursive:!0}),await E(y(t,n),`${JSON.stringify(i,null,2)}
3
- `)}const D=(t,n)=>M(y(t,n),{force:!0});async function at(t,n,i){const e=await I(x(t)).catch(()=>[]);for(const o of e.filter(s=>s.endsWith(".json")).sort()){const s=await C(b(x(t),o),"utf8").then(r=>JSON.parse(r)).catch(()=>{});if(s?.jobId!==void 0&&s.file===n&&s.inputHash===i)return s.jobId}}async function ot(t,n){try{return JSON.parse(await C(y(t,n),"utf8"))}catch{throw new f("unknown-job",`No job ${n} was started on this computer.`,{exit:w.notFound,hint:"Call the tool again: that is a new call."})}}async function it(t,n,i,e,o){const{url:s}=W.parse(await t.json(B(n,"attachment"),{signal:R(o)}));let r;try{const l=await t.config.fetch(s,o?{signal:o}:{});if(!l.ok)throw new f("download-failed",`Downloading ${i} answered ${String(l.status)}.`,{exit:w.platform,status:l.status});r=Buffer.from(await l.arrayBuffer())}catch(l){throw l instanceof f||o?.aborted===!0?l:new f("network",`Downloading ${i} failed: ${l instanceof Error?l.message:String(l)}`,{exit:w.platform,cause:l})}const c="dir"in e?b(e.dir,T(i)):N(e.file)?e.file:`${e.file}${N(i)}`;return await A(F(c),{recursive:!0}),await E(c,r),c}const R=t=>t?AbortSignal.any([t,AbortSignal.timeout(READ_SECONDS*1e3)]):AbortSignal.timeout(READ_SECONDS*1e3),v=(t,n)=>new Promise((i,e)=>{const o=setTimeout(i,t*1e3);n?.addEventListener("abort",()=>{clearTimeout(o),e(n.reason instanceof Error?n.reason:new Error("Stopped."))},{once:!0})});export async function callTool(t,n,i,e){const o=e.file,s=await V(t,i,e.base),r=U(n,s);let c=e.jobId,l=!1;if(o!==void 0&&c===void 0){c=await at(t,o,r);const u=c===void 0?await X(o):void 0,d=u?await Y(u):void 0;if(e.force===!0)l=d!==void 0;else{if(u&&!d)throw new f("file-exists",`${u} is there, without a record (${T(P(u))}) saying a tool call made it.`,{exit:w.conflict,hint:"Choose another --file, or pass --force to replace it."});if(u&&d){if(d.tool===n&&d.inputHash===r)return{tool:n,output:d.output??{},file:u,gen:P(u),kept:{at:d.at}};throw new f("file-exists",`${u} was made by ${d.tool??"another tool"} from another input on ${d.at}.`,{exit:w.conflict,hint:"Choose another --file to keep it, or pass --force to make it again over it (charged again)."})}}}if(c===void 0&&e.approve!==void 0&&!await e.approve())throw declined(n);const p=Date.now()+(e.timeoutSeconds??WAIT_SECONDS)*1e3,g=e.signal;let a=c===void 0?O.parse(await t.json(z(n),{method:"POST",body:{input:i},...g?{signal:g}:{}})):await J(t,c,p,e);a.status==="working"&&c===void 0&&await nt(t,a.id,{jobId:a.id,tool:n,input:{...i},inputHash:r,outDir:e.outDir,...o===void 0?{}:{file:o}});const H=()=>({tool:n,jobId:a.id,status:"running",elapsedSeconds:(Date.now()-Date.parse(a.createdAt))/1e3,...o===void 0?{}:{file:o}});for(;a.status==="working";){if(e.wait===!1||Date.now()>p)return H();e.onRunning?.(a),await v(Math.min(Math.max((a.pollAfterMs??0)/1e3,POLL_SECONDS.least),POLL_SECONDS.most),g),a=await J(t,a.id,p,e)}if(a.status==="failed")throw await D(t,a.id),new f(`tool-${(a.error?.kind??"failed").replaceAll("_","-")}`,a.error?.message??"The tool failed.",{exit:a.error?.kind==="input"?w.usage:w.platform,hint:a.error?.kind==="input"||a.error?.kind==="not_found"?"A failed call is not charged. Change the input and call again.":"A failed call is not charged. Call it again; a supplier's failure often passes."});const h=typeof a.output=="object"&&a.output!==null?a.output:{value:a.output},j=h.allyFilePath,S=typeof j=="string"?G(j):void 0,m={tool:n,output:h,...a.credits===void 0?{}:{credits:a.credits}};if(S){const u=a.id,d=await _(u,()=>it(t,S.id,S.name,o!==void 0?{file:o}:{dir:e.outDir},g),p,e);m.file=d,m.gen=await q(d,await K(d,{tool:n,input:s,output:h,credits:a.credits}));const k=Z(h),$=h.durationSeconds;k&&(m.words=await Q(d,k,typeof $=="number"&&$>=0?$:void 0)),l&&(m.replaced=!0)}return await D(t,a.id),m}async function J(t,n,i,e){try{return await _(n,async()=>O.parse(await t.json(L(n),{signal:R(e.signal)})),i,e)}catch(o){throw o instanceof f&&o.status===404&&await D(t,n),o}}async function _(t,n,i,e){const o=e.retrySeconds??RETRY_SECONDS;for(let s=o.least;;)try{return await n()}catch(r){if(!et(r)||e.signal?.aborted===!0)throw r;if(Date.now()+s*1e3>i)throw new f(r.code,r.message,{exit:r.exit,...r.status===void 0?{}:{status:r.status},hint:`Call ${t} goes on at AllyHub, charged once when it works: --resume ${t} waits for it again (so does the same call to the same --file).`,cause:r});e.onRetry?.(t,r,s),await v(s,e.signal),s=Math.min(s*2,o.most)}}export async function resumeJob(t,n,i){const e=await ot(t,n);if(i.tool!==void 0&&i.tool!==e.tool)throw new f("wrong-job",`Job ${n} is ${e.tool}'s, not ${i.tool}'s.`,{exit:w.usage,hint:`Resume it with ${e.tool}.`});return await callTool(t,e.tool,e.input,{...i,outDir:e.outDir,...e.file===void 0?{}:{file:e.file},jobId:n})}
2
+ import{mkdir as k,readdir as I,readFile as A,rm as M,writeFile as E}from"node:fs/promises";import{basename as T,dirname as F,extname as N,join as b}from"node:path";import{EXIT as w,FileContentResponseSchema as W,ToolCallSchema as O,callPath as L,fileContentPath as B,parseAllyFilePath as G,toolCallsPath as z}from"@allyhub-next/protocol";import{genFileFor as P}from"@allyhub-next/formats";import{CloudError as d}from"./errors.js";import{genRecord as K,inputHash as U,namedFile as X,readGen as Y,toolWords as Z,writeGen as q,writeWords as Q}from"./records.js";import{withKnownUploads as V}from"./uploads.js";export const WAIT_SECONDS=900,POLL_SECONDS={least:1,most:60},RETRY_SECONDS={least:1,most:30},READ_SECONDS=120;const tt=new Set([408,429,500,502,503,504]),et=t=>t instanceof d&&(t.code==="network"||t.status!==void 0&&tt.has(t.status)),x=t=>b(t.config.cacheDir,"jobs"),y=(t,n)=>b(x(t),`${n.replace(/[^A-Za-z0-9_-]/gu,"_")}.json`);async function nt(t,n,i){await k(F(y(t,n)),{recursive:!0}),await E(y(t,n),`${JSON.stringify(i,null,2)}
3
+ `)}const D=(t,n)=>M(y(t,n),{force:!0});async function at(t,n,i){const e=await I(x(t)).catch(()=>[]);for(const o of e.filter(s=>s.endsWith(".json")).sort()){const s=await A(b(x(t),o),"utf8").then(r=>JSON.parse(r)).catch(()=>{});if(s?.jobId!==void 0&&s.file===n&&s.inputHash===i)return s.jobId}}async function ot(t,n){try{return JSON.parse(await A(y(t,n),"utf8"))}catch{throw new d("unknown-job",`No job ${n} was started on this computer.`,{exit:w.notFound,hint:"Call the tool again: that is a new call."})}}async function it(t,n,i,e,o){const{url:s}=W.parse(await t.json(B(n,"attachment"),{signal:R(o)}));let r;try{const l=await t.config.fetch(s,o?{signal:o}:{});if(!l.ok)throw new d("download-failed",`Downloading ${i} answered ${String(l.status)}.`,{exit:w.platform,status:l.status});r=Buffer.from(await l.arrayBuffer())}catch(l){throw l instanceof d||o?.aborted===!0?l:new d("network",`Downloading ${i} failed: ${l instanceof Error?l.message:String(l)}`,{exit:w.platform,cause:l})}const u="dir"in e?b(e.dir,T(i)):N(e.file)?e.file:`${e.file}${N(i)}`;return await k(F(u),{recursive:!0}),await E(u,r),u}const R=t=>t?AbortSignal.any([t,AbortSignal.timeout(READ_SECONDS*1e3)]):AbortSignal.timeout(READ_SECONDS*1e3),J=(t,n)=>new Promise((i,e)=>{const o=setTimeout(i,t*1e3);n?.addEventListener("abort",()=>{clearTimeout(o),e(n.reason instanceof Error?n.reason:new Error("Stopped."))},{once:!0})});export async function callTool(t,n,i,e){const o=e.file,s=await V(t,i,e.base),r=U(n,s);let u=e.jobId,l=!1;if(o!==void 0&&u===void 0){u=await at(t,o,r);const c=u===void 0?await X(o):void 0,f=c?await Y(c):void 0;if(e.force===!0)l=f!==void 0;else{if(c&&!f)throw new d("file-exists",`${c} is there, without a record (${T(P(c))}) saying a tool call made it.`,{exit:w.conflict,hint:"Choose another --file, or pass --force to replace it."});if(c&&f){if(f.tool===n&&f.inputHash===r)return{tool:n,output:f.output??{},file:c,gen:P(c),kept:{at:f.at}};throw new d("file-exists",`${c} was made by ${f.tool??"another tool"} from another input on ${f.at}.`,{exit:w.conflict,hint:"Choose another --file to keep it, or pass --force to make it again over it (charged again)."})}}}const p=Date.now()+(e.timeoutSeconds??WAIT_SECONDS)*1e3,g=e.signal;let a=u===void 0?O.parse(await t.json(z(n),{method:"POST",body:{input:i},...g?{signal:g}:{}})):await _(t,u,p,e);a.status==="working"&&u===void 0&&await nt(t,a.id,{jobId:a.id,tool:n,input:{...i},inputHash:r,outDir:e.outDir,...o===void 0?{}:{file:o}});const H=()=>({tool:n,jobId:a.id,status:"running",elapsedSeconds:(Date.now()-Date.parse(a.createdAt))/1e3,...o===void 0?{}:{file:o}});for(;a.status==="working";){if(e.wait===!1||Date.now()>p)return H();e.onRunning?.(a),await J(Math.min(Math.max((a.pollAfterMs??0)/1e3,POLL_SECONDS.least),POLL_SECONDS.most),g),a=await _(t,a.id,p,e)}if(a.status==="failed")throw await D(t,a.id),new d(`tool-${(a.error?.kind??"failed").replaceAll("_","-")}`,a.error?.message??"The tool failed.",{exit:a.error?.kind==="input"?w.usage:w.platform,hint:a.error?.kind==="input"||a.error?.kind==="not_found"?"A failed call is not charged. Change the input and call again.":"A failed call is not charged. Call it again; a supplier's failure often passes."});const h=typeof a.output=="object"&&a.output!==null?a.output:{value:a.output},j=h.allyFilePath,S=typeof j=="string"?G(j):void 0,m={tool:n,output:h,...a.credits===void 0?{}:{credits:a.credits}};if(S){const c=a.id,f=await v(c,()=>it(t,S.id,S.name,o!==void 0?{file:o}:{dir:e.outDir},g),p,e);m.file=f,m.gen=await q(f,await K(f,{tool:n,input:s,output:h,credits:a.credits}));const C=Z(h),$=h.durationSeconds;C&&(m.words=await Q(f,C,typeof $=="number"&&$>=0?$:void 0)),l&&(m.replaced=!0)}return await D(t,a.id),m}async function _(t,n,i,e){try{return await v(n,async()=>O.parse(await t.json(L(n),{signal:R(e.signal)})),i,e)}catch(o){throw o instanceof d&&o.status===404&&await D(t,n),o}}async function v(t,n,i,e){const o=e.retrySeconds??RETRY_SECONDS;for(let s=o.least;;)try{return await n()}catch(r){if(!et(r)||e.signal?.aborted===!0)throw r;if(Date.now()+s*1e3>i)throw new d(r.code,r.message,{exit:r.exit,...r.status===void 0?{}:{status:r.status},hint:`Call ${t} goes on at AllyHub, charged once when it works: --resume ${t} waits for it again (so does the same call to the same --file).`,cause:r});e.onRetry?.(t,r,s),await J(s,e.signal),s=Math.min(s*2,o.most)}}export async function resumeJob(t,n,i){const e=await ot(t,n);if(i.tool!==void 0&&i.tool!==e.tool)throw new d("wrong-job",`Job ${n} is ${e.tool}'s, not ${i.tool}'s.`,{exit:w.usage,hint:`Resume it with ${e.tool}.`});return await callTool(t,e.tool,e.input,{...i,outDir:e.outDir,...e.file===void 0?{}:{file:e.file},jobId:n})}
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- export{cloudConfig,hostsText}from"./config.js";export{CloudError,notSignedIn,SIGN_IN}from"./errors.js";export{Cloud,scrub}from"./client.js";export{SignedOut,accessOf,accountOf,authServer,chooseOrganization,forgetCredentials,readCredentials,signInWithCode,signInWithDevice,signOut,writeCredentials}from"./credentials.js";export{SIGN_IN_WAIT_MS,openBrowser,signIn,signInDevice,signInInBackground,signedInAs}from"./signin.js";export{LoopbackError,startLoopback}from"./loopback.js";export{pkcePair,randomState}from"./pkce.js";export{accessToken,billingSummary,organizations}from"./token.js";export{CATALOG_CACHE_SECONDS,findTool,loadCatalog}from"./catalog.js";export{estimatePrice,priceCalls,priceText,withDefaults}from"./price.js";export{POLL_SECONDS,READ_SECONDS,RETRY_SECONDS,WAIT_SECONDS,callTool,declined,resumeJob}from"./call.js";export{REUSE_LEFT_MS,UPLOAD_CONCURRENCY,UPLOAD_TYPES,uploadFiles,urlExpiry,withKnownUploads}from"./uploads.js";export{BASE64_FROM,LARGE_FROM,canonical,genRecord,inputHash,isSecretName,namedFile,readGen,recordInput,recordOutput,toolWords,withoutSignature,writeGen,writeWords}from"./records.js";export{makeSay,quoteSays,sayInput}from"./voice.js";
2
+ export{cloudConfig,hostsText}from"./config.js";export{CloudError,notSignedIn,SIGN_IN}from"./errors.js";export{Cloud,scrub}from"./client.js";export{SignedOut,accessOf,accountOf,authServer,chooseOrganization,forgetCredentials,readCredentials,signInWithCode,signInWithDevice,signOut,writeCredentials}from"./credentials.js";export{SIGN_IN_WAIT_MS,openBrowser,signIn,signInByEmail,signInDevice,signInInBackground,signedInAs}from"./signin.js";export{LoopbackError,startLoopback}from"./loopback.js";export{pkcePair,randomState}from"./pkce.js";export{accessToken,billingSummary,browserLink,organizations}from"./token.js";export{CATALOG_CACHE_SECONDS,findTool,loadCatalog}from"./catalog.js";export{POLL_SECONDS,READ_SECONDS,RETRY_SECONDS,WAIT_SECONDS,callTool,resumeJob}from"./call.js";export{REUSE_LEFT_MS,UPLOAD_CONCURRENCY,UPLOAD_TYPES,uploadFiles,urlExpiry,withKnownUploads}from"./uploads.js";export{BASE64_FROM,LARGE_FROM,canonical,genRecord,inputHash,isSecretName,namedFile,readGen,recordInput,recordOutput,toolWords,withoutSignature,writeGen,writeWords}from"./records.js";export{makeSay,sayInput}from"./voice.js";
@@ -1,4 +1,4 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{spawn as g}from"node:child_process";import{mkdtemp as U,open as O,readFile as w,rename as k,rm as d,writeFile as p}from"node:fs/promises";import{tmpdir as v}from"node:os";import{join as a}from"node:path";import{fileURLToPath as N}from"node:url";import{CLIENT_ID as R,EXIT as h,SCOPES as L}from"@allyhub-next/protocol";import{accessOf as P,authServer as W,SignedOut as b,signInWithCode as D,signInWithDevice as C}from"./credentials.js";import{CloudError as y,unreachable as f}from"./errors.js";import{LoopbackError as J,startLoopback as S}from"./loopback.js";import{pkcePair as $,randomState as A}from"./pkce.js";export const SIGN_IN_WAIT_MS=5*6e4;const B=3e4;function I(){return{state:A(),...$()}}async function x(t,e,n){const i=await W(t),r=new URLSearchParams({response_type:"code",client_id:R,redirect_uri:n,scope:L,state:e.state,code_challenge:e.challenge,code_challenge_method:"S256",resource:t.url});return`${i.authorization_endpoint}?${r.toString()}`}export function openBrowser(t,e){if(e.BROWSER==="none")return;const[n,i]=process.platform==="darwin"?["open",[t]]:process.platform==="win32"?["cmd",["/c","start","",t]]:["xdg-open",[t]];try{const r=g(n,i,{stdio:"ignore",detached:!0});r.on("error",()=>{}),r.unref()}catch{}}export async function signedInAs(t){try{return(await P(t.config))?.credentials.account}catch(e){if(e instanceof b)return;throw f(t.url,e)}}const u=t=>new y(t instanceof J?t.code:"sign-in-failed",t instanceof Error?t.message:String(t),{exit:h.auth,hint:"Run allyhub auth login again."});async function _(t,e,n){let i;try{i=await n.result}catch(o){throw u(o)}let r;try{r=(await D(t,{code:i,codeVerifier:e.verifier,redirectUri:n.redirectUri})).account}catch(o){const c=u(o);throw await n.finish(c),c}return await n.finish(),r}const j=t=>`${t.url}/signed-in`;export async function signIn(t,e,n=SIGN_IN_WAIT_MS){const i=I(),r=await S({state:i.state,timeoutMs:n,landing:j(t.config)});try{e(await x(t.config,i,r.redirectUri))}catch(o){throw await r.close(),f(t.url,o)}return _(t.config,i,r)}export async function signInDevice(t,e,n){try{return(await C(t.config,e,n)).account}catch(i){throw u(i)}}const F=N(new URL("./signin-worker.js",import.meta.url));export async function signInInBackground(t,e,n=SIGN_IN_WAIT_MS){const i=I(),r=await U(a(v(),"allyhub-sign-in-"));await p(a(r,"flow.json"),JSON.stringify({flow:i,timeoutMs:n}),{mode:384});const o=await O(a(r,"worker.log"),"w"),c=g(process.execPath,[F,r],{detached:!0,stdio:["ignore","ignore",o.fd],env:{...e}});await o.close();const l={exited:!1};c.once("exit",()=>{l.exited=!0}),c.unref();const E=Date.now()+B;for(;Date.now()<E;)try{const s=JSON.parse(await w(a(r,"ready.json"),"utf8"));return await x(t.config,i,s.redirectUri)}catch(s){if(s instanceof Error&&!("code"in s))throw f(t.url,s);if(l.exited)break;await new Promise(T=>setTimeout(T,50))}const m=(await w(a(r,"worker.log"),"utf8").catch(()=>"")).trim().split(`
2
+ import{spawn as k}from"node:child_process";import{mkdtemp as j,open as E,readFile as u,rename as O,rm as g,writeFile as T}from"node:fs/promises";import{tmpdir as b}from"node:os";import{join as c}from"node:path";import{fileURLToPath as N}from"node:url";import{CLIENT_ID as P,EXIT as m,SCOPES as R}from"@allyhub-next/protocol";import{accessOf as v,authServer as L,SignedOut as $,signInWithCode as D,signInWithDevice as W}from"./credentials.js";import{CloudError as w,unreachable as f}from"./errors.js";import{LoopbackError as J,startLoopback as h}from"./loopback.js";import{pkcePair as A,randomState as B}from"./pkce.js";const C="idp_hint";export const SIGN_IN_WAIT_MS=5*6e4;const F=3e4;function p(){return{state:B(),...A()}}async function y(t,i,o,n={}){const e=await L(t),r=new URLSearchParams({response_type:"code",client_id:P,redirect_uri:o,scope:R,state:i.state,code_challenge:i.challenge,code_challenge_method:"S256",resource:t.url});return n.with!==void 0&&r.set(C,n.with),n.email!==void 0&&r.set("login_hint",n.email),`${e.authorization_endpoint}?${r.toString()}`}export function openBrowser(t,i){if(i.BROWSER==="none")return;const[o,n]=process.platform==="darwin"?["open",[t]]:process.platform==="win32"?["cmd",["/c","start","",t]]:["xdg-open",[t]];try{const e=k(o,n,{stdio:"ignore",detached:!0});e.on("error",()=>{}),e.unref()}catch{}}export async function signedInAs(t){try{return(await v(t.config))?.credentials.account}catch(i){if(i instanceof $)return;throw f(t.url,i)}}const S=t=>new w(t instanceof J?t.code:"sign-in-failed",t instanceof Error?t.message:String(t),{exit:m.auth,hint:"Run allyhub auth login again."});async function I(t,i,o){let n;try{n=await o.result}catch(r){throw S(r)}let e;try{e=(await D(t,{code:n,codeVerifier:i.verifier,redirectUri:o.redirectUri})).account}catch(r){const a=S(r);throw await o.finish(a),a}return await o.finish(),e}const x=t=>`${t.url}/signed-in`;export async function signIn(t,i,o=SIGN_IN_WAIT_MS,n={}){const e=p(),r=await h({state:e.state,timeoutMs:o,landing:x(t.config)});try{i(await y(t.config,e,r.redirectUri,n))}catch(a){throw await r.close(),f(t.url,a)}return I(t.config,e,r)}export async function signInByEmail(t,i,o,n=SIGN_IN_WAIT_MS){const e=t.config,r=p(),a=await h({state:r.state,timeoutMs:n,landing:x(e)});try{const s=await y(e,r,a.redirectUri),d=await e.fetch(`${e.url}/api/auth/sign-in/magic-link`,{method:"POST",headers:{"content-type":"application/json",origin:e.url},body:JSON.stringify({email:i,callbackURL:s})});if(!d.ok)throw new w("sign-in-failed",`AllyHub did not send the link (${String(d.status)}): ${(await d.text()).slice(0,300)}`,{exit:m.auth,status:d.status});await d.body?.cancel()}catch(s){throw await a.close(),s instanceof w?s:f(t.url,s)}return o(),I(e,r,a)}export async function signInDevice(t,i,o){try{return(await W(t.config,i,o)).account}catch(n){throw S(n)}}const M=N(new URL("./signin-worker.js",import.meta.url));export async function signInInBackground(t,i,o=SIGN_IN_WAIT_MS){const n=p(),e=await j(c(b(),"allyhub-sign-in-"));await T(c(e,"flow.json"),JSON.stringify({flow:n,timeoutMs:o}),{mode:384});const r=await E(c(e,"worker.log"),"w"),a=k(process.execPath,[M,e],{detached:!0,stdio:["ignore","ignore",r.fd],env:{...i}});await r.close();const s={exited:!1};a.once("exit",()=>{s.exited=!0}),a.unref();const d=Date.now()+F;for(;Date.now()<d;)try{const l=JSON.parse(await u(c(e,"ready.json"),"utf8"));return await y(t.config,n,l.redirectUri)}catch(l){if(l instanceof Error&&!("code"in l))throw f(t.url,l);if(s.exited)break;await new Promise(U=>setTimeout(U,50))}const _=(await u(c(e,"worker.log"),"utf8").catch(()=>"")).trim().split(`
3
3
  `).slice(-5).join(`
4
- `);throw await d(r,{recursive:!0,force:!0}),new y("sign-in-failed",m?`The sign-in listener did not start: ${m}`:"The sign-in listener did not start.",{exit:h.platform})}export async function serveSignIn(t,e){const{flow:n,timeoutMs:i}=JSON.parse(await w(a(e,"flow.json"),"utf8"));await d(a(e,"flow.json"),{force:!0});const r=await S({state:n.state,timeoutMs:i,landing:j(t)});await p(a(e,"ready.json.tmp"),JSON.stringify({redirectUri:r.redirectUri})),await k(a(e,"ready.json.tmp"),a(e,"ready.json"));try{await _(t,n,r)}finally{await new Promise(o=>setTimeout(o,200)),await d(e,{recursive:!0,force:!0})}}
4
+ `);throw await g(e,{recursive:!0,force:!0}),new w("sign-in-failed",_?`The sign-in listener did not start: ${_}`:"The sign-in listener did not start.",{exit:m.platform})}export async function serveSignIn(t,i){const{flow:o,timeoutMs:n}=JSON.parse(await u(c(i,"flow.json"),"utf8"));await g(c(i,"flow.json"),{force:!0});const e=await h({state:o.state,timeoutMs:n,landing:x(t)});await T(c(i,"ready.json.tmp"),JSON.stringify({redirectUri:e.redirectUri})),await O(c(i,"ready.json.tmp"),c(i,"ready.json"));try{await I(t,o,e)}finally{await new Promise(r=>setTimeout(r,200)),await g(i,{recursive:!0,force:!0})}}
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{BILLING_SUMMARY_PATH as a,BillingSummarySchema as t,ORGANIZATIONS_PATH as r,OrganizationsSchema as i}from"@allyhub-next/protocol";import{accessOf as c}from"./credentials.js";import{notSignedIn as s}from"./errors.js";export async function accessToken(n){const o=n.config.apiKey;if(o)return{accessToken:o,source:"env"};const e=await c(n.config).catch(()=>{});if(!e)throw s();return{accessToken:e.token,expiresAt:e.expiresAt,account:e.credentials.account,source:"oauth"}}export async function billingSummary(n){return t.parse(await n.json(a))}export async function organizations(n){return i.parse(await n.json(r)).organizations}
2
+ import{BILLING_SUMMARY_PATH as r,BROWSER_LINK_PATH as a,BillingSummarySchema as t,BrowserLinkSchema as i,ORGANIZATIONS_PATH as c,OrganizationsSchema as s}from"@allyhub-next/protocol";import{accessOf as u}from"./credentials.js";import{notSignedIn as m}from"./errors.js";export async function accessToken(n){const e=n.config.apiKey;if(e)return{accessToken:e,source:"env"};const o=await u(n.config).catch(()=>{});if(!o)throw m();return{accessToken:o.token,expiresAt:o.expiresAt,account:o.credentials.account,source:"oauth"}}export async function billingSummary(n){return t.parse(await n.json(r))}export async function organizations(n){return s.parse(await n.json(c)).organizations}export async function browserLink(n,e){return i.parse(await n.json(a,{method:"POST",body:{to:e}})).url}
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{copyFile as c,mkdir as f,mkdtemp as p,rm as w}from"node:fs/promises";import{tmpdir as v}from"node:os";import{dirname as x,extname as h,join as u}from"node:path";import{genFileFor as k}from"@allyhub-next/formats";import{EXIT as y}from"@allyhub-next/protocol";import{callTool as g}from"./call.js";import{findTool as F}from"./catalog.js";import{CloudError as I}from"./errors.js";import{estimatePrice as b,withDefaults as S}from"./price.js";import{toolWords as T,writeWords as $}from"./records.js";export function sayInput(o){return{...o.params??{},text:o.text,...o.voice===void 0?{}:{voiceId:o.voice}}}const j=o=>Math.round(o*1e6)/1e6;export async function quoteSays(o,a){const i=new Map,m=[];let d=0,t=!1;for(const e of a){let r=i.get(e.tool);r||(r=await F(o,e.tool),i.set(e.tool,r));const n=b(r.price,S(r.input,sayInput(e)));d+=n.credits,t||=n.lines.some(s=>s.unknown&&s.rate!==0),m.push({stem:e.stem,credits:n.credits,estimated:n.estimated})}return{says:m,credits:j(d),estimated:m.some(e=>e.estimated),unknown:t,titles:[...i.values()].map(e=>e.title)}}export async function makeSay(o,a,i,m){const d=await p(u(v(),"allyhub-voice-"));try{const t=await g(o,i.tool,sayInput(i),{...m,outDir:d,wait:!0});if("jobId"in t||t.file===void 0)throw new I("no-audio",`${i.tool} returned no audio file.`,{exit:y.platform});const e=T(t.output)??[],r=t.output.durationSeconds,n=typeof r=="number"?r:e.at(-1)?.end??0,s=`${i.stem}${h(t.file).toLowerCase()||".mp3"}`;await f(x(u(a,s)),{recursive:!0}),await c(t.file,u(a,s)),t.gen&&await c(t.gen,u(a,k(s)));const l=await $(u(a,s),e,n>=0?n:void 0);return{file:s,words:l.slice(a.length+1),seconds:n,credits:t.credits??0}}finally{await w(d,{recursive:!0,force:!0})}}
2
+ import{copyFile as c,mkdir as p,mkdtemp as s,rm as w}from"node:fs/promises";import{tmpdir as v}from"node:os";import{dirname as x,extname as g,join as i}from"node:path";import{genFileFor as k}from"@allyhub-next/formats";import{EXIT as F}from"@allyhub-next/protocol";import{callTool as I}from"./call.js";import{CloudError as b}from"./errors.js";import{toolWords as h,writeWords as $}from"./records.js";export function sayInput(t){return{...t.params??{},text:t.text,...t.voice===void 0?{}:{voiceId:t.voice}}}export async function makeSay(t,e,n,f){const d=await s(i(v(),"allyhub-voice-"));try{const o=await I(t,n.tool,sayInput(n),{...f,outDir:d,wait:!0});if("jobId"in o||o.file===void 0)throw new b("no-audio",`${n.tool} returned no audio file.`,{exit:F.platform});const a=h(o.output)??[],u=o.output.durationSeconds,m=typeof u=="number"?u:a.at(-1)?.end??0,r=`${n.stem}${g(o.file).toLowerCase()||".mp3"}`;await p(x(i(e,r)),{recursive:!0}),await c(o.file,i(e,r)),o.gen&&await c(o.gen,i(e,k(r)));const l=await $(i(e,r),a,m>=0?m:void 0);return{file:r,words:l.slice(e.length+1),seconds:m,credits:o.credits??0}}finally{await w(d,{recursive:!0,force:!0})}}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/cloud",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/components",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "callout",
5
5
  "description": "Marks places on screen, labels them and draws lines between them; a place may be another layer's element, which the mark follows.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/callout.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "captionLine",
5
5
  "description": "Two to four words a line; an accent box slides to the word being said; stressed words in the accent colour.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/caption-line.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "captionSubtitle",
5
5
  "description": "Small, quiet sentence-case subtitles with a soft shadow, one or two lines.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/caption-subtitle.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "captionWord",
5
5
  "description": "One big uppercase word at a time with a thick outline, popping in; stressed words bigger in the accent colour, an emoji above a word.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/caption-word.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "chart",
5
5
  "description": "Bars, horizontal bars, a line or a ring, with the scale, the label fitting and the draw-on worked out, and one thing made to stand out.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/chart.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "codePanel",
5
5
  "description": "Code, data or a terminal in a panel: coloured, typed or revealed, with lines and spans highlighted in time.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/code-panel.ts",
@@ -4,8 +4,12 @@
4
4
  "title": "screenDemo",
5
5
  "description": "A demo played on a screenshot: the camera goes in on what is acted on; a cursor travels and clicks, text is typed into a field, a button is pressed. uiDemo, in the same file, plays it on an interface drawn in code.",
6
6
  "license": "MIT",
7
- "dependencies": ["@allyhub/sdk"],
7
+ "dependencies": [
8
+ "@allyhub/sdk"
9
+ ],
8
10
  "registryDependencies": [],
11
+ "assets": [],
12
+ "bakes": [],
9
13
  "files": [
10
14
  {
11
15
  "path": "src/screen-demo.ts",
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{readFile as b,rm as m}from"node:fs/promises";import{join as h}from"node:path";import{fileLoudness as v}from"@allyhub-next/audio/node";import{startBrowser as g}from"@allyhub-next/browser";import{allyhubPlugin as y,listFiles as S,loadUserVite as $,prepare as D,readMedia as k,sourceOf as j}from"@allyhub-next/build";import{CACHE_DIR as B}from"@allyhub-next/formats";import{CheckSession as O,ProjectError as R,pickTarget as C,parseCombo as U}from"@allyhub-next/project";import{RPC_ERROR as p,RpcError as f}from"@allyhub-next/protocol";import{bundleScenery as E}from"@allyhub-next/scenery-host/bundle";import{sceneryHost as w,serveDecoders as P}from"@allyhub-next/stage";export const stageMedia=(r,e)=>`${r}media/${e.split("/").map(encodeURIComponent).join("/")}`;export function rpcErrorOf(r){return r instanceof R?new f(r.kind==="not-found"?p.notFound:p.invalid,r.hint?`${r.message} ${r.hint}`:r.message):r}export class Live{root;stageBase;buildDir;revision=0;prep;last;lease;old=new Set;browserStarted;gpu;sceneries=new Map;closed=!1;constructor(e,s,t={}){this.root=e,this.stageBase=s,this.buildDir=h(e,B,"build",`dev-${String(process.pid)}`),this.gpu=t.gpu}moved(e){this.revision=e,this.lease&&this.lease.revision!==e&&this.retire(this.lease)}prepared(){const e=this.revision;if(this.prep?.revision===e)return this.prep.promise;const s=(async()=>{const t=await D(this.root,{buildDir:h(this.buildDir,"stage"),mediaUrl:i=>stageMedia(this.stageBase,i),host:w(this.root)});return t.bundle&&await P(t.bundle.dir),this.last=t,t})();return this.prep={revision:e,promise:s},s.catch(()=>{this.prep?.promise===s&&(this.prep=void 0)}),s}latest(){return this.last}async target(e,s){const t=await this.prepared();try{const{piece:i,target:a}=C(t,e,s===void 0?void 0:[U(s)]);return{prepared:t,piece:i,combo:a.combo}}catch(i){throw rpcErrorOf(i)}}async source(e,s){const t=await this.prepared(),i=t.pieces.find(o=>o.id===e);if(!i)throw new f(p.notFound,`No piece ${e}.`);const a=j(t,i,`${s}${this.stageBase}build/`,o=>`${s}${stageMedia(this.stageBase,o)}`),c={...a.media},u=(o,n)=>`${s}${this.stageBase}${o}/${n.split("/").map(encodeURIComponent).join("/")}`;return await Promise.all(Object.entries(c).map(async([o,n])=>{let d=n;if(n.kind==="video"&&n.matte===!0&&(d={...d,matteUrl:u("matte",o)}),n.kind==="audio"||n.kind==="video"&&n.audio===!0){d={...d,denoisedUrl:u("sound",o)};const l=await v(h(this.root,o)).catch(()=>{});l!==void 0&&(d={...d,loudness:l.lufs,...l.by==="level"?{loudnessBy:"level"}:{}})}c[o]=d})),{...a,media:c}}browser(){return this.browserStarted??=g(this.gpu===void 0?{}:{gpu:this.gpu}),this.browserStarted}async withSession(e){if(this.closed)throw new f(p.unavailable,"allyhub dev is stopping.");let s=this.lease;if(s?.revision!==this.revision){s&&this.retire(s);const t=h(this.buildDir,`session-${String(this.revision)}`),i=await this.browser();s={revision:this.revision,session:O.open({root:this.root,browser:i,buildDir:t}),users:0,retired:!1,dir:t},this.lease=s,s.session.catch(()=>{this.lease===s&&(this.lease=void 0)})}s.users++;try{return await e(await s.session)}finally{s.users--,s.retired&&s.users===0&&this.end(s)}}retire(e){e.retired=!0,this.lease===e&&(this.lease=void 0),this.old.add(e),e.users===0&&this.end(e)}async end(e){this.old.delete(e),await e.session.then(s=>s.close()).catch(()=>{}),await m(e.dir,{recursive:!0,force:!0})}scenery(e){const s=this.sceneries.get(e);if(s?.revision===this.revision)return s.promise;const t=this.bundleScenery(e);return this.sceneries.set(e,{revision:this.revision,promise:t}),t.catch(()=>{this.sceneries.get(e)?.promise===t&&this.sceneries.delete(e)}),t}async bundleScenery(e){const s=await S(this.root),t=await k(this.root,s),i=await F(this.root),a=w(this.root),c=await $(this.root),u=h(this.buildDir,"scenery");return{...await E({root:this.root,file:e,outDir:u,dependencies:i,plugins:[y({root:this.root,media:t,dependencies:i,lent:a.lent,mediaUrl:n=>stageMedia(this.stageBase,n)})],...c?{user:c}:{}}),media:Object.keys(t)}}async close(){this.closed=!0,this.lease&&this.retire(this.lease),await Promise.all([...this.old].map(e=>this.end(e))),await this.browserStarted?.then(e=>e.close()).catch(()=>{}),await m(this.buildDir,{recursive:!0,force:!0})}}async function F(r){try{const e=JSON.parse(await b(h(r,"package.json"),"utf8")),s=new Set;for(const t of["dependencies","devDependencies","peerDependencies","optionalDependencies"]){const i=e[t];if(i&&typeof i=="object")for(const a of Object.keys(i))s.add(a)}return s}catch{return new Set}}
2
+ import{readFile as b,rm as m}from"node:fs/promises";import{join as h}from"node:path";import{fileLoudness as v}from"@allyhub-next/audio/node";import{startBrowser as g}from"@allyhub-next/browser";import{allyhubPlugin as y,listFiles as S,loadUserVite as $,prepare as D,readMedia as k,sourceOf as j}from"@allyhub-next/build";import{CACHE_DIR as B,entryKind as O}from"@allyhub-next/formats";import{CheckSession as R,ProjectError as C,pickTarget as U,parseCombo as E}from"@allyhub-next/project";import{RPC_ERROR as p,RpcError as f}from"@allyhub-next/protocol";import{bundleScenery as P,writeModelStage as F}from"@allyhub-next/scenery-host/bundle";import{sceneryHost as w,serveDecoders as M}from"@allyhub-next/stage";export const stageMedia=(r,e)=>`${r}media/${e.split("/").map(encodeURIComponent).join("/")}`;export function rpcErrorOf(r){return r instanceof C?new f(r.kind==="not-found"?p.notFound:p.invalid,r.hint?`${r.message} ${r.hint}`:r.message):r}export class Live{root;stageBase;buildDir;revision=0;prep;last;lease;old=new Set;browserStarted;gpu;sceneries=new Map;closed=!1;constructor(e,t,s={}){this.root=e,this.stageBase=t,this.buildDir=h(e,B,"build",`dev-${String(process.pid)}`),this.gpu=s.gpu}moved(e){this.revision=e,this.lease&&this.lease.revision!==e&&this.retire(this.lease)}prepared(){const e=this.revision;if(this.prep?.revision===e)return this.prep.promise;const t=(async()=>{const s=await D(this.root,{buildDir:h(this.buildDir,"stage"),mediaUrl:i=>stageMedia(this.stageBase,i),host:w(this.root)});return s.bundle&&await M(s.bundle.dir),this.last=s,s})();return this.prep={revision:e,promise:t},t.catch(()=>{this.prep?.promise===t&&(this.prep=void 0)}),t}latest(){return this.last}async target(e,t){const s=await this.prepared();try{const{piece:i,target:d}=U(s,e,t===void 0?void 0:[E(t)]);return{prepared:s,piece:i,combo:d.combo}}catch(i){throw rpcErrorOf(i)}}async source(e,t){const s=await this.prepared(),i=s.pieces.find(o=>o.id===e);if(!i)throw new f(p.notFound,`No piece ${e}.`);const d=j(s,i,`${t}${this.stageBase}build/`,o=>`${t}${stageMedia(this.stageBase,o)}`),c={...d.media},u=(o,n)=>`${t}${this.stageBase}${o}/${n.split("/").map(encodeURIComponent).join("/")}`;return await Promise.all(Object.entries(c).map(async([o,n])=>{let a=n;if(n.kind==="video"&&n.matte===!0&&(a={...a,matteUrl:u("matte",o)}),n.kind==="audio"||n.kind==="video"&&n.audio===!0){a={...a,denoisedUrl:u("sound",o)};const l=await v(h(this.root,o)).catch(()=>{});l!==void 0&&(a={...a,loudness:l.lufs,...l.by==="level"?{loudnessBy:"level"}:{}})}c[o]=a})),{...d,media:c}}browser(){return this.browserStarted??=g(this.gpu===void 0?{}:{gpu:this.gpu}),this.browserStarted}async withSession(e){if(this.closed)throw new f(p.unavailable,"allyhub dev is stopping.");let t=this.lease;if(t?.revision!==this.revision){t&&this.retire(t);const s=h(this.buildDir,`session-${String(this.revision)}`),i=await this.browser();t={revision:this.revision,session:R.open({root:this.root,browser:i,buildDir:s}),users:0,retired:!1,dir:s},this.lease=t,t.session.catch(()=>{this.lease===t&&(this.lease=void 0)})}t.users++;try{return await e(await t.session)}finally{t.users--,t.retired&&t.users===0&&this.end(t)}}retire(e){e.retired=!0,this.lease===e&&(this.lease=void 0),this.old.add(e),e.users===0&&this.end(e)}async end(e){this.old.delete(e),await e.session.then(t=>t.close()).catch(()=>{}),await m(e.dir,{recursive:!0,force:!0})}scenery(e){const t=this.sceneries.get(e);if(t?.revision===this.revision)return t.promise;const s=this.bundleScenery(e);return this.sceneries.set(e,{revision:this.revision,promise:s}),s.catch(()=>{this.sceneries.get(e)?.promise===s&&this.sceneries.delete(e)}),s}async bundleScenery(e){const t=await S(this.root),s=await k(this.root,t),i=await x(this.root),d=w(this.root),c=await $(this.root),u=h(this.buildDir,"scenery"),o=O(e)==="model"?await F({root:this.root,model:e,outDir:u}):e;return{...await P({root:this.root,file:o,outDir:u,dependencies:i,plugins:[y({root:this.root,media:s,dependencies:i,lent:d.lent,mediaUrl:a=>stageMedia(this.stageBase,a)})],...c?{user:c}:{}}),media:Object.keys(s)}}async close(){this.closed=!0,this.lease&&this.retire(this.lease),await Promise.all([...this.old].map(e=>this.end(e))),await this.browserStarted?.then(e=>e.close()).catch(()=>{}),await m(this.buildDir,{recursive:!0,force:!0})}}async function x(r){try{const e=JSON.parse(await b(h(r,"package.json"),"utf8")),t=new Set;for(const s of["dependencies","devDependencies","peerDependencies","optionalDependencies"]){const i=e[s];if(i&&typeof i=="object")for(const d of Object.keys(i))t.add(d)}return t}catch{return new Set}}
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{readFile as y}from"node:fs/promises";import{basename as P,join as b}from"node:path";import{listFiles as f}from"@allyhub-next/build";import{splitTitle as S,parseDoc as R,titleText as T}from"@allyhub-next/doc";import{ITEM_KINDS as E,scanItems as u}from"@allyhub-next/formats";import{OpenedVersion as O,parseCombo as F}from"@allyhub-next/project";import{RPC_ERROR as l,RpcError as m}from"@allyhub-next/protocol";import{readSpec as j}from"@allyhub-next/sheet";import{comboKey as w,formatInfo as v}from"@allyhub-next/video-core";import{rpcErrorOf as I}from"./live.js";import{loose as $}from"./paths.js";const g=s=>{const r=s.addresses?.();if(!r)throw new m(l.unavailable,"This server serves no pages: only allyhub dev's does.");return r},N=s=>({mime:"image/webp",data:s.toString("base64")});async function q(s,r,d){try{if(r.kind==="doc"){const e=await y(b(s,r.entry),"utf8");return T(S(R(e).blocks).title)||void 0}if(r.kind==="sheet"){const e=await y(b(s,r.dir,"sheet.yaml"),"utf8");return j(e).spec.title}}catch{return}return d.get(r.dir)}export function toolHandlers(s){const{live:r}=s,d=async(e,t,a,o)=>{try{return await r.withSession(async n=>{const i=await O.open({root:r.root,item:e,...t===void 0?{}:{combos:[F(t)]},purpose:a,session:n});try{return await o(i)}finally{await i.close()}})}catch(n){throw I(n)}};return{"project/items":async()=>{const e=await f(r.root),t=u(e),o=t.items.some(i=>i.kind==="video"||i.kind==="slides")?await r.prepared().catch(()=>r.latest()):void 0,n=new Map;for(const i of o?.pieces??[]){const c=typeof i.title=="string"?i.title:void 0;c&&n.set(i.dir,c)}return{items:await Promise.all(t.items.map(async i=>{const c=await q(r.root,i,n);return{...i,...c?{title:c}:{}}})),folder:r.root,name:P(r.root),revision:r.revision}},"video/versions":async({item:e})=>{const{piece:t}=await r.target(e),a=[...new Set(t.combos.map(o=>o.lang))];return{piece:t.id,kind:t.kind,...typeof t.title=="string"?{title:t.title}:{},width:v(t.formatEntry)?.width??0,height:v(t.formatEntry)?.height??0,versions:a.map(o=>o===void 0?{key:w({})}:{key:w({lang:o}),lang:o})}},"video/stage":async({item:e,version:t,start:a,play:o,chrome:n})=>{const{piece:i,combo:c}=await r.target(e,t),p=new URLSearchParams({piece:i.id,combo:w(c)});for(const[k,x]of Object.entries(a??{}))p.set(k,String(x));o&&p.set("play","1"),n===!1&&p.set("chrome","0");const h=g(s);return{url:h.stage(p),probe:h.probe}},"video/timeline":async({item:e,version:t})=>d(e,t,"play",a=>Promise.resolve({timeline:$(a.timeline),text:a.version.summary?.text??[]})),"video/still":async({item:e,version:t,t:a})=>d(e,t,"render",async o=>{const n=a??o.timeline.parts[0]?.still??0;return{t:n,image:N(await o.still(n,{format:"webp"})),width:o.timeline.width,height:o.timeline.height,duration:o.timeline.duration}}),"check/results":()=>Promise.resolve(s.checker?.state??{status:"off",revision:r.revision,problems:[],facts:[]}),"check/run":()=>{if(!s.checker)throw new m(l.unavailable,"This server runs no check.");return s.checker.schedule(!0),Promise.resolve({})},"export/start":async({item:e,versions:t,types:a})=>{const o=await f(r.root);if(!u(o).items.some(n=>n.dir===e))throw new m(l.notFound,`There is no item ${e}.`);return{job:s.exports.start(e,t??[],a??[])}},"export/cancel":({job:e})=>{if(!s.exports.cancel(e))throw new m(l.notFound,`No export ${e} is under way.`);return Promise.resolve({})},"export/list":()=>Promise.resolve({jobs:s.exports.list()}),"scenery/viewer":async({item:e,camera:t})=>{const a=await f(r.root),o=u(a).items.find(c=>c.dir===e&&c.kind==="scenery");if(!o)throw new m(l.notFound,`There is no scenery ${e}: a scenery is a folder with a ${E.scenery.entries[0]??"scenery.ts"}.`);const n=new URLSearchParams({file:o.entry});t&&n.set("camera",JSON.stringify(t));const i=g(s);return{url:i.viewer(n),probe:i.probe}}}}
2
+ import{readFile as y}from"node:fs/promises";import{basename as S,join as b}from"node:path";import{listFiles as f}from"@allyhub-next/build";import{splitTitle as R,parseDoc as T,titleText as E}from"@allyhub-next/doc";import{ITEM_KINDS as v,scanItems as u}from"@allyhub-next/formats";import{OpenedVersion as O,parseCombo as F}from"@allyhub-next/project";import{RPC_ERROR as l,RpcError as d}from"@allyhub-next/protocol";import{readSpec as $}from"@allyhub-next/sheet";import{comboKey as w,formatInfo as g}from"@allyhub-next/video-core";import{rpcErrorOf as j}from"./live.js";import{loose as I}from"./paths.js";const k=s=>{const r=s.addresses?.();if(!r)throw new d(l.unavailable,"This server serves no pages: only allyhub dev's does.");return r},D=s=>({mime:"image/webp",data:s.toString("base64")});async function N(s,r,m){try{if(r.kind==="doc"){const e=await y(b(s,r.entry),"utf8");return E(R(T(e).blocks).title)||void 0}if(r.kind==="sheet"){const e=await y(b(s,r.dir,"sheet.yaml"),"utf8");return $(e).spec.title}}catch{return}return m.get(r.dir)}export function toolHandlers(s){const{live:r}=s,m=async(e,t,a,o)=>{try{return await r.withSession(async n=>{const i=await O.open({root:r.root,item:e,...t===void 0?{}:{combos:[F(t)]},purpose:a,session:n});try{return await o(i)}finally{await i.close()}})}catch(n){throw j(n)}};return{"project/items":async()=>{const e=await f(r.root),t=u(e),o=t.items.some(i=>i.kind==="video"||i.kind==="slides")?await r.prepared().catch(()=>r.latest()):void 0,n=new Map;for(const i of o?.pieces??[]){const c=typeof i.title=="string"?i.title:void 0;c&&n.set(i.dir,c)}return{items:await Promise.all(t.items.map(async i=>{const c=await N(r.root,i,n);return{...i,...c?{title:c}:{}}})),folder:r.root,name:S(r.root),revision:r.revision}},"video/versions":async({item:e})=>{const{piece:t}=await r.target(e),a=[...new Set(t.combos.map(o=>o.lang))];return{piece:t.id,kind:t.kind,...typeof t.title=="string"?{title:t.title}:{},width:g(t.formatEntry)?.width??0,height:g(t.formatEntry)?.height??0,versions:a.map(o=>o===void 0?{key:w({})}:{key:w({lang:o}),lang:o})}},"video/stage":async({item:e,version:t,start:a,play:o,chrome:n})=>{const{piece:i,combo:c}=await r.target(e,t),p=new URLSearchParams({piece:i.id,combo:w(c)});for(const[x,P]of Object.entries(a??{}))p.set(x,String(P));o&&p.set("play","1"),n===!1&&p.set("chrome","0");const h=k(s);return{url:h.stage(p),probe:h.probe}},"video/timeline":async({item:e,version:t})=>m(e,t,"play",a=>Promise.resolve({timeline:I(a.timeline),text:a.version.summary?.text??[]})),"video/still":async({item:e,version:t,t:a})=>m(e,t,"render",async o=>{const n=a??o.timeline.parts[0]?.still??0;return{t:n,image:D(await o.still(n,{format:"webp"})),width:o.timeline.width,height:o.timeline.height,duration:o.timeline.duration}}),"check/results":()=>Promise.resolve(s.checker?.state??{status:"off",revision:r.revision,problems:[],facts:[]}),"check/run":()=>{if(!s.checker)throw new d(l.unavailable,"This server runs no check.");return s.checker.schedule(!0),Promise.resolve({})},"export/start":async({item:e,versions:t,types:a})=>{const o=await f(r.root);if(!u(o).items.some(n=>n.dir===e))throw new d(l.notFound,`There is no item ${e}.`);return{job:s.exports.start(e,t??[],a??[])}},"export/cancel":({job:e})=>{if(!s.exports.cancel(e))throw new d(l.notFound,`No export ${e} is under way.`);return Promise.resolve({})},"export/list":()=>Promise.resolve({jobs:s.exports.list()}),"scenery/viewer":async({item:e,camera:t})=>{const a=await f(r.root),o=u(a).items.find(c=>c.dir===e&&(c.kind==="scenery"||c.kind==="model"));if(!o)throw new d(l.notFound,`There is no scenery or 3D model ${e}: a scenery is a folder with a ${v.scenery.entries[0]??"scenery.ts"}, a model one with a ${v.model.entries[0]??"model.ts"}.`);const n=new URLSearchParams({file:o.entry});t&&n.set("camera",JSON.stringify(t));const i=k(s);return{url:i.viewer(n),probe:i.probe}}}}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/dev-server",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/doc",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -45,7 +45,7 @@ export interface ExportedFile extends VersionFile {
45
45
  name: string;
46
46
  path: string;
47
47
  piece: string;
48
- type: FileType | DocType | SheetType;
48
+ type: FileType | DocType | SheetType | ModelType;
49
49
  combo?: Combo;
50
50
  key?: string;
51
51
  format?: string;
@@ -56,7 +56,7 @@ export interface ExportedFile extends VersionFile {
56
56
  export interface NotExported {
57
57
  name: string;
58
58
  piece: string;
59
- type: FileType | DocType | SheetType;
59
+ type: FileType | DocType | SheetType | ModelType;
60
60
  code: string;
61
61
  message: string;
62
62
  hint?: string;
@@ -66,6 +66,9 @@ export declare const DOC_TYPES: readonly ["pdf", "png", "md"];
66
66
  export declare const SHEET_TYPES: readonly ["xlsx", "csv", "md", "json"];
67
67
  export type DocType = (typeof DOC_TYPES)[number];
68
68
  export type SheetType = (typeof SHEET_TYPES)[number];
69
+ /** What a 3D model renders to: its GLB, as `allyhub 3d-model` builds it. */
70
+ export declare const MODEL_TYPES: readonly ["glb"];
71
+ export type ModelType = (typeof MODEL_TYPES)[number];
69
72
  export interface ProjectExportOptions extends VersionExportOptions {
70
73
  root: string;
71
74
  /** Only these items (a piece, a doc or a sheet by id or place, a path into it, a folder of items); default every one. */
@@ -1,3 +1,3 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{mkdir as L,readFile as V,stat as X,writeFile as M}from"node:fs/promises";import{cpus as me}from"node:os";import{dirname as Z,isAbsolute as ue,join as $,relative as ee,sep as he}from"node:path";import{parseDoc as ge,markdownOf as we}from"@allyhub-next/doc";import{codeOf as be,fontsDigest as ye}from"@allyhub-next/build";import{RenderError as ke,VIDEO_TYPES as xe,renderCaptions as $e,renderCover as Pe,renderPdf as Se,renderPng as Ee,renderPptx as je,renderVideo as Ce,mixSound as Me,requireEncoder as Oe,requireFfmpeg as Te}from"@allyhub-next/render";import{renderDoc as Ne}from"@allyhub-next/render/doc";import{jsonOf as ve,markdownOf as Le,readSheet as Fe,readSpec as _e}from"@allyhub-next/sheet";import{SHEET_SPEC as Ae}from"@allyhub-next/formats";import{graphicsPage as De,stageIdentity as Ie}from"@allyhub-next/stage";import{DEFAULT_TYPES as Re,FILE_TYPES as te,comboKey as Y,exportProblem as Ve,pieceIs as z,pieceNamed as ne}from"@allyhub-next/video-core";import{runCheck as Ye}from"@allyhub-next/check";import{batchCsv as ze,batchRows as We}from"./batch.js";import{batchName as qe,checkName as He,exportName as se,exportNames as Je}from"./names.js";import{ConsolePool as Ke,mergeConsole as Ue,dedupe as oe,indexNeeds as Be,makeIndex as Ge,CheckSession as Qe,OpenedVersion as Xe,ProjectError as N,versionLabel as ie}from"@allyhub-next/project";const re=e=>xe.includes(e);export async function exportVersion(e,i,c,r={}){await L(Z(c),{recursive:!0});const p={page:e.graphics(),target:e.target,look:e.look,media:e.media,out:c};if(i==="wav"){const f=await Me({timeline:e.timeline,sound:await e.sound(),out:c});return r.onProgress?.(1),{files:[c],duration:Math.round(e.timeline.duration*e.timeline.fps)/e.timeline.fps,...f.loudness?{loudness:f.loudness}:{},master:{...f}}}if(re(i)&&(i!=="png"||e.piece.kind==="video")){const f=e.prepared,a=await ye(f.root,f.files,f.fonts),o=await Ce({target:e.target,look:e.look,media:e.media,pages:[e.graphics()],openPage:async()=>{const u=await e.session.open(e.piece,e.target,"render");return De(u.stage,{gpu:e.session.gpu})},...i==="gif"?{}:{sound:await e.sound()},out:c,type:i,...r.encode?{encode:r.encode}:{},...r.workers===void 0?{}:{workers:r.workers},...r.onProgress?{onProgress:r.onProgress}:{},code:await be(f,e.piece,Ie(a)),...r.partCache===!1?{partCache:!1}:{}});return{files:o.files??[o.out],duration:o.duration,parts:o.parts,...o.junctions?{junctions:o.junctions}:{},...o.loudness?{loudness:o.loudness}:{},...o.master?{master:{...o.master}}:{},timings:o.timings}}const m=i==="srt"||i==="vtt"?await $e({...p,type:i,...r.captions?{captions:r.captions}:{}}):i==="pdf"?await Se(p):i==="pptx"?await je(p):i==="png"?await Ee(p):await Pe(p);return r.onProgress?.(1),{files:m.files??[m.out]}}export const exportsAtOnce=e=>Math.max(1,Math.min(e,Math.floor(me().length/4))),DOC_TYPES=["pdf","png","md"],SHEET_TYPES=["xlsx","csv","md","json"];const W=(e,i)=>`${String(e)} ${i}${e===1?"":"s"}`;function Ze(e){if(e===void 0)return;const i=new Set([...Object.keys(te),...DOC_TYPES,...SHEET_TYPES]);for(const c of e)if(!i.has(c))throw new N("bad-type",`Cannot render ${c}.`,"usage",`Types: ${[...i].join(", ")}.`);return[...e]}export async function exportProject(e){const i=Ze(e.types);e.name!==void 0&&He(e.name);const c=e.items??[],r=c.length>0,p=d=>c.some(g=>z({id:d,path:d},g)),m=[],f=[],a=[],o=()=>Qe.open({root:e.root,...e.gpu===void 0?{}:{gpu:e.gpu},...e.consoleLimit===void 0?{}:{consoleLimit:e.consoleLimit}});let u=await o();try{const d=u.prepared,g=d.docs.filter(s=>!r||p(s.place)),C=d.sheets.filter(s=>!r||p(s.place)),k=c.filter(s=>!d.docs.some(t=>z({id:t.place,path:t.place},s))&&!d.sheets.some(t=>z({id:t.place,path:t.place},s)));for(const s of k)if(!d.pieces.some(t=>ne({id:t.id,path:t.place},s)))throw new N("no-item",`No item ${s}.`,"not-found",`Items: ${[...d.pieces.map(t=>t.id),...d.docs.map(t=>t.place),...d.sheets.map(t=>t.place)].join(", ")||"none"}.`);const O=r&&k.length===0,E=i?.filter(s=>s in te),_=O?[]:d.pieces.filter(s=>k.length===0||k.some(t=>ne({id:s.id,path:s.place},t))),T=i?.includes("csv")===!0?_.filter(s=>s.matrix!==void 0):[];if(i?.includes("csv")===!0&&k.length>0&&T.length===0)throw new N("bad-type",`${k.join(", ")} has no slots (matrix.slots), so there is no table of ads to write as csv.`,"usage","csv is a batch's table of ads, or a sheet's rows.");let v=[...d.problems];const A=new Ke(u.consoleLimit),ce=new Set([...d.docs.map(s=>s.file),...d.sheets.flatMap(s=>s.files)]),le=s=>s.where?.file!==void 0&&ce.has(s.where.file),q=s=>s.filter(t=>t.level==="error"&&!le(t)).length;if(!O&&(E?.length!==0||T.length>0)&&(d.pieces.length>0||q(d.problems)>0)){e.log?.("Checking…");const s=()=>tt(u,e.root,k,e.combos);let t=await s();const K=e.index===!1?[]:Be(t.results);if(K.length>0){const n=await Ge(d.root,K,nt(e.log),e.signal);a.push(...n.failed.map(l=>({file:l.need.file,error:l.error}))),n.done.length>0&&(await u.close(),u=await o(),t=await s())}v=t.problems,A.addReport(t.console);const U=q(t.problems);if(U>0)throw new N("check-failed",`The check found ${W(U,"must-fix problem")}, so nothing is rendered.`,"failed","allyhub check lists them; fix them, then render.");for(const n of T){const l=performance.now(),y=qe(n.id),P=We(n,t.plans,u.prepared.media,et(e.root,e.out),e.name);await L(Z($(e.out,y)),{recursive:!0}),await M($(e.out,y),ze(P));const S=t.targets.filter(w=>w.piece===n.id).length;P.length<S&&e.log?.(`${y}: ${W(S-P.length,"ad")} not listed, which did not load (allyhub check says why).`),m.push(await F(e.out,y,n.id,"csv",l))}const x=[],D=new Set;for(const n of t.targets){const l=u.prepared.pieces.find(h=>h.id===n.piece);if(!l)continue;if(!r&&l.options.deliverable===!1){D.add(n.piece);continue}const y=l.kind==="video"?"video":"paged",P=t.results.find(h=>h.piece===n.piece&&Y(h.combo)===n.key&&h.format===n.format),S=t.plans.find(h=>h.piece===n.piece&&Y(h.combo)===n.key&&h.format===n.format),w=P?.timeline?(P.timeline.audio.speech?.length??0)>0:S?.parts.some(h=>h.speech!==void 0)??!1,j={piece:n.piece,output:n.output,combo:n.combo,format:n.format,...n.id===void 0?{}:{id:n.id}};for(const h of E??Re[y]){const b=Ve({kind:y,speech:w},h);if(!(b?.code==="bad_type"&&!r)){if(b&&b.code!=="no_speech")throw new N("bad-type",`${ie(j)}: ${b.message}`,"usage",b.hint);x.push({target:j,type:h,...b?{refused:{name:se(j,h),piece:j.piece,type:h,code:"no-speech",message:`${ie(j)}: ${b.message}`,...b.hint?{hint:b.hint}:{}}}:{}})}}}if(D.size>0&&e.log?.(`Not rendered, shown only inside another video (deliverable: false): ${[...D].join(", ")}. Name one to render it.`),x.some(n=>re(n.type)||n.type==="wav")){await Te();for(const n of new Set(x.map(l=>l.type)))await Oe(n,e.encode?.codec)}const B=Je(x,e.name);x.forEach((n,l)=>{n.refused&&(n.refused.name=B[l]??n.refused.name)});const G=[],I=new Array(x.length).fill(0),R=(n,l)=>{I[n]=Math.max(I[n]??0,l),e.onProgress?.(I.reduce((y,P)=>y+P,0)/x.length)};let Q=0,de=0;const fe=async()=>{for(let n=Q++;n<x.length;n=Q++){const l=x[n];if(!l)continue;const y=B[n]??se(l.target,l.type);if(l.refused){f.push(l.refused),R(n,1);continue}const P=performance.now(),S=await Xe.open({root:e.root,session:u,item:l.target.piece,combos:[l.target.combo],purpose:"render"});try{const w=await exportVersion(S,l.type,$(e.out,y),{...e,onProgress:b=>{R(n,b)}}),j=(await Promise.all(w.files.map(b=>X(b)))).reduce((b,pe)=>b+pe.size,0),h=Math.round((performance.now()-P)/10)/100;m.push({...w,name:ae(ee(e.out,w.files[0]??"")),path:w.files[0]??$(e.out,y),piece:l.target.piece,type:l.type,combo:l.target.combo,key:Y(l.target.combo),format:l.target.format,bytes:j,seconds:h}),e.log?.(`[${String(++de)}/${String(x.length)}] ${y} (${h.toFixed(1)} s)`)}catch(w){if(!(w instanceof ke))throw w;f.push({name:y,piece:l.target.piece,type:l.type,code:w.code,message:w.message,...w.hint?{hint:w.hint}:{}})}finally{G.push(...await S.problems()),A.add(l.target,await S.console()),await S.close(),R(n,1)}}};x.length>0&&e.log?.(`Rendering ${W(x.length,"file")}…`),await Promise.all(Array.from({length:exportsAtOnce(x.length)},fe)),v=oe([...v,...G])}const H=s=>d.problems.find(t=>t.level==="error"&&t.where?.file!==void 0&&s.includes(t.where.file));for(const s of[...g,...C]){const t=H("files"in s?s.files:[s.file]);t&&f.push({name:s.place,piece:s.place,type:"files"in s?"xlsx":"pdf",code:"check-failed",message:`${t.where?.file??s.place}: ${t.message}`,hint:"Fix it, then render again."})}const J=s=>s.filter(t=>H("files"in t?t.files:[t.file])===void 0);return m.push(...await st(e.root,J(g),i,e.out,e.log),...await ot(e.root,J(C),i,e.out,e.log)),{out:e.out,files:m,failed:f,problems:v,console:A.report(),unindexed:a}}finally{await u.close()}}const ae=e=>e.split(he).join("/");function et(e,i){const c=ee(e,i);return c===""?".":c.startsWith("..")||ue(c)?i:ae(c)}async function tt(e,i,c,r){const p=[];for(const o of c.length>0?c:[void 0])p.push(await Ye({dir:i,session:e,facts:!1,...o===void 0?{}:{piece:o},...r?{combos:[...r]}:{}}));const m=new Set,f=p.flatMap(o=>o.targets).filter(o=>{const u=`${o.piece}\0${o.key}\0${o.format}`;return m.has(u)?!1:(m.add(u),!0)}),a=oe(p.flatMap(o=>o.problems));return{targets:f,results:p.flatMap(o=>o.results),plans:p.flatMap(o=>o.plans),problems:a,errors:a.filter(o=>o.level==="error").length,console:Ue(e.consoleLimit,...p.map(o=>o.console))}}function nt(e){let i="";return c=>{const r=c.status==="failed"?`Could not index ${c.file}: ${c.message}`:c.message;c.status==="running"&&c.share!==void 0||r!==i&&(i=r,e?.(r))}}async function F(e,i,c,r,p){const m=$(e,i);return{files:[m],name:i,path:m,piece:c,type:r,bytes:(await X(m)).size,seconds:Math.round((performance.now()-p)/10)/100}}async function st(e,i,c,r,p){const m=(c??["pdf"]).filter(a=>DOC_TYPES.includes(a)),f=[];for(const a of i){if(m.length===0)break;p?.(`Rendering ${a.file}…`);const o=performance.now();if(m.includes("md")){const g=await V($(e,a.file),"utf8");await L($(r,a.place),{recursive:!0}),await M($(r,a.place,"doc.md"),we(ge(g).blocks)),f.push(await F(r,`${a.place}/doc.md`,a.place,"md",o))}const u=m.filter(g=>g!=="md");if(u.length===0)continue;const d=await Ne({root:e,doc:{file:a.file,dir:a.dir,path:a.place,title:a.title},out:$(r,a.place),types:u});for(const g of d)f.push(await F(r,`${a.place}/${g}`,a.place,g.endsWith(".png")?"png":"pdf",o))}return f}async function ot(e,i,c,r,p){const m=(c??["xlsx"]).filter(a=>SHEET_TYPES.includes(a)),f=[];for(const a of i){if(m.length===0)break;p?.(`Writing ${a.file}…`);const o=performance.now(),u=await V($(e,a.file),"utf8"),d=await V($(e,a.dir,Ae),"utf8").catch(()=>{}),{data:g}=Fe(u),C=d===void 0?{columns:{}}:_e(d).spec;await L($(r,a.place),{recursive:!0});for(const k of m){const O=`${a.place}/sheet.${k}`,E=$(r,O);if(k==="csv")await M(E,u);else if(k==="md")await M(E,Le(g,C));else if(k==="json")await M(E,`${JSON.stringify(ve(g,C),null,2)}
3
- `);else{const{xlsxOf:_}=await import("@allyhub-next/sheet/xlsx"),T=(await import("exceljs")).default;await M(E,await _(T,g,C,{title:a.title??a.place.split("/").pop()??"Sheet"}))}f.push(await F(r,O,a.place,k,o))}}return f}
2
+ import{mkdir as F,readFile as W,stat as Z,writeFile as T}from"node:fs/promises";import{cpus as ue}from"node:os";import{basename as he,dirname as ee,isAbsolute as ge,join as g,relative as te,sep as we}from"node:path";import{parseDoc as be,markdownOf as ye}from"@allyhub-next/doc";import{codeOf as ke,fontsDigest as xe}from"@allyhub-next/build";import{RenderError as $e,VIDEO_TYPES as Pe,renderCaptions as Se,renderCover as Ee,renderPdf as Ce,renderPng as Me,renderPptx as je,renderVideo as Oe,mixSound as Te,requireEncoder as Ne,requireFfmpeg as ve}from"@allyhub-next/render";import{renderDoc as Le}from"@allyhub-next/render/doc";import{jsonOf as _e,markdownOf as De,readSheet as Fe,readSpec as Ae}from"@allyhub-next/sheet";import{CACHE_DIR as Ie,SHEET_SPEC as Re}from"@allyhub-next/formats";import{buildModule as Ye}from"@allyhub-next/model/build";import{graphicsPage as Ve,stageIdentity as ze}from"@allyhub-next/stage";import{DEFAULT_TYPES as He,FILE_TYPES as ne,comboKey as q,exportProblem as We,pieceIs as A,pieceNamed as se}from"@allyhub-next/video-core";import{runCheck as qe}from"@allyhub-next/check";import{batchCsv as Be,batchRows as Je}from"./batch.js";import{batchName as Ke,checkName as Ue,exportName as oe,exportNames as Ge}from"./names.js";import{ConsolePool as Qe,mergeConsole as Xe,dedupe as ie,indexNeeds as Ze,makeIndex as et,CheckSession as tt,OpenedVersion as nt,ProjectError as v,versionLabel as re}from"@allyhub-next/project";const ae=e=>Pe.includes(e);export async function exportVersion(e,o,c,r={}){await F(ee(c),{recursive:!0});const p={page:e.graphics(),target:e.target,look:e.look,media:e.media,out:c};if(o==="wav"){const f=await Te({timeline:e.timeline,sound:await e.sound(),out:c});return r.onProgress?.(1),{files:[c],duration:Math.round(e.timeline.duration*e.timeline.fps)/e.timeline.fps,...f.loudness?{loudness:f.loudness}:{},master:{...f}}}if(ae(o)&&(o!=="png"||e.piece.kind==="video")){const f=e.prepared,a=await xe(f.root,f.files,f.fonts),i=await Oe({target:e.target,look:e.look,media:e.media,pages:[e.graphics()],openPage:async()=>{const u=await e.session.open(e.piece,e.target,"render");return Ve(u.stage,{gpu:e.session.gpu})},...o==="gif"?{}:{sound:await e.sound()},out:c,type:o,...r.encode?{encode:r.encode}:{},...r.workers===void 0?{}:{workers:r.workers},...r.onProgress?{onProgress:r.onProgress}:{},code:await ke(f,e.piece,ze(a)),...r.partCache===!1?{partCache:!1}:{}});return{files:i.files??[i.out],duration:i.duration,parts:i.parts,...i.junctions?{junctions:i.junctions}:{},...i.loudness?{loudness:i.loudness}:{},...i.master?{master:{...i.master}}:{},timings:i.timings}}const m=o==="srt"||o==="vtt"?await Se({...p,type:o,...r.captions?{captions:r.captions}:{}}):o==="pdf"?await Ce(p):o==="pptx"?await je(p):o==="png"?await Me(p):await Ee(p);return r.onProgress?.(1),{files:m.files??[m.out]}}export const exportsAtOnce=e=>Math.max(1,Math.min(e,Math.floor(ue().length/4))),DOC_TYPES=["pdf","png","md"],SHEET_TYPES=["xlsx","csv","md","json"],MODEL_TYPES=["glb"];const B=(e,o)=>`${String(e)} ${o}${e===1?"":"s"}`;function st(e){if(e===void 0)return;const o=new Set([...Object.keys(ne),...DOC_TYPES,...SHEET_TYPES,...MODEL_TYPES]);for(const c of e)if(!o.has(c))throw new v("bad-type",`Cannot render ${c}.`,"usage",`Types: ${[...o].join(", ")}.`);return[...e]}export async function exportProject(e){const o=st(e.types);e.name!==void 0&&Ue(e.name);const c=e.items??[],r=c.length>0,p=d=>c.some(w=>A({id:d,path:d},w)),m=[],f=[],a=[],i=()=>tt.open({root:e.root,...e.gpu===void 0?{}:{gpu:e.gpu},...e.consoleLimit===void 0?{}:{consoleLimit:e.consoleLimit}});let u=await i();try{const d=u.prepared,w=d.docs.filter(t=>!r||p(t.place)),j=d.sheets.filter(t=>!r||p(t.place)),E=d.items.filter(t=>t.kind==="model"&&(!r||p(t.dir))),$=c.filter(t=>!d.items.some(n=>n.kind==="model"&&A({id:n.dir,path:n.dir},t))&&!d.docs.some(n=>A({id:n.place,path:n.place},t))&&!d.sheets.some(n=>A({id:n.place,path:n.place},t)));for(const t of $)if(!d.pieces.some(n=>se({id:n.id,path:n.place},t)))throw new v("no-item",`No item ${t}.`,"not-found",`Items: ${[...d.pieces.map(n=>n.id),...d.docs.map(n=>n.place),...d.sheets.map(n=>n.place),...d.items.filter(n=>n.kind==="model").map(n=>n.dir)].join(", ")||"none"}.`);const C=r&&$.length===0,_=o?.filter(t=>t in ne),I=C?[]:d.pieces.filter(t=>$.length===0||$.some(n=>se({id:t.id,path:t.place},n))),R=o?.includes("csv")===!0?I.filter(t=>t.matrix!==void 0):[];if(o?.includes("csv")===!0&&$.length>0&&R.length===0)throw new v("bad-type",`${$.join(", ")} has no slots (matrix.slots), so there is no table of ads to write as csv.`,"usage","csv is a batch's table of ads, or a sheet's rows.");let D=[...d.problems];const Y=new Qe(u.consoleLimit),le=new Set([...d.docs.map(t=>t.file),...d.sheets.flatMap(t=>t.files)]),de=t=>t.where?.file!==void 0&&le.has(t.where.file),J=t=>t.filter(n=>n.level==="error"&&!de(n)).length;if(!C&&(_?.length!==0||R.length>0)&&(d.pieces.length>0||J(d.problems)>0)){e.log?.("Checking…");const t=()=>it(u,e.root,$,e.combos);let n=await t();const N=e.index===!1?[]:Ze(n.results);if(N.length>0){const s=await et(d.root,N,rt(e.log),e.signal);a.push(...s.failed.map(l=>({file:l.need.file,error:l.error}))),s.done.length>0&&(await u.close(),u=await i(),n=await t())}D=n.problems,Y.addReport(n.console);const O=J(n.problems);if(O>0)throw new v("check-failed",`The check found ${B(O,"must-fix problem")}, so nothing is rendered.`,"failed","allyhub check lists them; fix them, then render.");for(const s of R){const l=performance.now(),k=Ke(s.id),P=Je(s,n.plans,u.prepared.media,ot(e.root,e.out),e.name);await F(ee(g(e.out,k)),{recursive:!0}),await T(g(e.out,k),Be(P));const S=n.targets.filter(b=>b.piece===s.id).length;P.length<S&&e.log?.(`${k}: ${B(S-P.length,"ad")} not listed, which did not load (allyhub check says why).`),m.push(await L(e.out,k,s.id,"csv",l))}const x=[],V=new Set;for(const s of n.targets){const l=u.prepared.pieces.find(h=>h.id===s.piece);if(!l)continue;if(!r&&l.options.deliverable===!1){V.add(s.piece);continue}const k=l.kind==="video"?"video":"paged",P=n.results.find(h=>h.piece===s.piece&&q(h.combo)===s.key&&h.format===s.format),S=n.plans.find(h=>h.piece===s.piece&&q(h.combo)===s.key&&h.format===s.format),b=P?.timeline?(P.timeline.audio.speech?.length??0)>0:S?.parts.some(h=>h.speech!==void 0)??!1,M={piece:s.piece,output:s.output,combo:s.combo,format:s.format,...s.id===void 0?{}:{id:s.id}};for(const h of _??He[k]){const y=We({kind:k,speech:b},h);if(!(y?.code==="bad_type"&&!r)){if(y&&y.code!=="no_speech")throw new v("bad-type",`${re(M)}: ${y.message}`,"usage",y.hint);x.push({target:M,type:h,...y?{refused:{name:oe(M,h),piece:M.piece,type:h,code:"no-speech",message:`${re(M)}: ${y.message}`,...y.hint?{hint:y.hint}:{}}}:{}})}}}if(V.size>0&&e.log?.(`Not rendered, shown only inside another video (deliverable: false): ${[...V].join(", ")}. Name one to render it.`),x.some(s=>ae(s.type)||s.type==="wav")){await ve();for(const s of new Set(x.map(l=>l.type)))await Ne(s,e.encode?.codec)}const G=Ge(x,e.name);x.forEach((s,l)=>{s.refused&&(s.refused.name=G[l]??s.refused.name)});const Q=[],z=new Array(x.length).fill(0),H=(s,l)=>{z[s]=Math.max(z[s]??0,l),e.onProgress?.(z.reduce((k,P)=>k+P,0)/x.length)};let X=0,fe=0;const pe=async()=>{for(let s=X++;s<x.length;s=X++){const l=x[s];if(!l)continue;const k=G[s]??oe(l.target,l.type);if(l.refused){f.push(l.refused),H(s,1);continue}const P=performance.now(),S=await nt.open({root:e.root,session:u,item:l.target.piece,combos:[l.target.combo],purpose:"render"});try{const b=await exportVersion(S,l.type,g(e.out,k),{...e,onProgress:y=>{H(s,y)}}),M=(await Promise.all(b.files.map(y=>Z(y)))).reduce((y,me)=>y+me.size,0),h=Math.round((performance.now()-P)/10)/100;m.push({...b,name:ce(te(e.out,b.files[0]??"")),path:b.files[0]??g(e.out,k),piece:l.target.piece,type:l.type,combo:l.target.combo,key:q(l.target.combo),format:l.target.format,bytes:M,seconds:h}),e.log?.(`[${String(++fe)}/${String(x.length)}] ${k} (${h.toFixed(1)} s)`)}catch(b){if(!(b instanceof $e))throw b;f.push({name:k,piece:l.target.piece,type:l.type,code:b.code,message:b.message,...b.hint?{hint:b.hint}:{}})}finally{Q.push(...await S.problems()),Y.add(l.target,await S.console()),await S.close(),H(s,1)}}};x.length>0&&e.log?.(`Rendering ${B(x.length,"file")}…`),await Promise.all(Array.from({length:exportsAtOnce(x.length)},pe)),D=ie([...D,...Q])}const K=t=>d.problems.find(n=>n.level==="error"&&n.where?.file!==void 0&&t.includes(n.where.file));for(const t of[...w,...j]){const n=K("files"in t?t.files:[t.file]);n&&f.push({name:t.place,piece:t.place,type:"files"in t?"xlsx":"pdf",code:"check-failed",message:`${n.where?.file??t.place}: ${n.message}`,hint:"Fix it, then render again."})}const U=t=>t.filter(n=>K("files"in n?n.files:[n.file])===void 0);if(m.push(...await at(e.root,U(w),o,e.out,e.log),...await ct(e.root,U(j),o,e.out,e.log)),o===void 0||o.includes("glb"))for(const t of E){e.log?.(`Building ${t.entry}…`);const n=performance.now(),N=`${t.dir}/${he(t.dir)}.glb`;try{await Ye(g(e.root,t.entry),{root:e.root,out:g(e.out,N),cache:g(e.root,Ie,"model"),log:()=>{}}),m.push(await L(e.out,N,t.dir,"glb",n))}catch(O){f.push({name:t.dir,piece:t.dir,type:"glb",code:"model-failed",message:`${t.entry}: ${O instanceof Error?O.message:String(O)}`,hint:"Fix it, then render again."})}}return{out:e.out,files:m,failed:f,problems:D,console:Y.report(),unindexed:a}}finally{await u.close()}}const ce=e=>e.split(we).join("/");function ot(e,o){const c=te(e,o);return c===""?".":c.startsWith("..")||ge(c)?o:ce(c)}async function it(e,o,c,r){const p=[];for(const i of c.length>0?c:[void 0])p.push(await qe({dir:o,session:e,facts:!1,...i===void 0?{}:{piece:i},...r?{combos:[...r]}:{}}));const m=new Set,f=p.flatMap(i=>i.targets).filter(i=>{const u=`${i.piece}\0${i.key}\0${i.format}`;return m.has(u)?!1:(m.add(u),!0)}),a=ie(p.flatMap(i=>i.problems));return{targets:f,results:p.flatMap(i=>i.results),plans:p.flatMap(i=>i.plans),problems:a,errors:a.filter(i=>i.level==="error").length,console:Xe(e.consoleLimit,...p.map(i=>i.console))}}function rt(e){let o="";return c=>{const r=c.status==="failed"?`Could not index ${c.file}: ${c.message}`:c.message;c.status==="running"&&c.share!==void 0||r!==o&&(o=r,e?.(r))}}async function L(e,o,c,r,p){const m=g(e,o);return{files:[m],name:o,path:m,piece:c,type:r,bytes:(await Z(m)).size,seconds:Math.round((performance.now()-p)/10)/100}}async function at(e,o,c,r,p){const m=(c??["pdf"]).filter(a=>DOC_TYPES.includes(a)),f=[];for(const a of o){if(m.length===0)break;p?.(`Rendering ${a.file}…`);const i=performance.now();if(m.includes("md")){const w=await W(g(e,a.file),"utf8");await F(g(r,a.place),{recursive:!0}),await T(g(r,a.place,"doc.md"),ye(be(w).blocks)),f.push(await L(r,`${a.place}/doc.md`,a.place,"md",i))}const u=m.filter(w=>w!=="md");if(u.length===0)continue;const d=await Le({root:e,doc:{file:a.file,dir:a.dir,path:a.place,title:a.title},out:g(r,a.place),types:u});for(const w of d)f.push(await L(r,`${a.place}/${w}`,a.place,w.endsWith(".png")?"png":"pdf",i))}return f}async function ct(e,o,c,r,p){const m=(c??["xlsx"]).filter(a=>SHEET_TYPES.includes(a)),f=[];for(const a of o){if(m.length===0)break;p?.(`Writing ${a.file}…`);const i=performance.now(),u=await W(g(e,a.file),"utf8"),d=await W(g(e,a.dir,Re),"utf8").catch(()=>{}),{data:w}=Fe(u),j=d===void 0?{columns:{}}:Ae(d).spec;await F(g(r,a.place),{recursive:!0});for(const E of m){const $=`${a.place}/sheet.${E}`,C=g(r,$);if(E==="csv")await T(C,u);else if(E==="md")await T(C,De(w,j));else if(E==="json")await T(C,`${JSON.stringify(_e(w,j),null,2)}
3
+ `);else{const{xlsxOf:_}=await import("@allyhub-next/sheet/xlsx"),I=(await import("exceljs")).default;await T(C,await _(I,w,j,{title:a.title??a.place.split("/").pop()??"Sheet"}))}f.push(await L(r,$,a.place,E,i))}}return f}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/export",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -29,6 +29,11 @@ export declare const ITEM_KINDS: {
29
29
  readonly editor: "scenery";
30
30
  readonly name: "3D scenery";
31
31
  };
32
+ readonly model: {
33
+ readonly entries: readonly string[];
34
+ readonly editor: "scenery";
35
+ readonly name: "3D model";
36
+ };
32
37
  readonly website: {
33
38
  readonly entries: readonly ["astro.config.mjs", "astro.config.ts", "astro.config.mts", "astro.config.js"];
34
39
  readonly editor: "website";
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{baseOf as p,dirOf as u,unscanned as l,within as m}from"./layout.js";export const CODE_EXTENSIONS=["ts","tsx","mts","js","jsx","mjs"];const a=e=>CODE_EXTENSIONS.map(n=>`${e}.${n}`);export const EDITORS=["video","slides","doc","sheet","scenery","website"],ITEM_KINDS={video:{entries:a("video"),editor:"video",name:"video"},slides:{entries:a("slides"),editor:"slides",name:"slides"},doc:{entries:["doc.mdx"],editor:"doc",name:"doc"},sheet:{entries:["sheet.csv"],editor:"sheet",name:"sheet"},scenery:{entries:a("scenery"),editor:"scenery",name:"3D scenery"},website:{entries:["astro.config.mjs","astro.config.ts","astro.config.mts","astro.config.js"],editor:"website",name:"website"}},KINDS=Object.keys(ITEM_KINDS);const h=new Map(KINDS.flatMap(e=>ITEM_KINDS[e].entries.map(n=>[n,e])));export function entryKind(e){return h.get(p(e))}export function scanItems(e){const n=[...e].filter(t=>!l(t)).sort(),o=[],i=new Map;for(const t of n){if(entryKind(t)===void 0)continue;const s=u(t);if(s===""){o.push({code:"at-root",entry:t});continue}i.set(s,[...i.get(s)??[],t])}const d=[];for(const[t,s]of i){const[r]=s;if(s.length>1||r===void 0){o.push({code:"two-entries",dir:t,entries:s});continue}const f=entryKind(r);f&&d.push({kind:f,dir:t,entry:r})}d.sort((t,s)=>t.dir.localeCompare(s.dir));const c=[];for(const t of d){const s=c.find(r=>m(t.dir,r.dir));s?o.push({code:"nested",dir:t.dir,inside:s.dir}):c.push(t)}return{items:c,problems:o}}export function itemFor(e,n){let o;for(const i of n)m(e,i.dir)&&(!o||i.dir.length>o.dir.length)&&(o=i);return o}export function itemManifest(){return{version:1,kinds:KINDS.map(e=>({kind:e,name:ITEM_KINDS[e].name,editor:ITEM_KINDS[e].editor,patterns:ITEM_KINDS[e].entries.map(n=>`**/${n}`)})),ignore:["**/.*/**","**/node_modules/**"]}}
2
+ import{baseOf as p,dirOf as l,unscanned as u,within as f}from"./layout.js";export const CODE_EXTENSIONS=["ts","tsx","mts","js","jsx","mjs"];const d=e=>CODE_EXTENSIONS.map(n=>`${e}.${n}`);export const EDITORS=["video","slides","doc","sheet","scenery","website"],ITEM_KINDS={video:{entries:d("video"),editor:"video",name:"video"},slides:{entries:d("slides"),editor:"slides",name:"slides"},doc:{entries:["doc.mdx"],editor:"doc",name:"doc"},sheet:{entries:["sheet.csv"],editor:"sheet",name:"sheet"},scenery:{entries:d("scenery"),editor:"scenery",name:"3D scenery"},model:{entries:d("model"),editor:"scenery",name:"3D model"},website:{entries:["astro.config.mjs","astro.config.ts","astro.config.mts","astro.config.js"],editor:"website",name:"website"}},KINDS=Object.keys(ITEM_KINDS);const h=new Map(KINDS.flatMap(e=>ITEM_KINDS[e].entries.map(n=>[n,e])));export function entryKind(e){return h.get(p(e))}export function scanItems(e){const n=[...e].filter(t=>!u(t)).sort(),o=[],i=new Map;for(const t of n){if(entryKind(t)===void 0)continue;const s=l(t);if(s===""){o.push({code:"at-root",entry:t});continue}i.set(s,[...i.get(s)??[],t])}const c=[];for(const[t,s]of i){const[r]=s;if(s.length>1||r===void 0){o.push({code:"two-entries",dir:t,entries:s});continue}const m=entryKind(r);m&&c.push({kind:m,dir:t,entry:r})}c.sort((t,s)=>t.dir.localeCompare(s.dir));const a=[];for(const t of c){const s=a.find(r=>f(t.dir,r.dir));s?o.push({code:"nested",dir:t.dir,inside:s.dir}):a.push(t)}return{items:a,problems:o}}export function itemFor(e,n){let o;for(const i of n)f(e,i.dir)&&(!o||i.dir.length>o.dir.length)&&(o=i);return o}export function itemManifest(){return{version:1,kinds:KINDS.map(e=>({kind:e,name:ITEM_KINDS[e].name,editor:ITEM_KINDS[e].editor,patterns:ITEM_KINDS[e].entries.map(n=>`**/${n}`)})),ignore:["**/.*/**","**/node_modules/**"]}}
@@ -18,6 +18,8 @@ export declare const LOCAL_DIR = ".allyhub";
18
18
  export declare const WORK_DIR = ".allyhub/work";
19
19
  /** Reference videos measured (allyhub reference), a folder each. */
20
20
  export declare const REFERENCE_DIR = ".allyhub/work/reference";
21
+ /** A running `allyhub dev`'s note: its page's address and its process, so another run opens that page. */
22
+ export declare const DEV_FILE = ".allyhub/dev.json";
21
23
  /** Renders and exports. */
22
24
  export declare const OUT_DIR = ".allyhub/out";
23
25
  /** What the tools rebuild. */
@@ -1,5 +1,5 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- export const ASSETS_DIR="assets",MEDIA_DIR="assets/media",DATA_DIR="assets/data",TEXT_DIR="assets/text",FONTS_DIR="assets/fonts",MODELS_DIR="assets/media/models",ENVIRONMENTS_DIR="assets/media/environments",TEXTURES_DIR="assets/media/textures",COMPONENTS_DIR="components",SCENERIES_DIR="sceneries",LOCAL_DIR=".allyhub",WORK_DIR=".allyhub/work",REFERENCE_DIR=".allyhub/work/reference",OUT_DIR=".allyhub/out",CACHE_DIR=".allyhub/cache",BAKE_DIR=".allyhub/cache/bake",TRASH_DIR=".allyhub/trash",MEMORY_DIR=".allyhub/memory",PACKAGE_FILE="package.json",TSCONFIG_FILE="tsconfig.json",AGENTS_FILE="AGENTS.md",CLAUDE_FILE="CLAUDE.md",CONFIG_FILES=["allyhub.config.ts","allyhub.config.mts","allyhub.config.js","allyhub.config.mjs"],GITIGNORE=`node_modules/
2
+ export const ASSETS_DIR="assets",MEDIA_DIR="assets/media",DATA_DIR="assets/data",TEXT_DIR="assets/text",FONTS_DIR="assets/fonts",MODELS_DIR="assets/media/models",ENVIRONMENTS_DIR="assets/media/environments",TEXTURES_DIR="assets/media/textures",COMPONENTS_DIR="components",SCENERIES_DIR="sceneries",LOCAL_DIR=".allyhub",WORK_DIR=".allyhub/work",REFERENCE_DIR=".allyhub/work/reference",DEV_FILE=".allyhub/dev.json",OUT_DIR=".allyhub/out",CACHE_DIR=".allyhub/cache",BAKE_DIR=".allyhub/cache/bake",TRASH_DIR=".allyhub/trash",MEMORY_DIR=".allyhub/memory",PACKAGE_FILE="package.json",TSCONFIG_FILE="tsconfig.json",AGENTS_FILE="AGENTS.md",CLAUDE_FILE="CLAUDE.md",CONFIG_FILES=["allyhub.config.ts","allyhub.config.mts","allyhub.config.js","allyhub.config.mjs"],GITIGNORE=`node_modules/
3
3
  ${LOCAL_DIR}/
4
4
  .playwright-cli/
5
5
  `;export function unscanned(t){return t.split("/").some(e=>e.startsWith(".")||e==="node_modules")}export function projectPath(t){return t.replaceAll("\\","/").replace(/^(?:\.\/)+/u,"").replace(/\/+$/u,"").replace(/\/{2,}/gu,"/")}export function dirOf(t){const e=t.lastIndexOf("/");return e<0?"":t.slice(0,e)}export function baseOf(t){return t.slice(t.lastIndexOf("/")+1)}export function within(t,e){return e===""||t===e||t.startsWith(`${e}/`)}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/formats",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/measure",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/media",
3
- "version": "0.2.0-next.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A built model as plain data: what `allyhub model inspect` and `model build`
2
+ * A built model as plain data: what `allyhub 3d-model inspect` and `model build`
3
3
  * print. Bounds are world-space surface bounds in Blender Z-up metres; a
4
4
  * transform-only object's are null. Textures name their image and where it
5
5
  * comes from, never its pixels.