allyhub 0.2.0-next.2 → 0.2.0-next.4

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 (67) hide show
  1. package/client.d.ts +5 -1
  2. package/dist/charge.js +2 -0
  3. package/dist/commands/add.js +5 -5
  4. package/dist/commands/check.js +1 -1
  5. package/dist/commands/cloud-tools.js +10 -10
  6. package/dist/commands/index.js +1 -1
  7. package/dist/commands/mcp.js +1 -1
  8. package/dist/commands/skills.js +4 -0
  9. package/dist/commands/voice.js +2 -2
  10. package/dist/lent/model-three.d.ts +2 -0
  11. package/dist/main.js +4 -4
  12. package/dist/resources.js +2 -0
  13. package/dist/works.js +1 -1
  14. package/editor-app/view.html +4 -4
  15. package/editor-web/assets/chevron-left-B1KTa28S.js +1 -0
  16. package/editor-web/assets/{chevron-up-DdzgESXy.js → chevron-up-B2IfPYC4.js} +1 -1
  17. package/editor-web/assets/{dist-CiIDdJZw.js → dist-ChU-0IAY.js} +1 -1
  18. package/editor-web/assets/{dist-DqFDRJ1S.js → dist-CuyNR3bn.js} +2 -2
  19. package/editor-web/assets/{dist-DlzWTZjN.js → dist-DCgVauDD.js} +1 -1
  20. package/editor-web/assets/{dist-ZLZyLIPV.js → dist-VsUk3S-a.js} +1 -1
  21. package/editor-web/assets/{floating-ui.react-dom-BrYYB6DH.js → floating-ui.react-dom-C6Aeuo3L.js} +1 -1
  22. package/editor-web/assets/index-BoYpZLq7.js +56 -0
  23. package/editor-web/assets/{react-DIqYlXoe.js → react-CSPssXRH.js} +3 -3
  24. package/editor-web/assets/{undo-2-oaEQLQ47.js → undo-2-D6qCS-P0.js} +1 -1
  25. package/editor-web/index.html +3 -3
  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/package.json +1 -1
  29. package/node_modules/@allyhub-next/check/package.json +1 -1
  30. package/node_modules/@allyhub-next/cloud/dist/call.js +2 -2
  31. package/node_modules/@allyhub-next/cloud/dist/client.js +1 -1
  32. package/node_modules/@allyhub-next/cloud/dist/errors.js +1 -1
  33. package/node_modules/@allyhub-next/cloud/dist/index.js +1 -1
  34. package/node_modules/@allyhub-next/cloud/dist/price.js +1 -1
  35. package/node_modules/@allyhub-next/cloud/dist/voice.js +1 -1
  36. package/node_modules/@allyhub-next/cloud/package.json +1 -1
  37. package/node_modules/@allyhub-next/components/package.json +1 -1
  38. package/node_modules/@allyhub-next/dev-server/dist/live.d.ts +1 -1
  39. package/node_modules/@allyhub-next/dev-server/dist/tools.js +1 -1
  40. package/node_modules/@allyhub-next/dev-server/package.json +1 -1
  41. package/node_modules/@allyhub-next/doc/package.json +1 -1
  42. package/node_modules/@allyhub-next/export/package.json +1 -1
  43. package/node_modules/@allyhub-next/formats/package.json +1 -1
  44. package/node_modules/@allyhub-next/measure/package.json +1 -1
  45. package/node_modules/@allyhub-next/media/package.json +1 -1
  46. package/node_modules/@allyhub-next/model/package.json +1 -1
  47. package/node_modules/@allyhub-next/project/package.json +1 -1
  48. package/node_modules/@allyhub-next/protocol/dist/cloud.d.ts +108 -1
  49. package/node_modules/@allyhub-next/protocol/dist/cloud.js +1 -1
  50. package/node_modules/@allyhub-next/protocol/package.json +1 -1
  51. package/node_modules/@allyhub-next/registry/package.json +1 -1
  52. package/node_modules/@allyhub-next/render/package.json +1 -1
  53. package/node_modules/@allyhub-next/runtime/package.json +1 -1
  54. package/node_modules/@allyhub-next/runtimes/manifest.json +88 -14
  55. package/node_modules/@allyhub-next/runtimes/package.json +1 -1
  56. package/node_modules/@allyhub-next/scaffold/LICENSE +241 -0
  57. package/node_modules/@allyhub-next/scaffold/dist/index.js +16 -0
  58. package/node_modules/@allyhub-next/scaffold/package.json +13 -0
  59. package/node_modules/@allyhub-next/scenery/package.json +1 -1
  60. package/node_modules/@allyhub-next/scenery-host/package.json +1 -1
  61. package/node_modules/@allyhub-next/sheet/package.json +1 -1
  62. package/node_modules/@allyhub-next/site/package.json +1 -1
  63. package/node_modules/@allyhub-next/stage/package.json +1 -1
  64. package/node_modules/@allyhub-next/video-core/package.json +1 -1
  65. package/package.json +32 -27
  66. package/editor-web/assets/chevron-left-BWWKfQPt.js +0 -1
  67. package/editor-web/assets/index-DYkuF7LX.js +0 -56
package/client.d.ts CHANGED
@@ -3,7 +3,8 @@
3
3
  // "allyhub/client" to `types` in tsconfig.json, beside "@allyhub/sdk/client".
4
4
  // Each points at `allyhub/lent/*`, the bundled package's own declarations,
5
5
  // written into the npm package when it is packed
6
- // (apps/create-allyhub/scripts/npm-packages.ts).
6
+ // (apps/create-allyhub/scripts/npm-packages.ts, which refuses to pack a
7
+ // lent entry this file does not declare).
7
8
 
8
9
  declare module "@allyhub-next/scenery-host/layer" {
9
10
  export * from "allyhub/lent/scenery-host/layer";
@@ -11,3 +12,6 @@ declare module "@allyhub-next/scenery-host/layer" {
11
12
  declare module "@allyhub-next/model" {
12
13
  export * from "allyhub/lent/model";
13
14
  }
15
+ declare module "@allyhub-next/model/three" {
16
+ export * from "allyhub/lent/model/three";
17
+ }
package/dist/charge.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
+ import{request as l}from"node:http";import{CHARGE_META as f,STUDIO_ASK_ENV as w}from"@allyhub-next/protocol";import{CommandError as u}from"./command.js";export const ASK_ABOVE=10;export function chargeOf(t,o,e){return{tool:t,title:o,credits:e.credits,estimated:e.estimated,unknown:e.lines.some(n=>n.unknown&&n.rate!==0)}}export const beyond=(t,o)=>t.unknown||t.credits>o,askingAbove=(t,o)=>e=>beyond(e,t)?o(e):Promise.resolve(!0);const m=t=>new Intl.NumberFormat("en-US",{maximumFractionDigits:2}).format(t);export function priceWords(t){return t.unknown?t.credits>0?`at least ${m(t.credits)} AllyHub credits`:"AllyHub credits, priced once it is made":`${t.estimated?"about ":""}${m(t.credits)} AllyHub credits`}export const chargeMessage=t=>`${t.title} (${t.tool}) costs ${priceWords(t)}. Allow it?`,chargeQuestion=t=>({mode:"form",message:chargeMessage(t),requestedSchema:{type:"object",properties:{}},_meta:{[f]:t}});export function studioApprove(t,o,e){const n=t[w];if(n===void 0||n==="")return;const s=t.ALLYHUB_ASK_ABOVE?.trim(),i=s===void 0||s===""?Number.NaN:Number(s),r=Number.isFinite(i)&&i>=0?i:ASK_ABOVE;return askingAbove(r,async a=>{const{status:c,text:p}=await g(n,JSON.stringify({cwd:o,params:chargeQuestion(a)}),e),d=JSON.parse(p);if(c!==200)throw new u("not-asked",`Studio could not ask the person: ${d.error?.message??`it answered ${String(c)}`}. Nothing was called or charged.`);return d.action==="accept"})}function g(t,o,e){return new Promise((n,s)=>{const i=l(t,{method:"POST",headers:{"content-type":"application/json"},signal:e},r=>{const a=[];r.on("data",c=>a.push(c)),r.on("end",()=>{n({status:r.statusCode??0,text:Buffer.concat(a).toString("utf8")})}),r.on("error",s)});i.on("error",r=>{s(new u("not-asked",`Studio could not be reached to ask the person: ${r.message}. Nothing was called or charged.`))}),i.end(o)})}
@@ -1,6 +1,6 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{createHash as z}from"node:crypto";import{mkdir as M,readFile as x,writeFile as Y}from"node:fs/promises";import{join as S,posix as B,relative as J}from"node:path";import{fileURLToPath as V}from"node:url";import{z as u}from"zod";import{modelInfo as W}from"@allyhub-next/build";import{COMPONENTS_DIR as E}from"@allyhub-next/formats";import{EXIT as b,RegistryIndexSchema as X}from"@allyhub-next/protocol";import{ASSET_DEFAULTS as q,ASSET_TYPES as G,PolyHaven as K,PolyHavenError as Z,RegistryError as Q,applyAdd as R,fetchBakes as ee,folderRegistry as I,planAdd as P,registryAt as te,searchIndex as L}from"@allyhub-next/registry";import{cacheHome as O}from"@allyhub-next/runtimes";import{CommandError as T,defineCommand as _}from"../command.js";import{projectRoot as se}from"../project.js";import{dir as ne}from"../fields.js";const oe=JSON.parse(await x(new URL("../../package.json",import.meta.url),"utf8")).version,C=V(new URL(".",import.meta.resolve("@allyhub-next/components/registry.json"))),j=Object.keys(G),ae="https://allyhub.com/r",ie=3600*1e3;async function H(e){const t=e.env.ALLYHUB_REGISTRY??ae,s=te(t),i=S(O(e.env),"registry",`${z("sha256").update(t).digest("hex").slice(0,16)}.json`),n=await x(i,"utf8").then(o=>{const d=JSON.parse(o),l=X.parse(d.index);return{read:d.read,index:l}}).catch(()=>{});if(n&&Date.now()-n.read<ie)return{source:s,index:n.index,from:"kept"};try{const o=await s.index();return await M(S(i,".."),{recursive:!0}),await Y(i,JSON.stringify({read:Date.now(),index:o})),{source:s,index:o,from:"read"}}catch{return n?{source:s,index:n.index,from:"stale"}:{source:s,from:"none"}}}async function re(e){const{source:t,index:s}=await H(e);return{source:t,names:new Set(s?.items.map(i=>i.name))}}const de={"allyhub:scenery":"Sceneries and settings","allyhub:model":"Models","allyhub:component":"Components","allyhub:template":"Templates","allyhub:skill":"Skills"};function N(e,t){if(e.from==="none")return["AllyHub's resources (models, sceneries) could not be read: check the connection."];const s=t??e.index?.items??[],i=Math.max(0,...s.map(o=>o.name.length)),n=[`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[o,d]of Object.entries(de)){const l=s.filter(h=>h.type===o);l.length!==0&&n.push(` ${d}:`,...l.map(h=>` ${h.name.padEnd(i)} ${h.description}`))}return n}const D=20,le=e=>e.replace(/\.(?:ts|js)$/u,"").replaceAll(/[A-Z]/gu,t=>`-${t.toLowerCase()}`).replace(/^-/u,"");function F(e){return e instanceof Q?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 Z?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 K({userAgent:`AllyHub/${oe} (+https://allyhub.com)`,cacheDir:S(O(e.env),"polyhaven"),...e.env.ALLYHUB_POLYHAVEN_API?{api:e.env.ALLYHUB_POLYHAVEN_API}:{}}),v=e=>`${e.toFixed(3)} m`;function ce(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(s=>s!==void 0);return` ${e.id}: ${e.name} (${t.join("; ")})
3
- found in ${e.matchedIn.join(", ")||"everything"}; tags ${e.tags.join(", ")}`}async function U(e,t){const s=[`${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 n=t.main[0]??"",o=await W(e,n,await x(S(e,n))),d=o.bounds;return s.push(d?`Measured: ${d.size.map(l=>l.toFixed(3)).join(" × ")} m along x, y (up), z; from ${d.min.map(l=>l.toFixed(3)).join(", ")} to ${d.max.map(l=>l.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("${n}");`),{lines:s,model:o}}if(t.type==="hdri")return s.push(`In a scenery: await w.environment("${t.main[0]??""}", { background: true });`),{lines:s};const i=t.covers;return s.push(i&&i.length>=2?`One tile covers ${v(i[0]??0)} × ${v(i[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((n,o)=>{const d=t.maps?.[o]??"";return` ${d}: await w.texture("${n}"${d==="diffuse"?"":', { colorSpace: "linear" }'})`})),{lines:s}}export const add=_({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 ${E} 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 ${q.resolution})`),dir:ne}),positional:["name"],output:u.looseObject({}),examples:["allyhub add","allyhub add caption-line","allyhub add metal_office_desk --res 2k"]},async(e,t)=>{const s=I(C);try{const i=await s.index();if(!e.name||e.name.length===0){const r=Math.max(...i.items.map(y=>y.name.length)),c=await H(t);return{value:{components:i.items,resources:c.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):",...i.items.map(y=>` ${y.name.padEnd(r)} ${y.description}`),"",...N(c,void 0)].join(`
4
- `)}}const n=await se(t.cwd,e.dir),o=new Set(i.items.map(r=>r.name)),d=e.name.map(le),l=d.filter(r=>o.has(r)),h=d.filter(r=>!o.has(r)),$=h.length>0?await re(t):void 0,w=h.filter(r=>$?.names.has(r)),p=e.name.filter((r,c)=>!o.has(d[c]??"")&&!($?.names.has(d[c]??"")??!1)),f=[],k={};if(l.length>0){const r=e.to??E,c=await P(s,l,{root:n,dir:r,force:e.force});await R(n,c);const y=m=>m==="up to date"?"Up to date":m==="added"?"Added":"Replaced";f.push(...c.files.map(m=>`${y(m.status)} ${m.path}`));const a=c.items.find(m=>m.name===l[0]),g=a?.files[0]?.target.replaceAll("{dir}",r);if(a&&g){const m=J(S(n,"name"),S(n,g)).split("\\").join("/").replace(/\.ts$/u,".js");f.push(`Import it from an item's entry file (<name>/video.ts): import { ${a.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.")}c.missing.length>0&&f.push(`It needs packages the project does not declare: ${c.missing.join(", ")}.`),k.components=c.files.map(m=>({item:m.item,path:m.path,status:m.status})),k.missing=c.missing}if($&&w.length>0){const r=await P($.source,w,{root:n,dir:".",force:e.force});await R(n,r),f.length>0&&f.push(""),f.push(...r.files.map(a=>`${a.status==="up to date"?"Up to date":a.status==="added"?"Added":"Replaced"} ${a.path}`));const c=A(t);for(const a of r.assets){const g=await c.add(a.id,{root:n,...a.resolution===void 0?{}:{resolution:a.resolution},...a.maps===void 0?{}:{maps:a.maps}});f.push(...(await U(n,g)).lines)}const y=await ee(n,r.bakes);f.push(...y.map(a=>`${a.status==="added"?"Downloaded":"Up to date"} the baked light for ${a.bake.scenery}: ${a.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(a=>({item:a.item,path:a.path,status:a.status})),assets:r.assets,bakes:y.map(a=>({path:a.path,status:a.status}))}}if(p.length>0){const r=A(t),c=[];for(const y of p){const a=await r.add(y,{root:n,...e.to===void 0||l.length>0?{}:{to:B.normalize(e.to)},...e.res===void 0?{}:{resolution:e.res}}),g=await U(n,a);f.length>0&&f.push(""),f.push(...g.lines),c.push({...a,...g.model===void 0?{}:{model:g.model}})}k.assets=c}return{value:k,text:f.join(`
5
- `)}}catch(i){throw F(i)}}),addSearch=_({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",...j])).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(D)} Poly Haven assets`)}),positional:["words"],output:u.looseObject({}),examples:['allyhub add search "office desk" --type model',"allyhub add search caption"]},async(e,t)=>{const s=e.words.join(" ").trim();try{const i=e.type??["component","resource",...j],n=i.includes("component")?L(await I(C).index(),s):[],o=i.includes("resource")?await H(t):void 0,d=o?.index?L(o.index,s):[],l=j.filter(p=>i.includes(p)),{found:h,total:$}=l.length>0?await A(t).search(s,l):{found:[],total:0},w=e.all?h:h.slice(0,D);return{value:{query:s,components:n,resources:d,assets:{total:$,matches:h.length,found:w}},text:[...i.includes("component")?[`Starter components: ${String(n.length)} hold "${s}".`,...n.map(p=>` ${p.name} (${p.title}): ${p.description}`)]:[],...o?o.from==="none"?N(o,[]):[`AllyHub's resources: ${String(d.length)} hold "${s}"${o.from==="stale"?" (as last read)":""}.`,...d.map(p=>` ${p.name} (${p.title}): ${p.description}`)]:[],...l.length>0?[`From Poly Haven (CC0): ${String(h.length)} of ${String($)} assets hold "${s}"${w.length<h.length?`; the first ${String(w.length)} (--all for every one)`:""}.`,...w.map(ce)]:[],"","Add one: allyhub add <name or id>"].join(`
6
- `)}}catch(i){throw F(i)}});
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)}});
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{z as t}from"zod";import{EXIT as i}from"@allyhub-next/protocol";import{formatCheck as c}from"../text/index.js";import{defineCommand as m}from"../command.js";import{projectRoot as a}from"../project.js";import{dir as l}from"../fields.js";import{checkIndexed as n}from"../version.js";export const check=m({name:"check",summary:"Problems: objective errors in the project's items, by rule; exits 1 when one must be fixed",input:t.strictObject({item:t.string().optional().describe("Only this item: its id, a path into it, or a folder of items"),dir:l,fresh:t.boolean().default(!1).describe("Load every version again, not the last check's results")}),positional:["item"],output:t.looseObject({}),examples:["allyhub check","allyhub check launch/film","allyhub check --json"]},async(o,r)=>{const s=await a(r.cwd,o.dir),e=await n({dir:s,...o.item===void 0?{}:{piece:o.item}},r);return{value:{check:e.check,problems:e.problems,stats:e.stats,facts:e.facts.length,console:e.console},text:c(e),exit:e.check.errors>0?i.failed:i.ok}});
2
+ import{z as o}from"zod";import{EXIT as s}from"@allyhub-next/protocol";import{formatCheck as c}from"../text/index.js";import{defineCommand as m}from"../command.js";import{projectRoot as a}from"../project.js";import{dir as l}from"../fields.js";import{checkIndexed as n}from"../version.js";export const check=m({name:"check",summary:"Problems: objective errors in the project's items, by rule; exits 1 when one must be fixed",input:o.strictObject({item:o.string().optional().describe("Only this item: its id, a path into it, or a folder of items"),dir:l,fresh:o.boolean().default(!1).describe("Load every version again, not the last check's results")}),positional:["item"],output:o.looseObject({}),examples:["allyhub check","allyhub check launch/film","allyhub check --json"]},async(t,r)=>{const i=await a(r.cwd,t.dir),e=await n({dir:i,fresh:t.fresh,...t.item===void 0?{}:{piece:t.item}},r);return{value:{check:e.check,problems:e.problems,stats:e.stats,facts:e.facts.length,console:e.console},text:c(e),exit:e.check.errors>0?s.failed:s.ok}});
@@ -1,12 +1,12 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{readFile as W}from"node:fs/promises";import{join as D,resolve as q}from"node:path";import{z as n}from"zod";import{callTool as J,findTool as T,loadCatalog as h,priceCalls as E,priceText as j,resumeJob as L,uploadFiles as M}from"@allyhub-next/cloud";import{WORK_DIR as H}from"@allyhub-next/formats";import{EXIT as g}from"@allyhub-next/protocol";import{CommandError as $,defineCommand as w}from"../command.js";import{optionName as U}from"../args.js";import{commandHelp as B}from"../help.js";import{requiredMissing as K,toolInput as _,toolOptions as G}from"../tool-options.js";import{cloudOf as p,cloudWork as f,placeFrom as z,plural as b}from"./cloud.js";const P=8e3,d={input:n.string().optional().describe("The input as JSON, or a file of it: the whole of it, or the fields no option gives; an option given beside it wins"),quote:n.boolean().default(!1).describe("Say what a call with this input would cost, calling nothing and charging nothing"),file:n.string().optional().describe("The path of the file it makes (without an extension it takes the result's); the same call with the same input then keeps it, calling nothing; default a folder under .allyhub/work/"),force:n.boolean().default(!1).describe("With --file: call even when the file was made from this input, and replace a file that has no record"),wait:n.boolean().default(!0).describe("Wait for a long tool (an audit, a video model); --no-wait returns its call at once"),resume:n.string().optional().describe("Wait for a call of this tool this computer made (from --no-wait, or a call that stopped waiting), and download its file")},y=(t,a)=>new $("usage",t,{exit:g.usage,...a===void 0?{}:{hint:a}}),C=t=>{const a=t.reduce((e,r)=>r.map((s,i)=>Math.max(e[i]??0,s.length)),[]);return t.map(e=>` ${e.map((r,s)=>s===e.length-1?r:r.padEnd(a[s]??0)).join(" ")}`.trimEnd())};function F(t){return w({name:t.id,summary:`${t.title}: ${t.description} Lists the tools, each a command`,input:n.strictObject({fresh:n.boolean().default(!1).describe("Read the catalog from AllyHub again, not the copy kept an hour")}),output:n.looseObject({work:n.string(),tools:n.array(n.looseObject({name:n.string()}))}),examples:[`allyhub ${t.id}`,`allyhub ${t.id} --json`]},(a,e)=>f(async()=>{const s=(await h(p(e),{refresh:a.fresh})).tools.filter(i=>i.work===t.id).sort((i,o)=>i.name.localeCompare(o.name));return{value:{work:t.id,title:t.title,tools:s.map(i=>({name:i.name,title:i.title,description:i.description,price:j(i.price),long:i.long}))},text:[`${t.title} (${b(s.length,"tool")}):`,...C(s.map(i=>[i.name,i.title,j(i.price)])),"",`Each costs credits when it worked; a failed call is free. allyhub help ${t.id} <tool> shows a tool's options; --quote prices a call first.`].join(`
3
- `)}}))}const k=t=>new Intl.NumberFormat("en-US",{maximumFractionDigits:4}).format(t),X=t=>`${t.estimated?`about ${new Intl.NumberFormat("en-US",{maximumFractionDigits:2}).format(t.credits)}`:k(t.credits)} credits`;function Q(t){return t.lines.map(a=>{if(a.unknown)return` ${a.what.replace(/^0 /u,"")}: ${k(a.rate)} each, counted by the call (not in the total)`;const e=a.what==="per call"?"":` (${k(a.rate)} each)`;return` ${a.what}: ${k(a.credits)}${e}${a.estimated?", estimated":""}`})}function V(t,a=5,e=160){const r=i=>{const o=i===void 0?"undefined":JSON.stringify(i);return o.length>e?`${o.slice(0,e-1)}…`:o},s=(i,o)=>[`${i?`${i}: `:""}${b(o.length,"item")}${o.length>a?`, the first ${String(a)}:`:":"}`,...o.slice(0,a).map((l,c)=>` [${String(c)}] ${r(l)}`)];return Array.isArray(t)?s("",t):typeof t!="object"||t===null?[r(t)]:Object.entries(t).flatMap(([i,o])=>Array.isArray(o)?s(i,o):[`${i}: ${r(o)}`])}function R(t,a){if("jobId"in t)return[`${t.tool} is still working (call ${t.jobId}).`,`Wait for it: allyhub ${a} --resume ${t.jobId}`].join(`
4
- `);if(t.kept)return[`Kept ${t.file??""}: ${t.tool} made it from this same input on ${t.kept.at} (${t.gen??""}).`,"Nothing was called or charged. --force makes it again."].join(`
5
- `);const e=JSON.stringify(t.output,null,2);return[e.length<=P?e:[`Output: ${String(Math.round(Buffer.byteLength(e)/1024))} KB of JSON, in short (--json prints it whole):`,...V(t.output)].join(`
6
- `),...t.file?[`File: ${t.file}${t.replaced?" (replaced: it was made from another input)":""}`]:[],...t.words?[`Words: ${t.words} (when each word is said: a part can say a stretch of the file)`]:[],...t.gen?[`Record: ${t.gen} (move it with the file)`]:[],`Credits used: ${t.credits===void 0?"not reported":String(t.credits)}`].join(`
7
- `)}const Y=t=>{const a=Math.max(0,Math.round(t));return`${String(Math.floor(a/60))}:${String(a%60).padStart(2,"0")}`},Z=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);async function tt(t,a){const e=t.trim().startsWith("{"),r=e?t:await W(q(a.cwd,t),"utf8").catch(i=>{throw new $("not-found",`--input: cannot read ${t}: ${i.message}`,{exit:g.notFound,hint:"Give a JSON object, or a file of one."})});let s;try{s=JSON.parse(r)}catch{s=void 0}if(!Z(s))throw y(`--input: ${e?"the text":t} is not a JSON object.`,`Give '{"field": …}', or a file of it.`);return s}export function toolCommand(t){const a=`${t.work} ${t.name}`,e=G(t.input,Object.keys(d)),r=n.strictObject({...e.shape,...d,input:d.input.describe(e.inputOnly.length?`${d.input.description??""}. Fields only it gives: ${e.inputOnly.join(", ")}`:d.input.description??"")}),s=j(t.price),i=[...e.fields].filter(([,o])=>e.required.includes(o)).map(([o,l])=>`--${U(o)} <${l}>`);return{spec:{name:a,summary:t.description,input:r,output:n.looseObject({tool:n.string(),output:n.record(n.string(),n.unknown()).optional(),credits:n.number().optional(),file:n.string().optional(),gen:n.string().optional(),words:n.string().optional(),jobId:n.string().optional(),quote:n.looseObject({credits:n.number()}).nullable().optional()}),examples:[`allyhub ${[a,...i,"--quote"].join(" ")}`,`allyhub ${[a,...i].join(" ")}`]},about:{lines:[t.title,"",`Costs credits: ${s}. A call is charged once, when it worked; a failed call is free. --quote prices an input first, charging nothing.`,...t.long?["A long tool: the command waits for it (--no-wait returns its call, --resume waits again)."]:[]],value:{work:t.work,title:t.title,price:s,priceRule:t.price,long:t.long,inputSchema:t.input,outputSchema:t.output}},run:(o,l)=>f(async()=>{const c=o,I=o,O=p(l),S=await z(l),m=_(e,I,c.input===void 0?{}:await tt(c.input,l));let v=0;const A={base:S,signal:l.signal,onRunning:u=>{Date.now()-v<3e4||(v=Date.now(),l.progress(`${t.name}: working · ${Y((Date.now()-Date.parse(u.createdAt))/1e3)} (call ${u.id})`))}};if(c.resume!==void 0){if(Object.keys(m).length>0||c.quote||c.file!==void 0)throw y("--resume waits for a call as it was made: give it no input, --quote or --file.");const u=await L(O,c.resume,{...A,tool:t.name});return{value:{...u},text:R(u,a)}}if(c.quote){const u=E(t.price,t.input,[m]).calls[0];if(!u)throw new Error("No quote.");return{value:{tool:t.name,quote:u},text:[`A call${Object.keys(m).length===0?" with the defaults":""}: ${X(u)}`,...Q(u),...u.estimated?["Estimated parts are charged by what the call returns."]:[],"A call is charged once, when it worked; a failed call is free."].join(`
8
- `)}}const x=K(e,m);if(x.length>0)throw y(`${t.name} needs ${x.join(", ")}.`,`allyhub help ${a} shows its options.`);const N=await J(O,t.name,m,{...A,outDir:D(S,H,t.work,t.name),...c.file===void 0?{}:{file:q(l.cwd,c.file)},force:c.force,wait:c.wait});return{value:{...N},text:R(N,a)}})}}const et=t=>h(p(t)).catch(()=>{});export const CLOUD_SOURCE={entries:[{name:"<work>",summary:"List a work's cloud tools (AllyHub's catalog), a line each with its price; allyhub help lists the works"},{name:"<work> <tool>",summary:"Run a cloud tool with the account's credits, its input as options (allyhub help <work> <tool>); --quote prices it first"}],async nouns(t){return(await h(p(t))).works.map(e=>({name:e.id,summary:`${e.title}: ${e.description}`}))},resolve:(t,a)=>f(async()=>{const[e,r]=t;if(e===void 0||e.startsWith("-"))return;let s=await et(a),i=s?.works.find(l=>l.id===e);if(i||(s=await h(p(a),{refresh:!0}).catch(()=>s),i=s?.works.find(l=>l.id===e)),!s||!i)return;if(r===void 0||r.startsWith("-"))return{command:F(i),rest:t.slice(1),list:!0};const o=s.tools.find(l=>l.name===r);if(o?.work!==i.id)throw new $("unknown-tool",o?`${r} is not a ${i.id} tool: it is in allyhub ${o.work}.`:`No ${i.id} tool ${r}.`,{exit:g.notFound,hint:o?`allyhub ${o.work} ${r}`:`allyhub ${i.id} lists its tools.`});return{command:toolCommand(o),rest:t.slice(2),list:!1}})},upload=w({name:"upload",summary:"Upload files from this computer for a cloud tool whose input takes a URL (a presenter photo, speech audio, a reference clip), and print a signed URL for each, which works for a few hours",input:n.strictObject({files:n.array(n.string()).min(1).describe("The files: images (.jpg, .png, .webp, .gif), sound (.mp3, .wav, .m4a, .aac, .ogg, .flac) or video (.mp4, .mov, .webm), 50 MB at most")}),positional:["files"],output:n.looseObject({files:n.array(n.looseObject({file:n.string(),ok:n.boolean(),url:n.string().optional()}))}),examples:["allyhub upload assets/media/presenter.jpg"]},(t,a)=>f(async()=>{const e=await M(p(a),t.files,{base:a.cwd}),r=e.filter(o=>!o.ok),s=e.map(o=>o.ok?`${o.file}
2
+ import{readFile as H}from"node:fs/promises";import{join as U,resolve as R}from"node:path";import{z as a}from"zod";import{callTool as B,findTool as C,loadCatalog as h,priceCalls as F,priceText as j,resumeJob as K,uploadFiles as _}from"@allyhub-next/cloud";import{WORK_DIR as G}from"@allyhub-next/formats";import{EXIT as $}from"@allyhub-next/protocol";import{CommandError as b,defineCommand as w}from"../command.js";import{optionName as P}from"../args.js";import{chargeOf as z}from"../charge.js";import{commandHelp as X}from"../help.js";import{requiredMissing as Q,toolInput as V,toolOptions as Y}from"../tool-options.js";import{cloudOf as p,cloudWork as f,placeFrom as Z,plural as y}from"./cloud.js";const ee=8e3,d={input:a.string().optional().describe("The input as JSON, or a file of it: the whole of it, or the fields no option gives; an option given beside it wins"),quote:a.boolean().default(!1).describe("Say what a call with this input would cost, calling nothing and charging nothing"),file:a.string().optional().describe("The path of the file it makes (without an extension it takes the result's); the same call with the same input then keeps it, calling nothing, and while its call works waits for that call; default a folder under .allyhub/work/"),force:a.boolean().default(!1).describe("With --file: call even when the file was made from this input, and replace a file that has no record"),wait:a.boolean().default(!0).describe("Wait for a long tool (an audit, a video model); --no-wait returns its call at once"),resume:a.string().optional().describe("Wait for a call of this tool this computer made (from --no-wait, or a call that stopped waiting), and download its file")},k=(e,t)=>new b("usage",e,{exit:$.usage,...t===void 0?{}:{hint:t}}),I=e=>{const t=e.reduce((n,r)=>r.map((s,i)=>Math.max(n[i]??0,s.length)),[]);return e.map(n=>` ${n.map((r,s)=>s===n.length-1?r:r.padEnd(t[s]??0)).join(" ")}`.trimEnd())};function W(e){return w({name:e.id,summary:`${e.title}: ${e.description} Lists the tools, each a command`,input:a.strictObject({fresh:a.boolean().default(!1).describe("Read the catalog from AllyHub again, not the copy kept an hour")}),output:a.looseObject({work:a.string(),tools:a.array(a.looseObject({name:a.string()}))}),examples:[`allyhub ${e.id}`,`allyhub ${e.id} --json`]},(t,n)=>f(async()=>{const s=(await h(p(n),{refresh:t.fresh})).tools.filter(i=>i.work===e.id).sort((i,o)=>i.name.localeCompare(o.name));return{value:{work:e.id,title:e.title,tools:s.map(i=>({name:i.name,title:i.title,description:i.description,price:j(i.price),long:i.long}))},text:[`${e.title} (${y(s.length,"tool")}):`,...I(s.map(i=>[i.name,i.title,j(i.price)])),"",`Each costs credits when it worked; a failed call is free. allyhub help ${e.id} <tool> shows a tool's options; --quote prices a call first.`].join(`
3
+ `)}}))}const g=e=>new Intl.NumberFormat("en-US",{maximumFractionDigits:4}).format(e),D=e=>`${e.estimated?`about ${new Intl.NumberFormat("en-US",{maximumFractionDigits:2}).format(e.credits)}`:g(e.credits)} credits`,O=e=>e.map(t=>`\`${t}\``).join(", ");function te(e){if(!e.unknown)return D(e);const t=e.dependsOn.length?`on ${O(e.dependsOn)}`:"on what the call counts";return e.credits>0?`${D(e)}, and more that depends ${t}`:`the total depends ${t} (not known before the call)`}function ne(e){return e.lines.map(t=>{if(t.what==="per call")return` per call: ${g(t.credits)}`;const n=t.rate===void 0?"a rate by its settings":`${g(t.rate)} credits per ${t.per}${t.label?` ${t.label}`:""}`,r=[...t.assumed.length?[`${O(t.assumed)} not given: the provider's default`]:[],...t.typical&&t.field!==void 0?[`\`${t.field}\` not given: counted as ${g(t.units)}`]:[]],s=r.length?` (${r.join("; ")})`:"";if(!t.unknown)return` ${t.what} × ${n}${s} = ${g(t.credits)}${t.estimated?", estimated":""}`;const i=t.dependsOn.length?`it depends on ${O(t.dependsOn)}`:"counted by the call";return` ${t.what} × ${n}${s}: not in the total, ${i}`})}function ie(e,t=5,n=160){const r=i=>{const o=i===void 0?"undefined":JSON.stringify(i);return o.length>n?`${o.slice(0,n-1)}…`:o},s=(i,o)=>[`${i?`${i}: `:""}${y(o.length,"item")}${o.length>t?`, the first ${String(t)}:`:":"}`,...o.slice(0,t).map((l,u)=>` [${String(u)}] ${r(l)}`)];return Array.isArray(e)?s("",e):typeof e!="object"||e===null?[r(e)]:Object.entries(e).flatMap(([i,o])=>Array.isArray(o)?s(i,o):[`${i}: ${r(o)}`])}function J(e,t){if("jobId"in e)return[`${e.tool} is still working (call ${e.jobId}).`,`Wait for it: allyhub ${t} --resume ${e.jobId}${e.file===void 0?"":" (or the same command again: it waits for this call)"}`].join(`
4
+ `);if(e.kept)return[`Kept ${e.file??""}: ${e.tool} made it from this same input on ${e.kept.at} (${e.gen??""}).`,"Nothing was called or charged. --force makes it again."].join(`
5
+ `);const n=JSON.stringify(e.output,null,2);return[n.length<=ee?n:[`Output: ${String(Math.round(Buffer.byteLength(n)/1024))} KB of JSON, in short (--json prints it whole):`,...ie(e.output)].join(`
6
+ `),...e.file?[`File: ${e.file}${e.replaced?" (replaced: it was made from another input)":""}`]:[],...e.words?[`Words: ${e.words} (when each word is said: a part can say a stretch of the file)`]:[],...e.gen?[`Record: ${e.gen} (move it with the file)`]:[],`Credits used: ${e.credits===void 0?"not reported":String(e.credits)}`].join(`
7
+ `)}const oe=e=>{const t=Math.max(0,Math.round(e));return`${String(Math.floor(t/60))}:${String(t%60).padStart(2,"0")}`},ae=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);async function se(e,t){const n=e.trim().startsWith("{"),r=n?e:await H(R(t.cwd,e),"utf8").catch(i=>{throw new b("not-found",`--input: cannot read ${e}: ${i.message}`,{exit:$.notFound,hint:"Give a JSON object, or a file of one."})});let s;try{s=JSON.parse(r)}catch{s=void 0}if(!ae(s))throw k(`--input: ${n?"the text":e} is not a JSON object.`,`Give '{"field": …}', or a file of it.`);return s}export function toolCommand(e){const t=`${e.work} ${e.name}`,n=Y(e.input,Object.keys(d)),r=a.strictObject({...n.shape,...d,input:d.input.describe(n.inputOnly.length?`${d.input.description??""}. Fields only it gives: ${n.inputOnly.join(", ")}`:d.input.description??"")}),s=j(e.price),i=[...n.fields].filter(([,o])=>n.required.includes(o)).map(([o,l])=>`--${P(o)} <${l}>`);return{spec:{name:t,summary:e.description,input:r,output:a.looseObject({tool:a.string(),output:a.record(a.string(),a.unknown()).optional(),credits:a.number().optional(),file:a.string().optional(),gen:a.string().optional(),words:a.string().optional(),jobId:a.string().optional(),quote:a.looseObject({credits:a.number()}).nullable().optional()}),examples:[`allyhub ${[t,...i,"--quote"].join(" ")}`,`allyhub ${[t,...i].join(" ")}`]},about:{lines:[e.title,"",`Costs ${s}. A call is charged once, when it worked; a failed call is free. --quote prices an input first, charging nothing.`,...e.long?["A long tool: the command waits for it (--no-wait returns its call, --resume waits again)."]:[]],value:{work:e.work,title:e.title,price:s,priceRule:e.price,long:e.long,inputSchema:e.input,outputSchema:e.output}},run:(o,l)=>f(async()=>{const u=o,E=o,v=p(l),S=await Z(l),m=V(n,E,u.input===void 0?{}:await se(u.input,l));let A=0;const q={base:S,signal:l.signal,onRunning:c=>{Date.now()-A<3e4||(A=Date.now(),l.progress(`${e.name}: working · ${oe((Date.now()-Date.parse(c.createdAt))/1e3)} (call ${c.id})`))},onRetry:(c,L,M)=>{l.progress(`${e.name}: ${L.message}; reading call ${c} again in ${String(M)} s`)}};if(u.resume!==void 0){if(Object.keys(m).length>0||u.quote||u.file!==void 0)throw k("--resume waits for a call as it was made: give it no input, --quote or --file.");const c=await K(v,u.resume,{...q,tool:e.name});return{value:{...c},text:J(c,t)}}if(u.quote){const c=F(e.price,e.input,[m]).calls[0];if(!c)throw new Error("No quote.");return{value:{tool:e.name,quote:c},text:[`A call${Object.keys(m).length===0?" with the defaults":""}: ${te(c)}`,...ne(c),...e.price.quote?[`The price: ${s}.`]:c.estimated?["Estimated parts are charged by what the call returns."]:[],"A call is charged once, when it worked; a failed call is free."].join(`
8
+ `)}}const N=Q(n,m);if(N.length>0)throw k(`${e.name} needs ${N.join(", ")}.`,`allyhub help ${t} shows its options.`);const x=l.approve,T=await B(v,e.name,m,{...q,...x===void 0?{}:{approve:()=>{const c=F(e.price,e.input,[m]).calls[0];return c===void 0?Promise.resolve(!0):x(z(t,e.title,c))}},outDir:U(S,G,e.work,e.name),...u.file===void 0?{}:{file:R(l.cwd,u.file)},force:u.force,wait:u.wait});return{value:{...T},text:J(T,t)}})}}const re=e=>h(p(e)).catch(()=>{});export const CLOUD_SOURCE={entries:[{name:"<work>",summary:"List a work's cloud tools (AllyHub's catalog), a line each with its price; allyhub help lists the works"},{name:"<work> <tool>",summary:"Run a cloud tool with the account's credits, its input as options (allyhub help <work> <tool>); --quote prices it first"}],async nouns(e){return(await h(p(e))).works.map(n=>({name:n.id,summary:`${n.title}: ${n.description}`}))},resolve:(e,t)=>f(async()=>{const[n,r]=e;if(n===void 0||n.startsWith("-"))return;let s=await re(t),i=s?.works.find(l=>l.id===n);if(i||(s=await h(p(t),{refresh:!0}).catch(()=>s),i=s?.works.find(l=>l.id===n)),!s||!i)return;if(r===void 0||r.startsWith("-"))return{command:W(i),rest:e.slice(1),list:!0};const o=s.tools.find(l=>l.name===r);if(o?.work!==i.id)throw new b("unknown-tool",o?`${r} is not a ${i.id} tool: it is in allyhub ${o.work}.`:`No ${i.id} tool ${r}.`,{exit:$.notFound,hint:o?`allyhub ${o.work} ${r}`:`allyhub ${i.id} lists its tools.`});return{command:toolCommand(o),rest:e.slice(2),list:!1}})},upload=w({name:"upload",summary:"Upload files from this computer for a cloud tool whose input takes a URL (a presenter photo, speech audio, a reference clip), and print a signed URL for each, which works for a few hours",input:a.strictObject({files:a.array(a.string()).min(1).describe("The files: images (.jpg, .png, .webp, .gif), sound (.mp3, .wav, .m4a, .aac, .ogg, .flac) or video (.mp4, .mov, .webm), 50 MB at most")}),positional:["files"],output:a.looseObject({files:a.array(a.looseObject({file:a.string(),ok:a.boolean(),url:a.string().optional()}))}),examples:["allyhub upload assets/media/presenter.jpg"]},(e,t)=>f(async()=>{const n=await _(p(t),e.files,{base:t.cwd}),r=n.filter(o=>!o.ok),s=n.map(o=>o.ok?`${o.file}
9
9
  ${o.url}${o.expiresAt===void 0?"":` (works until ${o.expiresAt})`}`:`${o.file}
10
- failed: ${o.error.message}${o.error.hint?` ${o.error.hint}`:""}`);e.length>1&&s.push(r.length===0?`${b(e.length,"file")} uploaded.`:`${String(e.length-r.length)} of ${b(e.length,"file")} uploaded; ${String(r.length)} failed.`);const i=r[0];return{value:{files:e.map(o=>o.ok?{...o}:{file:o.file,ok:!1,error:{code:o.error.code,message:o.error.message,...o.error.hint?{hint:o.error.hint}:{}}})},text:s.join(`
11
- `),...i?{exit:i.error.exit}:{}}})),cloudList=w({name:"cloud list",summary:"List AllyHub's cloud works, or one work's tools, a line each with its price",input:n.strictObject({work:n.string().optional().describe("The work whose tools to list; none lists the works")}),output:n.looseObject({})},(t,a)=>f(async()=>{const e=await h(p(a));if(t.work===void 0)return{value:{works:e.works},text:["AllyHub's cloud works:",...C(e.works.map(i=>[i.id,i.description]))].join(`
12
- `)};const r=e.works.find(i=>i.id===t.work);if(!r)throw new $("unknown-work",`No work ${t.work}.`,{exit:g.notFound,hint:`The works are ${e.works.map(i=>i.id).join(", ")}.`});const s=await F(r).run({fresh:!1},a);return{value:s.value,text:s.text}})),cloudTool=w({name:"cloud tool",summary:"Show a cloud tool: what it does, its input (as cloud call's input takes it) and its price",input:n.strictObject({tool:n.string().describe("The tool's name, as cloud list lists it")}),output:n.looseObject({name:n.string()})},(t,a)=>f(async()=>{const e=toolCommand(await T(p(a),t.tool));return{value:{name:t.tool,summary:e.spec.summary,about:e.about?.lines??[],...e.about?.value},text:B(e.spec,e.about?.lines)}})),cloudCall=w({name:"cloud call",summary:"Run a cloud tool with the account's credits: its output, the credits used, and the file it makes with its record; waits for a long one",input:n.strictObject({tool:n.string().describe("The tool's name, as cloud list lists it"),input:n.record(n.string(),n.unknown()).default({}).describe("Its input, by its input schema (cloud tool)"),quote:d.quote,file:d.file,force:d.force,wait:d.wait,resume:d.resume}),output:n.looseObject({tool:n.string()})},(t,a)=>f(async()=>{const{tool:e,input:r,...s}=t,i=toolCommand(await T(p(a),e)),o=i.spec.input.safeParse({...s,...Object.keys(r).length?{input:JSON.stringify(r)}:{}});if(!o.success)throw y(o.error.message);const l=await i.run(o.data,a);return{...l,value:{tool:e,...l.value}}}));
10
+ failed: ${o.error.message}${o.error.hint?` ${o.error.hint}`:""}`);n.length>1&&s.push(r.length===0?`${y(n.length,"file")} uploaded.`:`${String(n.length-r.length)} of ${y(n.length,"file")} uploaded; ${String(r.length)} failed.`);const i=r[0];return{value:{files:n.map(o=>o.ok?{...o}:{file:o.file,ok:!1,error:{code:o.error.code,message:o.error.message,...o.error.hint?{hint:o.error.hint}:{}}})},text:s.join(`
11
+ `),...i?{exit:i.error.exit}:{}}})),cloudList=w({name:"cloud list",summary:"List AllyHub's cloud works, or one work's tools, a line each with its price",input:a.strictObject({work:a.string().optional().describe("The work whose tools to list; none lists the works")}),output:a.looseObject({})},(e,t)=>f(async()=>{const n=await h(p(t));if(e.work===void 0)return{value:{works:n.works},text:["AllyHub's cloud works:",...I(n.works.map(i=>[i.id,i.description]))].join(`
12
+ `)};const r=n.works.find(i=>i.id===e.work);if(!r)throw new b("unknown-work",`No work ${e.work}.`,{exit:$.notFound,hint:`The works are ${n.works.map(i=>i.id).join(", ")}.`});const s=await W(r).run({fresh:!1},t);return{value:s.value,text:s.text}})),cloudTool=w({name:"cloud tool",summary:"Show a cloud tool: what it does, its input (as cloud call's input takes it) and its price",input:a.strictObject({tool:a.string().describe("The tool's name, as cloud list lists it")}),output:a.looseObject({name:a.string()})},(e,t)=>f(async()=>{const n=toolCommand(await C(p(t),e.tool));return{value:{name:e.tool,summary:n.spec.summary,about:n.about?.lines??[],...n.about?.value},text:X(n.spec,n.about?.lines)}})),cloudCall=w({name:"cloud call",summary:"Run a cloud tool with the account's credits: its output, the credits used, and the file it makes with its record; waits for a long one",input:a.strictObject({tool:a.string().describe("The tool's name, as cloud list lists it"),input:a.record(a.string(),a.unknown()).default({}).describe("Its input, by its input schema (cloud tool)"),quote:d.quote,file:d.file,force:d.force,wait:d.wait,resume:d.resume}),output:a.looseObject({tool:a.string()})},(e,t)=>f(async()=>{const{tool:n,input:r,...s}=e,i=toolCommand(await C(p(t),n)),o=i.spec.input.safeParse({...s,...Object.keys(r).length?{input:JSON.stringify(r)}:{}});if(!o.success)throw k(o.error.message);const l=await i.run(o.data,t);return{...l,value:{tool:n,...l.value}}}));
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{add as o,addSearch as r}from"./add.js";import{align as m}from"./align.js";import{audio as t,audioRecording as e}from"./audio.js";import{authLogin as i,authLogout as p,authStatus as f,authToken as d}from"./auth.js";import{orgsList as a,orgsSwitch as c}from"./orgs.js";import{check as C}from"./check.js";import{dev as S}from"./dev.js";import{doctor as s}from"./doctor.js";import{FRAMES_COMMANDS as M}from"./frames.js";import{INSPECT_COMMANDS as n}from"./inspect/index.js";import{mcp as u}from"./mcp.js";import{model as l,modelCapabilities as h}from"./model.js";import{reference as O,referenceCompare as N,referenceServed as A,referenceSetCommand as D}from"./reference.js";import{render as E}from"./render.js";import{SCENERY_COMMANDS as g}from"./scenery.js";import{sheet as _,sheetImport as L}from"./sheet.js";import{siteMediaCommand as R}from"./site.js";import{CLOUD_SOURCE as v,cloudCall as w,cloudList as x,cloudTool as T,upload as U}from"./cloud-tools.js";import{voice as k}from"./voice.js";import{words as I,wordsEdits as P}from"./words.js";export const COMMANDS=[C,...n,E,...M,t,e,_,L,S,o,r,R,I,P,m,O,N,A,D,...g,l,h,i,p,f,d,a,c,U,k,s,u],COMMAND_SOURCES=[v],MCP_COMMANDS=[x,T,w];
2
+ import{add as o,addSearch as r}from"./add.js";import{align as m}from"./align.js";import{audio as t,audioRecording as e}from"./audio.js";import{authLogin as i,authLogout as p,authStatus as f,authToken as d}from"./auth.js";import{orgsList as a,orgsSwitch as s}from"./orgs.js";import{check as c}from"./check.js";import{dev as C}from"./dev.js";import{doctor as S}from"./doctor.js";import{FRAMES_COMMANDS as l}from"./frames.js";import{INSPECT_COMMANDS as M}from"./inspect/index.js";import{mcp as n}from"./mcp.js";import{model as u,modelCapabilities as h}from"./model.js";import{reference as O,referenceCompare as N,referenceServed as A,referenceSetCommand as D}from"./reference.js";import{render as E}from"./render.js";import{SCENERY_COMMANDS as g}from"./scenery.js";import{sheet as _,sheetImport as L}from"./sheet.js";import{siteMediaCommand as R}from"./site.js";import{skills as k,skillsShow as w}from"./skills.js";import{CLOUD_SOURCE as v,cloudCall as x,cloudList as T,cloudTool as U,upload as I}from"./cloud-tools.js";import{voice as P}from"./voice.js";import{words as b,wordsEdits as F}from"./words.js";export const COMMANDS=[c,...M,E,...l,t,e,_,L,C,o,r,k,w,R,b,F,m,O,N,A,D,...g,u,h,i,p,f,d,a,s,I,P,S,n],COMMAND_SOURCES=[v],MCP_COMMANDS=[T,U,x];
@@ -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 w}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as y,ListResourcesRequestSchema as S,ListToolsRequestSchema as b,ReadResourceRequestSchema as R}from"@modelcontextprotocol/sdk/types.js";import{z as d}from"zod";import{describeCommand as v,toolName as C}from"@allyhub-next/protocol";import{CommandError as l,defineCommand as M}from"../command.js";import{CardApp as O}from"./mcp-app.js";import{docResources as j,readDoc as N}from"./mcp-docs.js";const $="0.0.0";async function q(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 l?e:new l("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=M({name:"mcp",summary:"The commands as MCP tools, over stdio",input:d.strictObject({}),output:d.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=>[C(s.spec.name),s]));console.log=console.error,console.info=console.error;const e=new w({name:"allyhub",version:$},{capabilities:{tools:{},resources:{}}}),o=new O(t),m=e.server;m.setRequestHandler(b,()=>({tools:[...o.tools(),...[...a].map(([s,r])=>{const n=Object.fromEntries(Object.entries(v(r.spec).input).filter(([h])=>h!=="$schema"));return{name:s,description:r.spec.summary,inputSchema:{type:"object",...n}}})]})),m.setRequestHandler(S,async()=>({resources:[...o.resources(),...await j(t.cwd)]})),m.setRequestHandler(R,async s=>{const{uri:r}=s.params;if(r===o.resources()[0]?.uri)return{contents:[await o.resource()]};const n=await N(t.cwd,r);if(n===void 0)throw new l("not-found",`allyhub has no resource ${r}.`);return{contents:[n]}}),m.setRequestHandler(y,(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 l("unknown-tool",`allyhub has no tool ${s.params.name}.`);return q(n,s.params.arguments,{...t,signal:AbortSignal.any([t.signal,r.signal])})});const p=new g,f=new Promise(s=>{p.onclose=s});return await e.connect(p),process.stdin.once("end",()=>{e.close()}),t.signal.addEventListener("abort",()=>{e.close()}),await f,await o.close(),{value:{},text:""}});
2
+ import{McpServer as k}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as v}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as g,ListResourcesRequestSchema as S,ListToolsRequestSchema as R,ReadResourceRequestSchema as A}from"@modelcontextprotocol/sdk/types.js";import{z as u}from"zod";import{describeCommand as C,toolName as h}from"@allyhub-next/protocol";import{ASK_ABOVE as M,askingAbove as O,chargeQuestion as j}from"../charge.js";import{CommandError as d,defineCommand as T}from"../command.js";import{CardApp as E}from"./mcp-app.js";import{docResources as I,readDoc as N}from"./mcp-docs.js";const _="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\` (${h("skills")}) lists AllyHub's work skills, each how to make one kind of work; \`allyhub skills show <name>\` (${h("skills show")}) prints one, and \`allyhub add <name>\` puts it in the project.`].join(" ");const $=2147483647;async function q(r,a,p){const l=(e,o=!1)=>({content:[{type:"text",text:JSON.stringify(e,null,2)}],...o?{isError:o}:{}}),c=r.spec.input.safeParse(a??{});if(!c.success){const e=c.error.issues[0];return l({error:{code:"usage",message:`${e?.path.join(".")??r.spec.name}: ${e?.message??"not valid"}.`}},!0)}try{const e=await r.run(c.data,p);return l(e.value)}catch(e){const o=e instanceof d?e:new d("failed",e instanceof Error?e.message:String(e));return l({error:{code:o.code,message:o.message,...o.hint===void 0?{}:{hint:o.hint}}},!0)}}export const mcp=T({name:"mcp",summary:"The commands as MCP tools, over stdio",input:u.strictObject({ask:u.boolean().default(!0).describe("Ask the person before a cloud call that costs more than --ask-above credits, or one priced only by what it makes, where the client can ask (MCP's elicitation); --no-ask never asks"),askAbove:u.number().nonnegative().default(M).describe("The credits a call may cost without asking the person; 0 asks before every call that costs credits")}),output:u.looseObject({}),examples:["claude mcp add allyhub -- allyhub mcp","allyhub mcp --ask-above 50"],mcp:!1},async(r,a)=>{const{COMMANDS:p,MCP_COMMANDS:l}=await import("./index.js"),c=new Map([...p,...l].filter(s=>s.spec.mcp!==!1).map(s=>[h(s.spec.name),s]));console.log=console.error,console.info=console.error;const e=new k({name:"allyhub",version:_},{capabilities:{tools:{},resources:{}},instructions:MCP_INSTRUCTIONS}),o=new E(a),i=e.server;i.setRequestHandler(R,()=>({tools:[...o.tools(),...[...c].map(([s,t])=>{const n=Object.fromEntries(Object.entries(C(t.spec).input).filter(([m])=>m!=="$schema"));return{name:s,description:t.spec.summary,inputSchema:{type:"object",...n}}})]})),i.setRequestHandler(S,async()=>({resources:[...o.resources(),...await I(a.cwd)]})),i.setRequestHandler(A,async s=>{const{uri:t}=s.params;if(t===o.resources()[0]?.uri)return{contents:[await o.resource()]};const n=await N(a.cwd,t);if(n===void 0)throw new d("not-found",`allyhub has no resource ${t}.`);return{contents:[n]}}),i.setRequestHandler(g,(s,t)=>{if(o.has(s.params.name))return o.call(s.params.arguments);const n=c.get(s.params.name);if(!n)throw new d("unknown-tool",`allyhub has no tool ${s.params.name}.`);const m=AbortSignal.any([a.signal,t.signal]),w=r.ask&&i.getClientCapabilities()?.elicitation?.form;return q(n,s.params.arguments,{...a,signal:m,...w?{approve:O(r.askAbove,async y=>(await i.elicitInput(j(y),{signal:m,timeout:$,relatedRequestId:t.requestId})).action==="accept")}:{}})});const f=new v,b=new Promise(s=>{f.onclose=s});return await e.connect(f),process.stdin.once("end",()=>{e.close()}),a.signal.addEventListener("abort",()=>{e.close()}),await b,await o.close(),{value:{},text:""}});
@@ -0,0 +1,4 @@
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}});
@@ -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 i}from"zod";import{projectSays as f}from"@allyhub-next/project";import{makeSay as y,quoteSays as $}from"@allyhub-next/cloud";import{EXIT as b}from"@allyhub-next/protocol";import{CommandError as v,defineCommand as w}from"../command.js";import{projectRoot as j}from"../project.js";import{cloudOf as k,cloudWork as S,plural as m}from"./cloud.js";import{dir as q}from"../fields.js";const h=o=>Math.round(o*1e6)/1e6;export const voice=w({name:"voice",summary:"Make the voice files a project's say(text) parts need, with their cloud voice tool, one per say that has none (assets/media/gen/say/<hash>, its words and its record); --quote prices them first",input:i.strictObject({item:i.string().optional().describe("Only this item's says: its id, a path into it, or a folder of items"),dir:q,quote:i.boolean().default(!1).describe("Say what making the missing files would cost, making nothing and charging nothing")}),positional:["item"],output:i.looseObject({says:i.number(),missing:i.array(i.looseObject({part:i.string(),stem:i.string()})).optional(),made:i.array(i.looseObject({part:i.string(),file:i.string()})).optional(),credits:i.number()}),examples:["allyhub voice --quote","allyhub voice launch/film"]},async(o,d)=>{const p=await j(d.cwd,o.dir),n=await f(p,o.item===void 0?{}:{piece:o.item});if(o.item!==void 0&&n.pieces.length===0)throw new v("no-item",`No video ${o.item} in the project.`,{exit:b.notFound});const s=n.says.filter(l=>l.file===void 0),g=`Every say has its file (${m(n.says.length,"say")}). Nothing to make.`;return await S(async()=>{const l=k(d);if(o.quote){const e=await $(l,s),a=t=>{const r=e.says[t]?.credits;return r===void 0?{}:{credits:r}};return{value:{says:n.says.length,missing:s.map((t,r)=>({piece:t.piece,part:t.part,tool:t.tool,...t.voice===void 0?{}:{voice:t.voice},...t.params===void 0?{}:{params:t.params},characters:t.text.length,stem:t.stem,...a(r)})),credits:e.credits,estimated:e.estimated},text:s.length===0?g:[`${m(s.length,"say")} without a file:`,...s.map((t,r)=>` ${t.piece} · ${t.part}: ${m(t.text.length,"character")}${a(r).credits===void 0?"":`, ${String(h(a(r).credits??0))} credits`}`),`${e.estimated?"About ":""}${String(e.credits)} credits in all. Nothing was charged; allyhub voice makes them.`].join(`
3
- `)}}const c=[];for(const e of s){d.progress(`${e.piece} · ${e.part}: ${e.tool}…`);const a=await y(l,p,e,{base:p,signal:d.signal});c.push({piece:e.piece,part:e.part,...a})}const u=h(c.reduce((e,a)=>e+a.credits,0));return{value:{says:n.says.length,made:c,kept:n.says.length-s.length,credits:u},text:c.length===0?g:[`Made ${m(c.length,"file")}:`,...c.map(e=>` ${e.file} (${e.piece} · ${e.part}, ${String(h(e.seconds))} s)`),`Credits used: ${String(u)}.`].join(`
2
+ import{z as i}from"zod";import{projectSays as y}from"@allyhub-next/project";import{declined as w,makeSay as $,quoteSays as g}from"@allyhub-next/cloud";import{EXIT as v}from"@allyhub-next/protocol";import{CommandError as b,defineCommand as j}from"../command.js";import{projectRoot as k}from"../project.js";import{cloudOf as S,cloudWork as q,plural as m}from"./cloud.js";import{dir as O}from"../fields.js";const h=a=>Math.round(a*1e6)/1e6;export const voice=j({name:"voice",summary:"Make the voice files a project's say(text) parts need, with their cloud voice tool, one per say that has none (assets/media/gen/say/<hash>, its words and its record); --quote prices them first",input:i.strictObject({item:i.string().optional().describe("Only this item's says: its id, a path into it, or a folder of items"),dir:O,quote:i.boolean().default(!1).describe("Say what making the missing files would cost, making nothing and charging nothing")}),positional:["item"],output:i.looseObject({says:i.number(),missing:i.array(i.looseObject({part:i.string(),stem:i.string()})).optional(),made:i.array(i.looseObject({part:i.string(),file:i.string()})).optional(),credits:i.number()}),examples:["allyhub voice --quote","allyhub voice launch/film"]},async(a,n)=>{const p=await k(n.cwd,a.dir),c=await y(p,a.item===void 0?{}:{piece:a.item});if(a.item!==void 0&&c.pieces.length===0)throw new b("no-item",`No video ${a.item} in the project.`,{exit:v.notFound});const o=c.says.filter(l=>l.file===void 0),u=`Every say has its file (${m(c.says.length,"say")}). Nothing to make.`;return await q(async()=>{const l=S(n);if(a.quote){const e=await g(l,o),s=t=>{const r=e.says[t]?.credits;return r===void 0?{}:{credits:r}};return{value:{says:c.says.length,missing:o.map((t,r)=>({piece:t.piece,part:t.part,tool:t.tool,...t.voice===void 0?{}:{voice:t.voice},...t.params===void 0?{}:{params:t.params},characters:t.text.length,stem:t.stem,...s(r)})),credits:e.credits,estimated:e.estimated},text:o.length===0?u:[`${m(o.length,"say")} without a file:`,...o.map((t,r)=>` ${t.piece} · ${t.part}: ${m(t.text.length,"character")}${s(r).credits===void 0?"":`, ${String(h(s(r).credits??0))} credits`}`),`${e.estimated?"About ":""}${String(e.credits)} credits in all. Nothing was charged; allyhub voice makes them.`].join(`
3
+ `)}}const d=[];if(n.approve!==void 0&&o.length>0){const e=await g(l,o);if(!await n.approve({tool:"voice",title:`${m(o.length,"voice file")} with ${e.titles.join(", ")}`,credits:e.credits,estimated:e.estimated,unknown:e.unknown}))throw w("voice")}for(const e of o){n.progress(`${e.piece} · ${e.part}: ${e.tool}…`);const s=await $(l,p,e,{base:p,signal:n.signal});d.push({piece:e.piece,part:e.part,...s})}const f=h(d.reduce((e,s)=>e+s.credits,0));return{value:{says:c.says.length,made:d,kept:c.says.length-o.length,credits:f},text:d.length===0?u:[`Made ${m(d.length,"file")}:`,...d.map(e=>` ${e.file} (${e.piece} · ${e.part}, ${String(h(e.seconds))} s)`),`Credits used: ${String(f)}.`].join(`
4
4
  `)}})});
@@ -0,0 +1,2 @@
1
+ // The types of @allyhub-next/model/three, which allyhub's build lends a project by that name.
2
+ export * from "../../node_modules/@allyhub-next/model/dist/three.js";
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)}
2
+ import{readFileSync as R}from"node:fs";import{EXIT as m,describeCommand as M}from"@allyhub-next/protocol";import{readArgs as T}from"./args.js";import{studioApprove as H}from"./charge.js";import{CommandError as h}from"./command.js";import{COMMAND_SOURCES as j,COMMANDS as x}from"./commands/index.js";import{commandHelp as A,listHelp as J,workHelp as G}from"./help.js";import{sdkDocs as U}from"./sdk-docs.js";import{WORKS as W,cloudNouns as F,workNamed as K,workTitle as E}from"./works.js";const N=JSON.parse(R(new URL("../package.json",import.meta.url),"utf8")).version,L={stdout:t=>process.stdout.write(t),stderr:t=>process.stderr.write(t),cwd:process.cwd(),env:process.env};export function findCommand(t,o=x){let i;for(const c of o){const u=c.spec.name.split(" ");u.every((r,a)=>t[a]===r)&&u.length>(i?.length??0)&&(i={command:c,length:u.length})}return i?{command:i.command,rest:t.slice(i.length)}:{rest:[...t]}}export async function resolveCommand(t,o,i=x,c=j){const u=findCommand(t,i);if(u.command)return u;for(const r of c){const a=await r.resolve(t,o);if(a)return a}return u}const D=t=>({...M(t.spec),...t.about?.value});export async function main(t,o=L,i=x,c=j){const u=t.includes("--json"),r=t.filter(n=>n!=="--json"),a=(n,e)=>{o.stdout(u?`${JSON.stringify(n,null,2)}
3
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?`
4
+ `)},S=i.map(n=>n.spec),v=new AbortController,$=H(o.env,o.cwd,v.signal),y={cwd:o.cwd,env:o.env,progress:n=>{o.stderr(`${n}
5
+ `)},signal:v.signal,...$===void 0?{}:{approve:$}};if(r[0]==="--version"||r[0]==="-v")return a({version:N},N),m.ok;if(r.length===0||r[0]==="help"||r[0]==="--help"||r[0]==="-h"){const n=r.slice(1),e=await U(o.cwd).catch(()=>{}),k=async()=>{const s=await Promise.all(c.map(l=>l.nouns(y).catch(()=>{})));return s.some(l=>l===void 0)?void 0:s.flatMap(l=>l??[])};if(n.length===0){const s=await k();return a({version:N,docs:e?.dir,works:W.map(l=>({...l,title:E(l,s)})),commands:S.map(M),made:[...c.flatMap(l=>l.entries),...s??[]]},J(S,c,s,e?.dir)),m.ok}const d=n.length===1?K(n[0]??""):void 0;if(d){const s=d.cloud===void 0?[]:await k();return a({...d,title:E(d,s),...d.cloud===void 0?{}:{cloudWorks:s&&F(d,s)},docsDir:e?.dir},G(d,S,c,e?.dir,s)),m.ok}let p;try{p=await resolveCommand(n,y,i,c)}catch(s){return g(s)}const{command:f,rest:I}=p;return f&&p.list===!0?await O(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(D(f),A(f.spec,f.about?.lines)),m.ok)}let C;try{C=await resolveCommand(r,y,i,c)}catch(n){return g(n)}const{command:w,rest:b}=C;if(!w)return g(new h("unknown-command",`allyhub has no command "${r[0]??""}".`,{exit:m.usage,hint:"allyhub help lists them."}));if(b.includes("--help")||b.includes("-h"))return a(D(w),A(w.spec,w.about?.lines)),m.ok;return await O(w,b);async function O(n,e){const k=()=>{v.abort(new h("interrupted","Stopped.",{exit:m.interrupted}))};process.once("SIGINT",k);try{const d=T(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:v.signal.aborted?new h("interrupted","Stopped.",{exit:m.interrupted}):new h("failed",n instanceof Error?n.message:String(n));return u?o.stdout(`${JSON.stringify({error:{code:e.code,message:e.message,...e.hint?{hint:e.hint}:{}}},null,2)}
6
+ `):o.stderr(`${e.message}${e.hint?`
7
7
  ${e.hint}`:""}
8
8
  `),e.exit}}
@@ -0,0 +1,2 @@
1
+ /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
+ import{createHash as d}from"node:crypto";import{mkdir as m,readFile as f,writeFile as p}from"node:fs/promises";import{join as s}from"node:path";import{RegistryIndexSchema as u}from"@allyhub-next/protocol";import{registryAt as x}from"@allyhub-next/registry";import{cacheHome as h}from"@allyhub-next/runtimes";import{REGISTRY as w}from"@allyhub-next/scaffold";const S=3600*1e3;export async function resourcesIndex(n){const o=n.env.ALLYHUB_REGISTRY??w,e=x(o),i=s(h(n.env),"registry",`${d("sha256").update(o).digest("hex").slice(0,16)}.json`),r=await f(i,"utf8").then(t=>{const a=JSON.parse(t),c=u.parse(a.index);return{read:a.read,index:c}}).catch(()=>{});if(r&&Date.now()-r.read<S)return{source:e,index:r.index,from:"kept"};try{const t=await e.index();return await m(s(i,".."),{recursive:!0}),await p(i,JSON.stringify({read:Date.now(),index:t})),{source:e,index:t,from:"read"}}catch{return r?{source:e,index:r.index,from:"stale"}:{source:e,from:"none"}}}
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 and the account",commands:["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 t=new Set(WORKS.flatMap(s=>s.cloud?.works??[])),o=e.cloud.works;return n.filter(s=>o===void 0?!t.has(s.name):o.includes(s.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 t=cloudNouns(e,n);return t.length===0?`${e.title}: no cloud tools yet`:`${e.title}: ${t.map(o=>o.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","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(", ")}`}