@pablopunk/webr 0.3.8 → 0.3.9

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.
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_DDOnT1vx.mjs";
3
+ import { n as $$Base, t as App } from "./App_C1bSzRXS.mjs";
4
4
  //#region src/pages/sessions/[session]/tabs/[tab].astro
5
5
  var _tab__exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$Tab,
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_DDOnT1vx.mjs";
3
+ import { n as $$Base, t as App } from "./App_C1bSzRXS.mjs";
4
4
  //#region src/pages/new.astro
5
5
  var new_exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$New,
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_DDOnT1vx.mjs";
3
+ import { n as $$Base, t as App } from "./App_C1bSzRXS.mjs";
4
4
  //#region src/pages/settings.astro
5
5
  var settings_exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$Settings,
@@ -2426,10 +2426,10 @@ var renderers = [Object.assign({
2426
2426
  //#endregion
2427
2427
  //#region \0virtual:astro:pages
2428
2428
  var _page0 = () => import("./chunks/node_C8FqXy_n.mjs").then((n) => n.t);
2429
- var _page1 = () => import("./chunks/new_CBpgizdT.mjs");
2430
- var _page2 = () => import("./chunks/_tab__1TQX6GGq.mjs");
2431
- var _page3 = () => import("./chunks/settings_BahL_Diu.mjs");
2432
- var _page4 = () => import("./chunks/_id__CRXKeG9Q.mjs");
2429
+ var _page1 = () => import("./chunks/new_DC57I9zU.mjs");
2430
+ var _page2 = () => import("./chunks/_tab__OowoBszk.mjs");
2431
+ var _page3 = () => import("./chunks/settings_BIpDm2u1.mjs");
2432
+ var _page4 = () => import("./chunks/_id__PkkT8hXq.mjs");
2433
2433
  var _page5 = () => import("./chunks/index_Bvv1gpI0.mjs");
2434
2434
  var pageMap = /* @__PURE__ */ new Map([
2435
2435
  ["node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node.js", _page0],
@@ -2441,7 +2441,7 @@ var pageMap = /* @__PURE__ */ new Map([
2441
2441
  ]);
2442
2442
  //#endregion
2443
2443
  //#region \0virtual:astro:manifest
2444
- var _manifest = deserializeManifest({"rootDir":"file:///home/runner/work/webr/webr/","cacheDir":"file:///home/runner/work/webr/webr/node_modules/.astro/","outDir":"file:///home/runner/work/webr/webr/dist/","srcDir":"file:///home/runner/work/webr/webr/src/","publicDir":"file:///home/runner/work/webr/webr/public/","buildClientDir":"file:///home/runner/work/webr/webr/dist/client/","buildServerDir":"file:///home/runner/work/webr/webr/dist/server/","adapterName":"@astrojs/node","assetsDir":"_astro","routes":[{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"page","component":"_server-islands.astro","params":["name"],"segments":[[{"content":"_server-islands","dynamic":false,"spread":false}],[{"content":"name","dynamic":true,"spread":false}]],"pattern":"^\\/_server-islands\\/([^/]+?)\\/?$","prerender":false,"isIndex":false,"fallbackRoutes":[],"route":"/_server-islands/[name]","origin":"internal","distURL":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/_image","component":"node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node.js","params":[],"pathname":"/_image","pattern":"^\\/_image\\/?$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"type":"endpoint","prerender":false,"fallbackRoutes":[],"distURL":[],"isIndex":false,"origin":"internal","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.B7RjqN_I.css"}],"routeData":{"route":"/new","isIndex":false,"type":"page","pattern":"^\\/new\\/?$","segments":[[{"content":"new","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/new.astro","pathname":"/new","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.B7RjqN_I.css"}],"routeData":{"route":"/sessions/[session]/tabs/[tab]","isIndex":false,"type":"page","pattern":"^\\/sessions\\/([^/]+?)\\/tabs\\/([^/]+?)\\/?$","segments":[[{"content":"sessions","dynamic":false,"spread":false}],[{"content":"session","dynamic":true,"spread":false}],[{"content":"tabs","dynamic":false,"spread":false}],[{"content":"tab","dynamic":true,"spread":false}]],"params":["session","tab"],"component":"src/pages/sessions/[session]/tabs/[tab].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.B7RjqN_I.css"}],"routeData":{"route":"/settings","isIndex":false,"type":"page","pattern":"^\\/settings\\/?$","segments":[[{"content":"settings","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/settings.astro","pathname":"/settings","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.B7RjqN_I.css"}],"routeData":{"route":"/threads/[id]","isIndex":false,"type":"page","pattern":"^\\/threads\\/([^/]+?)\\/?$","segments":[[{"content":"threads","dynamic":false,"spread":false}],[{"content":"id","dynamic":true,"spread":false}]],"params":["id"],"component":"src/pages/threads/[id].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}}],"serverLike":true,"middlewareMode":"classic","base":"/","trailingSlash":"ignore","compressHTML":"jsx","componentMetadata":[["/home/runner/work/webr/webr/src/pages/new.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/sessions/[session]/tabs/[tab].astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/settings.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/threads/[id].astro",{"propagation":"none","containsHead":true}]],"renderers":[],"clientDirectives":[["idle","(()=>{var l=(n,t)=>{let i=async()=>{await(await n())()},e=typeof t.value==\"object\"?t.value:void 0,s={timeout:e==null?void 0:e.timeout};\"requestIdleCallback\"in window?window.requestIdleCallback(i,s):setTimeout(i,s.timeout||200)};(self.Astro||(self.Astro={})).idle=l;window.dispatchEvent(new Event(\"astro:idle\"));})();"],["load","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event(\"astro:load\"));})();"],["media","(()=>{var n=(a,t)=>{let i=async()=>{await(await a())()};if(t.value){let e=matchMedia(t.value);e.matches?i():e.addEventListener(\"change\",i,{once:!0})}};(self.Astro||(self.Astro={})).media=n;window.dispatchEvent(new Event(\"astro:media\"));})();"],["only","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event(\"astro:only\"));})();"],["visible","(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value==\"object\"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event(\"astro:visible\"));})();"]],"entryModules":{"\u0000noop-middleware":"virtual_astro_middleware.mjs","\u0000virtual:astro:server-island-manifest":"chunks/_virtual_astro_server-island-manifest_C1Q2srgE.mjs","\u0000virtual:astro:session-driver":"chunks/_virtual_astro_session-driver_Dp7RXjOf.mjs","\u0000virtual:astro:actions/noop-entrypoint":"chunks/noop-entrypoint_Z3zFhrGC.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/@astrojs+react@7.0.0_@types+node@22.20.4_@types+react-dom@19.3.0_@types+react@19.3.0__@_02aa85dceaccf3d763325676dd8540a8/node_modules/@astrojs/react/dist/vnode-children.js":"chunks/vnode-children_BSi0H4PL.mjs","@astrojs/node/server.js":"entry.mjs","\u0000virtual:astro:page:src/pages/threads/[id]@_@astro":"chunks/_id__CRXKeG9Q.mjs","\u0000virtual:astro:page:src/pages/sessions/[session]/tabs/[tab]@_@astro":"chunks/_tab__1TQX6GGq.mjs","\u0000virtual:astro:page:src/pages/index@_@astro":"chunks/index_Bvv1gpI0.mjs","\u0000virtual:astro:page:src/pages/new@_@astro":"chunks/new_CBpgizdT.mjs","\u0000virtual:astro:page:node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node@_@js":"chunks/node_C8FqXy_n.mjs","\u0000virtual:astro:page:src/pages/settings@_@astro":"chunks/settings_BahL_Diu.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/services/sharp.js":"chunks/sharp_8J8zk4Wd.mjs","/home/runner/work/webr/webr/src/components/App.tsx":"_astro/App.CuhTptJe.js","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/components/ClientRouter.astro?astro&type=script&index=0&lang.ts":"_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","@astrojs/react/client.js":"_astro/client.BDkzSRUN.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-fit@0.10.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-fit/lib/addon-fit.js":"_astro/addon-fit.jgKqzwky.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-web-links@0.11.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-web-links/lib/addon-web-links.js":"_astro/addon-web-links.C9lyQRxI.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-webgl@0.18.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-webgl/lib/addon-webgl.js":"_astro/addon-webgl.Bd5ox92E.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+xterm@5.5.0/node_modules/@xterm/xterm/lib/xterm.js":"_astro/xterm.B-l5AIf6.js","astro:scripts/before-hydration.js":""},"inlinedScripts":[],"assets":["/apple-touch-icon.png","/brand-mark.png","/favicon-16.png","/favicon-32.png","/favicon.ico","/icon-192.png","/icon-512.png","/icon-maskable-512.png","/manifest.webmanifest","/_astro/App.CuhTptJe.js","/_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","/_astro/addon-fit.jgKqzwky.js","/_astro/addon-web-links.C9lyQRxI.js","/_astro/addon-webgl.Bd5ox92E.js","/_astro/client.BDkzSRUN.js","/_astro/client.Bbg9OBwT.js","/_astro/client.CLSsMr2g.js","/_astro/xterm.B-l5AIf6.js","/_astro/App.B7RjqN_I.css","/_astro/jetbrains-mono.CFJaiWaY.woff2","/_astro/symbols-nerd.CEdkKAK8.woff2"],"buildFormat":"directory","checkOrigin":true,"actionBodySizeLimit":1048576,"serverIslandBodySizeLimit":1048576,"allowedDomains":[],"key":"iVCcVi9giGk8wGpdPxt7Oy82WrH2RNoZ7NvJcqB4C/A=","sessionConfig":{"driver":"unstorage/drivers/fs-lite","options":{"base":"/home/runner/work/webr/webr/node_modules/.astro/sessions"}},"image":{},"devToolbar":{"enabled":false,"debugInfoOutput":""},"logLevel":"info","shouldInjectCspMetaTags":false});
2444
+ var _manifest = deserializeManifest({"rootDir":"file:///home/runner/work/webr/webr/","cacheDir":"file:///home/runner/work/webr/webr/node_modules/.astro/","outDir":"file:///home/runner/work/webr/webr/dist/","srcDir":"file:///home/runner/work/webr/webr/src/","publicDir":"file:///home/runner/work/webr/webr/public/","buildClientDir":"file:///home/runner/work/webr/webr/dist/client/","buildServerDir":"file:///home/runner/work/webr/webr/dist/server/","adapterName":"@astrojs/node","assetsDir":"_astro","routes":[{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"page","component":"_server-islands.astro","params":["name"],"segments":[[{"content":"_server-islands","dynamic":false,"spread":false}],[{"content":"name","dynamic":true,"spread":false}]],"pattern":"^\\/_server-islands\\/([^/]+?)\\/?$","prerender":false,"isIndex":false,"fallbackRoutes":[],"route":"/_server-islands/[name]","origin":"internal","distURL":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/_image","component":"node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node.js","params":[],"pathname":"/_image","pattern":"^\\/_image\\/?$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"type":"endpoint","prerender":false,"fallbackRoutes":[],"distURL":[],"isIndex":false,"origin":"internal","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/new","isIndex":false,"type":"page","pattern":"^\\/new\\/?$","segments":[[{"content":"new","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/new.astro","pathname":"/new","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/sessions/[session]/tabs/[tab]","isIndex":false,"type":"page","pattern":"^\\/sessions\\/([^/]+?)\\/tabs\\/([^/]+?)\\/?$","segments":[[{"content":"sessions","dynamic":false,"spread":false}],[{"content":"session","dynamic":true,"spread":false}],[{"content":"tabs","dynamic":false,"spread":false}],[{"content":"tab","dynamic":true,"spread":false}]],"params":["session","tab"],"component":"src/pages/sessions/[session]/tabs/[tab].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/settings","isIndex":false,"type":"page","pattern":"^\\/settings\\/?$","segments":[[{"content":"settings","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/settings.astro","pathname":"/settings","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/threads/[id]","isIndex":false,"type":"page","pattern":"^\\/threads\\/([^/]+?)\\/?$","segments":[[{"content":"threads","dynamic":false,"spread":false}],[{"content":"id","dynamic":true,"spread":false}]],"params":["id"],"component":"src/pages/threads/[id].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}}],"serverLike":true,"middlewareMode":"classic","base":"/","trailingSlash":"ignore","compressHTML":"jsx","componentMetadata":[["/home/runner/work/webr/webr/src/pages/new.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/sessions/[session]/tabs/[tab].astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/settings.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/threads/[id].astro",{"propagation":"none","containsHead":true}]],"renderers":[],"clientDirectives":[["idle","(()=>{var l=(n,t)=>{let i=async()=>{await(await n())()},e=typeof t.value==\"object\"?t.value:void 0,s={timeout:e==null?void 0:e.timeout};\"requestIdleCallback\"in window?window.requestIdleCallback(i,s):setTimeout(i,s.timeout||200)};(self.Astro||(self.Astro={})).idle=l;window.dispatchEvent(new Event(\"astro:idle\"));})();"],["load","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event(\"astro:load\"));})();"],["media","(()=>{var n=(a,t)=>{let i=async()=>{await(await a())()};if(t.value){let e=matchMedia(t.value);e.matches?i():e.addEventListener(\"change\",i,{once:!0})}};(self.Astro||(self.Astro={})).media=n;window.dispatchEvent(new Event(\"astro:media\"));})();"],["only","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event(\"astro:only\"));})();"],["visible","(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value==\"object\"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event(\"astro:visible\"));})();"]],"entryModules":{"\u0000noop-middleware":"virtual_astro_middleware.mjs","\u0000virtual:astro:server-island-manifest":"chunks/_virtual_astro_server-island-manifest_C1Q2srgE.mjs","\u0000virtual:astro:session-driver":"chunks/_virtual_astro_session-driver_Dp7RXjOf.mjs","\u0000virtual:astro:actions/noop-entrypoint":"chunks/noop-entrypoint_Z3zFhrGC.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/@astrojs+react@7.0.0_@types+node@22.20.4_@types+react-dom@19.3.0_@types+react@19.3.0__@_02aa85dceaccf3d763325676dd8540a8/node_modules/@astrojs/react/dist/vnode-children.js":"chunks/vnode-children_BSi0H4PL.mjs","@astrojs/node/server.js":"entry.mjs","\u0000virtual:astro:page:src/pages/threads/[id]@_@astro":"chunks/_id__PkkT8hXq.mjs","\u0000virtual:astro:page:src/pages/sessions/[session]/tabs/[tab]@_@astro":"chunks/_tab__OowoBszk.mjs","\u0000virtual:astro:page:src/pages/index@_@astro":"chunks/index_Bvv1gpI0.mjs","\u0000virtual:astro:page:src/pages/new@_@astro":"chunks/new_DC57I9zU.mjs","\u0000virtual:astro:page:node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node@_@js":"chunks/node_C8FqXy_n.mjs","\u0000virtual:astro:page:src/pages/settings@_@astro":"chunks/settings_BIpDm2u1.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/services/sharp.js":"chunks/sharp_8J8zk4Wd.mjs","/home/runner/work/webr/webr/src/components/App.tsx":"_astro/App.BtZuijDn.js","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/components/ClientRouter.astro?astro&type=script&index=0&lang.ts":"_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","@astrojs/react/client.js":"_astro/client.BDkzSRUN.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-fit@0.10.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-fit/lib/addon-fit.js":"_astro/addon-fit.jgKqzwky.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-web-links@0.11.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-web-links/lib/addon-web-links.js":"_astro/addon-web-links.C9lyQRxI.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-webgl@0.18.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-webgl/lib/addon-webgl.js":"_astro/addon-webgl.Bd5ox92E.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+xterm@5.5.0/node_modules/@xterm/xterm/lib/xterm.js":"_astro/xterm.B-l5AIf6.js","astro:scripts/before-hydration.js":""},"inlinedScripts":[],"assets":["/apple-touch-icon.png","/brand-mark.png","/favicon-16.png","/favicon-32.png","/favicon.ico","/icon-192.png","/icon-512.png","/icon-maskable-512.png","/manifest.webmanifest","/_astro/App.BtZuijDn.js","/_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","/_astro/addon-fit.jgKqzwky.js","/_astro/addon-web-links.C9lyQRxI.js","/_astro/addon-webgl.Bd5ox92E.js","/_astro/client.BDkzSRUN.js","/_astro/client.Bbg9OBwT.js","/_astro/client.CLSsMr2g.js","/_astro/xterm.B-l5AIf6.js","/_astro/App.CJakfIju.css","/_astro/jetbrains-mono.CFJaiWaY.woff2","/_astro/symbols-nerd.CEdkKAK8.woff2"],"buildFormat":"directory","checkOrigin":true,"actionBodySizeLimit":1048576,"serverIslandBodySizeLimit":1048576,"allowedDomains":[],"key":"bhQt7blSutZXqvha3yFzjOGVMCtQJ/c1nNTFxOla9cU=","sessionConfig":{"driver":"unstorage/drivers/fs-lite","options":{"base":"/home/runner/work/webr/webr/node_modules/.astro/sessions"}},"image":{},"devToolbar":{"enabled":false,"debugInfoOutput":""},"logLevel":"info","shouldInjectCspMetaTags":false});
2445
2445
  var manifestRoutes = _manifest.routes;
2446
2446
  var manifest = Object.assign(_manifest, {
2447
2447
  renderers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablopunk/webr",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Control your Herdr agents from anywhere.",
5
5
  "keywords": [
6
6
  "herdr",
@@ -1,4 +1,5 @@
1
1
  export type MachineHarness = { id: string; name: string; models: string[]; customModels?: boolean; launchEnabled?: boolean; reason?: string };
2
+ export type MachineOs = 'macos' | 'linux' | 'windows';
2
3
  export type Machine = {
3
4
  id: string;
4
5
  name: string;
@@ -7,6 +8,8 @@ export type Machine = {
7
8
  projectPaths: Record<string, string>;
8
9
  harnesses: MachineHarness[];
9
10
  modelsPending?: boolean;
11
+ isGitRepo?: boolean;
12
+ os?: MachineOs;
10
13
  error?: string;
11
14
  writable?: boolean;
12
15
  configVersion?: number;
@@ -14,7 +17,7 @@ export type Machine = {
14
17
 
15
18
  export const machines: Machine[] = [
16
19
  {
17
- id: 'local', name: 'Local', connected: true, session: 'default',
20
+ id: 'local', name: 'Local', os: 'macos', connected: true, session: 'default',
18
21
  projectPaths: { herdr: '~/src/herdr', maze: '~/src/maze/monorepo', spotifin: '~/src/spotifin' },
19
22
  harnesses: [
20
23
  { id: 'claude', name: 'Claude Code', models: ['Default', 'sonnet', 'opus'] },
@@ -24,7 +27,7 @@ export const machines: Machine[] = [
24
27
  ],
25
28
  },
26
29
  {
27
- id: 'demo-build', name: 'Build machine (demo)', connected: true, session: 'agents',
30
+ id: 'demo-build', name: 'Build machine (demo)', os: 'linux', connected: true, session: 'agents',
28
31
  projectPaths: { herdr: '/srv/herdr', spotifin: '/srv/spotifin' },
29
32
  harnesses: [
30
33
  { id: 'opencode', name: 'OpenCode', models: ['Default', 'demo/remote-model'] },
@@ -14,11 +14,13 @@ import { isTrustedHost } from './auth/access';
14
14
  import { registerDevHmrProxy } from './dev-hmr-proxy';
15
15
  import { registerVoiceRoutes } from './voice/routes';
16
16
  import type { Transcriber } from './voice/transcriber';
17
- import { MAX_IMAGE_BYTES, UploadStore, uploadContentTypes } from './uploads';
17
+ import { MAX_IMAGE_BYTES, UploadStore, isFileUpload, uploadContentTypes } from './uploads';
18
18
 
19
19
  const localOwner = 'local';
20
20
  type SsrHandler = (request: FastifyRequest['raw'], response: import('node:http').ServerResponse, next: (error?: unknown) => void, locals: Record<string, unknown>) => void;
21
21
 
22
+ const MAX_PROJECT_ICON_BYTES = 512 * 1024;
23
+
22
24
  export async function createHost(manager: RuntimeManager, origin: string, ssr?: SsrHandler, tls?: { key: Buffer; cert: Buffer }, uploads = new UploadStore(), authOptions: AuthOptions = {}, { devHmrPort, transcriber }: { devHmrPort?: number; transcriber?: Transcriber } = {}) {
23
25
  const app = Fastify({ logger: false, bodyLimit: 16 * 1024, ...(tls ? { https: tls } : {}), requestTimeout: 10_000 });
24
26
  const configuredOrigin = new URL(origin);
@@ -37,6 +39,8 @@ export async function createHost(manager: RuntimeManager, origin: string, ssr?:
37
39
  if (error instanceof z.ZodError) return reply.code(400).send({ error: 'invalid_request' });
38
40
  const status = (error as { statusCode?: number }).statusCode;
39
41
  if (status === 413) return reply.code(413).send({ error: 'image_too_large' });
42
+ if (message === 'file_too_large') return reply.code(413).send({ error: message });
43
+ if (message === 'empty_file') return reply.code(400).send({ error: message });
40
44
  if (status === 415 || message === 'unsupported_image') return reply.code(415).send({ error: 'unsupported_image' });
41
45
  if (status && status >= 400 && status < 500) return reply.code(status).send({ error: 'invalid_request' });
42
46
  if (message === 'thread_not_found') return reply.code(404).send({ error: message });
@@ -57,12 +61,13 @@ export async function createHost(manager: RuntimeManager, origin: string, ssr?:
57
61
  app.get('/api/runtime', async () => manager.bootstrap());
58
62
  app.addContentTypeParser(uploadContentTypes, { parseAs: 'buffer', bodyLimit: MAX_IMAGE_BYTES }, (_request, body, done) => done(null, body));
59
63
  app.post('/api/uploads', { bodyLimit: MAX_IMAGE_BYTES }, async (request, reply) => {
60
- const { machineId } = z.object({ machineId: opaqueId }).strict().parse(request.query);
64
+ const { machineId, name } = z.object({ machineId: opaqueId, name: z.string().max(255).optional() }).strict().parse(request.query);
61
65
  const supervisor = manager.supervisors.get(machineId);
62
66
  if (!supervisor?.connected) return reply.code(409).send({ error: 'machine_disconnected' });
63
67
  if (!supervisor.target.acceptsLocalFiles) return reply.code(409).send({ error: 'uploads_unsupported_target' });
64
68
  if (!Buffer.isBuffer(request.body)) return reply.code(415).send({ error: 'unsupported_image' });
65
- return { path: await uploads.save(request.headers['content-type'], request.body) };
69
+ const contentType = request.headers['content-type'];
70
+ return { path: isFileUpload(contentType) ? await uploads.saveFile(name, request.body) : await uploads.save(contentType, request.body) };
66
71
  });
67
72
  app.get('/api/directories', async (request, reply) => {
68
73
  const { machineId, prefix } = z.object({ machineId: opaqueId, prefix: z.string().max(1000).refine((path) => !/[\x00-\x1f]/.test(path)) }).strict().parse(request.query);
@@ -87,10 +92,20 @@ export async function createHost(manager: RuntimeManager, origin: string, ssr?:
87
92
  supervisor.invalidate();
88
93
  return reply.code(result.state === 'unknown' ? 202 : 201).send(result);
89
94
  });
95
+ const projectIconTarget = z.object({ machineId: opaqueId, logicalId: z.string().min(1).max(1000) }).strict();
96
+ app.put('/api/projects/icon', { bodyLimit: MAX_PROJECT_ICON_BYTES }, async (request, reply) => {
97
+ const { machineId, logicalId } = projectIconTarget.parse(request.query);
98
+ if (!Buffer.isBuffer(request.body)) return reply.code(415).send({ error: 'unsupported_image' });
99
+ manager.setProjectIcon(machineId, logicalId, request.headers['content-type'], request.body); return { saved: true };
100
+ });
101
+ app.delete('/api/projects/icon', async (request) => {
102
+ const { machineId, logicalId } = projectIconTarget.parse(request.query);
103
+ manager.resetProjectIcon(machineId, logicalId); return { reset: true };
104
+ });
90
105
  app.get('/api/projects/:machineId/:projectId/icon', async (request, reply) => {
91
106
  const { machineId, projectId } = z.object({ machineId: opaqueId, projectId: opaqueId }).parse(request.params);
92
107
  const target = manager.supervisors.get(machineId)?.target;
93
- const icon = await target?.icon?.(projectId);
108
+ const icon = manager.customProjectIcon(machineId, projectId) ?? await target?.icon?.(projectId);
94
109
  if (!icon) return reply.code(404).send();
95
110
  if (icon.contentType === 'image/svg+xml') reply.header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'");
96
111
  return reply.type(icon.contentType).send(icon.bytes);
@@ -3,7 +3,9 @@ import type { TargetAdapter } from './target';
3
3
  import type { Thread } from '../../lib/models';
4
4
  import { canClosePane, insertAfter } from '../../lib/pane-strip';
5
5
  import { TargetSupervisor } from './supervisor';
6
- import { projectNameKey, reconcile } from './reconcile';
6
+ import { projectIconKey, projectNameKey, reconcile } from './reconcile';
7
+ import { imageExtension } from '../uploads';
8
+ import type { Icon } from '../transport/icons';
7
9
  import type { MetadataDatabase } from '../storage/database';
8
10
  import type { Bootstrap, Projection } from '../../shared/runtime';
9
11
  import type { Machine } from '../../lib/machines';
@@ -39,9 +41,9 @@ export class RuntimeManager extends EventEmitter {
39
41
  async close() { this.closed = true; for (const supervisor of this.supervisors.values()) supervisor.stop(); await this.journal.stop(); this.removeAllListeners(); }
40
42
  bootstrap(): Bootstrap {
41
43
  const projections = [...this.projections.values()];
42
- return { projections, projects: projections.flatMap((projection) => projection.projects), threads: projections.flatMap((projection) => projection.threads), machines: [...this.supervisors.values()].map((supervisor) => ({ id: supervisor.target.id, name: supervisor.target.name, session: supervisor.target.session, connected: supervisor.connected, projectPaths: Object.fromEntries(supervisor.target.locations.map((location) => [location.projectId, location.path])), harnesses: [], error: supervisor.error, writable: supervisor.target.writable, configVersion: this.configVersions.get(supervisor.target.id) })) };
44
+ return { projections, projects: projections.flatMap((projection) => projection.projects), threads: projections.flatMap((projection) => projection.threads), machines: [...this.supervisors.values()].map((supervisor) => ({ id: supervisor.target.id, name: supervisor.target.name, session: supervisor.target.session, connected: supervisor.connected, projectPaths: Object.fromEntries(supervisor.target.locations.map((location) => [location.projectId, location.path])), harnesses: [], error: supervisor.error, writable: supervisor.target.writable, os: supervisor.target.os, configVersion: this.configVersions.get(supervisor.target.id) })) };
43
45
  }
44
- private machine(supervisor: TargetSupervisor): Machine { return { id: supervisor.target.id, name: supervisor.target.name, session: supervisor.target.session, connected: supervisor.connected, configVersion: supervisor.target.configVersion, writable: supervisor.target.writable, error: supervisor.error, projectPaths: Object.fromEntries(supervisor.target.locations.map((location) => [location.projectId, location.path])), harnesses: [] }; }
46
+ private machine(supervisor: TargetSupervisor): Machine { return { id: supervisor.target.id, name: supervisor.target.name, session: supervisor.target.session, connected: supervisor.connected, configVersion: supervisor.target.configVersion, writable: supervisor.target.writable, os: supervisor.target.os, error: supervisor.error, projectPaths: Object.fromEntries(supervisor.target.locations.map((location) => [location.projectId, location.path])), harnesses: [] }; }
45
47
  async catalog(machineId: string, projectId?: string) {
46
48
  const supervisor = this.supervisors.get(machineId);
47
49
  if (!supervisor?.connected) throw new Error('machine_disconnected');
@@ -125,6 +127,26 @@ export class RuntimeManager extends EventEmitter {
125
127
  this.database.setSetting(projectNameKey(machineId, logicalId), name);
126
128
  this.publish(supervisor);
127
129
  }
130
+ setProjectIcon(machineId: string, logicalId: string, contentType: string | undefined, bytes: Buffer) {
131
+ const supervisor = this.supervisors.get(machineId);
132
+ if (!supervisor) throw new Error('machine_disconnected');
133
+ imageExtension(contentType, bytes);
134
+ this.database.setSetting(projectIconKey(machineId, logicalId), `${contentType!.split(';')[0].trim()}|${bytes.toString('base64')}`);
135
+ this.publish(supervisor);
136
+ }
137
+ resetProjectIcon(machineId: string, logicalId: string) {
138
+ const supervisor = this.supervisors.get(machineId);
139
+ if (!supervisor) throw new Error('machine_disconnected');
140
+ this.database.deleteSetting(projectIconKey(machineId, logicalId));
141
+ this.publish(supervisor);
142
+ }
143
+ customProjectIcon(machineId: string, projectId: string): Icon | undefined {
144
+ const logicalId = this.projections.get(machineId)?.projects.find((project) => project.id === projectId)?.logicalId;
145
+ const stored = logicalId && this.database.getSetting(projectIconKey(machineId, logicalId));
146
+ if (!stored) return;
147
+ const [contentType, data] = stored.split('|');
148
+ return { contentType, bytes: Buffer.from(data, 'base64') };
149
+ }
128
150
  async renameThread(machineId: string, threadId: string, title: string) {
129
151
  const supervisor = this.supervisors.get(machineId);
130
152
  const thread = this.projections.get(machineId)?.threads.find((thread) => thread.id === threadId);
@@ -5,6 +5,9 @@ import type { Project, Thread } from '../../lib/models';
5
5
  import type { TargetAdapter } from './target';
6
6
  import { orderPanes } from '../../lib/pane-strip';
7
7
 
8
+ export const projectIconKey = (machineId: string, logicalId: string) => `project-icon:${machineId}:${logicalId}`;
9
+ const iconRoute = (machineId: string, id: string, version = '') => `/api/projects/${encodeURIComponent(machineId)}/${encodeURIComponent(id)}/icon${version}`;
10
+ const customIconVersion = (value?: string) => value ? `?v=${createHash('sha1').update(value).digest('hex').slice(0, 8)}` : undefined;
8
11
  export const projectNameKey = (machineId: string, logicalId: string) => `project-name:${machineId}:${logicalId}`;
9
12
  export const projectIdentity = (machineId: string, repository: string) => createHash('sha256').update(machineId + '\0' + repository).digest('hex').slice(0, 32);
10
13
  const sameBinding = (thread: Thread, target: TargetAdapter) => thread.session === target.session && thread.bindingFingerprint === target.fingerprint && thread.bindingConfigVersion === target.configVersion;
@@ -17,6 +20,12 @@ const threadTitle = (label: string | null | undefined, panes: NativeSnapshot['pa
17
20
  return agent?.title?.trim() || agent?.terminal_title_stripped?.trim() || namedTab || fallback;
18
21
  };
19
22
 
23
+ function projectIconUrl(database: MetadataDatabase, target: TargetAdapter, id: string, logicalId: string, located: boolean) {
24
+ const custom = customIconVersion(database.getSetting(projectIconKey(target.id, logicalId)));
25
+ if (custom) return iconRoute(target.id, id, custom);
26
+ return target.icon && located ? iconRoute(target.id, id) : undefined;
27
+ }
28
+
20
29
  export function reconcile(database: MetadataDatabase, target: TargetAdapter, snapshot: NativeSnapshot): { threads: Thread[]; projects: Project[] } {
21
30
  const saved = database.threadRows(target.id);
22
31
  const projects: Project[] = [];
@@ -26,7 +35,7 @@ export function reconcile(database: MetadataDatabase, target: TargetAdapter, sna
26
35
  const location = target.locations.find((location) => location.workspaceId === workspace.workspace_id || location.path === cwd);
27
36
  const id = location?.projectId ?? projectIdentity(target.id, workspace.worktree?.repo_key ?? (cwd || workspace.workspace_id));
28
37
  const logicalId = location?.logicalId ?? workspace.worktree?.repo_key ?? id;
29
- if (!projects.some((project) => project.id === id)) projects.push({ id, machineId: target.id, localId: location?.localId, logicalId, name: database.getSetting(projectNameKey(target.id, logicalId)) ?? workspace.worktree?.repo_name ?? workspace.label, path: location?.path ?? cwd, color: '#9b83df', initial: (workspace.label[0] ?? 'P').toUpperCase(), iconUrl: target.icon && location ? `/api/projects/${encodeURIComponent(target.id)}/${encodeURIComponent(id)}/icon` : undefined });
38
+ if (!projects.some((project) => project.id === id)) projects.push({ id, machineId: target.id, localId: location?.localId, logicalId, name: database.getSetting(projectNameKey(target.id, logicalId)) ?? workspace.worktree?.repo_name ?? workspace.label, path: location?.path ?? cwd, color: '#9b83df', initial: (workspace.label[0] ?? 'P').toUpperCase(), iconUrl: projectIconUrl(database, target, id, logicalId, !!location) });
30
39
  for (const tab of snapshot.tabs.filter((tab) => tab.workspace_id === workspace.workspace_id)) tabProjects.set(tab.tab_id, id);
31
40
  }
32
41
  const launching = database.operations().some((operation) => operation.input.machineId === target.id && ['pending', 'running'].includes(operation.state));
@@ -1,4 +1,4 @@
1
- import type { Machine } from '../../lib/machines';
1
+ import type { Machine, MachineOs } from '../../lib/machines';
2
2
  import type { NativeSnapshot } from '../protocol/native';
3
3
  import type { TerminalFrame, TerminalStream } from '../terminal/cli';
4
4
  import type { LaunchInput } from '../../shared/runtime';
@@ -14,6 +14,7 @@ export type TargetAdapter = {
14
14
  enabled?: boolean;
15
15
  locations: LaunchLocation[];
16
16
  writable: boolean;
17
+ os?: MachineOs;
17
18
  acceptsLocalFiles?: boolean;
18
19
  subscribe(onEvent: () => void, onClose: (reason: string) => void, paneIds?: string[]): Promise<() => void>;
19
20
  snapshot(): Promise<NativeSnapshot>;
@@ -25,6 +25,7 @@ export class MetadataDatabase {
25
25
  }
26
26
  close() { this.sqlite.close(); }
27
27
  getSetting(key: string) { return this.db.select().from(schema.settings).where(eq(schema.settings.key, key)).get()?.value; }
28
+ deleteSetting(key: string) { this.db.delete(schema.settings).where(eq(schema.settings.key, key)).run(); }
28
29
  setSetting(key: string, value: string) { this.db.insert(schema.settings).values({ key, value }).onConflictDoUpdate({ target: schema.settings.key, set: { value } }).run(); }
29
30
  threadRows(machineId?: string, session?: string) {
30
31
  return this.db.select().from(schema.threads).where(machineId ? and(eq(schema.threads.machineId, machineId), session ? eq(schema.threads.session, session) : undefined) : undefined).all();
@@ -7,7 +7,7 @@ import { openCliStream } from '../terminal/cli';
7
7
  import { boundedProcess } from './process';
8
8
  import { SshForward, sshOptions, remoteCommand, quoteShell } from './ssh';
9
9
  import type { LaunchInput } from '../../shared/runtime';
10
- import type { Machine } from '../../lib/machines';
10
+ import type { Machine, MachineOs } from '../../lib/machines';
11
11
  import { harnessName } from '../../lib/models';
12
12
  import { acceptsModelFlag, herdrAgentKinds, isHerdrAgentKind } from '../../shared/agent-kinds';
13
13
  import { modelListings, parseModelListing } from './model-discovery';
@@ -23,16 +23,19 @@ import { expandHome, listLocalDirectories, suggestDirectories } from '../directo
23
23
  const MODEL_LISTING_TIMEOUT_MS = 15_000;
24
24
  const MODEL_CACHE_TTL_MS = 10 * 60_000;
25
25
  const MODEL_LISTING_LIMIT = 4 * 1024 * 1024;
26
+ const osFromPlatform = (platform: string): MachineOs | undefined => platform === 'darwin' || platform === 'Darwin' ? 'macos' : platform === 'win32' || /^(MINGW|MSYS|CYGWIN)/i.test(platform) ? 'windows' : platform.toLowerCase() === 'linux' ? 'linux' : undefined;
26
27
  type Dependencies = { process: typeof boundedProcess; cli: typeof openCliStream };
27
28
  export class HerdrTarget implements TargetAdapter {
28
29
  readonly id; readonly name; readonly session; readonly locations: LaunchLocation[];
29
30
  readonly fingerprint; configVersion = 1;
30
31
  get enabled() { return this.profile.enabled; }
31
32
  get writable() { return this.compatible; }
33
+ get os() { return this.profile.transport === 'local' ? osFromPlatform(process.platform) : this.remoteOs; }
32
34
  get acceptsLocalFiles() { return this.profile.transport === 'local'; }
33
35
  private api?: SocketApi;
34
36
  private forwarding?: SshForward;
35
37
  private compatible = false;
38
+ private remoteOs?: MachineOs;
36
39
  private connecting?: Promise<SocketApi>;
37
40
  private streams = new Set<ReturnType<typeof openCliStream>>();
38
41
  private icons = new Map<string, { expires: number; value: Promise<Icon | undefined> }>();
@@ -67,6 +70,7 @@ export class HerdrTarget implements TargetAdapter {
67
70
  const schema = JSON.parse(await this.dependencies.process(schemaCommand.command, schemaCommand.args, schemaCommand.env, 5000, 2 * 1024 * 1024));
68
71
  if (validateInstalledSchema(schema).length) { this.compatible = false; throw new Error('unsupported_herdr_schema'); }
69
72
  this.compatible = true;
73
+ if (this.profile.transport === 'ssh') void this.detectRemoteOs();
70
74
  return this.api;
71
75
  })();
72
76
  try { return await this.connecting; }
@@ -89,7 +93,7 @@ export class HerdrTarget implements TargetAdapter {
89
93
  if (tab.workspace_id !== workspace.workspace_id || pane.workspace_id !== workspace.workspace_id || pane.tab_id !== tab.tab_id) throw new Error('workspace_identity_mismatch');
90
94
  return { workspaceId: workspace.workspace_id, tabId: tab.tab_id, terminalId: pane.terminal_id };
91
95
  }
92
- async catalog(_projectId?: string): Promise<Machine> {
96
+ async catalog(projectId?: string): Promise<Machine> {
93
97
  const api = await this.connect();
94
98
  await api.request('ping');
95
99
  const run = (command: string, args: string[]) => this.runOnTarget(command, args);
@@ -97,7 +101,16 @@ export class HerdrTarget implements TargetAdapter {
97
101
  const present = (await Promise.all(herdrAgentKinds.map(async (kind) => (await isPresent(kind)) ? kind : undefined))).filter((kind): kind is string => !!kind);
98
102
  present.forEach((kind) => this.discoverModels(kind));
99
103
  const modelsPending = present.some((kind) => this.pendingModels.has(kind));
100
- return { id: this.id, name: this.name, session: this.session, connected: true, writable: this.writable, configVersion: this.configVersion, modelsPending, projectPaths: Object.fromEntries(this.locations.map((location) => [location.projectId, location.path])), harnesses: present.map((id) => ({ id, name: harnessName(id), models: ['Default', ...(this.discoveredModels.get(id)?.models ?? [])], customModels: acceptsModelFlag(id), launchEnabled: true })) };
104
+ const isGitRepo = await this.isGitRepo(projectId);
105
+ return { id: this.id, name: this.name, session: this.session, connected: true, writable: this.writable, configVersion: this.configVersion, modelsPending, isGitRepo, projectPaths: Object.fromEntries(this.locations.map((location) => [location.projectId, location.path])), harnesses: present.map((id) => ({ id, name: harnessName(id), models: ['Default', ...(this.discoveredModels.get(id)?.models ?? [])], customModels: acceptsModelFlag(id), launchEnabled: true })) };
106
+ }
107
+ private async detectRemoteOs() {
108
+ this.remoteOs = await this.runOnTarget('uname', ['-s']).then((output) => osFromPlatform(output.trim()), () => undefined);
109
+ }
110
+ private async isGitRepo(projectId?: string) {
111
+ const location = this.locations.find((location) => location.projectId === projectId);
112
+ if (!location) return true;
113
+ return this.runOnTarget('git', ['-C', await this.expandHome(location.path), 'rev-parse', '--is-inside-work-tree']).then(() => true, () => false);
101
114
  }
102
115
  private discoveredModels = new Map<string, { models: string[]; expires: number }>();
103
116
  private pendingModels = new Set<string>();
@@ -2,7 +2,8 @@ import { readdir, realpath, stat, readFile } from 'node:fs/promises';
2
2
  import { basename, dirname, extname, isAbsolute, join, relative, sep } from 'node:path';
3
3
 
4
4
  const contentTypes: Record<string, string> = { '.ico': 'image/x-icon', '.png': 'image/png', '.svg': 'image/svg+xml', '.webp': 'image/webp' };
5
- const directories = ['', 'public', 'static', 'assets', 'web', 'web/public', 'app/public', 'src/assets', 'src/app', 'frontend/public'];
5
+ const maxBytes = 2 * 1024 * 1024;
6
+ const directories = ['build/Icon.icon/Assets', '', 'public', 'static', 'assets', 'web', 'web/public', 'app/public', 'src/assets', 'src/app', 'frontend/public'];
6
7
  export type Icon = { bytes: Buffer; contentType: string };
7
8
 
8
9
  function withinRoot(root: string, path: string) {
@@ -37,9 +38,9 @@ async function iconInDirectory(root: string, directory: string): Promise<Icon |
37
38
  const file = await realpath(join(actual, entry.name));
38
39
  if (!withinRoot(root, file)) continue;
39
40
  const info = await stat(file);
40
- if (!info.isFile() || info.size <= 0 || info.size > 1024 * 1024) continue;
41
+ if (!info.isFile() || info.size <= 0 || info.size > maxBytes) continue;
41
42
  const bytes = await readFile(file);
42
- if (bytes.length <= 1024 * 1024) return { bytes, contentType: contentTypes[extname(entry.name).toLowerCase()] };
43
+ if (bytes.length <= maxBytes) return { bytes, contentType: contentTypes[extname(entry.name).toLowerCase()] };
43
44
  } catch {}
44
45
  }
45
46
  }
@@ -55,10 +56,17 @@ async function appDirectories(root: string) {
55
56
  .slice(0, 32).flatMap((entry) => ['public', 'static', 'assets', 'src/assets', 'src/app'].map((part) => join(apps, entry.name, part)));
56
57
  }
57
58
 
59
+ async function sourceDirectories(root: string) {
60
+ try {
61
+ return (await readdir(join(root, 'src'), { withFileTypes: true })).filter((entry) => entry.isDirectory())
62
+ .map((entry) => entry.name).sort().slice(0, 8).map((name) => join(root, 'src', name, 'public'));
63
+ } catch { return []; }
64
+ }
65
+
58
66
  export async function readApprovedIcon(path: string): Promise<Icon | undefined> {
59
67
  let root: string;
60
68
  try { root = await realpath(path); } catch { return; }
61
- for (const directory of [...directories.map((part) => join(root, part)), ...await appDirectories(root)]) {
69
+ for (const directory of [...directories.map((part) => join(root, part)), ...await sourceDirectories(root), ...await appDirectories(root)]) {
62
70
  const icon = await iconInDirectory(root, directory);
63
71
  if (icon) return icon;
64
72
  }
@@ -3,6 +3,8 @@ import { mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { join, resolve } from 'node:path';
4
4
 
5
5
  export const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
6
+ export const MAX_FILE_BYTES = 5 * 1024 * 1024;
7
+ export const FILE_CONTENT_TYPE = 'application/octet-stream';
6
8
  const UPLOAD_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000;
7
9
  const startsWith = (bytes: Buffer, signature: number[], offset = 0) => signature.every((byte, index) => bytes[offset + index] === byte);
8
10
  const ascii = (text: string) => [...text].map((character) => character.charCodeAt(0));
@@ -12,7 +14,9 @@ const imageTypes: Record<string, { extension: string; matches: (bytes: Buffer) =
12
14
  'image/gif': { extension: 'gif', matches: (bytes) => startsWith(bytes, ascii('GIF87a')) || startsWith(bytes, ascii('GIF89a')) },
13
15
  'image/webp': { extension: 'webp', matches: (bytes) => startsWith(bytes, ascii('RIFF')) && startsWith(bytes, ascii('WEBP'), 8) },
14
16
  };
15
- export const uploadContentTypes = Object.keys(imageTypes);
17
+ export const uploadContentTypes = [...Object.keys(imageTypes), FILE_CONTENT_TYPE];
18
+ export const isFileUpload = (contentType: string | undefined) => contentType?.split(';')[0].trim().toLowerCase() === FILE_CONTENT_TYPE;
19
+ const extensionOf = (name: string | undefined) => /\.[A-Za-z0-9]{1,10}$/.exec(name ?? '')?.[0].toLowerCase() ?? '';
16
20
 
17
21
  export function imageExtension(contentType: string | undefined, bytes: Buffer) {
18
22
  const type = imageTypes[contentType?.split(';')[0].trim().toLowerCase() ?? ''];
@@ -23,16 +27,23 @@ export function imageExtension(contentType: string | undefined, bytes: Buffer) {
23
27
  export class UploadStore {
24
28
  constructor(readonly directory = resolve(process.env.WEBR_UPLOADS ?? '.data/uploads'), private lifetimeMs = UPLOAD_LIFETIME_MS) {}
25
29
  async save(contentType: string | undefined, bytes: Buffer) {
26
- const extension = imageExtension(contentType, bytes);
30
+ return this.write(`image-${randomUUID()}.${imageExtension(contentType, bytes)}`, bytes);
31
+ }
32
+ async saveFile(name: string | undefined, bytes: Buffer) {
33
+ if (!bytes.length) throw new Error('empty_file');
34
+ if (bytes.length > MAX_FILE_BYTES) throw new Error('file_too_large');
35
+ return this.write(`file-${randomUUID()}${extensionOf(name)}`, bytes);
36
+ }
37
+ private async write(fileName: string, bytes: Buffer) {
27
38
  await mkdir(this.directory, { recursive: true, mode: 0o700 });
28
- const path = join(this.directory, `image-${randomUUID()}.${extension}`);
39
+ const path = join(this.directory, fileName);
29
40
  await writeFile(path, bytes, { mode: 0o600, flag: 'wx' });
30
41
  void this.removeExpired().catch(() => {});
31
42
  return path;
32
43
  }
33
44
  async removeExpired(now = Date.now()) {
34
45
  for (const name of await readdir(this.directory).catch(() => [] as string[])) {
35
- if (!/^image-[0-9a-f-]{36}\.(png|jpg|gif|webp)$/.test(name)) continue;
46
+ if (!/^(image-[0-9a-f-]{36}\.(png|jpg|gif|webp)|file-[0-9a-f-]{36}(\.[a-z0-9]{1,10})?)$/.test(name)) continue;
36
47
  const path = join(this.directory, name);
37
48
  if (now - (await stat(path)).mtimeMs > this.lifetimeMs) await rm(path, { force: true });
38
49
  }