allyhub 0.7.0 → 0.7.1

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 (62) hide show
  1. package/dist/node_modules/@allyhub-next/audio/package.json +1 -1
  2. package/dist/node_modules/@allyhub-next/browser/package.json +1 -1
  3. package/dist/node_modules/@allyhub-next/build/package.json +1 -1
  4. package/dist/node_modules/@allyhub-next/chart/package.json +1 -1
  5. package/dist/node_modules/@allyhub-next/check/package.json +1 -1
  6. package/dist/node_modules/@allyhub-next/cloud/package.json +1 -1
  7. package/dist/node_modules/@allyhub-next/components/package.json +1 -1
  8. package/dist/node_modules/@allyhub-next/dev-server/dist/project.js +1 -1
  9. package/dist/node_modules/@allyhub-next/dev-server/dist/writer.js +2 -2
  10. package/dist/node_modules/@allyhub-next/dev-server/package.json +1 -1
  11. package/dist/node_modules/@allyhub-next/doc/package.json +1 -1
  12. package/dist/node_modules/@allyhub-next/export/package.json +1 -1
  13. package/dist/node_modules/@allyhub-next/formats/package.json +1 -1
  14. package/dist/node_modules/@allyhub-next/measure/package.json +1 -1
  15. package/dist/node_modules/@allyhub-next/media/package.json +1 -1
  16. package/dist/node_modules/@allyhub-next/model/package.json +1 -1
  17. package/dist/node_modules/@allyhub-next/ooxml/package.json +1 -1
  18. package/dist/node_modules/@allyhub-next/project/package.json +1 -1
  19. package/dist/node_modules/@allyhub-next/protocol/dist/snapshots.d.ts +19 -0
  20. package/dist/node_modules/@allyhub-next/protocol/dist/snapshots.js +1 -1
  21. package/dist/node_modules/@allyhub-next/protocol/package.json +1 -1
  22. package/dist/node_modules/@allyhub-next/registry/package.json +1 -1
  23. package/dist/node_modules/@allyhub-next/render/package.json +1 -1
  24. package/dist/node_modules/@allyhub-next/runtime/package.json +1 -1
  25. package/dist/node_modules/@allyhub-next/runtimes/dist/download.d.ts +19 -1
  26. package/dist/node_modules/@allyhub-next/runtimes/dist/download.js +1 -1
  27. package/dist/node_modules/@allyhub-next/runtimes/dist/index.d.ts +2 -2
  28. package/dist/node_modules/@allyhub-next/runtimes/dist/index.js +1 -1
  29. package/dist/node_modules/@allyhub-next/runtimes/dist/lock.d.ts +6 -0
  30. package/dist/node_modules/@allyhub-next/runtimes/dist/lock.js +1 -1
  31. package/dist/node_modules/@allyhub-next/runtimes/dist/packages.d.ts +19 -2
  32. package/dist/node_modules/@allyhub-next/runtimes/dist/packages.js +3 -3
  33. package/dist/node_modules/@allyhub-next/runtimes/dist/runtimes.d.ts +3 -0
  34. package/dist/node_modules/@allyhub-next/runtimes/dist/runtimes.js +3 -3
  35. package/dist/node_modules/@allyhub-next/runtimes/package.json +1 -1
  36. package/dist/node_modules/@allyhub-next/scaffold/package.json +1 -1
  37. package/dist/node_modules/@allyhub-next/scenery/package.json +1 -1
  38. package/dist/node_modules/@allyhub-next/scenery-host/package.json +1 -1
  39. package/dist/node_modules/@allyhub-next/sheet/package.json +1 -1
  40. package/dist/node_modules/@allyhub-next/slides-core/package.json +1 -1
  41. package/dist/node_modules/@allyhub-next/slides-pptx/package.json +1 -1
  42. package/dist/node_modules/@allyhub-next/slides-runtime/package.json +1 -1
  43. package/dist/node_modules/@allyhub-next/stage/package.json +1 -1
  44. package/dist/node_modules/@allyhub-next/stage-page/package.json +1 -1
  45. package/dist/node_modules/@allyhub-next/video-core/package.json +1 -1
  46. package/dist/node_modules/@allyhub-next/website/package.json +1 -1
  47. package/editor-web/assets/{dist-B3UoDUa5.js → dist-B-BI8jma.js} +1 -1
  48. package/editor-web/assets/{dist-DNZ8PQ-E.js → dist-CS-Z5JSF.js} +1 -1
  49. package/editor-web/assets/{dist-CTQIm8Im.js → dist-ClE1xUdI.js} +1 -1
  50. package/editor-web/assets/{dist-Bc5iA5k1.js → dist-Cqu9f1N5.js} +1 -1
  51. package/editor-web/assets/{dist-0emakyZE.js → dist-CsDICOqP.js} +1 -1
  52. package/editor-web/assets/{dist-B6KzR3pN.js → dist-DegNYCaB.js} +1 -1
  53. package/editor-web/assets/{dist-CiHNHwyq.js → dist-EotuIcA_.js} +1 -1
  54. package/editor-web/assets/index-CwDs3z43.js +69 -0
  55. package/editor-web/assets/minus-DLhpe7h_.js +1 -0
  56. package/editor-web/assets/{react-C_-rEEBU.js → react-CT8OUeCe.js} +1 -1
  57. package/editor-web/assets/{tabs-Den4gHrU.js → tabs-BEPTOFm5.js} +1 -1
  58. package/editor-web/assets/{text-align-start-CsW4nqqL.js → text-align-start-CfqIQSWH.js} +1 -1
  59. package/editor-web/index.html +2 -2
  60. package/package.json +2 -2
  61. package/editor-web/assets/index-CFt3ovRY.js +0 -69
  62. package/editor-web/assets/minus-BhoPZC5P.js +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/audio",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/browser",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/build",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/chart",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/check",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/cloud",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/components",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{randomBytes as m}from"node:crypto";import{resolve as v}from"node:path";import{FILE_METHODS as y,HOST_METHODS as w,JSONRPC as c,PROJECT_TOOLS as h,RPC_ERROR as l,RequestSchema as g,RpcError as d,answer as f}from"@allyhub-next/protocol";import{Checker as O,Indexer as x}from"./background.js";import{Exports as S}from"./exports.js";import{hasFileManager as k,revealFile as R}from"./reveal.js";import{Live as E}from"./live.js";import{fileIn as u}from"./paths.js";import{toolHandlers as p}from"./tools.js";import{Watcher as T}from"./watcher.js";import{Writer as N}from"./writer.js";export const SERVER_METHODS={...y,"tools/call":w["tools/call"]};export class ProjectServer{root;writer;watcher;live;exports;reveal;checker;serving;indexer;clients=new Map;constructor(r){this.root=v(r.root),this.live=new E(this.root,r.gpu===void 0?{}:{gpu:r.gpu}),this.watcher=new T(this.root,(e,s)=>{this.sendTo(this.watchOwner(e),"files/changed",{watch:e,changes:s})},e=>{this.live.moved(e),this.notify("project/changed",{revision:e}),this.checker?.schedule()}),this.writer=new N(this.root,(e,s)=>{this.watcher.wrote(e,s)}),this.reveal=r.reveal===!1?void 0:r.reveal??(k()?R:void 0),this.exports=new S(this.root,e=>{this.notify("export/changed",{job:e})});const i=r.index??process.env.ALLYHUB_NO_INDEX!=="1";if(r.check!==!1){const e=i?new x({root:this.root,emit:s=>{this.notify("index/changed",{jobs:s})},made:()=>{this.checker?.schedule(!0)}}):void 0;this.indexer=e,this.checker=new O({live:this.live,emit:s=>{this.notify("check/changed",s)},...e?{needs:s=>{e.want(s)}}:{},...r.settle===void 0?{}:{settle:r.settle}})}}static open(r){const i=new ProjectServer(r);return i.watcher.start(),i.checker?.schedule(),Promise.resolve(i)}owners=new Map;watchOwner(r){return this.owners.get(r)}connect(r,i={}){const e=`c${m(6).toString("hex")}`;this.clients.set(e,r);const s=this.handlersFor(e,i.origin);return{id:e,receive:async o=>{const t=await this.answerOne(s,o);t&&r(t)},close:()=>{this.clients.delete(e),this.watcher.forget(e);for(const[o,t]of this.owners)t===e&&this.owners.delete(o)}}}channel(){const r=new Set,i=this.connect(e=>{const s=JSON.parse(JSON.stringify(e));queueMicrotask(()=>{for(const o of r)o(s)})});return{send:e=>{i.receive(JSON.parse(JSON.stringify(e)))},listen:e=>(r.add(e),()=>r.delete(e)),close:()=>{i.close()}}}notify(r,i){for(const e of this.clients.values())e({jsonrpc:c,method:r,params:i})}sendTo(r,i,e){r!==void 0&&this.clients.get(r)?.({jsonrpc:c,method:i,params:e})}async answerOne(r,i){const e=g.safeParse(i);if(!e.success){const o=i?.id;return typeof o!="string"&&typeof o!="number"?void 0:{jsonrpc:c,id:o,error:{code:l.invalidRequest,message:"Not a JSON-RPC request."}}}const s=e.data;return Object.hasOwn(w,s.method)&&s.method!=="tools/call"?{jsonrpc:c,id:s.id,error:{code:l.methodNotFound,message:`${s.method} is answered by the page that hosts the editor, not by allyhub dev's server.`}}:f(SERVER_METHODS,r,s)}handlersFor(r,i){const{writer:e,watcher:s}=this,o=p({live:this.live,checker:this.checker,exports:this.exports,reveal:this.reveal,tree:()=>this.serving?.tree(i)});return{"files/read":({path:t})=>e.read(t),"files/write":async({path:t,text:a,over:n})=>({version:await e.write(t,a,n,r)}),"files/upload":async({dir:t,name:a,data:n})=>({path:await e.upload(t,a,Buffer.from(n,"base64"),r)}),"files/url":async({path:t})=>{if(await u(this.root,t),!this.serving)throw new d(l.unavailable,"This server serves no files over HTTP: only allyhub dev's does.");return{url:this.serving.file(i,t)}},"files/list":({dir:t})=>e.list(t),"files/move":async({from:t,to:a})=>({rewritten:await e.move(t,a,r)}),"files/remove":async({path:t})=>({trash:await e.remove(t,r)}),"files/trash":async()=>({entries:await e.trash()}),"files/restore":async({id:t})=>({path:await e.restore(t,r)}),"files/watch":async({paths:t})=>{for(const n of t)await u(this.root,n);const a=await s.watch(r,t);return this.owners.set(a,r),{watch:a}},"files/unwatch":({watch:t})=>(this.owners.get(t)===r&&(s.unwatch(t),this.owners.delete(t)),Promise.resolve({})),"tools/call":async({name:t,arguments:a})=>{const n=await f(h,o,{jsonrpc:c,id:0,method:t,params:a});if("error"in n)throw new d(n.error.code,n.error.message,n.error.data);return{result:n.result}}}}async tool(r,i,e={}){const s=await f(h,p({live:this.live,checker:this.checker,exports:this.exports,reveal:this.reveal,tree:()=>e.tree??this.serving?.tree(void 0)}),{jsonrpc:c,id:0,method:r,params:i});if("error"in s)throw new d(s.error.code,s.error.message,s.error.data);return h[r].result.parse(s.result)}async close(){this.watcher.close(),this.clients.clear(),await this.checker?.close(),await this.indexer?.close(),await this.exports.close(),await this.live.close()}}
2
+ import{randomBytes as m}from"node:crypto";import{resolve as v}from"node:path";import{FILE_METHODS as y,HOST_METHODS as w,JSONRPC as c,PROJECT_TOOLS as h,RPC_ERROR as l,RequestSchema as g,RpcError as d,answer as f}from"@allyhub-next/protocol";import{Checker as O,Indexer as x}from"./background.js";import{Exports as S}from"./exports.js";import{hasFileManager as k,revealFile as R}from"./reveal.js";import{Live as E}from"./live.js";import{fileIn as u}from"./paths.js";import{toolHandlers as p}from"./tools.js";import{Watcher as T}from"./watcher.js";import{Writer as N}from"./writer.js";export const SERVER_METHODS={...y,"tools/call":w["tools/call"]};export class ProjectServer{root;writer;watcher;live;exports;reveal;checker;serving;indexer;clients=new Map;constructor(r){this.root=v(r.root),this.live=new E(this.root,r.gpu===void 0?{}:{gpu:r.gpu}),this.watcher=new T(this.root,(e,s)=>{this.sendTo(this.watchOwner(e),"files/changed",{watch:e,changes:s})},e=>{this.live.moved(e),this.notify("project/changed",{revision:e}),this.checker?.schedule()}),this.writer=new N(this.root,(e,s)=>{this.watcher.wrote(e,s)},r.writable),this.reveal=r.reveal===!1?void 0:r.reveal??(k()?R:void 0),this.exports=new S(this.root,e=>{this.notify("export/changed",{job:e})});const i=r.index??process.env.ALLYHUB_NO_INDEX!=="1";if(r.check!==!1){const e=i?new x({root:this.root,emit:s=>{this.notify("index/changed",{jobs:s})},made:()=>{this.checker?.schedule(!0)}}):void 0;this.indexer=e,this.checker=new O({live:this.live,emit:s=>{this.notify("check/changed",s)},...e?{needs:s=>{e.want(s)}}:{},...r.settle===void 0?{}:{settle:r.settle}})}}static open(r){const i=new ProjectServer(r);return i.watcher.start(),i.checker?.schedule(),Promise.resolve(i)}owners=new Map;watchOwner(r){return this.owners.get(r)}connect(r,i={}){const e=`c${m(6).toString("hex")}`;this.clients.set(e,r);const s=this.handlersFor(e,i.origin);return{id:e,receive:async o=>{const t=await this.answerOne(s,o);t&&r(t)},close:()=>{this.clients.delete(e),this.watcher.forget(e);for(const[o,t]of this.owners)t===e&&this.owners.delete(o)}}}channel(){const r=new Set,i=this.connect(e=>{const s=JSON.parse(JSON.stringify(e));queueMicrotask(()=>{for(const o of r)o(s)})});return{send:e=>{i.receive(JSON.parse(JSON.stringify(e)))},listen:e=>(r.add(e),()=>r.delete(e)),close:()=>{i.close()}}}notify(r,i){for(const e of this.clients.values())e({jsonrpc:c,method:r,params:i})}sendTo(r,i,e){r!==void 0&&this.clients.get(r)?.({jsonrpc:c,method:i,params:e})}async answerOne(r,i){const e=g.safeParse(i);if(!e.success){const o=i?.id;return typeof o!="string"&&typeof o!="number"?void 0:{jsonrpc:c,id:o,error:{code:l.invalidRequest,message:"Not a JSON-RPC request."}}}const s=e.data;return Object.hasOwn(w,s.method)&&s.method!=="tools/call"?{jsonrpc:c,id:s.id,error:{code:l.methodNotFound,message:`${s.method} is answered by the page that hosts the editor, not by allyhub dev's server.`}}:f(SERVER_METHODS,r,s)}handlersFor(r,i){const{writer:e,watcher:s}=this,o=p({live:this.live,checker:this.checker,exports:this.exports,reveal:this.reveal,tree:()=>this.serving?.tree(i)});return{"files/read":({path:t})=>e.read(t),"files/write":async({path:t,text:a,over:n})=>({version:await e.write(t,a,n,r)}),"files/upload":async({dir:t,name:a,data:n})=>({path:await e.upload(t,a,Buffer.from(n,"base64"),r)}),"files/url":async({path:t})=>{if(await u(this.root,t),!this.serving)throw new d(l.unavailable,"This server serves no files over HTTP: only allyhub dev's does.");return{url:this.serving.file(i,t)}},"files/list":({dir:t})=>e.list(t),"files/move":async({from:t,to:a})=>({rewritten:await e.move(t,a,r)}),"files/remove":async({path:t})=>({trash:await e.remove(t,r)}),"files/trash":async()=>({entries:await e.trash()}),"files/restore":async({id:t})=>({path:await e.restore(t,r)}),"files/watch":async({paths:t})=>{for(const n of t)await u(this.root,n);const a=await s.watch(r,t);return this.owners.set(a,r),{watch:a}},"files/unwatch":({watch:t})=>(this.owners.get(t)===r&&(s.unwatch(t),this.owners.delete(t)),Promise.resolve({})),"tools/call":async({name:t,arguments:a})=>{const n=await f(h,o,{jsonrpc:c,id:0,method:t,params:a});if("error"in n)throw new d(n.error.code,n.error.message,n.error.data);return{result:n.result}}}}async tool(r,i,e={}){const s=await f(h,p({live:this.live,checker:this.checker,exports:this.exports,reveal:this.reveal,tree:()=>e.tree??this.serving?.tree(void 0)}),{jsonrpc:c,id:0,method:r,params:i});if("error"in s)throw new d(s.error.code,s.error.message,s.error.data);return h[r].result.parse(s.result)}async close(){this.watcher.close(),this.clients.clear(),await this.checker?.close(),await this.indexer?.close(),await this.exports.close(),await this.live.close()}}
@@ -1,3 +1,3 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{randomBytes as A}from"node:crypto";import{mkdir as v,readFile as E,readdir as x,rename as b,rm as D,stat as S,writeFile as P}from"node:fs/promises";import{dirname as g,extname as _,join as w,posix as R}from"node:path";import I from"write-file-atomic";import{moveEdits as C,movedPath as F}from"@allyhub-next/build";import{CACHE_DIR as N,TRASH_DIR as d}from"@allyhub-next/formats";import{RPC_ERROR as l,RpcError as f}from"@allyhub-next/protocol";import{current as y,fileIn as p,pathProblem as k,versionOf as j}from"./paths.js";import{writeProblems as O}from"./validate.js";export const UPLOAD_MAX_BYTES=200*1024*1024,LIST_LIMIT=5e3;const L=[N,d];function M(m){const e=(m.split(/[/\\]/u).pop()??"").replace(/\p{Cc}/gu,"").replace(/^\.+/u,"").trim();return e===""?"file":e}export class Writer{root;wrote;queue=Promise.resolve();constructor(t,e=()=>{}){this.root=t,this.wrote=e}serial(t){const e=this.queue.then(t,t);return this.queue=e.catch(()=>{}),e}async read(t){const e=await y(await p(this.root,t));return e?{text:e.bytes.toString("utf8"),version:e.version}:{text:"",version:null}}write(t,e,n,o){return this.serial(async()=>{const i=await p(this.root,t,{write:!0}),s=await y(i),c=s?.version??null;if(c!==n)throw new f(l.conflict,`${t} changed since it was read.`,{text:s?.bytes.toString("utf8")??"",version:c});const a=O(t,e);if(a.length>0)throw new f(l.invalid,`${t} would not be a file AllyHub can read: ${a[0]??""}`,{problems:a});const u=j(e);return u===c||(await v(g(i),{recursive:!0}),await I(i,e),this.wrote([{path:t,version:u}],o)),u})}upload(t,e,n,o){return n.length>UPLOAD_MAX_BYTES?Promise.reject(new f(l.invalid,`${e} is larger than ${String(UPLOAD_MAX_BYTES/1024/1024)} MB.`)):this.serial(async()=>{const i=M(e),s=_(i),c=i.slice(0,i.length-s.length);for(let a=1;;a++){const u=a===1?i:`${c}-${String(a)}${s}`,r=t===""?u:`${t}/${u}`,h=await p(this.root,r,{write:!0});if(!await $(h))return await v(g(h),{recursive:!0}),await P(h,n,{flag:"wx"}),this.wrote([{path:r,version:j(n)}],o),r}})}move(t,e,n){return this.serial(async()=>{const o=await p(this.root,t,{write:!0}),i=await p(this.root,e,{write:!0});if(e===t||e.startsWith(`${t}/`))throw new f(l.invalid,`${t} cannot move into itself.`);if(!await $(o))throw new f(l.notFound,`There is no ${t}.`);if(await $(i))throw new f(l.conflict,`${e} is taken.`,{text:"",version:null});const s=await C(this.root,t,e);for(const r of s)await I(w(this.root,r.path),r.text);const c=await T(t,o);await v(g(i),{recursive:!0}),await b(o,i);const a=c.map(r=>({path:r,version:null}));for(const r of c){const h=F(r,t,e);a.push({path:h,version:(await y(w(this.root,h)))?.version??null})}const u=s.map(r=>F(r.path,t,e));for(const r of s)F(r.path,t,e)===r.path&&a.push({path:r.path,version:j(r.text)});return this.wrote(a,n),u})}remove(t,e){return this.serial(async()=>{const n=await p(this.root,t,{write:!0});if(!await $(n))throw new f(l.notFound,`There is no ${t}.`);const o=await T(t,n),i=`${String(Date.now())}-${A(4).toString("hex")}`,s=w(this.root,d,i,...t.split("/"));await v(g(s),{recursive:!0}),await b(n,s);const c={path:t,removed:Date.now()};return await P(w(this.root,d,`${i}.json`),`${JSON.stringify(c)}
3
- `),this.wrote(o.map(a=>({path:a,version:null})),e),i})}async trash(){const t=w(this.root,d),e=await x(t).catch(()=>[]),n=[];for(const o of e){if(!o.endsWith(".json"))continue;const i=await E(w(t,o),"utf8").then(s=>JSON.parse(s)).catch(()=>{});typeof i?.path!="string"||typeof i.removed!="number"||k(i.path)!==void 0||n.push({id:o.slice(0,-5),path:i.path,removed:i.removed})}return n.sort((o,i)=>i.removed-o.removed)}restore(t,e){return this.serial(async()=>{const n=(await this.trash()).find(a=>a.id===t);if(!n)throw new f(l.notFound,`Nothing in the bin is ${t}.`);const o=await p(this.root,n.path,{write:!0});if(await $(o))throw new f(l.conflict,`${n.path} is there again: move it before putting this back.`,{text:"",version:null});const i=w(this.root,d,t,...n.path.split("/"));await v(g(o),{recursive:!0}),await b(i,o),await D(w(this.root,d,t),{recursive:!0,force:!0}),await D(w(this.root,d,`${t}.json`),{force:!0});const s=await T(n.path,o),c=[];for(const a of s)c.push({path:a,version:(await y(w(this.root,a)))?.version??null});return this.wrote(c,e),n.path})}async list(t){const e=t===""?this.root:await p(this.root,t),n=[],o=[],i=async(s,c)=>{const a=await x(s,{withFileTypes:!0}).catch(()=>[]);for(const u of a.sort((r,h)=>r.name.localeCompare(h.name))){if(n.length>=LIST_LIMIT)return;const r=c===""?u.name:`${c}/${u.name}`;if(k(r)===void 0&&!(L.some(h=>r===h)&&!(t===r||t.startsWith(`${r}/`)))){if(u.isDirectory())o.push(r),await i(w(s,u.name),r);else if(u.isFile()){const h=await S(w(s,u.name)).catch(()=>{});h&&n.push({path:r,size:h.size,modified:h.mtimeMs})}}}};return await i(e,t),{files:n,dirs:o}}}const $=m=>S(m).then(()=>!0,()=>!1);async function T(m,t){const e=await S(t).catch(()=>{});if(!e)return[];if(!e.isDirectory())return[m];const n=[],o=await x(t,{recursive:!0,withFileTypes:!0});for(const i of o)i.isFile()&&n.push(R.join(m,R.relative(t.split("\\").join("/"),w(i.parentPath,i.name).split("\\").join("/"))));return n}
2
+ import{randomBytes as A}from"node:crypto";import{mkdir as v,readFile as E,readdir as b,rename as x,rm as T,stat as S,writeFile as k}from"node:fs/promises";import{dirname as g,extname as _,join as w,posix as D}from"node:path";import R from"write-file-atomic";import{moveEdits as C,movedPath as F}from"@allyhub-next/build";import{CACHE_DIR as N,TRASH_DIR as d}from"@allyhub-next/formats";import{RPC_ERROR as l,RpcError as f}from"@allyhub-next/protocol";import{current as y,fileIn as p,pathProblem as I,versionOf as j}from"./paths.js";import{writeProblems as O}from"./validate.js";export const UPLOAD_MAX_BYTES=200*1024*1024,LIST_LIMIT=5e3;const L=[N,d];function M(m){const e=(m.split(/[/\\]/u).pop()??"").replace(/\p{Cc}/gu,"").replace(/^\.+/u,"").trim();return e===""?"file":e}export class Writer{root;wrote;writable;queue=Promise.resolve();constructor(t,e=()=>{},i=()=>Promise.resolve()){this.root=t,this.wrote=e,this.writable=i}serial(t){const e=async()=>(await this.writable(),t()),i=this.queue.then(e,e);return this.queue=i.catch(()=>{}),i}async read(t){const e=await y(await p(this.root,t));return e?{text:e.bytes.toString("utf8"),version:e.version}:{text:"",version:null}}write(t,e,i,o){return this.serial(async()=>{const r=await p(this.root,t,{write:!0}),s=await y(r),c=s?.version??null;if(c!==i)throw new f(l.conflict,`${t} changed since it was read.`,{text:s?.bytes.toString("utf8")??"",version:c});const a=O(t,e);if(a.length>0)throw new f(l.invalid,`${t} would not be a file AllyHub can read: ${a[0]??""}`,{problems:a});const u=j(e);return u===c||(await v(g(r),{recursive:!0}),await R(r,e),this.wrote([{path:t,version:u}],o)),u})}upload(t,e,i,o){return i.length>UPLOAD_MAX_BYTES?Promise.reject(new f(l.invalid,`${e} is larger than ${String(UPLOAD_MAX_BYTES/1024/1024)} MB.`)):this.serial(async()=>{const r=M(e),s=_(r),c=r.slice(0,r.length-s.length);for(let a=1;;a++){const u=a===1?r:`${c}-${String(a)}${s}`,n=t===""?u:`${t}/${u}`,h=await p(this.root,n,{write:!0});if(!await $(h))return await v(g(h),{recursive:!0}),await k(h,i,{flag:"wx"}),this.wrote([{path:n,version:j(i)}],o),n}})}move(t,e,i){return this.serial(async()=>{const o=await p(this.root,t,{write:!0}),r=await p(this.root,e,{write:!0});if(e===t||e.startsWith(`${t}/`))throw new f(l.invalid,`${t} cannot move into itself.`);if(!await $(o))throw new f(l.notFound,`There is no ${t}.`);if(await $(r))throw new f(l.conflict,`${e} is taken.`,{text:"",version:null});const s=await C(this.root,t,e);for(const n of s)await R(w(this.root,n.path),n.text);const c=await P(t,o);await v(g(r),{recursive:!0}),await x(o,r);const a=c.map(n=>({path:n,version:null}));for(const n of c){const h=F(n,t,e);a.push({path:h,version:(await y(w(this.root,h)))?.version??null})}const u=s.map(n=>F(n.path,t,e));for(const n of s)F(n.path,t,e)===n.path&&a.push({path:n.path,version:j(n.text)});return this.wrote(a,i),u})}remove(t,e){return this.serial(async()=>{const i=await p(this.root,t,{write:!0});if(!await $(i))throw new f(l.notFound,`There is no ${t}.`);const o=await P(t,i),r=`${String(Date.now())}-${A(4).toString("hex")}`,s=w(this.root,d,r,...t.split("/"));await v(g(s),{recursive:!0}),await x(i,s);const c={path:t,removed:Date.now()};return await k(w(this.root,d,`${r}.json`),`${JSON.stringify(c)}
3
+ `),this.wrote(o.map(a=>({path:a,version:null})),e),r})}async trash(){const t=w(this.root,d),e=await b(t).catch(()=>[]),i=[];for(const o of e){if(!o.endsWith(".json"))continue;const r=await E(w(t,o),"utf8").then(s=>JSON.parse(s)).catch(()=>{});typeof r?.path!="string"||typeof r.removed!="number"||I(r.path)!==void 0||i.push({id:o.slice(0,-5),path:r.path,removed:r.removed})}return i.sort((o,r)=>r.removed-o.removed)}restore(t,e){return this.serial(async()=>{const i=(await this.trash()).find(a=>a.id===t);if(!i)throw new f(l.notFound,`Nothing in the bin is ${t}.`);const o=await p(this.root,i.path,{write:!0});if(await $(o))throw new f(l.conflict,`${i.path} is there again: move it before putting this back.`,{text:"",version:null});const r=w(this.root,d,t,...i.path.split("/"));await v(g(o),{recursive:!0}),await x(r,o),await T(w(this.root,d,t),{recursive:!0,force:!0}),await T(w(this.root,d,`${t}.json`),{force:!0});const s=await P(i.path,o),c=[];for(const a of s)c.push({path:a,version:(await y(w(this.root,a)))?.version??null});return this.wrote(c,e),i.path})}async list(t){const e=t===""?this.root:await p(this.root,t),i=[],o=[],r=async(s,c)=>{const a=await b(s,{withFileTypes:!0}).catch(()=>[]);for(const u of a.sort((n,h)=>n.name.localeCompare(h.name))){if(i.length>=LIST_LIMIT)return;const n=c===""?u.name:`${c}/${u.name}`;if(I(n)===void 0&&!(L.some(h=>n===h)&&!(t===n||t.startsWith(`${n}/`)))){if(u.isDirectory())o.push(n),await r(w(s,u.name),n);else if(u.isFile()){const h=await S(w(s,u.name)).catch(()=>{});h&&i.push({path:n,size:h.size,modified:h.mtimeMs})}}}};return await r(e,t),{files:i,dirs:o}}}const $=m=>S(m).then(()=>!0,()=>!1);async function P(m,t){const e=await S(t).catch(()=>{});if(!e)return[];if(!e.isDirectory())return[m];const i=[],o=await b(t,{recursive:!0,withFileTypes:!0});for(const r of o)r.isFile()&&i.push(D.join(m,D.relative(t.split("\\").join("/"),w(r.parentPath,r.name).split("\\").join("/"))));return i}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/dev-server",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/doc",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/export",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/formats",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/measure",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/media",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/model",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/ooxml",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/project",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -374,4 +374,23 @@ export declare const SNAPSHOT_VIEW: {
374
374
  * it.
375
375
  */
376
376
  export declare const NEEDS_MACHINE = -32010;
377
+ /**
378
+ * The JSON-RPC error a Host answers for a change to a project another
379
+ * executor works on (an editor's save, an agent's turn, an upload): one
380
+ * place works on a project at a time, every other reads it; the window
381
+ * offers the move.
382
+ */
383
+ export declare const ELSEWHERE = -32011;
384
+ /**
385
+ * The JSON-RPC error a move answers when the folder it would replace has
386
+ * changes made here since its base (the snapshot it last recorded or was
387
+ * made from) that the latest snapshot lacks: nothing moved; the data names
388
+ * them (MoveChangesSchema), and the move asked again with `replace`
389
+ * replaces them.
390
+ */
391
+ export declare const CHANGED_HERE = -32012;
392
+ export declare const MoveChangesSchema: z.ZodObject<{
393
+ changed: z.ZodArray<z.ZodString>;
394
+ }, z.core.$strip>;
395
+ export type MoveChanges = z.infer<typeof MoveChangesSchema>;
377
396
  //# sourceMappingURL=snapshots.d.ts.map
@@ -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 e}from"zod";import{BlobSchema as f,Sha256Schema as l,TreeFileSchema as h,TreePathSchema as p}from"./blobs.js";import{placeParams as x}from"./editor.js";import{openEnum as S,readable as b}from"./open.js";export const SNAPSHOT_FORMAT=1,ConversationEntrySchema=e.object({sessionId:e.string().min(1),agent:e.string().min(1),title:e.string().optional(),createdAt:e.iso.datetime(),updatedAt:e.iso.datetime(),transcript:l}),TranscriptSchema=e.object({sessionId:e.string().min(1),messages:e.array(e.object({method:e.string().min(1),params:e.unknown()}))}),SNAPSHOT_KINDS=["project","home"];const $=e.string().refine(t=>t.endsWith("/")&&p.safeParse(t.slice(0,-1)).success,"a folder of the tree, ending in /");export const ItemReachSchema=e.object({tree:e.array(p),files:e.array(p),own:e.record(p,h)}),SnapshotReachSchema=e.object({shared:e.array($),items:e.record(e.string(),ItemReachSchema)}),SnapshotManifestSchema=e.object({format:e.literal(SNAPSHOT_FORMAT),kind:e.enum(SNAPSHOT_KINDS),project:e.object({name:e.string().min(1).max(255)}),host:e.object({version:e.string().min(1)}),madeAt:e.iso.datetime(),files:e.record(p,f),tree:e.record(p,h),conversations:e.array(ConversationEntrySchema),reach:SnapshotReachSchema.optional()});export function scopedManifest(t,s){const o=t.reach,c=o!==void 0&&Object.hasOwn(o.items,s)?o.items[s]:void 0;if(o===void 0||c===void 0)return;const r={};for(const[n,i]of Object.entries(t.tree))o.shared.some(m=>n.startsWith(m))&&(r[n]=i);for(const n of c.tree){const i=Object.hasOwn(t.tree,n)?t.tree[n]:void 0;i!==void 0&&(r[n]=i)}Object.assign(r,c.own);const a={};for(const n of c.files){const i=Object.hasOwn(t.files,n)?t.files[n]:void 0;i!==void 0&&(a[n]=i)}return{format:t.format,kind:t.kind,project:t.project,host:t.host,madeAt:t.madeAt,files:a,tree:r,conversations:[]}}export const SNAPSHOT_SKIPPED=["node_modules",".allyhub/cache",".allyhub/out"],SNAPSHOT_TREE={editor:"editor/",runtime:"runtime/",build:"build/",media:"media/",matte:"matte/",scenery:"scenery/",bakes:"bakes/",stills:"stills/",answers:"answers/",previews:"previews/"};function d(t){return Array.isArray(t)?t.map(d):t!==null&&typeof t=="object"?Object.fromEntries(Object.keys(t).sort().map(s=>[s,d(t[s])])):t}export const snapshotAnswerPath=(t,s)=>`${SNAPSHOT_TREE.answers}${t}/${encodeURIComponent(JSON.stringify(d(s??{})))}.json`,stillPath=t=>`${SNAPSHOT_TREE.stills}${t}.webp`,stillsIndexPath=(t,s)=>`${SNAPSHOT_TREE.stills}index/${encodeURIComponent(t)}/${encodeURIComponent(s)}.json`,StillsIndexSchema=e.object({stills:e.record(e.string(),e.string().regex(/^[0-9a-f]{64}$/u))}),stillsIndexKey=t=>JSON.stringify(d(t)),snapshotPreviewPath=t=>`${SNAPSHOT_TREE.previews}${encodeURIComponent(t===""?".":t)}.json`,TREE_PAGES={stage:`${SNAPSHOT_TREE.runtime}stage.html`,viewer:`${SNAPSHOT_TREE.runtime}viewer.html`,probe:`${SNAPSHOT_TREE.runtime}probe.html`},pieceSourcePath=t=>`${SNAPSHOT_TREE.build}sources/${encodeURIComponent(t)}.json`,scenerySourcePath=t=>`${SNAPSHOT_TREE.scenery}sources/${encodeURIComponent(t)}.json`,treeHref=t=>t.split("/").map(encodeURIComponent).join("/"),SOUND_FOLDER="sound/";export function stageCsp(t,s){const o=u=>`${t}${u}`,c=o(SNAPSHOT_TREE.runtime),r=o(SNAPSHOT_TREE.build),a=o(SNAPSHOT_TREE.scenery),n=o(SNAPSHOT_TREE.media),i=o(SNAPSHOT_TREE.matte),m=o(SOUND_FOLDER);return["default-src 'none'",`script-src 'unsafe-eval' 'wasm-unsafe-eval' ${c} ${r} ${a}`,"worker-src blob:",`style-src 'unsafe-inline' ${c} ${r} ${a}`,`font-src ${c} ${r} ${n} data:`,`img-src ${n} ${r} ${a} data: blob:`,`media-src ${n} ${i} ${m} blob:`,`connect-src ${n} ${i} ${m} ${r} ${a} ${o(SNAPSHOT_TREE.bakes)} ${c} blob: data:`,`frame-ancestors ${s.length>0?s.join(" "):"'none'"}`].join("; ")}export function snapshotEditorUrl(t,s,o={}){const c=Object.entries({item:s,embed:"studio",snapshot:"1",...o.viewer===!0?{viewer:"1"}:{},...o.theme===void 0?{}:{theme:o.theme},...x(o.place)}).map(([r,a])=>`${r}=${encodeURIComponent(a)}`);return`${t.replace(/\/$/u,"")}/${SNAPSHOT_TREE.editor}?${c.join("&")}`}export const EXECUTOR_KINDS=["cloud","computer"],COMPUTER_HEADER="x-allyhub-computer",ComputerIdSchema=e.string().regex(/^[A-Za-z0-9_-]{16,64}$/u),WORKED_ELSEWHERE="worked_elsewhere",ExecutorSchema=e.object({kind:S(EXECUTOR_KINDS),here:e.boolean()}),SNAPSHOT_BLOBS_PATH="/api/snapshots/blobs",SNAPSHOTS_PATH="/api/snapshots",SnapshotCreateSchema=e.strictObject({project:e.strictObject({id:e.string().min(1).optional(),kind:e.enum(SNAPSHOT_KINDS),name:e.string().min(1).max(255)}),manifest:l}),SnapshotCreatedSchema=e.object({id:e.string().min(1),projectId:e.string().min(1)}),SnapshotMissingSchema=e.object({missing:e.array(l)}),PROJECTS_PATH="/api/projects",SnapshotInfoSchema=e.object({id:e.string().min(1),madeAt:e.iso.datetime(),hostVersion:e.string()}),ProjectInfoSchema=e.object({id:e.string().min(1),kind:e.enum(SNAPSHOT_KINDS),name:e.string(),latest:SnapshotInfoSchema.nullable(),executor:ExecutorSchema}),ProjectsResponseSchema=e.object({projects:b(ProjectInfoSchema)}),projectPath=t=>`${PROJECTS_PATH}/${encodeURIComponent(t)}`,projectExecutorPath=t=>`${projectPath(t)}/executor`,ExecutorMoveSchema=e.strictObject({kind:e.enum(EXECUTOR_KINDS)}),snapshotViewPath=t=>`${SNAPSHOTS_PATH}/${encodeURIComponent(t)}/view`,SnapshotViewSchema=e.object({origin:e.url(),expiresAt:e.iso.datetime()}),SNAPSHOT_VIEW={manifest:"manifest.json",files:"files/",conversations:"conversations/"},NEEDS_MACHINE=-32010;
2
+ import{z as e}from"zod";import{BlobSchema as f,Sha256Schema as h,TreeFileSchema as l,TreePathSchema as p}from"./blobs.js";import{placeParams as x}from"./editor.js";import{openEnum as S,readable as b}from"./open.js";export const SNAPSHOT_FORMAT=1,ConversationEntrySchema=e.object({sessionId:e.string().min(1),agent:e.string().min(1),title:e.string().optional(),createdAt:e.iso.datetime(),updatedAt:e.iso.datetime(),transcript:h}),TranscriptSchema=e.object({sessionId:e.string().min(1),messages:e.array(e.object({method:e.string().min(1),params:e.unknown()}))}),SNAPSHOT_KINDS=["project","home"];const $=e.string().refine(t=>t.endsWith("/")&&p.safeParse(t.slice(0,-1)).success,"a folder of the tree, ending in /");export const ItemReachSchema=e.object({tree:e.array(p),files:e.array(p),own:e.record(p,l)}),SnapshotReachSchema=e.object({shared:e.array($),items:e.record(e.string(),ItemReachSchema)}),SnapshotManifestSchema=e.object({format:e.literal(SNAPSHOT_FORMAT),kind:e.enum(SNAPSHOT_KINDS),project:e.object({name:e.string().min(1).max(255)}),host:e.object({version:e.string().min(1)}),madeAt:e.iso.datetime(),files:e.record(p,f),tree:e.record(p,l),conversations:e.array(ConversationEntrySchema),reach:SnapshotReachSchema.optional()});export function scopedManifest(t,s){const o=t.reach,c=o!==void 0&&Object.hasOwn(o.items,s)?o.items[s]:void 0;if(o===void 0||c===void 0)return;const r={};for(const[n,i]of Object.entries(t.tree))o.shared.some(m=>n.startsWith(m))&&(r[n]=i);for(const n of c.tree){const i=Object.hasOwn(t.tree,n)?t.tree[n]:void 0;i!==void 0&&(r[n]=i)}Object.assign(r,c.own);const a={};for(const n of c.files){const i=Object.hasOwn(t.files,n)?t.files[n]:void 0;i!==void 0&&(a[n]=i)}return{format:t.format,kind:t.kind,project:t.project,host:t.host,madeAt:t.madeAt,files:a,tree:r,conversations:[]}}export const SNAPSHOT_SKIPPED=["node_modules",".allyhub/cache",".allyhub/out"],SNAPSHOT_TREE={editor:"editor/",runtime:"runtime/",build:"build/",media:"media/",matte:"matte/",scenery:"scenery/",bakes:"bakes/",stills:"stills/",answers:"answers/",previews:"previews/"};function d(t){return Array.isArray(t)?t.map(d):t!==null&&typeof t=="object"?Object.fromEntries(Object.keys(t).sort().map(s=>[s,d(t[s])])):t}export const snapshotAnswerPath=(t,s)=>`${SNAPSHOT_TREE.answers}${t}/${encodeURIComponent(JSON.stringify(d(s??{})))}.json`,stillPath=t=>`${SNAPSHOT_TREE.stills}${t}.webp`,stillsIndexPath=(t,s)=>`${SNAPSHOT_TREE.stills}index/${encodeURIComponent(t)}/${encodeURIComponent(s)}.json`,StillsIndexSchema=e.object({stills:e.record(e.string(),e.string().regex(/^[0-9a-f]{64}$/u))}),stillsIndexKey=t=>JSON.stringify(d(t)),snapshotPreviewPath=t=>`${SNAPSHOT_TREE.previews}${encodeURIComponent(t===""?".":t)}.json`,TREE_PAGES={stage:`${SNAPSHOT_TREE.runtime}stage.html`,viewer:`${SNAPSHOT_TREE.runtime}viewer.html`,probe:`${SNAPSHOT_TREE.runtime}probe.html`},pieceSourcePath=t=>`${SNAPSHOT_TREE.build}sources/${encodeURIComponent(t)}.json`,scenerySourcePath=t=>`${SNAPSHOT_TREE.scenery}sources/${encodeURIComponent(t)}.json`,treeHref=t=>t.split("/").map(encodeURIComponent).join("/"),SOUND_FOLDER="sound/";export function stageCsp(t,s){const o=u=>`${t}${u}`,c=o(SNAPSHOT_TREE.runtime),r=o(SNAPSHOT_TREE.build),a=o(SNAPSHOT_TREE.scenery),n=o(SNAPSHOT_TREE.media),i=o(SNAPSHOT_TREE.matte),m=o(SOUND_FOLDER);return["default-src 'none'",`script-src 'unsafe-eval' 'wasm-unsafe-eval' ${c} ${r} ${a}`,"worker-src blob:",`style-src 'unsafe-inline' ${c} ${r} ${a}`,`font-src ${c} ${r} ${n} data:`,`img-src ${n} ${r} ${a} data: blob:`,`media-src ${n} ${i} ${m} blob:`,`connect-src ${n} ${i} ${m} ${r} ${a} ${o(SNAPSHOT_TREE.bakes)} ${c} blob: data:`,`frame-ancestors ${s.length>0?s.join(" "):"'none'"}`].join("; ")}export function snapshotEditorUrl(t,s,o={}){const c=Object.entries({item:s,embed:"studio",snapshot:"1",...o.viewer===!0?{viewer:"1"}:{},...o.theme===void 0?{}:{theme:o.theme},...x(o.place)}).map(([r,a])=>`${r}=${encodeURIComponent(a)}`);return`${t.replace(/\/$/u,"")}/${SNAPSHOT_TREE.editor}?${c.join("&")}`}export const EXECUTOR_KINDS=["cloud","computer"],COMPUTER_HEADER="x-allyhub-computer",ComputerIdSchema=e.string().regex(/^[A-Za-z0-9_-]{16,64}$/u),WORKED_ELSEWHERE="worked_elsewhere",ExecutorSchema=e.object({kind:S(EXECUTOR_KINDS),here:e.boolean()}),SNAPSHOT_BLOBS_PATH="/api/snapshots/blobs",SNAPSHOTS_PATH="/api/snapshots",SnapshotCreateSchema=e.strictObject({project:e.strictObject({id:e.string().min(1).optional(),kind:e.enum(SNAPSHOT_KINDS),name:e.string().min(1).max(255)}),manifest:h}),SnapshotCreatedSchema=e.object({id:e.string().min(1),projectId:e.string().min(1)}),SnapshotMissingSchema=e.object({missing:e.array(h)}),PROJECTS_PATH="/api/projects",SnapshotInfoSchema=e.object({id:e.string().min(1),madeAt:e.iso.datetime(),hostVersion:e.string()}),ProjectInfoSchema=e.object({id:e.string().min(1),kind:e.enum(SNAPSHOT_KINDS),name:e.string(),latest:SnapshotInfoSchema.nullable(),executor:ExecutorSchema}),ProjectsResponseSchema=e.object({projects:b(ProjectInfoSchema)}),projectPath=t=>`${PROJECTS_PATH}/${encodeURIComponent(t)}`,projectExecutorPath=t=>`${projectPath(t)}/executor`,ExecutorMoveSchema=e.strictObject({kind:e.enum(EXECUTOR_KINDS)}),snapshotViewPath=t=>`${SNAPSHOTS_PATH}/${encodeURIComponent(t)}/view`,SnapshotViewSchema=e.object({origin:e.url(),expiresAt:e.iso.datetime()}),SNAPSHOT_VIEW={manifest:"manifest.json",files:"files/",conversations:"conversations/"},NEEDS_MACHINE=-32010,ELSEWHERE=-32011,CHANGED_HERE=-32012,MoveChangesSchema=e.object({changed:e.array(e.string())});
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/protocol",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/registry",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/render",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/runtime",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,9 +1,27 @@
1
- export interface DownloadOptions {
1
+ import { RuntimeError } from "./errors.js";
2
+ /** How long a download may go without a byte before that try is given up (ms). */
3
+ export declare const DOWNLOAD_STALL_MS = 30000;
4
+ /** How many times a download that failed on the way is tried again. */
5
+ export declare const DOWNLOAD_RETRIES = 3;
6
+ /** The wait before the first try again, doubled before each next one (ms). */
7
+ export declare const DOWNLOAD_RETRY_DELAY_MS = 1000;
8
+ /** How a download waits and tries again; each default is its constant above. */
9
+ export interface DownloadTimings {
10
+ /** No byte for this long gives up a try (default: DOWNLOAD_STALL_MS). */
11
+ stallMs?: number | undefined;
12
+ /** Tries again after the first (default: DOWNLOAD_RETRIES). */
13
+ retries?: number | undefined;
14
+ /** The first wait before trying again (default: DOWNLOAD_RETRY_DELAY_MS). */
15
+ retryDelayMs?: number | undefined;
16
+ }
17
+ export interface DownloadOptions extends DownloadTimings {
2
18
  size: number;
3
19
  sha256: string;
4
20
  fetch: typeof globalThis.fetch;
5
21
  signal?: AbortSignal | undefined;
6
22
  onProgress?: ((received: number, total: number) => void) | undefined;
23
+ /** Before each try again: what went wrong, the try that failed and how many there are. */
24
+ onRetry?: ((error: RuntimeError, attempt: number, attempts: number) => void) | undefined;
7
25
  }
8
26
  /**
9
27
  * Downloads `url` to `dest`, hashing as it comes; a file of another size or
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{createHash as m}from"node:crypto";import{createWriteStream as u}from"node:fs";import{rm as g}from"node:fs/promises";import{Readable as y,Transform as S}from"node:stream";import{pipeline as p}from"node:stream/promises";import{RuntimeError as o}from"./errors.js";export async function download(e,r,n){const{size:i,sha256:w,signal:a,onProgress:$}=n;let s;try{s=await n.fetch(e,{redirect:"follow",...a?{signal:a}:{}})}catch(t){throw a?.aborted?new o("cancelled",`Stopped downloading ${e}.`,{cause:t}):new o("download",`Could not reach ${e}: ${l(t)}`,{cause:t})}if(!s.ok||!s.body)throw new o("download",`${e} answered ${String(s.status)}.`);const d=m("sha256");let c=0;const b=new S({transform(t,x,h){if(c+=t.length,c>i){h(new o("checksum",`${e} is larger than its pin (${String(i)} bytes).`));return}d.update(t),$?.(c,i),h(null,t)}});try{await p(y.fromWeb(s.body),b,u(r),...a?[{signal:a}]:[])}catch(t){throw await g(r,{force:!0}),t instanceof o?t:a?.aborted?new o("cancelled",`Stopped downloading ${e}.`,{cause:t}):new o("download",`Downloading ${e} failed: ${l(t)}`,{cause:t})}const f=d.digest("hex");if(c!==i||f!==w)throw await g(r,{force:!0}),new o("checksum",`${e} came as ${String(c)} bytes with sha256 ${f}; its pin is ${String(i)} bytes, ${w}.`)}export async function sha256Of(e){const{createReadStream:r}=await import("node:fs"),n=m("sha256");return await p(r(e),n),n.digest("hex")}const l=e=>e instanceof Error?e.message:String(e);
2
+ import{createHash as T}from"node:crypto";import{createReadStream as _,createWriteStream as z}from"node:fs";import{rm as f,stat as k}from"node:fs/promises";import{Readable as C,Transform as Y}from"node:stream";import{pipeline as x}from"node:stream/promises";import{setTimeout as H}from"node:timers/promises";import{RuntimeError as i}from"./errors.js";export const DOWNLOAD_STALL_MS=3e4,DOWNLOAD_RETRIES=3,DOWNLOAD_RETRY_DELAY_MS=1e3;const I=e=>e===408||e===425||e===429||e>=500,p=(e,r)=>new i("cancelled",`Stopped downloading ${e}.`,{cause:r}),P=async e=>k(e).then(r=>r.size,()=>0),j=e=>{const r=/^bytes (\d+)-/u.exec(e??"")?.[1];return r===void 0?void 0:Number(r)};export async function download(e,r,$){const{size:s,sha256:b,signal:c,onProgress:A,onRetry:W,stallMs:S=DOWNLOAD_STALL_MS,retries:E=DOWNLOAD_RETRIES,retryDelayMs:L=DOWNLOAD_RETRY_DELAY_MS}=$,g=E+1;let h;const v=async()=>{const o=await P(r);if(o>=s)return{received:o,digest:await sha256Of(r)};const a=new AbortController,d=c?AbortSignal.any([c,a.signal]):a.signal;let m;const u=()=>{clearTimeout(m),m=setTimeout(()=>{a.abort()},S)},D=()=>new i("download",`${e} sent nothing for ${String(S/1e3)} s.`);u();try{const M=o>0?{range:`bytes=${String(o)}-`,...h?{"if-range":h}:{}}:{};let n;try{n=await $.fetch(e,{redirect:"follow",headers:M,signal:d})}catch(t){if(c?.aborted)throw p(e,t);return a.signal.aborted?D():new i("download",`Could not reach ${e}: ${R(t)}`,{cause:t})}if(!n.ok||!n.body){await n.body?.cancel().catch(()=>{});const t=new i("download",`${e} answered ${String(n.status)}.`);if(I(n.status))return t;throw t}const l=n.status===206&&j(n.headers.get("content-range"))===o;if(n.status===206&&!l)return await n.body.cancel().catch(()=>{}),await f(r,{force:!0}),new i("download",`${e} answered another range than bytes=${String(o)}-.`);if(!l){const t=n.headers.get("etag");h=t&&!t.startsWith("W/")?t:n.headers.get("last-modified")??void 0}const y=T("sha256");let w=0;if(l)for await(const t of _(r))y.update(t),w+=t.length;const N=new Y({transform(t,q,O){if(u(),w+=t.length,w>s){O(new i("checksum",`${e} is larger than its pin (${String(s)} bytes).`));return}y.update(t),A?.(w,s),O(null,t)}});u();try{await x(C.fromWeb(n.body),N,z(r,{flags:l?"a":"w"}),{signal:d})}catch(t){if(t instanceof i)throw t;if(c?.aborted)throw p(e,t);return a.signal.aborted?D():new i("download",`Downloading ${e} failed: ${R(t)}`,{cause:t})}return{received:w,digest:y.digest("hex")}}finally{clearTimeout(m)}};await f(r,{force:!0});for(let o=1;;o++){let a;try{a=await v()}catch(d){throw await f(r,{force:!0}),d}if(!(a instanceof i)){if(a.received!==s||a.digest!==b)throw await f(r,{force:!0}),new i("checksum",`${e} came as ${String(a.received)} bytes with sha256 ${a.digest}; its pin is ${String(s)} bytes, ${b}.`);return}if(o>=g)throw await f(r,{force:!0}),new i("download",g>1?`${a.message} Tried ${String(g)} times.`:a.message,{cause:a});W?.(a,o,g);try{await H(L*2**(o-1),void 0,c?{signal:c}:{})}catch(d){throw await f(r,{force:!0}),p(e,d)}}}export async function sha256Of(e){const r=T("sha256");return await x(_(e),r),r.digest("hex")}const R=e=>e instanceof Error?e.message:String(e);
@@ -1,10 +1,10 @@
1
1
  export { binVariable, createRuntimes, type EnsureOptions, type Located, type Progress, type Runtimes, type RuntimesOptions, type Status, } from "./runtimes.js";
2
- export { createPackages, npmCi, type EnsurePackageOptions, type LoadOptions, type Install, type LocatedPackage, type Packages, type PackagesOptions, } from "./packages.js";
2
+ export { createPackages, npmCi, NPM_FETCH_TIMEOUT_MS, NPM_STALL_MS, type EnsurePackageOptions, type InstallOptions, type LoadOptions, type Install, type LocatedPackage, type Packages, type PackagesOptions, } from "./packages.js";
3
3
  export { bundledEnv, type BundledOptions } from "./bundled.js";
4
4
  export { RuntimeError, type RuntimeErrorCode } from "./errors.js";
5
5
  export { cacheHome } from "./home.js";
6
6
  export { shippedManifest } from "./manifest.js";
7
- export { download, sha256Of } from "./download.js";
7
+ export { download, sha256Of, DOWNLOAD_RETRIES, DOWNLOAD_RETRY_DELAY_MS, DOWNLOAD_STALL_MS, type DownloadOptions, type DownloadTimings, } from "./download.js";
8
8
  export { withLock } from "./lock.js";
9
9
  export { unzip, untgz } from "./unpack.js";
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- export{binVariable,createRuntimes}from"./runtimes.js";export{createPackages,npmCi}from"./packages.js";export{bundledEnv}from"./bundled.js";export{RuntimeError}from"./errors.js";export{cacheHome}from"./home.js";export{shippedManifest}from"./manifest.js";export{download,sha256Of}from"./download.js";export{withLock}from"./lock.js";export{unzip,untgz}from"./unpack.js";
2
+ export{binVariable,createRuntimes}from"./runtimes.js";export{createPackages,npmCi,NPM_FETCH_TIMEOUT_MS,NPM_STALL_MS}from"./packages.js";export{bundledEnv}from"./bundled.js";export{RuntimeError}from"./errors.js";export{cacheHome}from"./home.js";export{shippedManifest}from"./manifest.js";export{download,sha256Of,DOWNLOAD_RETRIES,DOWNLOAD_RETRY_DELAY_MS,DOWNLOAD_STALL_MS}from"./download.js";export{withLock}from"./lock.js";export{unzip,untgz}from"./unpack.js";
@@ -5,4 +5,10 @@ export interface LockOptions {
5
5
  onWait?: () => void;
6
6
  }
7
7
  export declare function withLock<T>(path: string, run: () => Promise<T>, options?: LockOptions): Promise<T>;
8
+ /** A new staging folder's path for `dir`, owned by this process. */
9
+ export declare const partialOf: (dir: string) => string;
10
+ /** Whether a process of this computer runs by that pid (one of another user's counts). */
11
+ export declare function running(pid: number): boolean;
12
+ /** Removes the staging folders in `root` of any version of `name` whose process has ended. */
13
+ export declare function sweepPartials(root: string, name: string): Promise<void>;
8
14
  //# sourceMappingURL=lock.d.ts.map
@@ -1,2 +1,2 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{setTimeout as c}from"node:timers/promises";import{lock as m}from"proper-lockfile";export async function withLock(r,l,f={}){const{signal:o,pollMs:n=150,onWait:s}=f;let e,a,i=!1;for(;;){o?.throwIfAborted();try{a=await m(r,{lockfilePath:r,realpath:!1,onCompromised:t=>{e=t}});break}catch(t){if(t.code!=="ELOCKED")throw t;i||s?.(),i=!0,await c(n,void 0,o?{signal:o}:{})}}try{const t=await l();if(e)throw e;return t}finally{e||await a()}}
2
+ import{randomBytes as l}from"node:crypto";import{readdir as p,rm as u}from"node:fs/promises";import{join as m}from"node:path";import{setTimeout as d}from"node:timers/promises";import{lock as w}from"proper-lockfile";export async function withLock(r,o,i={}){const{signal:t,pollMs:a=150,onWait:f}=i;let n,c,s=!1;for(;;){t?.throwIfAborted();try{c=await w(r,{lockfilePath:r,realpath:!1,onCompromised:e=>{n=e}});break}catch(e){if(e.code!=="ELOCKED")throw e;s||f?.(),s=!0,await d(a,void 0,t?{signal:t}:{})}}try{const e=await o();if(n)throw n;return e}finally{n||await c()}}const h=/\.partial-(\d+)-[0-9a-f]+$/u;export const partialOf=r=>`${r}.partial-${String(process.pid)}-${l(4).toString("hex")}`;export function running(r){try{return process.kill(r,0),!0}catch(o){return o.code==="EPERM"}}export async function sweepPartials(r,o){let i;try{i=await p(r)}catch{return}for(const t of i){if(!t.startsWith(`${o}@`))continue;const a=h.exec(t)?.[1];a!==void 0&&!running(Number(a))&&await u(m(r,t),{recursive:!0,force:!0})}}
@@ -6,11 +6,28 @@ export interface PackagesOptions {
6
6
  env?: Readonly<Record<string, string | undefined>>;
7
7
  /** What fills a folder from its package-lock.json (default: `npm ci`). */
8
8
  install?: Install;
9
+ /** How long npm waits on a quiet connection (default: NPM_FETCH_TIMEOUT_MS). */
10
+ fetchTimeoutMs?: number | undefined;
11
+ /** How long an install may go without progress before it is stopped (default: NPM_STALL_MS). */
12
+ stallMs?: number | undefined;
9
13
  }
10
- export type Install = (dir: string, options: {
14
+ export interface InstallOptions {
11
15
  home: string;
12
16
  signal?: AbortSignal | undefined;
13
- }) => Promise<void>;
17
+ /** npm's `--fetch-timeout`, its socket's idle limit (default: NPM_FETCH_TIMEOUT_MS). */
18
+ fetchTimeoutMs?: number | undefined;
19
+ /** No output and no change in its folders for this long stops it (default: NPM_STALL_MS). */
20
+ stallMs?: number | undefined;
21
+ }
22
+ export type Install = (dir: string, options: InstallOptions) => Promise<void>;
23
+ /** npm's `--fetch-timeout`: a connection quiet this long is dropped and tried again by npm (ms). */
24
+ export declare const NPM_FETCH_TIMEOUT_MS = 30000;
25
+ /**
26
+ * An install with no output and no change in its folder or npm's download
27
+ * folder for this long is stopped (ms): longer than npm's own timeout and
28
+ * retry, so npm says what went wrong first when it can.
29
+ */
30
+ export declare const NPM_STALL_MS = 120000;
14
31
  export interface LocatedPackage {
15
32
  name: string;
16
33
  version: string;
@@ -1,4 +1,4 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{spawn as _}from"node:child_process";import{createHash as T,randomBytes as H}from"node:crypto";import{existsSync as A,readFileSync as b}from"node:fs";import{copyFile as R,mkdir as E,readdir as y,readFile as C,rename as F,rm as g,writeFile as L}from"node:fs/promises";import{createRequire as I}from"node:module";import{delimiter as z,join as o}from"node:path";import{fileURLToPath as J,pathToFileURL as U}from"node:url";import{RuntimeError as k}from"./errors.js";import{cacheHome as W}from"./home.js";import{withLock as B}from"./lock.js";import{nodeCommand as D}from"./node-program.js";import{createRuntimes as M}from"./runtimes.js";const N=".allyhub-package.json",q=J(new URL("../pins/",import.meta.url));async function G(s){const c=o(s,"node_modules","onnxruntime-node","bin","napi-v6");for(const n of await y(c)){if(n!==process.platform){await g(o(c,n),{recursive:!0,force:!0});continue}for(const p of await y(o(c,n)))p!==process.arch&&await g(o(c,n,p),{recursive:!0,force:!0})}if(process.platform!=="darwin")return;const i=o(c,"darwin",process.arch);for(const n of await y(i))/^libonnxruntime\.\d+\.\d+\.\d+\.dylib$/u.test(n)&&await g(o(i,n),{force:!0})}const V={"onnxruntime-node":G};export const NPX_SETTING=/^npm_config_(global|prefix|location|cache|.*_prefix)/iu;export function npmProblem(s,c){return s.split(`
3
- `).map(n=>n.replace(/^npm (error|ERR!)\s*/u,"").trim()).find(n=>n!==""&&!/^(code|errno|syscall|path|type|A complete log|Exit handler|This is an error with npm|<https)/u.test(n))??`npm ended with code ${String(c)}`}const X=(s,c)=>(c.PATH??"").split(z).some(i=>i!==""&&A(o(i,s)));export async function findNpm(s,c){const i=s.ALLYHUB_NPM??s.npm_execpath;if(i?.endsWith(".js"))return{command:D(s),script:[i]};if(X("npm",s))return{command:"npm",script:[]};const n=await c(),{node:p,npm:l}=n;if(!p||!l)throw new Error("AllyHub's Node has no npm");return{command:p,script:[l]}}export const npmCi=async(s,{home:c,signal:i})=>{const n=await findNpm(process.env,async()=>(await M({home:c}).ensure("node",{signal:i})).bins),p=Object.fromEntries(Object.entries(process.env).filter(([f])=>!NPX_SETTING.test(f))),l=["ci","--ignore-scripts","--omit=dev","--no-audit","--no-fund","--omit=peer","--loglevel=error","--fetch-retries=1"];await new Promise((f,m)=>{const w=_(n.command,[...n.script,...l],{cwd:s,env:{...p,npm_config_cache:o(c,"packages",".npm")},stdio:["ignore","ignore","pipe"],...i?{signal:i}:{}});let h="";w.stderr.on("data",v=>{h=`${h}${v.toString()}`.slice(-1e3)}),w.on("error",m),w.on("close",v=>{v===0?f():m(new Error(npmProblem(h,v)))})})};export function createPackages(s={}){const c=s.env??process.env,i=s.home??W(c),n=s.pins??q,p=s.install??npmCi,l=o(i,"packages"),f=e=>{const r=o(n,e);let t,a;try{t=JSON.parse(b(o(r,"package.json"),"utf8")),a=b(o(r,"package-lock.json"),"utf8")}catch{throw new k("unknown-package",`AllyHub has no package "${e}".`)}const u=t.dependencies??{},d=u[e]??t.version;if(!d)throw new k("unknown-package",`The pin of "${e}" does not name it.`);return{version:d,holds:u,lock:T("sha256").update(a).digest("hex"),dir:r,size:t.allyhubSize??0}},m=(e,r)=>o(l,`${e}@${r.version}`),w=(e,r)=>{try{const t=JSON.parse(b(o(m(e,r),N),"utf8"));return t.version===r.version&&t.lock===r.lock}catch{return!1}},h=(e,r)=>({name:e,version:r.version,dir:m(e,r)}),v=async(e,r,t)=>{const a=m(e,r),u=`${a}.partial-${String(process.pid)}-${H(4).toString("hex")}`;await E(u,{recursive:!0});try{await R(o(r.dir,"package.json"),o(u,"package.json")),await R(o(r.dir,"package-lock.json"),o(u,"package-lock.json")),await p(u,{home:i,signal:t});for(const[d,j]of Object.entries(r.holds)){const O=JSON.parse(await C(o(u,"node_modules",d,"package.json"),"utf8"));if(O.version!==j)throw new Error(`it holds ${d} ${String(O.version)}, not ${j}`)}await V[e]?.(u),await L(o(u,N),`${JSON.stringify({version:r.version,lock:r.lock,at:new Date().toISOString()})}
4
- `),await g(a,{recursive:!0,force:!0}),await F(u,a),await P(e,r)}catch(d){throw await g(u,{recursive:!0,force:!0}),t?.aborted?new k("cancelled",`Stopped getting ${e}.`,{cause:d}):new k("install",`Could not get ${e} ${r.version} (${d instanceof Error?d.message:String(d)}). Check the connection and run it again.`,{cause:d})}},P=async(e,r)=>{const t=`${e}@${r.version}`;for(const a of await y(l))a.startsWith(`${e}@`)&&a!==t&&!a.startsWith(`${t}.`)&&await g(o(l,a),{recursive:!0,force:!0})},$=e=>{const r=f(e);return w(e,r)?h(e,r):void 0},S=(e,r)=>I(o(e,"package.json")).resolve(r),x=async(e,r={})=>{const t=f(e);return w(e,t)||(await E(l,{recursive:!0}).catch(a=>{throw new k("install",`Could not get ${e} ${t.version}: AllyHub cannot write to ${l}.`,{cause:a})}),await B(`${m(e,t)}.lock`,async()=>{w(e,t)||(r.onFetch?.(e),await v(e,t,r.signal))},{signal:r.signal})),h(e,t)};return{home:i,locate:$,ensure:x,size:e=>f(e).size,entry(e){const r=$(e);return r?S(r.dir,e):void 0},resolve(e,r){const t=$(e);return t?S(t.dir,r):void 0},async load(e,r){const{dir:t}=await x(e,r),a=S(t,r?.module??e);return await import(U(a).href)},async remove(e){await g(m(e,f(e)),{recursive:!0,force:!0})}}}
2
+ import{spawn as R}from"node:child_process";import{createHash as I}from"node:crypto";import{existsSync as M,readFileSync as T}from"node:fs";import{copyFile as P,mkdir as j,readdir as _,readFile as A,rename as D,rm as k,stat as H,writeFile as C}from"node:fs/promises";import{createRequire as F}from"node:module";import{delimiter as z,join as o}from"node:path";import{fileURLToPath as U,pathToFileURL as J}from"node:url";import{DOWNLOAD_STALL_MS as W}from"./download.js";import{RuntimeError as b}from"./errors.js";import{cacheHome as G}from"./home.js";import{partialOf as q,sweepPartials as B,withLock as K}from"./lock.js";import{nodeCommand as V}from"./node-program.js";import{createRuntimes as X}from"./runtimes.js";const E=".allyhub-package.json",Y=U(new URL("../pins/",import.meta.url));export const NPM_FETCH_TIMEOUT_MS=W,NPM_STALL_MS=12e4;async function Q(n){const i=o(n,"node_modules","onnxruntime-node","bin","napi-v6");for(const s of await _(i)){if(s!==process.platform){await k(o(i,s),{recursive:!0,force:!0});continue}for(const l of await _(o(i,s)))l!==process.arch&&await k(o(i,s,l),{recursive:!0,force:!0})}if(process.platform!=="darwin")return;const a=o(i,"darwin",process.arch);for(const s of await _(a))/^libonnxruntime\.\d+\.\d+\.\d+\.dylib$/u.test(s)&&await k(o(a,s),{force:!0})}const Z={"onnxruntime-node":Q};export const NPX_SETTING=/^npm_config_(global|prefix|location|cache|.*_prefix)/iu;export function npmProblem(n,i){return n.split(`
3
+ `).map(s=>s.replace(/^npm (error|ERR!)\s*/u,"").trim()).find(s=>s!==""&&!/^(code|errno|syscall|path|type|A complete log|Exit handler|This is an error with npm|<https)/u.test(s))??`npm ended with code ${String(i)}`}const tt=(n,i)=>(i.PATH??"").split(z).some(a=>a!==""&&M(o(a,n)));export async function findNpm(n,i){const a=n.ALLYHUB_NPM??n.npm_execpath;if(a?.endsWith(".js"))return{command:V(n),script:[a]};if(tt("npm",n))return{command:"npm",script:[]};const s=await i(),{node:l,npm:d}=s;if(!l||!d)throw new Error("AllyHub's Node has no npm");return{command:l,script:[d]}}async function et(n,i){const a=await _(n,{recursive:!0}).catch(()=>[]);let s=0;for(const l of await _(i).catch(()=>[]))s+=await H(o(i,l)).then(d=>d.size,()=>0);return`${String(a.length)}:${String(s)}`}export const npmCi=async(n,{home:i,signal:a,fetchTimeoutMs:s=NPM_FETCH_TIMEOUT_MS,stallMs:l=NPM_STALL_MS})=>{const d=await findNpm(process.env,async()=>(await X({home:i}).ensure("node",{signal:a})).bins),O=Object.fromEntries(Object.entries(process.env).filter(([p])=>!NPX_SETTING.test(p))),m=o(i,"packages",".npm"),y=["ci","--ignore-scripts","--omit=dev","--no-audit","--no-fund","--omit=peer","--loglevel=error","--fetch-retries=1",`--fetch-timeout=${String(s)}`];await new Promise((p,h)=>{const w=R(d.command,[...d.script,...y],{cwd:n,env:{...O,npm_config_cache:m},stdio:["ignore","ignore","pipe"],...a?{signal:a}:{}});let g="",x=Date.now(),$="",v=!1,S=!1;const t=setInterval(()=>{v||S||(v=!0,et(n,o(m,"_cacache","tmp")).then(e=>{e!==$?($=e,x=Date.now()):Date.now()-x>=l&&(S=!0,w.kill(),setTimeout(()=>w.kill("SIGKILL"),5e3).unref())}).finally(()=>{v=!1}))},Math.max(20,Math.min(5e3,l/4)));w.stderr.on("data",e=>{x=Date.now(),g=`${g}${e.toString()}`.slice(-1e3)}),w.on("error",e=>{clearInterval(t),h(e)}),w.on("close",e=>{clearInterval(t),S?h(new b("install",`npm made no progress for ${String(l/1e3)} s and was stopped${g.trim()?`; it said: ${npmProblem(g,e)}`:""}`)):e===0?p():h(new Error(npmProblem(g,e)))})})};export function createPackages(n={}){const i=n.env??process.env,a=n.home??G(i),s=n.pins??Y,l=n.install??npmCi,{fetchTimeoutMs:d,stallMs:O}=n,m=o(a,"packages"),y=t=>{const e=o(s,t);let r,c;try{r=JSON.parse(T(o(e,"package.json"),"utf8")),c=T(o(e,"package-lock.json"),"utf8")}catch{throw new b("unknown-package",`AllyHub has no package "${t}".`)}const u=r.dependencies??{},f=u[t]??r.version;if(!f)throw new b("unknown-package",`The pin of "${t}" does not name it.`);return{version:f,holds:u,lock:I("sha256").update(c).digest("hex"),dir:e,size:r.allyhubSize??0}},p=(t,e)=>o(m,`${t}@${e.version}`),h=(t,e)=>{try{const r=JSON.parse(T(o(p(t,e),E),"utf8"));return r.version===e.version&&r.lock===e.lock}catch{return!1}},w=(t,e)=>({name:t,version:e.version,dir:p(t,e)}),g=async(t,e,r)=>{const c=p(t,e),u=q(c);await j(u,{recursive:!0});try{await P(o(e.dir,"package.json"),o(u,"package.json")),await P(o(e.dir,"package-lock.json"),o(u,"package-lock.json")),await l(u,{home:a,signal:r,fetchTimeoutMs:d,stallMs:O});for(const[f,L]of Object.entries(e.holds)){const N=JSON.parse(await A(o(u,"node_modules",f,"package.json"),"utf8"));if(N.version!==L)throw new Error(`it holds ${f} ${String(N.version)}, not ${L}`)}await Z[t]?.(u),await C(o(u,E),`${JSON.stringify({version:e.version,lock:e.lock,at:new Date().toISOString()})}
4
+ `),await k(c,{recursive:!0,force:!0}),await D(u,c),await x(t,e)}catch(f){throw await k(u,{recursive:!0,force:!0}),r?.aborted?new b("cancelled",`Stopped getting ${t}.`,{cause:f}):new b("install",`Could not get ${t} ${e.version} (${f instanceof Error?f.message:String(f)}). Check the connection and run it again.`,{cause:f})}},x=async(t,e)=>{const r=`${t}@${e.version}`;for(const c of await _(m))c.startsWith(`${t}@`)&&c!==r&&!c.startsWith(`${r}.`)&&await k(o(m,c),{recursive:!0,force:!0})},$=t=>{const e=y(t);return h(t,e)?w(t,e):void 0},v=(t,e)=>F(o(t,"package.json")).resolve(e),S=async(t,e={})=>{const r=y(t);return h(t,r)||(await j(m,{recursive:!0}).catch(c=>{throw new b("install",`Could not get ${t} ${r.version}: AllyHub cannot write to ${m}.`,{cause:c})}),await K(`${p(t,r)}.lock`,async()=>{h(t,r)||(await B(m,t),e.onFetch?.(t),await g(t,r,e.signal))},{signal:e.signal})),w(t,r)};return{home:a,locate:$,ensure:S,size:t=>y(t).size,entry(t){const e=$(t);return e?v(e.dir,t):void 0},resolve(t,e){const r=$(t);return r?v(r.dir,e):void 0},async load(t,e){const{dir:r}=await S(t,e),c=v(r,e?.module??t);return await import(J(c).href)},async remove(t){await k(p(t,y(t)),{recursive:!0,force:!0})}}}
@@ -1,4 +1,5 @@
1
1
  import { type Platform, type RuntimeManifest } from "@allyhub-next/protocol";
2
+ import { type DownloadTimings } from "./download.js";
2
3
  export interface RuntimesOptions {
3
4
  /** The cache folder (default: the system's, `cacheHome`). */
4
5
  home?: string;
@@ -13,6 +14,8 @@ export interface RuntimesOptions {
13
14
  * read as a hang to whoever runs them.
14
15
  */
15
16
  log?: (line: string) => void;
17
+ /** How a download waits and tries again (defaults: download.ts's constants). */
18
+ downloads?: DownloadTimings;
16
19
  }
17
20
  export interface Located {
18
21
  name: string;
@@ -1,4 +1,4 @@
1
1
  /*! AllyHub. Copyright (c) 2026 AllyHub, Inc. All rights reserved. Use is subject to LICENSE.md. */
2
- import{randomBytes as W}from"node:crypto";import{chmod as _,mkdir as R,readFile as T,rename as P,rm as k,writeFile as V}from"node:fs/promises";import{dirname as U,join as d}from"node:path";import{binsOf as A,filesFor as B,platformOf as Y}from"@allyhub-next/protocol";import{download as q,sha256Of as G}from"./download.js";import{RuntimeError as m}from"./errors.js";import{cacheHome as K}from"./home.js";import{withLock as Q}from"./lock.js";import{shippedManifest as X}from"./manifest.js";import{inside as C,native as Z,undmg as tt,untgz as et,unzip as rt}from"./unpack.js";const D=".allyhub-runtime.json";export const binVariable=i=>`ALLYHUB_${i.toUpperCase().replaceAll("-","_")}`;const x=i=>`${String(Math.max(1,Math.round(i/1e6)))} MB`;function nt(i,b,w,g){g(`Downloading ${i} ${b} (${x(w)}), once…`);let h=0;return u=>{const $=Math.floor(u/w*10)*10;$!==h&&(h=$,g(` ${String($)}% (${x(u)} of ${x(w)})`))}}export function createRuntimes(i={}){const b=i.log??(t=>{process.stderr.write(`${t}
3
- `)}),w=i.env??process.env,g=i.home??K(w),h=i.manifest??X(),u="platform"in i?i.platform:Y(process.platform,process.arch),$=i.fetch??globalThis.fetch,S=d(g,"runtimes"),z=t=>{const e=Object.hasOwn(h.runtimes,t)?h.runtimes[t]:void 0;if(!e)throw new m("unknown-runtime",`AllyHub has no runtime "${t}".`);return e},p=(t,e)=>d(S,`${t}@${e.version}`),O=(t,e)=>{const r=u?B(e,u):void 0;if(!r)throw new m("unsupported",`${t} ${e.version} has no build for ${u??`${process.platform}-${process.arch}`}.`);return r},F=(t,e,r)=>Object.fromEntries(Object.entries(A(O(t,e))).map(([n,s])=>[n,d(r,Z(s))])),E=(t,e)=>{const r=[...new Set(Object.values(e.platforms).flatMap(c=>Object.keys(A(c))))];if(r.length===0)return;const n=r.map(c=>[c,w[binVariable(c)]]);if(n.some(([,c])=>!c))return;const s=Object.fromEntries(n),a=n[0]?.[1]??"";return{name:t,version:void 0,dir:U(a),bins:s,from:"env"}},j=async(t,e)=>{try{const r=JSON.parse(await T(d(p(t,e),D),"utf8")),n=O(t,e).map(s=>s.sha256);return r.version===e.version&&JSON.stringify(r.files)===JSON.stringify(n)}catch{return!1}},M=async t=>{const e=z(t),r=E(t,e);if(r)return r;if(!await j(t,e))return;const n=p(t,e);return{name:t,version:e.version,dir:n,bins:F(t,e,n),from:"cache"}},H=async(t,e,r)=>{const n=O(t,e),s=p(t,e),a=`${s}.partial-${String(process.pid)}-${W(4).toString("hex")}`,c=d(a,".downloads");await R(c,{recursive:!0});const I=r.onProgress?void 0:nt(t,e.version,n.reduce((l,o)=>l+o.size,0),b);let N=0;try{for(const[l,o]of n.entries()){const v=d(c,String(l));if(await q(o.url,v,{size:o.size,sha256:o.sha256,fetch:$,signal:r.signal,onProgress:(f,y)=>{I?.(N+f),r.onProgress?.({runtime:t,url:o.url,received:f,total:y})}}),N+=o.size,o.unpack==="zip")await rt(v,a);else if(o.unpack==="tgz")await et(v,a);else if(o.unpack==="dmg")await tt(v,a);else{const f=C(a,o.name??st(o.url));if(!f)throw new m("unpack",`${o.url} would be kept outside its folder.`);await R(U(f),{recursive:!0}),await P(v,f),o.executable&&await _(f,493)}for(const f of o.check){const y=C(a,f.path),J=y?await G(y).catch(()=>"missing"):"missing";if(J!==f.sha256)throw new m("checksum",`${f.path} from ${o.url} has sha256 ${J}; its pin is ${f.sha256}.`)}}await k(c,{recursive:!0,force:!0}),await V(d(a,D),`${JSON.stringify({version:e.version,files:n.map(l=>l.sha256),at:new Date().toISOString()})}
4
- `),await k(s,{recursive:!0,force:!0}),await P(a,s)}catch(l){throw await k(a,{recursive:!0,force:!0}),l}},L=async(t,e={})=>{const r=z(t),n=await M(t);if(n)return n;O(t,r),await R(S,{recursive:!0}),await Q(`${p(t,r)}.lock`,async()=>{await j(t,r)||await H(t,r,e)},{signal:e.signal,onWait:()=>{b(`Waiting for ${t}, which another command is getting…`)}});const s=await M(t);if(!s)throw new m("download",`${t} was installed but cannot be found in ${S}.`);return s};return{home:g,platform:u,locate:M,ensure:L,async bin(t,e,r){const s=(await L(t,r)).bins[e];if(!s)throw new m("unknown-runtime",`${t} holds no program "${e}".`);return s},async remove(t){const e=z(t);await k(p(t,e),{recursive:!0,force:!0})},async status(){return Promise.all(Object.entries(h.runtimes).map(async([t,e])=>{const r=u?B(e,u):void 0,n=p(t,e),s=E(t,e)!==void 0?"env":r!==void 0&&await j(t,e)?"cache":void 0;return{name:t,about:e.about,version:e.version,license:e.license,available:r!==void 0,installed:s!==void 0,...s?{from:s}:{},size:(r??[]).reduce((a,c)=>a+c.size,0),dir:n}}))}}}const st=i=>decodeURIComponent(new URL(i).pathname.split("/").pop()??"file");
2
+ import{chmod as T,mkdir as E,readFile as W,rename as J,rm as z,writeFile as _}from"node:fs/promises";import{dirname as U,join as d}from"node:path";import{binsOf as A,filesFor as C,platformOf as V}from"@allyhub-next/protocol";import{download as Y,sha256Of as q}from"./download.js";import{RuntimeError as g}from"./errors.js";import{cacheHome as G}from"./home.js";import{partialOf as K,sweepPartials as Q,withLock as X}from"./lock.js";import{shippedManifest as Z}from"./manifest.js";import{inside as D,native as tt,undmg as et,untgz as rt,unzip as nt}from"./unpack.js";const F=".allyhub-runtime.json";export const binVariable=o=>`ALLYHUB_${o.toUpperCase().replaceAll("-","_")}`;const L=o=>`${String(Math.max(1,Math.round(o/1e6)))} MB`;function st(o,$,w,v){v(`Downloading ${o} ${$} (${L(w)}), once…`);let h=0;return u=>{const b=Math.floor(u/w*10)*10;b!==h&&(h=b,v(` ${String(b)}% (${L(u)} of ${L(w)})`))}}export function createRuntimes(o={}){const $=o.log??(t=>{process.stderr.write(`${t}
3
+ `)}),w=o.env??process.env,v=o.home??G(w),h=o.manifest??Z(),u="platform"in o?o.platform:V(process.platform,process.arch),b=o.fetch??globalThis.fetch,y=d(v,"runtimes"),j=t=>{const e=Object.hasOwn(h.runtimes,t)?h.runtimes[t]:void 0;if(!e)throw new g("unknown-runtime",`AllyHub has no runtime "${t}".`);return e},p=(t,e)=>d(y,`${t}@${e.version}`),k=(t,e)=>{const r=u?C(e,u):void 0;if(!r)throw new g("unsupported",`${t} ${e.version} has no build for ${u??`${process.platform}-${process.arch}`}.`);return r},H=(t,e,r)=>Object.fromEntries(Object.entries(A(k(t,e))).map(([n,s])=>[n,d(r,tt(s))])),N=(t,e)=>{const r=[...new Set(Object.values(e.platforms).flatMap(f=>Object.keys(A(f))))];if(r.length===0)return;const n=r.map(f=>[f,w[binVariable(f)]]);if(n.some(([,f])=>!f))return;const s=Object.fromEntries(n),c=n[0]?.[1]??"";return{name:t,version:void 0,dir:U(c),bins:s,from:"env"}},R=async(t,e)=>{try{const r=JSON.parse(await W(d(p(t,e),F),"utf8")),n=k(t,e).map(s=>s.sha256);return r.version===e.version&&JSON.stringify(r.files)===JSON.stringify(n)}catch{return!1}},M=async t=>{const e=j(t),r=N(t,e);if(r)return r;if(!await R(t,e))return;const n=p(t,e);return{name:t,version:e.version,dir:n,bins:H(t,e,n),from:"cache"}},I=async(t,e,r)=>{const n=k(t,e),s=p(t,e),c=K(s),f=d(c,".downloads");await E(f,{recursive:!0});const B=r.onProgress?void 0:st(t,e.version,n.reduce((l,i)=>l+i.size,0),$);let x=0;try{for(const[l,i]of n.entries()){const O=d(f,String(l));if(await Y(i.url,O,{size:i.size,sha256:i.sha256,fetch:b,signal:r.signal,...o.downloads,onRetry:(a,m,S)=>{$(` ${a.message} Trying again (${String(m+1)} of ${String(S)})…`)},onProgress:(a,m)=>{B?.(x+a),r.onProgress?.({runtime:t,url:i.url,received:a,total:m})}}),x+=i.size,i.unpack==="zip")await nt(O,c);else if(i.unpack==="tgz")await rt(O,c);else if(i.unpack==="dmg")await et(O,c);else{const a=D(c,i.name??ot(i.url));if(!a)throw new g("unpack",`${i.url} would be kept outside its folder.`);await E(U(a),{recursive:!0}),await J(O,a),i.executable&&await T(a,493)}for(const a of i.check){const m=D(c,a.path),S=m?await q(m).catch(()=>"missing"):"missing";if(S!==a.sha256)throw new g("checksum",`${a.path} from ${i.url} has sha256 ${S}; its pin is ${a.sha256}.`)}}await z(f,{recursive:!0,force:!0}),await _(d(c,F),`${JSON.stringify({version:e.version,files:n.map(l=>l.sha256),at:new Date().toISOString()})}
4
+ `),await z(s,{recursive:!0,force:!0}),await J(c,s)}catch(l){throw await z(c,{recursive:!0,force:!0}),l}},P=async(t,e={})=>{const r=j(t),n=await M(t);if(n)return n;k(t,r),await E(y,{recursive:!0}),await X(`${p(t,r)}.lock`,async()=>{await R(t,r)||(await Q(y,t),await I(t,r,e))},{signal:e.signal,onWait:()=>{$(`Waiting for ${t}, which another command is getting…`)}});const s=await M(t);if(!s)throw new g("download",`${t} was installed but cannot be found in ${y}.`);return s};return{home:v,platform:u,locate:M,ensure:P,async bin(t,e,r){const s=(await P(t,r)).bins[e];if(!s)throw new g("unknown-runtime",`${t} holds no program "${e}".`);return s},async remove(t){const e=j(t);await z(p(t,e),{recursive:!0,force:!0})},async status(){return Promise.all(Object.entries(h.runtimes).map(async([t,e])=>{const r=u?C(e,u):void 0,n=p(t,e),s=N(t,e)!==void 0?"env":r!==void 0&&await R(t,e)?"cache":void 0;return{name:t,about:e.about,version:e.version,license:e.license,available:r!==void 0,installed:s!==void 0,...s?{from:s}:{},size:(r??[]).reduce((c,f)=>c+f.size,0),dir:n}}))}}}const ot=o=>decodeURIComponent(new URL(o).pathname.split("/").pop()??"file");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/runtimes",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/scaffold",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/scenery",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/scenery-host",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/sheet",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/slides-core",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allyhub-next/slides-pptx",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {