allyhub 0.2.0 → 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 (84) hide show
  1. package/dist/cli-version.js +2 -0
  2. package/dist/commands/add.js +5 -5
  3. package/dist/commands/dev.js +2 -2
  4. package/dist/commands/mcp.js +1 -1
  5. package/dist/commands/model.js +1 -1
  6. package/dist/commands/render.js +1 -1
  7. package/dist/commands/skills.js +3 -3
  8. package/dist/main.js +7 -7
  9. package/dist/works.js +1 -1
  10. package/editor-app/view.html +3 -3
  11. package/editor-web/assets/chevron-left-W6sP7nB1.js +1 -0
  12. package/editor-web/assets/{chevron-up-BMNXDEBs.js → chevron-up-566GQpAm.js} +1 -1
  13. package/editor-web/assets/{dist-B4VKfY-H.js → dist-BKDr73BR.js} +4 -4
  14. package/editor-web/assets/dist-CPCNTYVu.js +8 -0
  15. package/editor-web/assets/{dist-Coblfc3T.js → dist-C_NajYp0.js} +1 -1
  16. package/editor-web/assets/{dist-BcInw4os.js → dist-mA6NrAKh.js} +34 -34
  17. package/editor-web/assets/{floating-ui.react-dom-DSyn5TRd.js → floating-ui.react-dom-BUxFRF6t.js} +1 -1
  18. package/editor-web/assets/index-CFZhjTQW.js +56 -0
  19. package/editor-web/assets/index-DgU-lyX6.css +2 -0
  20. package/editor-web/assets/{react-Bo429Bov.js → react-Ff_buMj8.js} +1 -1
  21. package/editor-web/assets/{undo-2-CWgOKQ3L.js → undo-2-BKFItM3X.js} +1 -1
  22. package/editor-web/index.html +4 -4
  23. package/node_modules/@allyhub-next/audio/package.json +1 -1
  24. package/node_modules/@allyhub-next/browser/package.json +1 -1
  25. package/node_modules/@allyhub-next/build/dist/scan.js +1 -1
  26. package/node_modules/@allyhub-next/build/package.json +1 -1
  27. package/node_modules/@allyhub-next/check/dist/check.js +3 -3
  28. package/node_modules/@allyhub-next/check/package.json +1 -1
  29. package/node_modules/@allyhub-next/cloud/dist/index.js +1 -1
  30. package/node_modules/@allyhub-next/cloud/dist/token.js +1 -1
  31. package/node_modules/@allyhub-next/cloud/package.json +1 -1
  32. package/node_modules/@allyhub-next/components/package.json +1 -1
  33. package/node_modules/@allyhub-next/dev-server/dist/live.js +1 -1
  34. package/node_modules/@allyhub-next/dev-server/dist/tools.js +1 -1
  35. package/node_modules/@allyhub-next/dev-server/package.json +1 -1
  36. package/node_modules/@allyhub-next/doc/package.json +1 -1
  37. package/node_modules/@allyhub-next/export/dist/export.d.ts +5 -2
  38. package/node_modules/@allyhub-next/export/dist/export.js +2 -2
  39. package/node_modules/@allyhub-next/export/package.json +1 -1
  40. package/node_modules/@allyhub-next/formats/dist/items.d.ts +5 -0
  41. package/node_modules/@allyhub-next/formats/dist/items.js +1 -1
  42. package/node_modules/@allyhub-next/formats/dist/layout.d.ts +2 -0
  43. package/node_modules/@allyhub-next/formats/dist/layout.js +1 -1
  44. package/node_modules/@allyhub-next/formats/package.json +1 -1
  45. package/node_modules/@allyhub-next/measure/package.json +1 -1
  46. package/node_modules/@allyhub-next/media/package.json +1 -1
  47. package/node_modules/@allyhub-next/model/dist/report.d.ts +1 -1
  48. package/node_modules/@allyhub-next/model/package.json +1 -1
  49. package/node_modules/@allyhub-next/project/package.json +1 -1
  50. package/node_modules/@allyhub-next/protocol/dist/app.d.ts +3 -0
  51. package/node_modules/@allyhub-next/protocol/dist/auth.d.ts +17 -0
  52. package/node_modules/@allyhub-next/protocol/dist/auth.js +1 -1
  53. package/node_modules/@allyhub-next/protocol/dist/cli.d.ts +1 -1
  54. package/node_modules/@allyhub-next/protocol/dist/cli.js +1 -1
  55. package/node_modules/@allyhub-next/protocol/dist/editor.d.ts +4 -0
  56. package/node_modules/@allyhub-next/protocol/dist/registry.d.ts +3 -1
  57. package/node_modules/@allyhub-next/protocol/dist/registry.js +1 -1
  58. package/node_modules/@allyhub-next/protocol/dist/routes.js +1 -1
  59. package/node_modules/@allyhub-next/protocol/dist/tools.d.ts +1 -0
  60. package/node_modules/@allyhub-next/protocol/package.json +1 -1
  61. package/node_modules/@allyhub-next/registry/package.json +1 -1
  62. package/node_modules/@allyhub-next/render/package.json +1 -1
  63. package/node_modules/@allyhub-next/runtime/dist/page.js +1 -1
  64. package/node_modules/@allyhub-next/runtime/package.json +1 -1
  65. package/node_modules/@allyhub-next/runtimes/package.json +1 -1
  66. package/node_modules/@allyhub-next/scaffold/dist/index.js +5 -5
  67. package/node_modules/@allyhub-next/scaffold/package.json +1 -1
  68. package/node_modules/@allyhub-next/scenery/package.json +1 -1
  69. package/node_modules/@allyhub-next/scenery-host/dist/bundle.d.ts +1 -0
  70. package/node_modules/@allyhub-next/scenery-host/dist/bundle.js +3 -3
  71. package/node_modules/@allyhub-next/scenery-host/dist/model-stage.d.ts +32 -0
  72. package/node_modules/@allyhub-next/scenery-host/dist/model-stage.js +3 -0
  73. package/node_modules/@allyhub-next/scenery-host/package.json +1 -1
  74. package/node_modules/@allyhub-next/sheet/package.json +1 -1
  75. package/node_modules/@allyhub-next/site/package.json +1 -1
  76. package/node_modules/@allyhub-next/stage/package.json +1 -1
  77. package/node_modules/@allyhub-next/video-core/dist/problem.d.ts +4 -0
  78. package/node_modules/@allyhub-next/video-core/dist/problem.js +1 -1
  79. package/node_modules/@allyhub-next/video-core/package.json +1 -1
  80. package/package.json +28 -28
  81. package/editor-web/assets/chevron-left-D1vfOP5S.js +0 -1
  82. package/editor-web/assets/dist-BIEloJVS.js +0 -8
  83. package/editor-web/assets/index-BSFR4plz.css +0 -2
  84. package/editor-web/assets/index-Dc8FX_sp.js +0 -56
@@ -0,0 +1,2 @@
1
+ /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
+ import{readFileSync as e}from"node:fs";export const CLI_VERSION=JSON.parse(e(new URL("../package.json",import.meta.url),"utf8")).version;
@@ -1,6 +1,6 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{readFile as x}from"node:fs/promises";import{join as S,posix as z,relative as U}from"node:path";import{fileURLToPath as M}from"node:url";import{z as u}from"zod";import{modelInfo as Y}from"@allyhub-next/build";import{COMPONENTS_DIR as P}from"@allyhub-next/formats";import{EXIT as b}from"@allyhub-next/protocol";import{ASSET_DEFAULTS as B,ASSET_TYPES as V,PolyHaven as W,PolyHavenError as q,RegistryError as J,applyAdd as E,fetchBakes as K,folderRegistry as I,planAdd as R,searchIndex as L}from"@allyhub-next/registry";import{cacheHome as X}from"@allyhub-next/runtimes";import{CommandError as T,defineCommand as O}from"../command.js";import{projectRoot as Z}from"../project.js";import{dir as G}from"../fields.js";import{resourcesIndex as j}from"../resources.js";const Q=JSON.parse(await x(new URL("../../package.json",import.meta.url),"utf8")).version,C=M(new URL(".",import.meta.resolve("@allyhub-next/components/registry.json"))),H=Object.keys(V);async function ee(e){const{source:t,index:n}=await j(e);return{source:t,names:new Set(n?.items.map(a=>a.name))}}const te={"allyhub:scenery":"Sceneries and settings","allyhub:model":"Models","allyhub:component":"Components","allyhub:template":"Templates","allyhub:skill":"Skills"};function _(e,t){if(e.from==="none")return["AllyHub's resources (models, sceneries) could not be read: check the connection."];const n=t??e.index?.items??[],a=Math.max(0,...n.map(i=>i.name.length)),o=[`AllyHub's resources (allyhub add <name> copies one with what it needs, its Poly Haven assets and its baked light)${e.from==="stale"?", as last read (the registry cannot be read now)":""}:`];for(const[i,d]of Object.entries(te)){const c=n.filter(h=>h.type===i);c.length!==0&&o.push(` ${d}:`,...c.map(h=>` ${h.name.padEnd(a)} ${h.description}`))}return o}const N=20,se=e=>e.replace(/\.(?:ts|js)$/u,"").replaceAll(/[A-Z]/gu,t=>`-${t.toLowerCase()}`).replace(/^-/u,"");function D(e){return e instanceof J?new T(e.code,e.message,{exit:e.code==="conflict"?b.conflict:e.code==="no-item"?b.notFound:b.failed,...e.hint===void 0?{}:{hint:e.hint}}):e instanceof q?new T(e.code,e.message,{exit:e.code==="network"?b.platform:e.code==="not-found"?b.notFound:e.code==="bad-choice"?b.usage:b.failed,...e.hint===void 0?{}:{hint:e.hint}}):e}const A=e=>new W({userAgent:`AllyHub/${Q} (+https://allyhub.com)`,cacheDir:S(X(e.env),"polyhaven"),...e.env.ALLYHUB_POLYHAVEN_API?{api:e.env.ALLYHUB_POLYHAVEN_API}:{}}),v=e=>`${e.toFixed(3)} m`;function ne(e){const t=[e.type,e.category,e.size?`${v(e.size.width)} wide, ${v(e.size.depth)} deep, ${v(e.size.height)} tall`:void 0,e.covers?`one tile covers ${e.covers.map(v).join(" × ")}`:void 0,e.polycount!==void 0?`${e.polycount.toLocaleString("en")} polygons`:void 0,e.maxResolution?`up to ${String(e.maxResolution[0])} px`:void 0,e.authors.length>0?`by ${e.authors.join(", ")}`:void 0].filter(n=>n!==void 0);return` ${e.id}: ${e.name} (${t.join("; ")})
3
- found in ${e.matchedIn.join(", ")||"everything"}; tags ${e.tags.join(", ")}`}async function F(e,t){const n=[`${t.name} from Poly Haven (CC0, by ${t.source.authors.join(", ")||"its authors"}): ${String(t.files.length)} files in ${t.dir}/, ${t.fetched>0?`${String(t.fetched)} fetched`:"all already there"}. Records: ${t.records.join(", ")}.`];if(t.type==="model"){const o=t.main[0]??"",i=await Y(e,o,await x(S(e,o))),d=i.bounds;return n.push(d?`Measured: ${d.size.map(c=>c.toFixed(3)).join(" × ")} m along x, y (up), z; from ${d.min.map(c=>c.toFixed(3)).join(", ")} to ${d.max.map(c=>c.toFixed(3)).join(", ")}.`:`Not measured: ${i.contentsMissing??"no contents"}.`,...i.missing.length>0?[`Missing: ${i.missing.join(", ")}.`]:[],`In a scenery: const m = await w.load("${o}");`),{lines:n,model:i}}if(t.type==="hdri")return n.push(`In a scenery: await w.environment("${t.main[0]??""}", { background: true });`),{lines:n};const a=t.covers;return n.push(a&&a.length>=2?`One tile covers ${v(a[0]??0)} × ${v(a[1]??0)} (Poly Haven's dimensions): a surface's repeat is its size over that.`:"Poly Haven gives no size for one tile.",...t.main.map((o,i)=>{const d=t.maps?.[i]??"";return` ${d}: await w.texture("${o}"${d==="diffuse"?"":', { colorSpace: "linear" }'})`})),{lines:n}}export const add=O({name:"add",summary:"Adds what the registry has to the project: a starter component, a resource from AllyHub's registry (a model, a scenery, a skill), or a CC0 model, HDRI or texture from Poly Haven; alone it lists the components",input:u.strictObject({name:u.array(u.string()).optional().describe("What to add: a starter component (caption-line, or captionLine), a resource from AllyHub's registry (a model, a scenery or a skill: home-desk-demo-day, product-demos; a skill goes in .agents/skills, linked from .claude/skills), or a Poly Haven asset's id (as allyhub add search prints it)"),to:u.string().optional().describe(`The folder the files go in, from the project (default ${P} for a component; for an asset, its kind's folder under assets/media/ and its id)`),force:u.boolean().default(!1).describe("Replace a component's copy the project changed (its changes are lost)"),res:u.string().optional().describe(`An asset's resolution, of its textures or of the HDRI (default ${B.resolution})`),dir:G}),positional:["name"],output:u.looseObject({}),examples:["allyhub add","allyhub add caption-line","allyhub add metal_office_desk --res 2k"]},async(e,t)=>{const n=I(C);try{const a=await n.index();if(!e.name||e.name.length===0){const r=Math.max(...a.items.map(y=>y.name.length)),l=await j(t);return{value:{components:a.items,resources:l.index?.items??[]},text:["Starter components (allyhub add <name> copies one into components/; allyhub add search <words> finds resources and Poly Haven's models, HDRIs and textures too):",...a.items.map(y=>` ${y.name.padEnd(r)} ${y.description}`),"",..._(l,void 0)].join(`
4
- `)}}const o=await Z(t.cwd,e.dir),i=new Set(a.items.map(r=>r.name)),d=e.name.map(se),c=d.filter(r=>i.has(r)),h=d.filter(r=>!i.has(r)),$=h.length>0?await ee(t):void 0,w=h.filter(r=>$?.names.has(r)),p=e.name.filter((r,l)=>!i.has(d[l]??"")&&!($?.names.has(d[l]??"")??!1)),f=[],k={};if(c.length>0){const r=e.to??P,l=await R(n,c,{root:o,dir:r,force:e.force});await E(o,l);const y=m=>m==="up to date"?"Up to date":m==="added"?"Added":"Replaced";f.push(...l.files.map(m=>`${y(m.status)} ${m.path}`));const s=l.items.find(m=>m.name===c[0]),g=s?.files[0]?.target.replaceAll("{dir}",r);if(s&&g){const m=U(S(o,"name"),S(o,g)).split("\\").join("/").replace(/\.ts$/u,".js");f.push(`Import it from an item's entry file (<name>/video.ts): import { ${s.title} } from "${m.startsWith(".")?m:`./${m}`}"; one "../" more for each folder the item is deeper.`,"The file is the project's: change it as you like. allyhub add <name> --force puts the registry's back.")}l.missing.length>0&&f.push(`It needs packages the project does not declare: ${l.missing.join(", ")}.`),k.components=l.files.map(m=>({item:m.item,path:m.path,status:m.status})),k.missing=l.missing}if($&&w.length>0){const r=await R($.source,w,{root:o,dir:".",force:e.force});await E(o,r),f.length>0&&f.push(""),f.push(...r.files.map(s=>`${s.status==="up to date"?"Up to date":s.status==="added"?"Added":"Replaced"} ${s.path}`));const l=A(t);for(const s of r.assets){const g=await l.add(s.id,{root:o,...s.resolution===void 0?{}:{resolution:s.resolution},...s.maps===void 0?{}:{maps:s.maps}});f.push(...(await F(o,g)).lines)}const y=await K(o,r.bakes);f.push(...y.map(s=>`${s.status==="added"?"Downloaded":"Up to date"} the baked light for ${s.bake.scenery}: ${s.path}`),"The files are the project's: change them as you like (a changed scenery is drawn unbaked until allyhub scenery bake bakes it again). allyhub add <name> --force puts the registry's back."),k.resources={files:r.files.map(s=>({item:s.item,path:s.path,status:s.status})),assets:r.assets,bakes:y.map(s=>({path:s.path,status:s.status}))}}if(p.length>0){const r=A(t),l=[];for(const y of p){const s=await r.add(y,{root:o,...e.to===void 0||c.length>0?{}:{to:z.normalize(e.to)},...e.res===void 0?{}:{resolution:e.res}}),g=await F(o,s);f.length>0&&f.push(""),f.push(...g.lines),l.push({...s,...g.model===void 0?{}:{model:g.model}})}k.assets=l}return{value:k,text:f.join(`
5
- `)}}catch(a){throw D(a)}}),addSearch=O({name:"add search",summary:"Finds the starter components, AllyHub's resources (models, sceneries) and Poly Haven's CC0 models, HDRIs and textures whose names, tags, categories or descriptions hold every word, each with where its words were found",input:u.strictObject({words:u.array(u.string()).min(1).describe("The words to find (a word may start one of theirs)"),type:u.array(u.enum(["component","resource",...H])).optional().describe("Only these kinds (default all): component, resource (AllyHub's models and sceneries), or a Poly Haven kind"),all:u.boolean().default(!1).describe(`Every match, not only the first ${String(N)} Poly Haven assets`)}),positional:["words"],output:u.looseObject({}),examples:['allyhub add search "office desk" --type model',"allyhub add search caption"]},async(e,t)=>{const n=e.words.join(" ").trim();try{const a=e.type??["component","resource",...H],o=a.includes("component")?L(await I(C).index(),n):[],i=a.includes("resource")?await j(t):void 0,d=i?.index?L(i.index,n):[],c=H.filter(p=>a.includes(p)),{found:h,total:$}=c.length>0?await A(t).search(n,c):{found:[],total:0},w=e.all?h:h.slice(0,N);return{value:{query:n,components:o,resources:d,assets:{total:$,matches:h.length,found:w}},text:[...a.includes("component")?[`Starter components: ${String(o.length)} hold "${n}".`,...o.map(p=>` ${p.name} (${p.title}): ${p.description}`)]:[],...i?i.from==="none"?_(i,[]):[`AllyHub's resources: ${String(d.length)} hold "${n}"${i.from==="stale"?" (as last read)":""}.`,...d.map(p=>` ${p.name} (${p.title}): ${p.description}`)]:[],...c.length>0?[`From Poly Haven (CC0): ${String(h.length)} of ${String($)} assets hold "${n}"${w.length<h.length?`; the first ${String(w.length)} (--all for every one)`:""}.`,...w.map(ne)]:[],"","Add one: allyhub add <name or id>"].join(`
6
- `)}}catch(a){throw D(a)}});
2
+ import{readFile as Y}from"node:fs/promises";import{join as A,posix as B,relative as V}from"node:path";import{fileURLToPath as W}from"node:url";import{z as h}from"zod";import{modelInfo as q}from"@allyhub-next/build";import{COMPONENTS_DIR as I}from"@allyhub-next/formats";import{EXIT as f}from"@allyhub-next/protocol";import{ASSET_DEFAULTS as G,ASSET_TYPES as K,PolyHaven as X,PolyHavenError as Z,RegistryError as J,applyAdd as T,fetchBakes as Q,folderRegistry as D,planAdd as L,searchIndex as O}from"@allyhub-next/registry";import{cacheHome as ee}from"@allyhub-next/runtimes";import{CLI_VERSION as te}from"../cli-version.js";import{CommandError as j,defineCommand as C}from"../command.js";import{projectRoot as ne}from"../project.js";import{dir as oe}from"../fields.js";import{resourcesIndex as P}from"../resources.js";const _=W(new URL(".",import.meta.resolve("@allyhub-next/components/registry.json"))),se=Object.keys(K),x=["component","3d-model","scenery","skill","template","lib","@polyhaven"],ae={"allyhub:component":"component","allyhub:model":"3d-model","allyhub:scenery":"scenery","allyhub:skill":"skill","allyhub:template":"template","allyhub:lib":"lib"},H=e=>ae[e]??e.replace(/^allyhub:/u,""),E=e=>`${H(e.type)}/${e.name}`;function ie(e){const n=/^(@?[a-z0-9-]+)\/(.+)$/u.exec(e),a=x.find(d=>d===n?.[1]);if(a===void 0||n?.[2]===void 0)throw new j("usage",n===null?`Name what to add as <type>/<name>: ${e} has no type.`:`${n[1]??""} is not a type: name what to add as <type>/<name>.`,{exit:f.usage,hint:`Types: ${x.join(", ")} (3d-model/monitor, scenery/home-desk-day, skill/product-demos, component/caption-line, @polyhaven/<id>). allyhub add lists every one.`});return{type:a,name:n[2]}}const le={"allyhub:model":"3D models","allyhub:scenery":"3D sceneries","allyhub:skill":"Skills","allyhub:template":"Templates","allyhub:component":"Components","allyhub:lib":"Libs (added with what uses them)"};function F(e,n){if(e.from==="none")return["AllyHub's resources (models, sceneries) could not be read: check the connection."];const a=n??e.index?.items??[],d=Math.max(0,...a.map(o=>E(o).length)),r=[`AllyHub's resources (allyhub add <type>/<name> copies one with what it needs, its Poly Haven assets and its baked light)${e.from==="stale"?", as last read (the registry cannot be read now)":""}:`];for(const[o,p]of Object.entries(le)){const c=a.filter(u=>u.type===o);c.length!==0&&r.push(` ${p}:`,...c.map(u=>` ${E(u).padEnd(d)} ${u.description}`))}return r}const N=20,z=e=>e.replace(/\.(?:ts|js)$/u,"").replaceAll(/[A-Z]/gu,n=>`-${n.toLowerCase()}`).replace(/^-/u,"");function M(e){return e instanceof J?new j(e.code,e.message,{exit:e.code==="conflict"?f.conflict:e.code==="no-item"?f.notFound:f.failed,...e.hint===void 0?{}:{hint:e.hint}}):e instanceof Z?new j(e.code,e.message,{exit:e.code==="network"?f.platform:e.code==="not-found"?f.notFound:e.code==="bad-choice"?f.usage:f.failed,...e.hint===void 0?{}:{hint:e.hint}}):e}const R=e=>new X({userAgent:`AllyHub/${te} (+https://allyhub.com)`,cacheDir:A(ee(e.env),"polyhaven"),...e.env.ALLYHUB_POLYHAVEN_API?{api:e.env.ALLYHUB_POLYHAVEN_API}:{}}),k=e=>`${e.toFixed(3)} m`;function de(e){const n=[e.type,e.category,e.size?`${k(e.size.width)} wide, ${k(e.size.depth)} deep, ${k(e.size.height)} tall`:void 0,e.covers?`one tile covers ${e.covers.map(k).join(" × ")}`:void 0,e.polycount!==void 0?`${e.polycount.toLocaleString("en")} polygons`:void 0,e.maxResolution?`up to ${String(e.maxResolution[0])} px`:void 0,e.authors.length>0?`by ${e.authors.join(", ")}`:void 0].filter(a=>a!==void 0);return` @polyhaven/${e.id}: ${e.name} (${n.join("; ")})
3
+ found in ${e.matchedIn.join(", ")||"everything"}; tags ${e.tags.join(", ")}`}async function U(e,n){const a=[`${n.name} from Poly Haven (CC0, by ${n.source.authors.join(", ")||"its authors"}): ${String(n.files.length)} files in ${n.dir}/, ${n.fetched>0?`${String(n.fetched)} fetched`:"all already there"}. Records: ${n.records.join(", ")}.`];if(n.type==="model"){const r=n.main[0]??"",o=await q(e,r,await Y(A(e,r))),p=o.bounds;return a.push(p?`Measured: ${p.size.map(c=>c.toFixed(3)).join(" × ")} m along x, y (up), z; from ${p.min.map(c=>c.toFixed(3)).join(", ")} to ${p.max.map(c=>c.toFixed(3)).join(", ")}.`:`Not measured: ${o.contentsMissing??"no contents"}.`,...o.missing.length>0?[`Missing: ${o.missing.join(", ")}.`]:[],`In a scenery: const m = await w.load("${r}");`),{lines:a,model:o}}if(n.type==="hdri")return a.push(`In a scenery: await w.environment("${n.main[0]??""}", { background: true });`),{lines:a};const d=n.covers;return a.push(d&&d.length>=2?`One tile covers ${k(d[0]??0)} × ${k(d[1]??0)} (Poly Haven's dimensions): a surface's repeat is its size over that.`:"Poly Haven gives no size for one tile.",...n.main.map((r,o)=>{const p=n.maps?.[o]??"";return` ${p}: await w.texture("${r}"${p==="diffuse"?"":', { colorSpace: "linear" }'})`})),{lines:a}}export const add=C({name:"add",summary:"Adds to the project what AllyHub and Poly Haven have, each named <type>/<name>: a starter component, a 3D model, a 3D scenery, a skill or a template from AllyHub's registry, or a CC0 model, HDRI or texture from Poly Haven; alone it lists everything AllyHub has, by type",input:h.strictObject({name:h.array(h.string()).optional().describe("What to add, each <type>/<name>: component/caption-line, 3d-model/monitor, scenery/home-desk-demo-day, skill/product-demos (a skill goes in .agents/skills, linked from .claude/skills), template/<name>, lib/<name>, or @polyhaven/<id> (as allyhub add search prints it)"),to:h.string().optional().describe(`The folder the files go in, from the project (default ${I} for a component; for an asset, its kind's folder under assets/media/ and its id)`),force:h.boolean().default(!1).describe("Replace a component's copy the project changed (its changes are lost)"),res:h.string().optional().describe(`An asset's resolution, of its textures or of the HDRI (default ${G.resolution})`),dir:oe}),positional:["name"],output:h.looseObject({}),examples:["allyhub add","allyhub add component/caption-line","allyhub add 3d-model/monitor scenery/home-desk-day","allyhub add @polyhaven/metal_office_desk --res 2k"]},async(e,n)=>{const a=D(_);try{const d=await a.index();if(!e.name||e.name.length===0){const t=Math.max(...d.items.map(y=>`component/${y.name}`.length)),l=await P(n);return{value:{components:d.items,resources:l.index?.items??[]},text:["Starter components (allyhub add component/<name> copies one into components/; allyhub add search <words> finds among everything, Poly Haven's models, HDRIs and textures too):",...d.items.map(y=>` ${`component/${y.name}`.padEnd(t)} ${y.description}`),"",...F(l,void 0)].join(`
4
+ `)}}const r=e.name.map(ie),o=await ne(n.cwd,e.dir),p=new Set(d.items.map(t=>t.name)),c=t=>t.type==="component"&&p.has(z(t.name)),u=r.filter(c).map(t=>z(t.name)),v=r.filter(t=>t.type==="@polyhaven").map(t=>t.name),g=r.filter(t=>t.type!=="@polyhaven"&&!c(t)),b=g.length>0?await P(n):void 0;for(const t of g){const l=b?.index?.items.find(y=>y.name===t.name);if(!l)throw new j("no-item",`There is no ${t.type}/${t.name}.`,{exit:f.notFound,hint:b?.from==="none"?"AllyHub's registry could not be read: check the connection.":`allyhub add search --type ${t.type} lists them.`});if(H(l.type)!==t.type)throw new j("usage",`${t.name} is a ${H(l.type)}, not a ${t.type}.`,{exit:f.usage,hint:`allyhub add ${E(l)}`})}const w=g.map(t=>t.name),i=[],S={};if(u.length>0){const t=e.to??I,l=await L(a,u,{root:o,dir:t,force:e.force});await T(o,l);const y=m=>m==="up to date"?"Up to date":m==="added"?"Added":"Replaced";i.push(...l.files.map(m=>`${y(m.status)} ${m.path}`));const s=l.items.find(m=>m.name===u[0]),$=s?.files[0]?.target.replaceAll("{dir}",t);if(s&&$){const m=V(A(o,"name"),A(o,$)).split("\\").join("/").replace(/\.ts$/u,".js");i.push(`Import it from an item's entry file (<name>/video.ts): import { ${s.title} } from "${m.startsWith(".")?m:`./${m}`}"; one "../" more for each folder the item is deeper.`,"The file is the project's: change it as you like. allyhub add component/<name> --force puts the registry's back.")}l.missing.length>0&&i.push(`It needs packages the project does not declare: ${l.missing.join(", ")}.`),S.components=l.files.map(m=>({item:m.item,path:m.path,status:m.status})),S.missing=l.missing}if(b&&w.length>0){const t=await L(b.source,w,{root:o,dir:".",force:e.force});await T(o,t),i.length>0&&i.push(""),i.push(...t.files.map(s=>`${s.status==="up to date"?"Up to date":s.status==="added"?"Added":"Replaced"} ${s.path}`));const l=R(n);for(const s of t.assets){const $=await l.add(s.id,{root:o,...s.resolution===void 0?{}:{resolution:s.resolution},...s.maps===void 0?{}:{maps:s.maps}});i.push(...(await U(o,$)).lines)}const y=await Q(o,t.bakes);i.push(...y.map(s=>`${s.status==="added"?"Downloaded":"Up to date"} the baked light for ${s.bake.scenery}: ${s.path}`),"The files are the project's: change them as you like (a changed scenery is drawn unbaked until allyhub scenery bake bakes it again). allyhub add <type>/<name> --force puts the registry's back."),S.resources={files:t.files.map(s=>({item:s.item,path:s.path,status:s.status})),assets:t.assets,bakes:y.map(s=>({path:s.path,status:s.status}))}}if(v.length>0){const t=R(n),l=[];for(const y of v){const s=await t.add(y,{root:o,...e.to===void 0||u.length>0?{}:{to:B.normalize(e.to)},...e.res===void 0?{}:{resolution:e.res}}),$=await U(o,s);i.length>0&&i.push(""),i.push(...$.lines),l.push({...s,...$.model===void 0?{}:{model:$.model}})}S.assets=l}return{value:S,text:i.join(`
5
+ `)}}catch(d){throw M(d)}}),addSearch=C({name:"add search",summary:"Finds the starter components, AllyHub's resources (models, sceneries) and Poly Haven's CC0 models, HDRIs and textures whose names, tags, categories or descriptions hold every word, each with where its words were found; with no words, lists every one",input:h.strictObject({words:h.array(h.string()).default([]).describe("The words to find (a word may start one of theirs); none lists every one"),type:h.array(h.enum(x)).optional().describe(`Only these types (default all): ${x.join(", ")} (@polyhaven: Poly Haven's models, HDRIs and textures)`),all:h.boolean().default(!1).describe(`Every match, not only the first ${String(N)} Poly Haven assets`)}),positional:["words"],output:h.looseObject({}),examples:["allyhub add search --type 3d-model",'allyhub add search "office desk" --type @polyhaven',"allyhub add search caption --type component"]},async(e,n)=>{const a=e.words.join(" ").trim(),d=a===""?"":` hold "${a}"`;try{const r=e.type??x,o=r.includes("component")?O(await D(_).index(),a):[],p=r.filter(i=>i!=="@polyhaven"),c=p.length>0?await P(n):void 0,u=c?.index?O(c.index,a).filter(i=>p.includes(H(i.type))):[],v=r.includes("@polyhaven")?se:[],{found:g,total:b}=v.length>0?await R(n).search(a,v):{found:[],total:0},w=e.all?g:g.slice(0,N);return{value:{query:a,components:o,resources:u,assets:{total:b,matches:g.length,found:w}},text:[...r.includes("component")?[`Starter components: ${String(o.length)}${d}.`,...o.map(i=>` component/${i.name} (${i.title}): ${i.description}`)]:[],...c?c.from==="none"?F(c,[]):[`AllyHub's resources: ${String(u.length)}${d}${c.from==="stale"?" (as last read)":""}.`,...u.map(i=>` ${E(i)} (${i.title}): ${i.description}`)]:[],...v.length>0?[`From Poly Haven (CC0): ${String(g.length)} of ${String(b)} assets${d}${w.length<g.length?`; the first ${String(w.length)} (--all for every one)`:""}.`,...w.map(de)]:[],"","Add one: allyhub add <type>/<name>"].join(`
6
+ `)}}catch(r){throw M(r)}});
@@ -1,3 +1,3 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{dirname as f,join as h}from"node:path";import{fileURLToPath as u}from"node:url";import{z as s}from"zod";import{listFiles as w}from"@allyhub-next/build";import{openBrowser as b}from"@allyhub-next/cloud";import{startDevServer as p}from"@allyhub-next/dev-server";import{scanItems as v}from"@allyhub-next/formats";import{EXIT as g}from"@allyhub-next/protocol";import{CommandError as d,defineCommand as y}from"../command.js";import{dir as E,indexing as R}from"../fields.js";import{projectRoot as S}from"../project.js";export const PAGE=h(f(u(import.meta.url)),"../../editor-web");const l=4340,T=20;export async function start(r,e,a){const i={root:r,ui:PAGE,index:a};if(e!==void 0)return p({...i,port:e}).catch(t=>{throw t.code==="EADDRINUSE"?new d("port-in-use",`Port ${String(e)} is in use.`,{hint:"Choose another with --port, or leave it out."}):t});for(let t=l;t<l+T;t++)try{return await p({...i,port:t})}catch(o){if(o.code!=="EADDRINUSE")throw o}return p(i)}export const dev=y({name:"dev",summary:"The project's items in the browser, each in its editor (docs, sheets, videos, 3D sceneries), as their files change; runs until stopped",input:s.strictObject({item:s.string().optional().describe("Open the page on this item: its folder or its entry file"),dir:E,port:s.number().int().min(1).max(65535).optional().describe(`The port; default the first free one from ${String(l)}`),open:s.boolean().default(!0).describe("Open the page in the browser; --no-open only prints it")}),positional:["item"],output:s.strictObject({url:s.string()}),mcp:!1,examples:["allyhub dev","allyhub dev launch/plan","allyhub dev --port 4400 --no-open"]},async(r,e)=>{const a=await S(e.cwd,r.dir),i=await start(a,r.port,R(e.env));try{let t=i.url;if(r.item!==void 0){const{items:o}=v(await w(a)),n=r.item.replace(/^\.\//u,"").replace(/\/+$/u,""),c=o.find(m=>m.dir===n||m.entry===n);if(!c)throw new d("not-found",`There is no item ${r.item} in the project.`,{exit:g.notFound,hint:`Its items: ${o.map(m=>m.dir).join(", ")||"none"}.`});t=`${i.url}?${new URLSearchParams({item:c.dir}).toString()}`}return e.progress(`allyhub dev is at ${t}
3
- Press Ctrl+C to stop.`),r.open&&b(t,e.env),await new Promise(o=>{const n=()=>{process.off("SIGTERM",n),e.signal.removeEventListener("abort",n),o()};e.signal.aborted&&o(),e.signal.addEventListener("abort",n),process.once("SIGTERM",n)}),{value:{url:t},text:""}}finally{await i.close()}});
2
+ import{mkdir as y,readFile as f,rm as b,writeFile as S}from"node:fs/promises";import{dirname as m,join as p}from"node:path";import{fileURLToPath as E}from"node:url";import{z as a}from"zod";import{listFiles as R}from"@allyhub-next/build";import{openBrowser as h}from"@allyhub-next/cloud";import{startDevServer as u}from"@allyhub-next/dev-server";import{DEV_FILE as w,scanItems as T}from"@allyhub-next/formats";import{EXIT as I}from"@allyhub-next/protocol";import{CommandError as g,defineCommand as $}from"../command.js";import{dir as O,indexing as D}from"../fields.js";import{projectRoot as P}from"../project.js";export const PAGE=p(m(E(import.meta.url)),"../../editor-web");const l=4340,j=20;export async function start(r,e,n){const s={root:r,ui:PAGE,index:n};if(e!==void 0)return u({...s,port:e}).catch(t=>{throw t.code==="EADDRINUSE"?new g("port-in-use",`Port ${String(e)} is in use.`,{hint:"Choose another with --port, or leave it out."}):t});for(let t=l;t<l+j;t++)try{return await u({...s,port:t})}catch(o){if(o.code!=="EADDRINUSE")throw o}return u(s)}async function F(r){try{const e=JSON.parse(await f(p(r,w),"utf8"));return typeof e.url!="string"||typeof e.pid!="number"?void 0:(process.kill(e.pid,0),(await fetch(e.url,{signal:AbortSignal.timeout(2e3)})).ok?e.url:void 0)}catch{return}}async function v(r,e,n){if(n===void 0)return e;const{items:s}=T(await R(r)),t=n.replace(/^\.\//u,"").replace(/\/+$/u,""),o=s.find(i=>i.dir===t||i.entry===t);if(!o)throw new g("not-found",`There is no item ${n} in the project.`,{exit:I.notFound,hint:`Its items: ${s.map(i=>i.dir).join(", ")||"none"}.`});return`${e}?${new URLSearchParams({item:o.dir}).toString()}`}export const dev=$({name:"dev",summary:"The project's items in the browser, each in its editor (docs, sheets, videos, 3D sceneries), as their files change; runs until stopped, and run again while it runs, opens its page and ends",input:a.strictObject({item:a.string().optional().describe("Open the page on this item: its folder or its entry file"),dir:O,port:a.number().int().min(1).max(65535).optional().describe(`The port; default the first free one from ${String(l)}`),open:a.boolean().default(!0).describe("Open the page in the browser; --no-open only prints it")}),positional:["item"],output:a.strictObject({url:a.string()}),mcp:!1,examples:["allyhub dev","allyhub dev launch/plan","allyhub dev --port 4400 --no-open"]},async(r,e)=>{const n=await P(e.cwd,r.dir),s=r.port===void 0?await F(n):void 0;if(s!==void 0){const i=await v(n,s,r.item);return e.progress(`allyhub dev is already running at ${i}`),r.open&&h(i,e.env),{value:{url:i},text:""}}const t=await start(n,r.port,D(e.env)),o=p(n,w);try{const i=await v(n,t.url,r.item);return await y(m(o),{recursive:!0}),await S(o,JSON.stringify({url:t.url,pid:process.pid})),e.progress(`allyhub dev is at ${i}
3
+ Press Ctrl+C to stop.`),r.open&&h(i,e.env),await new Promise(c=>{const d=()=>{process.off("SIGTERM",d),e.signal.removeEventListener("abort",d),c()};e.signal.aborted&&c(),e.signal.addEventListener("abort",d),process.once("SIGTERM",d)}),{value:{url:i},text:""}}finally{(await f(o,"utf8").catch(()=>"")).includes(`"pid":${String(process.pid)}`)&&await b(o,{force:!0}),await t.close()}});
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{McpServer as y}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as b}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as S,ListResourcesRequestSchema as g,ListToolsRequestSchema as k,ReadResourceRequestSchema as v}from"@modelcontextprotocol/sdk/types.js";import{z as f}from"zod";import{describeCommand as R,toolName as d}from"@allyhub-next/protocol";import{CommandError as m,defineCommand as C}from"../command.js";import{CardApp as j}from"./mcp-app.js";import{docResources as M,readDoc as O}from"./mcp-docs.js";const A="0.0.0";export const MCP_INSTRUCTIONS=["AllyHub makes videos, decks, docs, sheets and 3D sceneries as code in a project folder, checks, previews and renders them on this computer, and runs AI models and research tools in its cloud, which cost the person's credits.","Each tool here is one of its commands: `allyhub help` on the command line (`npx -y allyhub help`) is their reference and says where the SDK's docs are.",`\`allyhub skills\` (${d("skills")}) lists AllyHub's work skills, each how to make one kind of work; \`allyhub skills show <name>\` (${d("skills show")}) prints one, and \`allyhub add <name>\` puts it in the project.`].join(" ");async function N(i,t,u){const c=(e,o=!1)=>({content:[{type:"text",text:JSON.stringify(e,null,2)}],...o?{isError:o}:{}}),a=i.spec.input.safeParse(t??{});if(!a.success){const e=a.error.issues[0];return c({error:{code:"usage",message:`${e?.path.join(".")??i.spec.name}: ${e?.message??"not valid"}.`}},!0)}try{const e=await i.run(a.data,u);return c(e.value)}catch(e){const o=e instanceof m?e:new m("failed",e instanceof Error?e.message:String(e));return c({error:{code:o.code,message:o.message,...o.hint===void 0?{}:{hint:o.hint}}},!0)}}export const mcp=C({name:"mcp",summary:"The commands as MCP tools, over stdio",input:f.strictObject({}),output:f.looseObject({}),examples:["claude mcp add allyhub -- allyhub mcp"],mcp:!1},async(i,t)=>{const{COMMANDS:u,MCP_COMMANDS:c}=await import("./index.js"),a=new Map([...u,...c].filter(s=>s.spec.mcp!==!1).map(s=>[d(s.spec.name),s]));console.log=console.error,console.info=console.error;const e=new y({name:"allyhub",version:A},{capabilities:{tools:{},resources:{}},instructions:MCP_INSTRUCTIONS}),o=new j(t),l=e.server;l.setRequestHandler(k,()=>({tools:[...o.tools(),...[...a].map(([s,n])=>{const r=Object.fromEntries(Object.entries(R(n.spec).input).filter(([p])=>p!=="$schema"));return{name:s,description:n.spec.summary,inputSchema:{type:"object",...r}}})]})),l.setRequestHandler(g,async()=>({resources:[...o.resources(),...await M(t.cwd)]})),l.setRequestHandler(v,async s=>{const{uri:n}=s.params;if(n===o.resources()[0]?.uri)return{contents:[await o.resource()]};const r=await O(t.cwd,n);if(r===void 0)throw new m("not-found",`allyhub has no resource ${n}.`);return{contents:[r]}}),l.setRequestHandler(S,(s,n)=>{if(o.has(s.params.name))return o.call(s.params.arguments);const r=a.get(s.params.name);if(!r)throw new m("unknown-tool",`allyhub has no tool ${s.params.name}.`);const p=AbortSignal.any([t.signal,n.signal]);return N(r,s.params.arguments,{...t,signal:p})});const h=new b,w=new Promise(s=>{h.onclose=s});return await e.connect(h),process.stdin.once("end",()=>{e.close()}),t.signal.addEventListener("abort",()=>{e.close()}),await w,await o.close(),{value:{},text:""}});
2
+ import{McpServer as y}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as b}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as S,ListResourcesRequestSchema as g,ListToolsRequestSchema as k,ReadResourceRequestSchema as v}from"@modelcontextprotocol/sdk/types.js";import{z as f}from"zod";import{describeCommand as R,toolName as d}from"@allyhub-next/protocol";import{CommandError as m,defineCommand as C}from"../command.js";import{CLI_VERSION as j}from"../cli-version.js";import{CardApp as M}from"./mcp-app.js";import{docResources as O,readDoc as A}from"./mcp-docs.js";export const MCP_INSTRUCTIONS=["AllyHub makes videos, decks, docs, sheets and 3D sceneries as code in a project folder, checks, previews and renders them on this computer, and runs AI models and research tools in its cloud, which cost the person's credits.","Each tool here is one of its commands: `allyhub help` on the command line (`npx -y allyhub help`) is their reference and says where the SDK's docs are.",`\`allyhub skills\` (${d("skills")}) lists AllyHub's work skills, each how to make one kind of work; \`allyhub skills show <name>\` (${d("skills show")}) prints one, and \`allyhub add skill/<name>\` puts it in the project.`].join(" ");async function N(i,t,u){const c=(e,o=!1)=>({content:[{type:"text",text:JSON.stringify(e,null,2)}],...o?{isError:o}:{}}),a=i.spec.input.safeParse(t??{});if(!a.success){const e=a.error.issues[0];return c({error:{code:"usage",message:`${e?.path.join(".")??i.spec.name}: ${e?.message??"not valid"}.`}},!0)}try{const e=await i.run(a.data,u);return c(e.value)}catch(e){const o=e instanceof m?e:new m("failed",e instanceof Error?e.message:String(e));return c({error:{code:o.code,message:o.message,...o.hint===void 0?{}:{hint:o.hint}}},!0)}}export const mcp=C({name:"mcp",summary:"The commands as MCP tools, over stdio",input:f.strictObject({}),output:f.looseObject({}),examples:["claude mcp add allyhub -- allyhub mcp"],mcp:!1},async(i,t)=>{const{COMMANDS:u,MCP_COMMANDS:c}=await import("./index.js"),a=new Map([...u,...c].filter(s=>s.spec.mcp!==!1).map(s=>[d(s.spec.name),s]));console.log=console.error,console.info=console.error;const e=new y({name:"allyhub",version:j},{capabilities:{tools:{},resources:{}},instructions:MCP_INSTRUCTIONS}),o=new M(t),l=e.server;l.setRequestHandler(k,()=>({tools:[...o.tools(),...[...a].map(([s,r])=>{const n=Object.fromEntries(Object.entries(R(r.spec).input).filter(([p])=>p!=="$schema"));return{name:s,description:r.spec.summary,inputSchema:{type:"object",...n}}})]})),l.setRequestHandler(g,async()=>({resources:[...o.resources(),...await O(t.cwd)]})),l.setRequestHandler(v,async s=>{const{uri:r}=s.params;if(r===o.resources()[0]?.uri)return{contents:[await o.resource()]};const n=await A(t.cwd,r);if(n===void 0)throw new m("not-found",`allyhub has no resource ${r}.`);return{contents:[n]}}),l.setRequestHandler(S,(s,r)=>{if(o.has(s.params.name))return o.call(s.params.arguments);const n=a.get(s.params.name);if(!n)throw new m("unknown-tool",`allyhub has no tool ${s.params.name}.`);const p=AbortSignal.any([t.signal,r.signal]);return N(n,s.params.arguments,{...t,signal:p})});const h=new b,w=new Promise(s=>{h.onclose=s});return await e.connect(h),process.stdin.once("end",()=>{e.close()}),t.signal.addEventListener("abort",()=>{e.close()}),await w,await o.close(),{value:{},text:""}});
@@ -1,3 +1,3 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
2
  import{stat as j}from"node:fs/promises";import{basename as c,dirname as u,join as f,resolve as p}from"node:path";import{z as i}from"zod";import{CACHE_DIR as y,OUT_DIR as h}from"@allyhub-next/formats";import{BUILD_DEFAULTS as x,ModelError as S,buildModule as O,capabilities as b}from"@allyhub-next/model/build";import{EXIT as m}from"@allyhub-next/protocol";import{CommandError as n,defineCommand as g}from"../command.js";import{projectAt as v,projectRoot as w}from"../project.js";import{parameters as A}from"../parameters.js";const $=`${y}/model`,T=t=>t.toFixed(3);function C(t){const r=c(t).replace(/\.[^.]+$/u,"");return r==="model"?c(u(t)):r}function B(t,r){const a=t.objects.filter(e=>e.triangles===0&&!e.curve);return[`${r}: ${String(t.objects.length)} objects, ${String(t.triangles)} triangles (${t.upAxis} up, ${t.units}s).`,...t.objects.map(e=>{const l=e.bounds?e.bounds.max.map((s,d)=>T(s-(e.bounds?.min[d]??0))).join(" × "):void 0,o=e.materials.flatMap(s=>s?[s.name]:[]);return` ${e.name}${e.parent?` (in ${e.parent})`:""}: ${String(e.triangles)} triangles${l?`, ${l} m`:""}${o.length>0?`, ${o.join(", ")}`:""}${e.curve?`, a curve of ${String(e.curve.splines.length)} splines`:""}${e.instance?`, an instance of ${e.instance.object}`:""}`}),...a.length>0?[`No triangles after their modifiers: ${a.map(e=>e.name).join(", ")}.`]:[]].join(`
3
- `)}export const model=g({name:"model",summary:"Builds a TypeScript model module (defineModel) into a GLB; --inspect measures it without writing one",input:i.strictObject({file:i.string().describe("The model module, a .ts, .mts, .js or .mjs file whose default export is defineModel(build)"),inspect:i.boolean().default(!1).describe("Measure it without writing a GLB: each object's parent, matrix, bounds (Blender Z-up metres), triangles, materials as glTF reads them, UV layers, a curve's splines"),args:i.string().default("{}").describe("What the module's build is given (defineModel's parameters), a JSON object"),out:i.string().optional().describe(`The GLB to write (default ${h}/<module>.glb, the folder's name for a model.ts); one there is replaced only after the build succeeds`),dir:i.string().optional().describe("The project, where the module's image paths start (default the one the module is in, else the folder it is run in)"),fresh:i.boolean().default(!1).describe(`Build anew instead of reusing the build kept in ${$} (one is reused while the module, the files it read, its parameters, seed and the modelling runtime are unchanged); the new build is kept too`),params:A({seed:i.int().default(x.seed).describe("Seed of the build's random generator (bpy.random, 32 bits)")})}),positional:["file"],output:i.looseObject({}),examples:["allyhub model table/model.ts",`allyhub model table/model.ts --inspect --args '{"width":1.8}'`]},async(t,r)=>{const a=p(r.cwd,t.file);if(!(await j(a).catch(()=>{}))?.isFile())throw new n("not-found",`There is no file ${t.file}.`,{exit:m.notFound});if(!/\.(?:ts|mts|js|mjs)$/u.test(a))throw new n("usage","A model module is a .ts, .mts, .js or .mjs file.",{exit:m.usage});let e;try{e=JSON.parse(t.args)}catch{e=void 0}if(typeof e!="object"||e===null||Array.isArray(e))throw new n("usage","--args is a JSON object.",{exit:m.usage});const l=t.dir===void 0?await v(u(a))??await w(r.cwd):await w(r.cwd,t.dir),o=t.inspect?void 0:p(r.cwd,t.out??f(l,h,`${C(a)}.glb`));if(o&&!o.toLowerCase().endsWith(".glb"))throw new n("usage","--out is a .glb file.",{exit:m.usage});try{const s=await O(a,{parameters:e,seed:t.params.seed,root:l,...o?{out:o}:{},cache:f(l,$),fresh:t.fresh}),d=s.cache==="hit"?" (cached)":"";return{value:{...s.report,...s.glb?{glb:s.glb}:{},cache:s.cache},text:B(s.report,s.glb?`Wrote ${s.glb}${d}`:`${t.file}${d}`)}}catch(s){throw s instanceof S?new n(`model-${s.code.toLowerCase().replace(/^model[-_]/u,"").replaceAll(/[^a-z0-9]+/gu,"-")}`,s.message):s}}),modelCapabilities=g({name:"model capabilities",summary:"What the modelling API supports (the Blender subset, its limits and deliberate differences), as JSON",input:i.strictObject({}),output:i.looseObject({}),examples:["allyhub model capabilities"]},()=>Promise.resolve({value:b,text:JSON.stringify(b,null,2)}));
3
+ `)}export const model=g({name:"3d-model",summary:"Builds a 3D model's TypeScript module (defineModel) into a GLB; --inspect measures it without writing one",input:i.strictObject({file:i.string().describe("The model module, a .ts, .mts, .js or .mjs file whose default export is defineModel(build)"),inspect:i.boolean().default(!1).describe("Measure it without writing a GLB: each object's parent, matrix, bounds (Blender Z-up metres), triangles, materials as glTF reads them, UV layers, a curve's splines"),args:i.string().default("{}").describe("What the module's build is given (defineModel's parameters), a JSON object"),out:i.string().optional().describe(`The GLB to write (default ${h}/<module>.glb, the folder's name for a model.ts); one there is replaced only after the build succeeds`),dir:i.string().optional().describe("The project, where the module's image paths start (default the one the module is in, else the folder it is run in)"),fresh:i.boolean().default(!1).describe(`Build anew instead of reusing the build kept in ${$} (one is reused while the module, the files it read, its parameters, seed and the modelling runtime are unchanged); the new build is kept too`),params:A({seed:i.int().default(x.seed).describe("Seed of the build's random generator (bpy.random, 32 bits)")})}),positional:["file"],output:i.looseObject({}),examples:["allyhub 3d-model table/model.ts",`allyhub 3d-model table/model.ts --inspect --args '{"width":1.8}'`]},async(t,r)=>{const a=p(r.cwd,t.file);if(!(await j(a).catch(()=>{}))?.isFile())throw new n("not-found",`There is no file ${t.file}.`,{exit:m.notFound});if(!/\.(?:ts|mts|js|mjs)$/u.test(a))throw new n("usage","A model module is a .ts, .mts, .js or .mjs file.",{exit:m.usage});let e;try{e=JSON.parse(t.args)}catch{e=void 0}if(typeof e!="object"||e===null||Array.isArray(e))throw new n("usage","--args is a JSON object.",{exit:m.usage});const l=t.dir===void 0?await v(u(a))??await w(r.cwd):await w(r.cwd,t.dir),o=t.inspect?void 0:p(r.cwd,t.out??f(l,h,`${C(a)}.glb`));if(o&&!o.toLowerCase().endsWith(".glb"))throw new n("usage","--out is a .glb file.",{exit:m.usage});try{const s=await O(a,{parameters:e,seed:t.params.seed,root:l,...o?{out:o}:{},cache:f(l,$),fresh:t.fresh}),d=s.cache==="hit"?" (cached)":"";return{value:{...s.report,...s.glb?{glb:s.glb}:{},cache:s.cache},text:B(s.report,s.glb?`Wrote ${s.glb}${d}`:`${t.file}${d}`)}}catch(s){throw s instanceof S?new n(`model-${s.code.toLowerCase().replace(/^model[-_]/u,"").replaceAll(/[^a-z0-9]+/gu,"-")}`,s.message):s}}),modelCapabilities=g({name:"3d-model capabilities",summary:"What the modelling API supports (the Blender subset, its limits and deliberate differences), as JSON",input:i.strictObject({}),output:i.looseObject({}),examples:["allyhub 3d-model capabilities"]},()=>Promise.resolve({value:b,text:JSON.stringify(b,null,2)}));
@@ -1,3 +1,3 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{join as u,resolve as b}from"node:path";import{z as a}from"zod";import{formatConsole as y,formatProblem as g}from"../text/index.js";import{exportProject as x}from"@allyhub-next/export";import{SHARE_ENCODE as s}from"@allyhub-next/render";import{consoleJson as m}from"@allyhub-next/project";import{OUT_DIR as v}from"@allyhub-next/formats";import{EXIT as d}from"@allyhub-next/protocol";import{defineCommand as w}from"../command.js";import{parameters as $}from"../parameters.js";import{projectRoot as k}from"../project.js";import{combosOf as P,commandError as O}from"../version.js";import{dir as j,indexing as B}from"../fields.js";const S={codec:a.enum(["h264","h265"]).default("h264").describe("The mp4's video codec; h265 is smaller"),crf:a.number().int().min(0).max(63).optional().describe(`Quality, lower is better: default ${String(s.crf)} for an mp4, ready to share (a platform profile's format its own), 30 for a webm`),maxBitrate:a.number().int().min(0).max(5e5).optional().describe(`An mp4's most video bitrate, kbit/s, 0 for none: default ${String(s.maxKbps)} at 1080p ${String(s.fps)} fps, scaled by the frame's pixels and the frames a second (16000 at 1080p 60, 32000 at 4K 30), so the file is ready to share; a platform profile's format its own`),audioBitrate:a.number().int().min(32).max(512).optional().describe("The sound's bitrate, kbit/s: default 192 (AAC in an mp4), 160 (Opus in a webm); a mov keeps PCM")},T=(o,r)=>`${String(o)} ${r}${o===1?"":"s"}`;export const render=w({name:"render",summary:"The files: a video, its frames, subtitles, cover and mastered mix; a batch's table of ads; a deck's PDF, PPTX and PNGs; a doc's PDF and Markdown; a sheet's xlsx, CSV, Markdown and JSON",input:a.strictObject({items:a.array(a.string()).optional().describe("Only these items: an id, a path into one, or a folder of items; default every one"),combo:a.array(a.string()).optional().describe("Only the combos matching name=value pairs joined with commas (lang=zh-cn,hook=b), or a combo's id (c03)"),type:a.array(a.string()).optional().describe("File types: mp4, mov, webm, gif, png, srt, vtt (cues cut as the piece's captions layer cuts them, else by default), cover, wav (the mix, mastered as the render masters it: 48 kHz, 16 bits, the film's length), pdf, pptx; csv for a piece with slots, its table of ads (<piece>/ads.csv: a row per ad with its combo, picks, length, file, name and generated media); a doc's pdf, png, md; a sheet's xlsx, csv, md, json. Default mp4 for a video, pdf for a deck and a doc, xlsx for a sheet; a type an item does not have is skipped"),out:a.string().optional().describe("The folder the files go in; default .allyhub/out"),name:a.string().optional().describe("Name a video's or deck's files <name>.<ext> in its folder of the output, with what tells its versions apart before the extension when there are several (<name>.zh-cn.mp4, <name>.c03.mp4); default <output>.<combo>.<ext>"),params:$(S),dir:j}),positional:["items"],output:a.looseObject({out:a.string(),files:a.array(a.looseObject({name:a.string(),path:a.string(),type:a.string()})),failed:a.array(a.looseObject({name:a.string(),code:a.string()}))}),examples:["allyhub render","allyhub render launch/film --type mp4,srt","allyhub render launch/film --type wav","allyhub render ads/tidyform --type csv","allyhub render launch/plan --type pdf,md","allyhub render --combo lang=zh-cn --param crf=16","allyhub render launch/film --name launch-film --param max-bitrate=0"]},async(o,r)=>{const n=await k(r.cwd,o.dir),l=b(r.cwd,o.out??u(n,v)),i=o.params,f={codec:i.codec,...i.crf===void 0?{}:{crf:i.crf},...i.maxBitrate===void 0?{}:{maxBitrate:i.maxBitrate},...i.audioBitrate===void 0?{}:{audioBitrate:i.audioBitrate}};let t;try{t=await x({root:n,out:l,...o.items===void 0?{}:{items:o.items},combos:P(o),...o.type===void 0?{}:{types:o.type},...o.name===void 0?{}:{name:o.name},encode:f,index:B(r.env),log:e=>{r.progress(e)},signal:r.signal})}catch(e){throw O(e)}const p=e=>e?` (${e.integrated.toFixed(1)} LUFS, true peak ${e.truePeak===void 0?"–":e.truePeak.toFixed(1)} dBTP)`:"",c=t.problems.filter(e=>e.level==="error"),h=[`${T(t.files.length,"file")} in ${t.out}:`,...t.files.map(e=>` ${e.path}${p(e.loudness)}`),...t.failed.length>0?["Not made:",...t.failed.map(e=>` ${e.name}: ${e.message}${e.hint?` ${e.hint}`:""}`)]:[],...t.unindexed.map(e=>`Not indexed, drawn the fallback way: ${e.file} (${e.error})`),...c.map(g),...y(m(t.console))];return{value:{out:t.out,files:t.files.map(e=>({...e})),failed:t.failed.map(e=>({...e})),problems:t.problems,...t.unindexed.length>0?{unindexed:t.unindexed}:{},...m(t.console)},text:h.join(`
2
+ import{join as u,resolve as b}from"node:path";import{z as a}from"zod";import{formatConsole as g,formatProblem as y}from"../text/index.js";import{exportProject as x}from"@allyhub-next/export";import{SHARE_ENCODE as s}from"@allyhub-next/render";import{consoleJson as m}from"@allyhub-next/project";import{OUT_DIR as v}from"@allyhub-next/formats";import{EXIT as d}from"@allyhub-next/protocol";import{defineCommand as w}from"../command.js";import{parameters as $}from"../parameters.js";import{projectRoot as k}from"../project.js";import{combosOf as P,commandError as O}from"../version.js";import{dir as j,indexing as B}from"../fields.js";const S={codec:a.enum(["h264","h265"]).default("h264").describe("The mp4's video codec; h265 is smaller"),crf:a.number().int().min(0).max(63).optional().describe(`Quality, lower is better: default ${String(s.crf)} for an mp4, ready to share (a platform profile's format its own), 30 for a webm`),maxBitrate:a.number().int().min(0).max(5e5).optional().describe(`An mp4's most video bitrate, kbit/s, 0 for none: default ${String(s.maxKbps)} at 1080p ${String(s.fps)} fps, scaled by the frame's pixels and the frames a second (16000 at 1080p 60, 32000 at 4K 30), so the file is ready to share; a platform profile's format its own`),audioBitrate:a.number().int().min(32).max(512).optional().describe("The sound's bitrate, kbit/s: default 192 (AAC in an mp4), 160 (Opus in a webm); a mov keeps PCM")},T=(o,r)=>`${String(o)} ${r}${o===1?"":"s"}`;export const render=w({name:"render",summary:"The files: a video, its frames, subtitles, cover and mastered mix; a batch's table of ads; a deck's PDF, PPTX and PNGs; a doc's PDF and Markdown; a sheet's xlsx, CSV, Markdown and JSON; a 3D model's GLB",input:a.strictObject({items:a.array(a.string()).optional().describe("Only these items: an id, a path into one, or a folder of items; default every one"),combo:a.array(a.string()).optional().describe("Only the combos matching name=value pairs joined with commas (lang=zh-cn,hook=b), or a combo's id (c03)"),type:a.array(a.string()).optional().describe("File types: mp4, mov, webm, gif, png, srt, vtt (cues cut as the piece's captions layer cuts them, else by default), cover, wav (the mix, mastered as the render masters it: 48 kHz, 16 bits, the film's length), pdf, pptx; csv for a piece with slots, its table of ads (<piece>/ads.csv: a row per ad with its combo, picks, length, file, name and generated media); a doc's pdf, png, md; a sheet's xlsx, csv, md, json; a 3D model's glb. Default mp4 for a video, pdf for a deck and a doc, xlsx for a sheet, glb for a model; a type an item does not have is skipped"),out:a.string().optional().describe("The folder the files go in; default .allyhub/out"),name:a.string().optional().describe("Name a video's or deck's files <name>.<ext> in its folder of the output, with what tells its versions apart before the extension when there are several (<name>.zh-cn.mp4, <name>.c03.mp4); default <output>.<combo>.<ext>"),params:$(S),dir:j}),positional:["items"],output:a.looseObject({out:a.string(),files:a.array(a.looseObject({name:a.string(),path:a.string(),type:a.string()})),failed:a.array(a.looseObject({name:a.string(),code:a.string()}))}),examples:["allyhub render","allyhub render launch/film --type mp4,srt","allyhub render launch/film --type wav","allyhub render ads/tidyform --type csv","allyhub render launch/plan --type pdf,md","allyhub render --combo lang=zh-cn --param crf=16","allyhub render launch/film --name launch-film --param max-bitrate=0"]},async(o,r)=>{const n=await k(r.cwd,o.dir),l=b(r.cwd,o.out??u(n,v)),i=o.params,f={codec:i.codec,...i.crf===void 0?{}:{crf:i.crf},...i.maxBitrate===void 0?{}:{maxBitrate:i.maxBitrate},...i.audioBitrate===void 0?{}:{audioBitrate:i.audioBitrate}};let t;try{t=await x({root:n,out:l,...o.items===void 0?{}:{items:o.items},combos:P(o),...o.type===void 0?{}:{types:o.type},...o.name===void 0?{}:{name:o.name},encode:f,index:B(r.env),log:e=>{r.progress(e)},signal:r.signal})}catch(e){throw O(e)}const p=e=>e?` (${e.integrated.toFixed(1)} LUFS, true peak ${e.truePeak===void 0?"–":e.truePeak.toFixed(1)} dBTP)`:"",c=t.problems.filter(e=>e.level==="error"),h=[`${T(t.files.length,"file")} in ${t.out}:`,...t.files.map(e=>` ${e.path}${p(e.loudness)}`),...t.failed.length>0?["Not made:",...t.failed.map(e=>` ${e.name}: ${e.message}${e.hint?` ${e.hint}`:""}`)]:[],...t.unindexed.map(e=>`Not indexed, drawn the fallback way: ${e.file} (${e.error})`),...c.map(y),...g(m(t.console))];return{value:{out:t.out,files:t.files.map(e=>({...e})),failed:t.failed.map(e=>({...e})),problems:t.problems,...t.unindexed.length>0?{unindexed:t.unindexed}:{},...m(t.console)},text:h.join(`
3
3
  `),exit:t.failed.length>0?d.failed:d.ok}});
@@ -1,4 +1,4 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{z as r}from"zod";import{EXIT as m}from"@allyhub-next/protocol";import{RegistryError as y}from"@allyhub-next/registry";import{workSkills as b}from"@allyhub-next/scaffold";import{CommandError as d,defineCommand as f}from"../command.js";import{resourcesIndex as p}from"../resources.js";const w="Check the connection and run the same command again.";function k(i){if(i.index===void 0)throw new d("no-registry",`AllyHub's skills could not be read from ${i.source.name}.`,{exit:m.platform,hint:w})}export const skills=f({name:"skills",summary:"Lists AllyHub's work skills, each how to make one kind of work, with what it is for: allyhub skills show <name> prints one, allyhub add <name> puts it in the project",input:r.strictObject({}),output:r.looseObject({}),examples:["allyhub skills","allyhub skills show product-demos"]},async(i,c)=>{const s=await p(c);k(s);const a=b(s.index.items).map(t=>{const l=s.index.items.find(h=>h.name===t.name)?.version;return{...t,...l===void 0?{}:{version:l}}}),e=s.from==="stale",o=Math.max(0,...a.map(t=>t.name.length));return{value:{skills:a,stale:e},text:[`AllyHub's work skills (allyhub skills show <name> prints one; allyhub add <name> puts it in the project, in .agents/skills)${e?", as last read (the registry cannot be read now)":""}:`,...a.length===0?[" none"]:a.map(t=>` ${t.name.padEnd(o)} ${t.description}`)].join(`
3
- `)}}),skillsShow=f({name:"skills show",summary:"Prints one of AllyHub's skills in full, its SKILL.md and the names of its other files, without adding it to the project",input:r.strictObject({name:r.string().min(1).describe("The skill, as allyhub skills lists it (product-demos)")}),positional:["name"],output:r.looseObject({}),examples:["allyhub skills show product-demos"]},async(i,c)=>{const s=await p(c);if(k(s),s.index.items.find(e=>e.name===i.name)?.type!=="allyhub:skill")throw new d("no-item",`AllyHub has no skill ${i.name}.`,{exit:m.notFound,hint:"allyhub skills lists them."});try{const e=await s.source.item(i.name);if(!e)throw new d("no-item",`AllyHub has no skill ${i.name}.`,{exit:m.notFound,hint:"allyhub skills lists them."});const o=`.agents/skills/${e.name}/`,t=n=>n.startsWith(o)?n.slice(o.length):n,l=e.files.find(n=>t(n.target)==="SKILL.md");if(!l)throw new d("bad-registry",`The registry's ${e.name} has no SKILL.md.`);const h=l.content??new TextDecoder().decode(await s.source.file(l.path)),u=e.files.map(n=>t(n.target)).filter(n=>n!=="SKILL.md");return{value:{name:e.name,...e.version===void 0?{}:{version:e.version},skill:h,files:u},text:[h.trimEnd(),"",u.length===0?`It has no other files. allyhub add ${e.name} puts it in ${o}.`:`Its other files, which allyhub add ${e.name} puts beside it in ${o}: ${u.join(", ")}.`].join(`
4
- `)}}catch(e){throw e instanceof y?new d(e.code,e.message,{exit:e.code==="no-registry"?m.platform:m.failed,hint:e.hint??w}):e}});
2
+ import{z as r}from"zod";import{EXIT as m}from"@allyhub-next/protocol";import{RegistryError as y}from"@allyhub-next/registry";import{workSkills as b}from"@allyhub-next/scaffold";import{CommandError as d,defineCommand as f}from"../command.js";import{resourcesIndex as k}from"../resources.js";const p="Check the connection and run the same command again.";function w(i){if(i.index===void 0)throw new d("no-registry",`AllyHub's skills could not be read from ${i.source.name}.`,{exit:m.platform,hint:p})}export const skills=f({name:"skills",summary:"Lists AllyHub's work skills, each how to make one kind of work, with what it is for: allyhub skills show <name> prints one, allyhub add skill/<name> puts it in the project",input:r.strictObject({}),output:r.looseObject({}),examples:["allyhub skills","allyhub skills show product-demos"]},async(i,c)=>{const s=await k(c);w(s);const a=b(s.index.items).map(t=>{const o=s.index.items.find(h=>h.name===t.name)?.version;return{...t,...o===void 0?{}:{version:o}}}),e=s.from==="stale",l=Math.max(0,...a.map(t=>t.name.length));return{value:{skills:a,stale:e},text:[`AllyHub's work skills (allyhub skills show <name> prints one; allyhub add skill/<name> puts it in the project, in .agents/skills)${e?", as last read (the registry cannot be read now)":""}:`,...a.length===0?[" none"]:a.map(t=>` ${t.name.padEnd(l)} ${t.description}`)].join(`
3
+ `)}}),skillsShow=f({name:"skills show",summary:"Prints one of AllyHub's skills in full, its SKILL.md and the names of its other files, without adding it to the project",input:r.strictObject({name:r.string().min(1).describe("The skill, as allyhub skills lists it (product-demos)")}),positional:["name"],output:r.looseObject({}),examples:["allyhub skills show product-demos"]},async(i,c)=>{const s=await k(c);if(w(s),s.index.items.find(e=>e.name===i.name)?.type!=="allyhub:skill")throw new d("no-item",`AllyHub has no skill ${i.name}.`,{exit:m.notFound,hint:"allyhub skills lists them."});try{const e=await s.source.item(i.name);if(!e)throw new d("no-item",`AllyHub has no skill ${i.name}.`,{exit:m.notFound,hint:"allyhub skills lists them."});const l=`.agents/skills/${e.name}/`,t=n=>n.startsWith(l)?n.slice(l.length):n,o=e.files.find(n=>t(n.target)==="SKILL.md");if(!o)throw new d("bad-registry",`The registry's ${e.name} has no SKILL.md.`);const h=o.content??new TextDecoder().decode(await s.source.file(o.path)),u=e.files.map(n=>t(n.target)).filter(n=>n!=="SKILL.md");return{value:{name:e.name,...e.version===void 0?{}:{version:e.version},skill:h,files:u},text:[h.trimEnd(),"",u.length===0?`It has no other files. allyhub add skill/${e.name} puts it in ${l}.`:`Its other files, which allyhub add skill/${e.name} puts beside it in ${l}: ${u.join(", ")}.`].join(`
4
+ `)}}catch(e){throw e instanceof y?new d(e.code,e.message,{exit:e.code==="no-registry"?m.platform:m.failed,hint:e.hint??p}):e}});
package/dist/main.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{readFileSync as D}from"node:fs";import{EXIT as m,describeCommand as I}from"@allyhub-next/protocol";import{readArgs as R}from"./args.js";import{CommandError as h}from"./command.js";import{COMMAND_SOURCES as M,COMMANDS as x}from"./commands/index.js";import{commandHelp as j,listHelp as T,workHelp as H}from"./help.js";import{sdkDocs as J}from"./sdk-docs.js";import{WORKS as G,cloudNouns as U,workNamed as W,workTitle as E}from"./works.js";const N=JSON.parse(D(new URL("../package.json",import.meta.url),"utf8")).version,F={stdout:t=>process.stdout.write(t),stderr:t=>process.stderr.write(t),cwd:process.cwd(),env:process.env};export function findCommand(t,s=x){let i;for(const c of s){const l=c.spec.name.split(" ");l.every((o,a)=>t[a]===o)&&l.length>(i?.length??0)&&(i={command:c,length:l.length})}return i?{command:i.command,rest:t.slice(i.length)}:{rest:[...t]}}export async function resolveCommand(t,s,i=x,c=M){const l=findCommand(t,i);if(l.command)return l;for(const o of c){const a=await o.resolve(t,s);if(a)return a}return l}const A=t=>({...I(t.spec),...t.about?.value});export async function main(t,s=F,i=x,c=M){const l=t.includes("--json"),o=t.filter(n=>n!=="--json"),a=(n,e)=>{s.stdout(l?`${JSON.stringify(n,null,2)}
3
- `:`${e}
4
- `)},S=i.map(n=>n.spec),b=new AbortController,y={cwd:s.cwd,env:s.env,progress:n=>{s.stderr(`${n}
5
- `)},signal:b.signal};if(o[0]==="--version"||o[0]==="-v")return a({version:N},N),m.ok;if(o.length===0||o[0]==="help"||o[0]==="--help"||o[0]==="-h"){const n=o.slice(1),e=await J(s.cwd).catch(()=>{}),k=async()=>{const r=await Promise.all(c.map(u=>u.nouns(y).catch(()=>{})));return r.some(u=>u===void 0)?void 0:r.flatMap(u=>u??[])};if(n.length===0){const r=await k();return a({version:N,docs:e?.dir,works:G.map(u=>({...u,title:E(u,r)})),commands:S.map(I),made:[...c.flatMap(u=>u.entries),...r??[]]},T(S,c,r,e?.dir)),m.ok}const d=n.length===1?W(n[0]??""):void 0;if(d){const r=d.cloud===void 0?[]:await k();return a({...d,title:E(d,r),...d.cloud===void 0?{}:{cloudWorks:r&&U(d,r)},docsDir:e?.dir},H(d,S,c,e?.dir,r)),m.ok}let p;try{p=await resolveCommand(n,y,i,c)}catch(r){return g(r)}const{command:f,rest:O}=p;return f&&p.list===!0?await C(f,O):!f||O.length>0?g(new h("unknown-command",`allyhub has no command "${n.join(" ")}".`,{exit:m.usage,hint:"allyhub help lists them and the works."})):(a(A(f),j(f.spec,f.about?.lines)),m.ok)}let $;try{$=await resolveCommand(o,y,i,c)}catch(n){return g(n)}const{command:w,rest:v}=$;if(!w)return g(new h("unknown-command",`allyhub has no command "${o[0]??""}".`,{exit:m.usage,hint:"allyhub help lists them."}));if(v.includes("--help")||v.includes("-h"))return a(A(w),j(w.spec,w.about?.lines)),m.ok;return await C(w,v);async function C(n,e){const k=()=>{b.abort(new h("interrupted","Stopped.",{exit:m.interrupted}))};process.once("SIGINT",k);try{const d=R(n.spec,e),p=await n.run(d,y);return a(p.value,p.text),p.exit??m.ok}catch(d){return g(d)}finally{process.off("SIGINT",k)}}function g(n){const e=n instanceof h?n:b.signal.aborted?new h("interrupted","Stopped.",{exit:m.interrupted}):new h("failed",n instanceof Error?n.message:String(n));return l?s.stdout(`${JSON.stringify({error:{code:e.code,message:e.message,...e.hint?{hint:e.hint}:{}}},null,2)}
6
- `):s.stderr(`${e.message}${e.hint?`
7
- ${e.hint}`:""}
8
- `),e.exit}}
2
+ import{EXIT as m,describeCommand as O}from"@allyhub-next/protocol";import{readArgs as D}from"./args.js";import{CLI_VERSION as S}from"./cli-version.js";import{CommandError as h}from"./command.js";import{COMMAND_SOURCES as M,COMMANDS as N}from"./commands/index.js";import{commandHelp as E,listHelp as T,workHelp as H}from"./help.js";import{sdkDocs as R}from"./sdk-docs.js";import{WORKS as G,cloudNouns as J,workNamed as W,workTitle as j}from"./works.js";const _={stdout:e=>process.stdout.write(e),stderr:e=>process.stderr.write(e),cwd:process.cwd(),env:process.env};export function findCommand(e,s=N){let i;for(const c of s){const l=c.spec.name.split(" ");l.every((o,a)=>e[a]===o)&&l.length>(i?.length??0)&&(i={command:c,length:l.length})}return i?{command:i.command,rest:e.slice(i.length)}:{rest:[...e]}}export async function resolveCommand(e,s,i=N,c=M){const l=findCommand(e,i);if(l.command)return l;for(const o of c){const a=await o.resolve(e,s);if(a)return a}return l}const A=e=>({...O(e.spec),...e.about?.value});export async function main(e,s=_,i=N,c=M){const l=e.includes("--json"),o=e.filter(n=>n!=="--json"),a=(n,t)=>{s.stdout(l?`${JSON.stringify(n,null,2)}
3
+ `:`${t}
4
+ `)},b=i.map(n=>n.spec),v=new AbortController,y={cwd:s.cwd,env:s.env,progress:n=>{s.stderr(`${n}
5
+ `)},signal:v.signal};if(o[0]==="--version"||o[0]==="-v")return a({version:S},S),m.ok;if(o.length===0||o[0]==="help"||o[0]==="--help"||o[0]==="-h"){const n=o.slice(1),t=await R(s.cwd).catch(()=>{}),k=async()=>{const r=await Promise.all(c.map(u=>u.nouns(y).catch(()=>{})));return r.some(u=>u===void 0)?void 0:r.flatMap(u=>u??[])};if(n.length===0){const r=await k();return a({version:S,docs:t?.dir,works:G.map(u=>({...u,title:j(u,r)})),commands:b.map(O),made:[...c.flatMap(u=>u.entries),...r??[]]},T(b,c,r,t?.dir)),m.ok}const d=n.length===1?W(n[0]??""):void 0;if(d){const r=d.cloud===void 0?[]:await k();return a({...d,title:j(d,r),...d.cloud===void 0?{}:{cloudWorks:r&&J(d,r)},docsDir:t?.dir},H(d,b,c,t?.dir,r)),m.ok}let p;try{p=await resolveCommand(n,y,i,c)}catch(r){return g(r)}const{command:f,rest:I}=p;return f&&p.list===!0?await $(f,I):!f||I.length>0?g(new h("unknown-command",`allyhub has no command "${n.join(" ")}".`,{exit:m.usage,hint:"allyhub help lists them and the works."})):(a(A(f),E(f.spec,f.about?.lines)),m.ok)}let C;try{C=await resolveCommand(o,y,i,c)}catch(n){return g(n)}const{command:w,rest:x}=C;if(!w)return g(new h("unknown-command",`allyhub has no command "${o[0]??""}".`,{exit:m.usage,hint:"allyhub help lists them."}));if(x.includes("--help")||x.includes("-h"))return a(A(w),E(w.spec,w.about?.lines)),m.ok;return await $(w,x);async function $(n,t){const k=()=>{v.abort(new h("interrupted","Stopped.",{exit:m.interrupted}))};process.once("SIGINT",k);try{const d=D(n.spec,t),p=await n.run(d,y);return a(p.value,p.text),p.exit??m.ok}catch(d){return g(d)}finally{process.off("SIGINT",k)}}function g(n){const t=n instanceof h?n:v.signal.aborted?new h("interrupted","Stopped.",{exit:m.interrupted}):new h("failed",n instanceof Error?n.message:String(n));return l?s.stdout(`${JSON.stringify({error:{code:t.code,message:t.message,...t.hint?{hint:t.hint}:{}}},null,2)}
6
+ `):s.stderr(`${t.message}${t.hint?`
7
+ ${t.hint}`:""}
8
+ `),t.exit}}
package/dist/works.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- export const WORKS=[{name:"video",title:"Making a video (one piece, or a batch of ads in combos)",commands:["check","frames","inspect","inspect layout","inspect timeline","inspect motion","inspect still","inspect contrast","inspect seams","inspect flash","inspect loop","inspect combos","audio","add","dev","render"],docs:["video.md","layers.md","speech.md","sound.md"]},{name:"footage",title:"Cutting recorded footage: a talking head, an interview",commands:["words","words edits","align","frames cuts","audio recording","render"],docs:["footage.md","speech.md"]},{name:"slides",title:"Making a deck",commands:["check","frames","inspect layout","dev","render"],docs:["slides.md","layers.md"]},{name:"docs",title:"Writing a doc",commands:["check","dev","render"],docs:["docs.md"]},{name:"sheets",title:"Making a sheet",commands:["sheet import","sheet","check","dev","render"],docs:["sheets.md"]},{name:"websites",title:"Showing a project's videos in a website (an Astro project)",commands:["site media","render"],docs:["project.md"]},{name:"generating",title:"Making media with AI models",commands:["<work>","<work> <tool>","upload","voice"],cloud:{works:["ai-media"]}},{name:"researching",title:"Researching a market online",commands:["<work>","<work> <tool>","frames web"],cloud:{}},{name:"references",title:"Studying reference videos, and a piece against them",commands:["reference","reference compare","reference served","reference set","frames","frames compare","words","audio","align"]},{name:"3d",title:"Building a 3D scenery and its cameras",commands:["scenery","scenery view","scenery map","scenery see","scenery measure","scenery motion","scenery solve","scenery bake","inspect fidelity","dev"],docs:["scenery.md"]},{name:"modeling",title:"Modelling a prop in code, or adding one from a library",commands:["add search","add","model","model capabilities"],docs:["modeling.md","scenery.md"]},{name:"setup",title:"This computer, the account and AllyHub's skills",commands:["skills","skills show","auth login","auth status","auth logout","auth token","orgs list","orgs switch","doctor","mcp"]}],workNamed=e=>WORKS.find(n=>n.name===e);export function cloudNouns(e,n){if(e.cloud===void 0)return[];const s=new Set(WORKS.flatMap(o=>o.cloud?.works??[])),t=e.cloud.works;return n.filter(o=>t===void 0?!s.has(o.name):t.includes(o.name))}export function workTitle(e,n){if(e.cloud===void 0)return e.title;if(n===void 0)return`${e.title} (the cloud catalog could not be read)`;const s=cloudNouns(e,n);return s.length===0?`${e.title}: no cloud tools yet`:`${e.title}: ${s.map(t=>t.name).join(", ")}`}
2
+ export const WORKS=[{name:"video",title:"Making a video (one piece, or a batch of ads in combos)",commands:["check","frames","inspect","inspect layout","inspect timeline","inspect motion","inspect still","inspect contrast","inspect seams","inspect flash","inspect loop","inspect combos","audio","add","dev","render"],docs:["video.md","layers.md","speech.md","sound.md"]},{name:"footage",title:"Cutting recorded footage: a talking head, an interview",commands:["words","words edits","align","frames cuts","audio recording","render"],docs:["footage.md","speech.md"]},{name:"slides",title:"Making a deck",commands:["check","frames","inspect layout","dev","render"],docs:["slides.md","layers.md"]},{name:"docs",title:"Writing a doc",commands:["check","dev","render"],docs:["docs.md"]},{name:"sheets",title:"Making a sheet",commands:["sheet import","sheet","check","dev","render"],docs:["sheets.md"]},{name:"websites",title:"Showing a project's videos in a website (an Astro project)",commands:["site media","render"],docs:["project.md"]},{name:"generating",title:"Making media with AI models",commands:["<work>","<work> <tool>","upload","voice"],cloud:{works:["ai-media"]}},{name:"researching",title:"Researching a market online",commands:["<work>","<work> <tool>","frames web"],cloud:{}},{name:"references",title:"Studying reference videos, and a piece against them",commands:["reference","reference compare","reference served","reference set","frames","frames compare","words","audio","align"]},{name:"3d",title:"Building a 3D scenery and its cameras",commands:["scenery","scenery view","scenery map","scenery see","scenery measure","scenery motion","scenery solve","scenery bake","inspect fidelity","dev"],docs:["scenery.md"]},{name:"modeling",title:"Modelling a prop in code, or adding one from a library",commands:["add search","add","3d-model","3d-model capabilities"],docs:["modeling.md","scenery.md"]},{name:"setup",title:"This computer, the account and AllyHub's skills",commands:["skills","skills show","auth login","auth status","auth logout","auth token","orgs list","orgs switch","doctor","mcp"]}],workNamed=e=>WORKS.find(n=>n.name===e);export function cloudNouns(e,n){if(e.cloud===void 0)return[];const s=new Set(WORKS.flatMap(o=>o.cloud?.works??[])),t=e.cloud.works;return n.filter(o=>t===void 0?!s.has(o.name):t.includes(o.name))}export function workTitle(e,n){if(e.cloud===void 0)return e.title;if(n===void 0)return`${e.title} (the cloud catalog could not be read)`;const s=cloudNouns(e,n);return s.length===0?`${e.title}: no cloud tools yet`:`${e.title}: ${s.map(t=>t.name).join(", ")}`}