@pablopunk/webr 0.3.10 → 0.3.11
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.
- package/dist/client/_astro/{App.CJakfIju.css → App.8h0zS6BR.css} +1 -1
- package/dist/client/_astro/{App.bmHm9djw.js → App.CCpaClja.js} +2 -2
- package/dist/server/chunks/{App_BbzCRPiT.mjs → App_DDDJNk_6.mjs} +43 -0
- package/dist/server/chunks/{_id___SMAFJxg.mjs → _id__Bg5VthsE.mjs} +1 -1
- package/dist/server/chunks/{_tab__O2UOM0DH.mjs → _tab__D95X1O-n.mjs} +1 -1
- package/dist/server/chunks/{new_n3R-7e9Z.mjs → new_C-ziQti8.mjs} +1 -1
- package/dist/server/chunks/{settings_CP_J5pGZ.mjs → settings_BYiZ6OG8.mjs} +1 -1
- package/dist/server/entry.mjs +5 -5
- package/package.json +1 -1
- package/src/server/host.ts +13 -0
- package/src/server/runtime/target.ts +2 -1
- package/src/server/transport/herdr-target.ts +10 -1
- package/src/server/transport/icons.ts +23 -5
|
@@ -345,6 +345,10 @@ async function setProjectIcon(project, png) {
|
|
|
345
345
|
body: png
|
|
346
346
|
})).ok) throw new Error("Could not save this icon.");
|
|
347
347
|
}
|
|
348
|
+
async function fetchIconCandidates(project) {
|
|
349
|
+
const response = await fetch(`/api/projects/${encodeURIComponent(project.machineId ?? "local")}/${encodeURIComponent(project.id)}/icon-candidates`);
|
|
350
|
+
return response.ok ? await response.json() : [];
|
|
351
|
+
}
|
|
348
352
|
async function resetProjectIcon(project) {
|
|
349
353
|
if (!(await fetch(projectIconUrl(project), { method: "DELETE" })).ok) throw new Error("Could not reset this icon.");
|
|
350
354
|
}
|
|
@@ -2457,6 +2461,22 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
|
|
|
2457
2461
|
const picker = useRef(null);
|
|
2458
2462
|
const [dragging, setDragging] = useState(false);
|
|
2459
2463
|
const [busy, setBusy] = useState(false);
|
|
2464
|
+
const [candidates, setCandidates] = useState([]);
|
|
2465
|
+
useEffect(() => {
|
|
2466
|
+
if (!open) return;
|
|
2467
|
+
let current = true;
|
|
2468
|
+
fetchIconCandidates(project).then((found) => {
|
|
2469
|
+
if (current) setCandidates(found);
|
|
2470
|
+
}).catch(() => {});
|
|
2471
|
+
return () => {
|
|
2472
|
+
current = false;
|
|
2473
|
+
setCandidates([]);
|
|
2474
|
+
};
|
|
2475
|
+
}, [
|
|
2476
|
+
open,
|
|
2477
|
+
project.id,
|
|
2478
|
+
project.machineId
|
|
2479
|
+
]);
|
|
2460
2480
|
const run = async (action) => {
|
|
2461
2481
|
setBusy(true);
|
|
2462
2482
|
try {
|
|
@@ -2471,6 +2491,7 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
|
|
|
2471
2491
|
const upload = (file) => {
|
|
2472
2492
|
if (file) run(async () => setProjectIcon(project, await squarePng(file)));
|
|
2473
2493
|
};
|
|
2494
|
+
const choose = (candidate) => void run(async () => setProjectIcon(project, await squarePng(await (await fetch(candidate.url)).blob())));
|
|
2474
2495
|
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
2475
2496
|
open,
|
|
2476
2497
|
onOpenChange: (next) => {
|
|
@@ -2483,6 +2504,10 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
|
|
|
2483
2504
|
className: "dialog-message",
|
|
2484
2505
|
children: ["Change icon for ", project.name]
|
|
2485
2506
|
}),
|
|
2507
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2508
|
+
className: "icon-current",
|
|
2509
|
+
children: [/* @__PURE__ */ jsx(ProjectIcon, { project }), /* @__PURE__ */ jsx("span", { children: "Current icon" })]
|
|
2510
|
+
}),
|
|
2486
2511
|
/* @__PURE__ */ jsxs("button", {
|
|
2487
2512
|
type: "button",
|
|
2488
2513
|
className: `icon-dropzone ${dragging ? "is-dragging" : ""}`,
|
|
@@ -2513,6 +2538,24 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
|
|
|
2513
2538
|
event.target.value = "";
|
|
2514
2539
|
}
|
|
2515
2540
|
}),
|
|
2541
|
+
candidates.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
2542
|
+
className: "icon-candidates",
|
|
2543
|
+
role: "group",
|
|
2544
|
+
"aria-label": "Images found in this project",
|
|
2545
|
+
children: candidates.map((candidate) => /* @__PURE__ */ jsx("button", {
|
|
2546
|
+
type: "button",
|
|
2547
|
+
className: "icon-candidate",
|
|
2548
|
+
disabled: busy,
|
|
2549
|
+
title: candidate.name,
|
|
2550
|
+
"aria-label": `Use ${candidate.name}`,
|
|
2551
|
+
onClick: () => choose(candidate),
|
|
2552
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
2553
|
+
src: candidate.url,
|
|
2554
|
+
alt: "",
|
|
2555
|
+
loading: "lazy"
|
|
2556
|
+
})
|
|
2557
|
+
}, candidate.url))
|
|
2558
|
+
}),
|
|
2516
2559
|
/* @__PURE__ */ jsxs("div", {
|
|
2517
2560
|
className: "dialog-actions",
|
|
2518
2561
|
children: [/* @__PURE__ */ jsx("button", {
|
|
@@ -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 "./
|
|
3
|
+
import { n as $$Base, t as App } from "./App_DDDJNk_6.mjs";
|
|
4
4
|
//#region src/pages/threads/[id].astro
|
|
5
5
|
var _id__exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
default: () => $$Id,
|
|
@@ -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 "./
|
|
3
|
+
import { n as $$Base, t as App } from "./App_DDDJNk_6.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 "./
|
|
3
|
+
import { n as $$Base, t as App } from "./App_DDDJNk_6.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 "./
|
|
3
|
+
import { n as $$Base, t as App } from "./App_DDDJNk_6.mjs";
|
|
4
4
|
//#region src/pages/settings.astro
|
|
5
5
|
var settings_exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
default: () => $$Settings,
|
package/dist/server/entry.mjs
CHANGED
|
@@ -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/
|
|
2430
|
-
var _page2 = () => import("./chunks/
|
|
2431
|
-
var _page3 = () => import("./chunks/
|
|
2432
|
-
var _page4 = () => import("./chunks/
|
|
2429
|
+
var _page1 = () => import("./chunks/new_C-ziQti8.mjs");
|
|
2430
|
+
var _page2 = () => import("./chunks/_tab__D95X1O-n.mjs");
|
|
2431
|
+
var _page3 = () => import("./chunks/settings_BYiZ6OG8.mjs");
|
|
2432
|
+
var _page4 = () => import("./chunks/_id__Bg5VthsE.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.
|
|
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.8h0zS6BR.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.8h0zS6BR.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.8h0zS6BR.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.8h0zS6BR.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__Bg5VthsE.mjs","\u0000virtual:astro:page:src/pages/sessions/[session]/tabs/[tab]@_@astro":"chunks/_tab__D95X1O-n.mjs","\u0000virtual:astro:page:src/pages/index@_@astro":"chunks/index_Bvv1gpI0.mjs","\u0000virtual:astro:page:src/pages/new@_@astro":"chunks/new_C-ziQti8.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_BYiZ6OG8.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.CCpaClja.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.CCpaClja.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.8h0zS6BR.css","/_astro/jetbrains-mono.CFJaiWaY.woff2","/_astro/symbols-nerd.CEdkKAK8.woff2"],"buildFormat":"directory","checkOrigin":true,"actionBodySizeLimit":1048576,"serverIslandBodySizeLimit":1048576,"allowedDomains":[],"key":"zhV/mlRUF6aAPRL/kGMDJ+5B47sw/eEY3vjPTu56EeA=","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
package/src/server/host.ts
CHANGED
|
@@ -111,6 +111,19 @@ export async function createHost(manager: RuntimeManager, origin: string, ssr?:
|
|
|
111
111
|
if (icon.contentType === 'image/svg+xml') reply.header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'");
|
|
112
112
|
return reply.type(icon.contentType).send(icon.bytes);
|
|
113
113
|
});
|
|
114
|
+
const candidateParams = z.object({ machineId: opaqueId, projectId: opaqueId });
|
|
115
|
+
app.get('/api/projects/:machineId/:projectId/icon-candidates', async (request) => {
|
|
116
|
+
const { machineId, projectId } = candidateParams.parse(request.params);
|
|
117
|
+
const found = await manager.supervisors.get(machineId)?.target.iconCandidates?.(projectId) ?? [];
|
|
118
|
+
return found.map((candidate, index) => ({ name: candidate.name, url: `/api/projects/${encodeURIComponent(machineId)}/${encodeURIComponent(projectId)}/icon-candidates/${index}` }));
|
|
119
|
+
});
|
|
120
|
+
app.get('/api/projects/:machineId/:projectId/icon-candidates/:index', async (request, reply) => {
|
|
121
|
+
const { machineId, projectId, index } = candidateParams.extend({ index: z.coerce.number().int().min(0).max(99) }).parse(request.params);
|
|
122
|
+
const candidate = (await manager.supervisors.get(machineId)?.target.iconCandidates?.(projectId))?.[index];
|
|
123
|
+
if (!candidate) return reply.code(404).send();
|
|
124
|
+
if (candidate.contentType === 'image/svg+xml') reply.header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'");
|
|
125
|
+
return reply.type(candidate.contentType).send(candidate.bytes);
|
|
126
|
+
});
|
|
114
127
|
app.post('/api/threads', async (request, reply) => {
|
|
115
128
|
const input = launchInput.parse(request.body);
|
|
116
129
|
const key = z.uuid().parse(request.headers['idempotency-key']);
|
|
@@ -3,7 +3,7 @@ import type { NativeSnapshot } from '../protocol/native';
|
|
|
3
3
|
import type { TerminalFrame, TerminalStream } from '../terminal/cli';
|
|
4
4
|
import type { LaunchInput } from '../../shared/runtime';
|
|
5
5
|
import type { WorktreeEntry } from './herdr-actions';
|
|
6
|
-
import type { Icon } from '../transport/icons';
|
|
6
|
+
import type { Icon, IconCandidate } from '../transport/icons';
|
|
7
7
|
|
|
8
8
|
export type TerminalDirection = 'right' | 'down';
|
|
9
9
|
export type LaunchLocation = { projectId: string; localId?: string; logicalId?: string; path: string; workspaceId: string };
|
|
@@ -22,6 +22,7 @@ export type TargetAdapter = {
|
|
|
22
22
|
catalog(projectId?: string): Promise<Machine>;
|
|
23
23
|
canLaunch?(input: LaunchInput): boolean;
|
|
24
24
|
icon?(projectId: string): Promise<Icon | undefined>;
|
|
25
|
+
iconCandidates?(projectId: string): Promise<IconCandidate[]>;
|
|
25
26
|
worktrees?(projectId: string): Promise<WorktreeEntry[]>;
|
|
26
27
|
openWorktree?(projectId: string, path: string): Promise<void>;
|
|
27
28
|
suggestDirectories?(prefix: string): Promise<string[]>;
|
|
@@ -13,7 +13,7 @@ import { harnessName } from '../../lib/models';
|
|
|
13
13
|
import { acceptsModelFlag, herdrAgentKinds, isHerdrAgentKind } from '../../shared/agent-kinds';
|
|
14
14
|
import { modelListings, parseModelListing } from './model-discovery';
|
|
15
15
|
import { HerdrActions } from '../runtime/herdr-actions';
|
|
16
|
-
import { readApprovedIcon, type Icon } from './icons';
|
|
16
|
+
import { listIconCandidates, readApprovedIcon, type Icon, type IconCandidate } from './icons';
|
|
17
17
|
import { targetFingerprint } from './identity';
|
|
18
18
|
import { scopedProjectId } from '../../shared/projects';
|
|
19
19
|
import { validateInstalledSchema } from '../protocol/validate';
|
|
@@ -41,6 +41,7 @@ export class HerdrTarget implements TargetAdapter {
|
|
|
41
41
|
private connecting?: Promise<SocketApi>;
|
|
42
42
|
private streams = new Set<ReturnType<typeof openCliStream>>();
|
|
43
43
|
private icons = new Map<string, { expires: number; value: Promise<Icon | undefined> }>();
|
|
44
|
+
private candidates = new Map<string, { expires: number; value: Promise<IconCandidate[]> }>();
|
|
44
45
|
constructor(private profile: TargetProfile, private dependencies: Dependencies = { process: boundedProcess, cli: openCliStream }) {
|
|
45
46
|
if (profile.transport === 'ssh') this.storeUpload = (fileName, bytes) => uploadOverSsh(profile.host!, fileName, bytes);
|
|
46
47
|
this.id = profile.id; this.name = profile.name; this.session = profile.session;
|
|
@@ -149,6 +150,14 @@ export class HerdrTarget implements TargetAdapter {
|
|
|
149
150
|
const value = readApprovedIcon(location.path);
|
|
150
151
|
this.icons.set(projectId, { expires: Date.now() + 60_000, value }); return value;
|
|
151
152
|
}
|
|
153
|
+
async iconCandidates(projectId: string) {
|
|
154
|
+
const location = this.locations.find((location) => location.projectId === projectId);
|
|
155
|
+
if (this.profile.transport !== 'local' || !location) return [];
|
|
156
|
+
const cached = this.candidates.get(projectId);
|
|
157
|
+
if (cached && cached.expires > Date.now()) return cached.value;
|
|
158
|
+
const value = listIconCandidates(location.path);
|
|
159
|
+
this.candidates.set(projectId, { expires: Date.now() + 60_000, value }); return value;
|
|
160
|
+
}
|
|
152
161
|
openTerminal: TargetAdapter['openTerminal'] = (terminalId, mode, cols, rows, takeover, onFrame, onClose) => {
|
|
153
162
|
if (!this.compatible || mode === 'control' && !this.writable || mode === 'observe' && takeover) throw new Error('terminal_control_unavailable');
|
|
154
163
|
const command = this.command(['terminal', 'session', mode, terminalId, '--cols', String(cols), '--rows', String(rows), ...(takeover ? ['--takeover'] : [])]);
|
|
@@ -5,6 +5,8 @@ const contentTypes: Record<string, string> = { '.ico': 'image/x-icon', '.png': '
|
|
|
5
5
|
const maxBytes = 2 * 1024 * 1024;
|
|
6
6
|
const directories = ['build/Icon.icon/Assets', '', 'public', 'static', 'assets', 'web', 'web/public', 'app/public', 'src/assets', 'src/app', 'frontend/public'];
|
|
7
7
|
export type Icon = { bytes: Buffer; contentType: string };
|
|
8
|
+
export type IconCandidate = Icon & { name: string; rank: number };
|
|
9
|
+
const maxCandidates = 12;
|
|
8
10
|
|
|
9
11
|
function withinRoot(root: string, path: string) {
|
|
10
12
|
const part = relative(root, path);
|
|
@@ -24,7 +26,7 @@ function rank(name: string) {
|
|
|
24
26
|
return Infinity;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
async function
|
|
29
|
+
async function* iconsInDirectory(root: string, directory: string): AsyncGenerator<IconCandidate> {
|
|
28
30
|
let actual: string;
|
|
29
31
|
try { actual = await realpath(directory); } catch { return; }
|
|
30
32
|
if (!withinRoot(root, actual)) return;
|
|
@@ -40,7 +42,7 @@ async function iconInDirectory(root: string, directory: string): Promise<Icon |
|
|
|
40
42
|
const info = await stat(file);
|
|
41
43
|
if (!info.isFile() || info.size <= 0 || info.size > maxBytes) continue;
|
|
42
44
|
const bytes = await readFile(file);
|
|
43
|
-
if (bytes.length <= maxBytes)
|
|
45
|
+
if (bytes.length <= maxBytes) yield { bytes, contentType: contentTypes[extname(entry.name).toLowerCase()], name: relative(root, file), rank: rank(entry.name) };
|
|
44
46
|
} catch {}
|
|
45
47
|
}
|
|
46
48
|
}
|
|
@@ -63,11 +65,27 @@ async function sourceDirectories(root: string) {
|
|
|
63
65
|
} catch { return []; }
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
async function searchDirectories(root: string) {
|
|
69
|
+
return [...directories.map((part) => join(root, part)), ...await sourceDirectories(root), ...await appDirectories(root)];
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
export async function readApprovedIcon(path: string): Promise<Icon | undefined> {
|
|
67
73
|
let root: string;
|
|
68
74
|
try { root = await realpath(path); } catch { return; }
|
|
69
|
-
for (const directory of
|
|
70
|
-
const icon
|
|
71
|
-
|
|
75
|
+
for (const directory of await searchDirectories(root)) {
|
|
76
|
+
for await (const icon of iconsInDirectory(root, directory)) return icon;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function listIconCandidates(path: string): Promise<IconCandidate[]> {
|
|
81
|
+
let root: string;
|
|
82
|
+
try { root = await realpath(path); } catch { return []; }
|
|
83
|
+
const seen = new Set<string>(); const found: IconCandidate[] = [];
|
|
84
|
+
for (const directory of await searchDirectories(root)) {
|
|
85
|
+
for await (const icon of iconsInDirectory(root, directory)) {
|
|
86
|
+
if (seen.has(icon.name)) continue;
|
|
87
|
+
seen.add(icon.name); found.push(icon);
|
|
88
|
+
}
|
|
72
89
|
}
|
|
90
|
+
return found.sort((a, b) => a.rank - b.rank).slice(0, maxCandidates);
|
|
73
91
|
}
|