@mindstudio-ai/remy 0.1.308 → 0.1.310

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/headless.js CHANGED
@@ -3243,6 +3243,7 @@ async function renderHtmlViaSidecar(opts) {
3243
3243
  html: opts.html,
3244
3244
  width: opts.width,
3245
3245
  height: opts.height,
3246
+ ...opts.autoHeight ? { autoHeight: true } : {},
3246
3247
  ...opts.transparent ? { transparent: true } : {},
3247
3248
  ...opts.scale != null ? { scale: opts.scale } : {}
3248
3249
  },
@@ -4566,7 +4567,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4566
4567
  ## What Remy apps are NOT good for
4567
4568
 
4568
4569
  - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
4569
- - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
4570
+ - Fast-twitch multiplayer and live co-editing (shared cursors, 60fps sync) \u2014 client\u2192server is always a method invoke, so sub-100ms bidirectional interaction isn't a fit.
4570
4571
  </platform_brief>`;
4571
4572
  }
4572
4573
 
@@ -4933,9 +4934,146 @@ async function execute2(input, onLog) {
4933
4934
  // src/subagents/designExpert/tools/analyzeDesign.ts
4934
4935
  var analyzeDesign_exports = {};
4935
4936
  __export(analyzeDesign_exports, {
4937
+ definition: () => definition4,
4938
+ execute: () => execute4
4939
+ });
4940
+ import { readFile as readFile2 } from "fs/promises";
4941
+ import { extname as extname2, join as join2 } from "path";
4942
+
4943
+ // src/subagents/designExpert/tools/images/renderImage.ts
4944
+ var renderImage_exports = {};
4945
+ __export(renderImage_exports, {
4946
+ RENDER_ANALYZE_PROMPT: () => RENDER_ANALYZE_PROMPT,
4936
4947
  definition: () => definition3,
4937
4948
  execute: () => execute3
4938
4949
  });
4950
+ import { mkdir, unlink, writeFile } from "fs/promises";
4951
+ import { tmpdir } from "os";
4952
+ import { dirname, join, resolve as resolve2, sep } from "path";
4953
+ import { randomUUID } from "crypto";
4954
+ var MIN_DIMENSION = 16;
4955
+ var MAX_DIMENSION = 4096;
4956
+ var RENDER_ANALYZE_PROMPT = 'You are reviewing a browser-rendered graphic (composed from HTML/CSS by a designer) for fidelity. Report: whether the composition fills the full canvas or leaves unintended gaps at any edge, any clipped or overflowing text, whether custom webfonts appear to have loaded (distinctive letterforms vs generic fallback serif/sans), any misalignment or uneven spacing, any unintended scrollbars or default-styling artifacts, and \u2014 if the background is transparent \u2014 any fringing or stray opaque pixels at the edges. Then briefly describe the overall composition and how polished it looks. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Render Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
4957
+ var definition3 = {
4958
+ name: "renderImage",
4959
+ description: "Render a self-contained HTML document in a real browser and capture it as a hosted PNG at exact pixel dimensions, with a fidelity review included. Deterministic \u2014 exact hex colors, real loaded webfonts, precise geometry \u2014 unlike generateImages, which is an image model. Use for token-exact graphics: Open Graph share cards, wordmarks, flat/geometric icon tiles, badges, any composition where letterforms and spacing carry the design. Compose with HTML/CSS (link webfonts from CDNs \u2014 the renderer waits for them to load); inline existing SVG markup when needed, but never hand-write new SVG path data.",
4960
+ inputSchema: {
4961
+ type: "object",
4962
+ properties: {
4963
+ html: {
4964
+ type: "string",
4965
+ description: "A complete, self-contained HTML document sized to fill the viewport (style html/body to the full dimensions with margin 0). External webfonts and images from CDNs are fine \u2014 loading is awaited before capture."
4966
+ },
4967
+ width: {
4968
+ type: "number",
4969
+ description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
4970
+ },
4971
+ height: {
4972
+ type: "number",
4973
+ description: "Viewport height in CSS pixels. Range: 16-4096."
4974
+ },
4975
+ scale: {
4976
+ type: "number",
4977
+ description: "Device scale factor, 1-3. Output pixels = css \xD7 scale. Use 2 for crisp icon masters (e.g. a 512\xD7512 document captured at 1024\xD71024)."
4978
+ },
4979
+ transparentBackground: {
4980
+ type: "boolean",
4981
+ description: "Capture with true alpha: leave the document background transparent (no background on html/body) and the PNG keeps it. No background-removal model involved."
4982
+ },
4983
+ savePath: {
4984
+ type: "string",
4985
+ description: "Optional project-relative path to also save the PNG into the app (e.g. 'dist/interfaces/web/public/og-image.png' so the deployed site self-hosts it)."
4986
+ }
4987
+ },
4988
+ required: ["html", "width", "height"]
4989
+ }
4990
+ };
4991
+ async function execute3(input, onLog, context) {
4992
+ const html = typeof input.html === "string" ? input.html : "";
4993
+ const width = Math.round(Number(input.width));
4994
+ const height = Math.round(Number(input.height));
4995
+ if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
4996
+ return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
4997
+ }
4998
+ const release = await acquireBrowserLock();
4999
+ let rendered;
5000
+ try {
5001
+ onLog?.("Rendering document in the sandbox browser...");
5002
+ rendered = await renderHtmlViaSidecar({
5003
+ html,
5004
+ width,
5005
+ height,
5006
+ transparent: input.transparentBackground === true,
5007
+ scale: typeof input.scale === "number" ? input.scale : void 0
5008
+ });
5009
+ } catch (err) {
5010
+ return `Error: render failed: ${err?.message ?? err}`;
5011
+ } finally {
5012
+ release();
5013
+ }
5014
+ let bytes;
5015
+ try {
5016
+ const res = await fetch(rendered.url);
5017
+ if (!res.ok) {
5018
+ throw new Error(`fetch returned ${res.status}`);
5019
+ }
5020
+ bytes = Buffer.from(await res.arrayBuffer());
5021
+ } catch (err) {
5022
+ return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
5023
+ }
5024
+ let savedPath;
5025
+ if (typeof input.savePath === "string" && input.savePath) {
5026
+ const absolute = resolve2(PROJECT_ROOT, input.savePath);
5027
+ if (!absolute.startsWith(PROJECT_ROOT + sep)) {
5028
+ return "Error: savePath must resolve inside the project.";
5029
+ }
5030
+ await mkdir(dirname(absolute), { recursive: true });
5031
+ await writeFile(absolute, bytes);
5032
+ savedPath = input.savePath;
5033
+ }
5034
+ onLog?.("Hosting the capture...");
5035
+ const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
5036
+ let url = rendered.url;
5037
+ let temporary = true;
5038
+ try {
5039
+ await writeFile(tmpPath, bytes);
5040
+ const upload = await runMindstudioCliResult(["upload", tmpPath], {
5041
+ timeout: 6e4,
5042
+ onLog,
5043
+ caller: "designExpert"
5044
+ });
5045
+ const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
5046
+ if (match?.length) {
5047
+ url = match[match.length - 1];
5048
+ temporary = false;
5049
+ }
5050
+ } finally {
5051
+ await unlink(tmpPath).catch(() => {
5052
+ });
5053
+ }
5054
+ const analysis = await analyzeImage({
5055
+ prompt: RENDER_ANALYZE_PROMPT,
5056
+ image: url,
5057
+ onLog,
5058
+ model: resolveModel("imageAnalysis", context?.models, context?.model)
5059
+ }).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
5060
+ return JSON.stringify({
5061
+ images: [
5062
+ {
5063
+ url,
5064
+ ...temporary ? {
5065
+ note: "Durable hosting failed \u2014 this URL is dev-session scratch storage and may expire. Do not use it for app metadata; retry if a durable URL is needed."
5066
+ } : {},
5067
+ ...savedPath ? { savedPath } : {},
5068
+ analysis,
5069
+ width: rendered.width,
5070
+ height: rendered.height
5071
+ }
5072
+ ]
5073
+ });
5074
+ }
5075
+
5076
+ // src/subagents/designExpert/tools/analyzeDesign.ts
4939
5077
  var DESIGN_REFERENCE_PROMPT = `
4940
5078
  You are analyzing a screenshot of a real website or app for a designer's personal technique/inspiration reference notes.
4941
5079
 
@@ -4959,15 +5097,15 @@ Identify the specific design moves that make this page interesting and unique, d
4959
5097
 
4960
5098
  Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
4961
5099
  `;
4962
- var definition3 = {
5100
+ var definition4 = {
4963
5101
  name: "analyzeDesign",
4964
- description: "Analyze the visual design of a website, an image URL, or an image file on disk. Websites are automatically screenshotted first. Provides static image analysis only, will not capture animations or video. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific design question instead. Use a bulleted list to ask many questions at once.",
5102
+ description: "Analyze the visual design of a website, an image (URL or file on disk), or an HTML document on disk. Websites are screenshotted first; an HTML file is rendered in a real browser first and the render is analyzed, which is how you look at a wireframe you authored. Provides static image analysis only, will not capture animations or video. With no prompt, a website or image gets a full design reference analysis (mood, color, typography, layout, distinctiveness) and a rendered HTML document gets a fidelity review (clipping, overflow, webfont loading, alignment). Provide a custom prompt to ask a specific design question instead. Use a bulleted list to ask many questions at once.",
4965
5103
  inputSchema: {
4966
5104
  type: "object",
4967
5105
  properties: {
4968
5106
  url: {
4969
5107
  type: "string",
4970
- description: "What to analyze: a website URL (will be screenshotted), an image URL, or the path of an image file on disk (e.g. a reference the user uploaded, under src/.user-uploads/). Local files are hosted automatically and their URL comes back in the result."
5108
+ description: "What to analyze: a website URL (will be screenshotted), an image URL, the path of an image file on disk (e.g. a reference the user uploaded, under src/.user-uploads/), or the path of an HTML file on disk (rendered in a browser, then analyzed). Local files are hosted automatically and their URL comes back in the result."
4971
5109
  },
4972
5110
  prompt: {
4973
5111
  type: "string",
@@ -4977,53 +5115,140 @@ var definition3 = {
4977
5115
  required: ["url"]
4978
5116
  }
4979
5117
  };
4980
- async function execute3(input, onLog, context) {
4981
- const url = input.url;
4982
- const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
4983
- const isImage = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
4984
- let image = url;
4985
- if (!isImage) {
4986
- const ss = await runMindstudioCliResult(
4987
- [
4988
- "screenshot-url",
4989
- "--url",
4990
- url,
4991
- "--mode",
4992
- "viewport",
4993
- "--width",
4994
- "1440",
4995
- "--delay",
4996
- "2000"
4997
- ],
4998
- {
4999
- outputKey: "screenshotUrl",
5000
- timeout: 12e4,
5001
- onLog,
5002
- caller: "designExpert"
5003
- }
5118
+ var IMAGE_EXT_RE = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i;
5119
+ var HTML_EXTS = /* @__PURE__ */ new Set([".html", ".htm"]);
5120
+ var HTML_RENDER_WIDTH = 1280;
5121
+ var HTML_RENDER_START_HEIGHT = 1400;
5122
+ function splitFrontmatter(raw) {
5123
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n?/);
5124
+ if (!match) {
5125
+ return { html: raw };
5126
+ }
5127
+ const fields = {};
5128
+ for (const line of match[1].split("\n")) {
5129
+ const idx = line.indexOf(":");
5130
+ if (idx > 0) {
5131
+ fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
5132
+ }
5133
+ }
5134
+ return {
5135
+ name: fields.name,
5136
+ description: fields.description,
5137
+ html: raw.slice(match[0].length)
5138
+ };
5139
+ }
5140
+ async function execute4(input, onLog, context) {
5141
+ const url = String(input.url ?? "").trim();
5142
+ if (!url) {
5143
+ return "Error: url is required.";
5144
+ }
5145
+ const customPrompt = typeof input.prompt === "string" && input.prompt.trim() ? input.prompt : void 0;
5146
+ if (!isFetchableUrl(url)) {
5147
+ if (HTML_EXTS.has(extname2(url).toLowerCase())) {
5148
+ return analyzeLocalHtml(url, customPrompt, onLog, context);
5149
+ }
5150
+ return analyze(
5151
+ url,
5152
+ customPrompt ?? DESIGN_REFERENCE_PROMPT,
5153
+ onLog,
5154
+ context
5004
5155
  );
5005
- if (!ss.ok) {
5006
- return `Could not screenshot ${url}: ${ss.value}`;
5156
+ }
5157
+ if (IMAGE_EXT_RE.test(url)) {
5158
+ return analyze(
5159
+ url,
5160
+ customPrompt ?? DESIGN_REFERENCE_PROMPT,
5161
+ onLog,
5162
+ context
5163
+ );
5164
+ }
5165
+ const ss = await runMindstudioCliResult(
5166
+ [
5167
+ "screenshot-url",
5168
+ "--url",
5169
+ url,
5170
+ "--mode",
5171
+ "viewport",
5172
+ "--width",
5173
+ "1440",
5174
+ "--delay",
5175
+ "2000"
5176
+ ],
5177
+ {
5178
+ outputKey: "screenshotUrl",
5179
+ timeout: 12e4,
5180
+ onLog,
5181
+ caller: "designExpert"
5007
5182
  }
5008
- image = ss.value;
5183
+ );
5184
+ if (!ss.ok) {
5185
+ return `Could not screenshot ${url}: ${ss.value}`;
5009
5186
  }
5187
+ return analyze(
5188
+ ss.value,
5189
+ customPrompt ?? DESIGN_REFERENCE_PROMPT,
5190
+ onLog,
5191
+ context
5192
+ );
5193
+ }
5194
+ async function analyze(image, prompt, onLog, context, extra) {
5010
5195
  const analyzed = await analyzeImage({
5011
- prompt: analysisPrompt,
5196
+ prompt,
5012
5197
  image,
5013
5198
  apiConfig: context?.apiConfig,
5014
5199
  onLog,
5015
5200
  model: resolveModel("imageAnalysis", context?.models, context?.model)
5016
5201
  });
5017
- return JSON.stringify({ url: analyzed.url, analysis: analyzed.analysis });
5202
+ return JSON.stringify({
5203
+ url: analyzed.url,
5204
+ analysis: analyzed.analysis,
5205
+ ...extra
5206
+ });
5207
+ }
5208
+ async function analyzeLocalHtml(path13, customPrompt, onLog, context) {
5209
+ let raw;
5210
+ try {
5211
+ raw = await readFile2(join2(PROJECT_ROOT, path13), "utf8");
5212
+ } catch {
5213
+ return `No file at "${path13}". Paths resolve from the project root; list the directory to check the name.`;
5214
+ }
5215
+ const { name, description, html } = splitFrontmatter(raw);
5216
+ if (!html.trim()) {
5217
+ return `"${path13}" has no HTML to render.`;
5218
+ }
5219
+ onLog?.(`Rendering ${path13} in the sandbox browser...`);
5220
+ const release = await acquireBrowserLock();
5221
+ let rendered;
5222
+ try {
5223
+ rendered = await renderHtmlViaSidecar({
5224
+ html,
5225
+ width: HTML_RENDER_WIDTH,
5226
+ height: HTML_RENDER_START_HEIGHT,
5227
+ autoHeight: true
5228
+ });
5229
+ } catch (err) {
5230
+ return `Could not render ${path13}: ${err?.message ?? err}`;
5231
+ } finally {
5232
+ release();
5233
+ }
5234
+ const basePrompt = customPrompt ?? RENDER_ANALYZE_PROMPT;
5235
+ const subject = [name, description].filter(Boolean).join(" \u2014 ");
5236
+ const prompt = subject ? `The document rendered below is described by its author as: ${subject}
5237
+
5238
+ ${basePrompt}` : basePrompt;
5239
+ return analyze(rendered.url, prompt, onLog, context, {
5240
+ renderedWidth: rendered.width,
5241
+ renderedHeight: rendered.height
5242
+ });
5018
5243
  }
5019
5244
 
5020
5245
  // src/subagents/designExpert/tools/analyzeImage.ts
5021
5246
  var analyzeImage_exports = {};
5022
5247
  __export(analyzeImage_exports, {
5023
- definition: () => definition4,
5024
- execute: () => execute4
5248
+ definition: () => definition5,
5249
+ execute: () => execute5
5025
5250
  });
5026
- var definition4 = {
5251
+ var definition5 = {
5027
5252
  name: "analyzeImage",
5028
5253
  description: "Analyze an image using a vision model. Provides static image analysis only, will not capture animations or video. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. If you are analyzing a screenshot of the app preview, you can reuse the same screenshot URL multiple times to ask multiple questions.",
5029
5254
  inputSchema: {
@@ -5044,7 +5269,7 @@ var definition4 = {
5044
5269
  required: ["imageUrl"]
5045
5270
  }
5046
5271
  };
5047
- async function execute4(input, onLog, context) {
5272
+ async function execute5(input, onLog, context) {
5048
5273
  const prompt = buildScreenshotAnalysisPrompt({
5049
5274
  prompt: input.prompt
5050
5275
  });
@@ -5061,8 +5286,8 @@ async function execute4(input, onLog, context) {
5061
5286
  // src/subagents/designExpert/tools/images/generateImages.ts
5062
5287
  var generateImages_exports = {};
5063
5288
  __export(generateImages_exports, {
5064
- definition: () => definition5,
5065
- execute: () => execute5
5289
+ definition: () => definition6,
5290
+ execute: () => execute6
5066
5291
  });
5067
5292
 
5068
5293
  // src/subagents/designExpert/tools/images/enhancePrompt.ts
@@ -5289,7 +5514,7 @@ async function generateImageAssets(opts) {
5289
5514
  }
5290
5515
 
5291
5516
  // src/subagents/designExpert/tools/images/generateImages.ts
5292
- var definition5 = {
5517
+ var definition6 = {
5293
5518
  name: "generateImages",
5294
5519
  description: "Generate images. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
5295
5520
  inputSchema: {
@@ -5322,7 +5547,7 @@ var definition5 = {
5322
5547
  required: ["prompts"]
5323
5548
  }
5324
5549
  };
5325
- async function execute5(input, onLog, context) {
5550
+ async function execute6(input, onLog, context) {
5326
5551
  return generateImageAssets({
5327
5552
  prompts: input.prompts,
5328
5553
  width: input.width,
@@ -5353,10 +5578,10 @@ async function execute5(input, onLog, context) {
5353
5578
  // src/subagents/designExpert/tools/images/editImages.ts
5354
5579
  var editImages_exports = {};
5355
5580
  __export(editImages_exports, {
5356
- definition: () => definition6,
5357
- execute: () => execute6
5581
+ definition: () => definition7,
5582
+ execute: () => execute7
5358
5583
  });
5359
- var definition6 = {
5584
+ var definition7 = {
5360
5585
  name: "editImages",
5361
5586
  description: "Edit or transform existing images. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more references into something new. Returns CDN URLs with analysis.",
5362
5587
  inputSchema: {
@@ -5392,7 +5617,7 @@ var definition6 = {
5392
5617
  required: ["prompts", "sourceImages"]
5393
5618
  }
5394
5619
  };
5395
- async function execute6(input, onLog, context) {
5620
+ async function execute7(input, onLog, context) {
5396
5621
  return generateImageAssets({
5397
5622
  prompts: input.prompts,
5398
5623
  sourceImages: input.sourceImages,
@@ -5420,138 +5645,6 @@ async function execute6(input, onLog, context) {
5420
5645
  });
5421
5646
  }
5422
5647
 
5423
- // src/subagents/designExpert/tools/images/renderImage.ts
5424
- var renderImage_exports = {};
5425
- __export(renderImage_exports, {
5426
- definition: () => definition7,
5427
- execute: () => execute7
5428
- });
5429
- import { mkdir, unlink, writeFile } from "fs/promises";
5430
- import { tmpdir } from "os";
5431
- import { dirname, join, resolve as resolve2, sep } from "path";
5432
- import { randomUUID } from "crypto";
5433
- var MIN_DIMENSION = 16;
5434
- var MAX_DIMENSION = 4096;
5435
- var RENDER_ANALYZE_PROMPT = 'You are reviewing a browser-rendered graphic (composed from HTML/CSS by a designer) for fidelity. Report: whether the composition fills the full canvas or leaves unintended gaps at any edge, any clipped or overflowing text, whether custom webfonts appear to have loaded (distinctive letterforms vs generic fallback serif/sans), any misalignment or uneven spacing, any unintended scrollbars or default-styling artifacts, and \u2014 if the background is transparent \u2014 any fringing or stray opaque pixels at the edges. Then briefly describe the overall composition and how polished it looks. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Render Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
5436
- var definition7 = {
5437
- name: "renderImage",
5438
- description: "Render a self-contained HTML document in a real browser and capture it as a hosted PNG at exact pixel dimensions, with a fidelity review included. Deterministic \u2014 exact hex colors, real loaded webfonts, precise geometry \u2014 unlike generateImages, which is an image model. Use for token-exact graphics: Open Graph share cards, wordmarks, flat/geometric icon tiles, badges, any composition where letterforms and spacing carry the design. Compose with HTML/CSS (link webfonts from CDNs \u2014 the renderer waits for them to load); inline existing SVG markup when needed, but never hand-write new SVG path data.",
5439
- inputSchema: {
5440
- type: "object",
5441
- properties: {
5442
- html: {
5443
- type: "string",
5444
- description: "A complete, self-contained HTML document sized to fill the viewport (style html/body to the full dimensions with margin 0). External webfonts and images from CDNs are fine \u2014 loading is awaited before capture."
5445
- },
5446
- width: {
5447
- type: "number",
5448
- description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
5449
- },
5450
- height: {
5451
- type: "number",
5452
- description: "Viewport height in CSS pixels. Range: 16-4096."
5453
- },
5454
- scale: {
5455
- type: "number",
5456
- description: "Device scale factor, 1-3. Output pixels = css \xD7 scale. Use 2 for crisp icon masters (e.g. a 512\xD7512 document captured at 1024\xD71024)."
5457
- },
5458
- transparentBackground: {
5459
- type: "boolean",
5460
- description: "Capture with true alpha: leave the document background transparent (no background on html/body) and the PNG keeps it. No background-removal model involved."
5461
- },
5462
- savePath: {
5463
- type: "string",
5464
- description: "Optional project-relative path to also save the PNG into the app (e.g. 'dist/interfaces/web/public/og-image.png' so the deployed site self-hosts it)."
5465
- }
5466
- },
5467
- required: ["html", "width", "height"]
5468
- }
5469
- };
5470
- async function execute7(input, onLog, context) {
5471
- const html = typeof input.html === "string" ? input.html : "";
5472
- const width = Math.round(Number(input.width));
5473
- const height = Math.round(Number(input.height));
5474
- if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
5475
- return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
5476
- }
5477
- const release = await acquireBrowserLock();
5478
- let rendered;
5479
- try {
5480
- onLog?.("Rendering document in the sandbox browser...");
5481
- rendered = await renderHtmlViaSidecar({
5482
- html,
5483
- width,
5484
- height,
5485
- transparent: input.transparentBackground === true,
5486
- scale: typeof input.scale === "number" ? input.scale : void 0
5487
- });
5488
- } catch (err) {
5489
- return `Error: render failed: ${err?.message ?? err}`;
5490
- } finally {
5491
- release();
5492
- }
5493
- let bytes;
5494
- try {
5495
- const res = await fetch(rendered.url);
5496
- if (!res.ok) {
5497
- throw new Error(`fetch returned ${res.status}`);
5498
- }
5499
- bytes = Buffer.from(await res.arrayBuffer());
5500
- } catch (err) {
5501
- return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
5502
- }
5503
- let savedPath;
5504
- if (typeof input.savePath === "string" && input.savePath) {
5505
- const absolute = resolve2(PROJECT_ROOT, input.savePath);
5506
- if (!absolute.startsWith(PROJECT_ROOT + sep)) {
5507
- return "Error: savePath must resolve inside the project.";
5508
- }
5509
- await mkdir(dirname(absolute), { recursive: true });
5510
- await writeFile(absolute, bytes);
5511
- savedPath = input.savePath;
5512
- }
5513
- onLog?.("Hosting the capture...");
5514
- const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
5515
- let url = rendered.url;
5516
- let temporary = true;
5517
- try {
5518
- await writeFile(tmpPath, bytes);
5519
- const upload = await runMindstudioCliResult(["upload", tmpPath], {
5520
- timeout: 6e4,
5521
- onLog,
5522
- caller: "designExpert"
5523
- });
5524
- const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
5525
- if (match?.length) {
5526
- url = match[match.length - 1];
5527
- temporary = false;
5528
- }
5529
- } finally {
5530
- await unlink(tmpPath).catch(() => {
5531
- });
5532
- }
5533
- const analysis = await analyzeImage({
5534
- prompt: RENDER_ANALYZE_PROMPT,
5535
- image: url,
5536
- onLog,
5537
- model: resolveModel("imageAnalysis", context?.models, context?.model)
5538
- }).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
5539
- return JSON.stringify({
5540
- images: [
5541
- {
5542
- url,
5543
- ...temporary ? {
5544
- note: "Durable hosting failed \u2014 this URL is dev-session scratch storage and may expire. Do not use it for app metadata; retry if a durable URL is needed."
5545
- } : {},
5546
- ...savedPath ? { savedPath } : {},
5547
- analysis,
5548
- width: rendered.width,
5549
- height: rendered.height
5550
- }
5551
- ]
5552
- });
5553
- }
5554
-
5555
5648
  // src/subagents/designExpert/tools/polishCopy.ts
5556
5649
  var polishCopy_exports = {};
5557
5650
  __export(polishCopy_exports, {
@@ -5697,7 +5790,7 @@ __export(createWireframe_exports, {
5697
5790
  execute: () => execute10
5698
5791
  });
5699
5792
  import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
5700
- import { join as join2 } from "path";
5793
+ import { join as join3 } from "path";
5701
5794
  var log9 = createLogger("createWireframe");
5702
5795
  var WIREFRAMES_DIR = "src/.wireframes";
5703
5796
  var UPLOAD_TIMEOUT_MS2 = 3e4;
@@ -5816,12 +5909,12 @@ async function execute10(input, onLog, context) {
5816
5909
  html.trim(),
5817
5910
  ""
5818
5911
  ].join("\n");
5819
- await mkdir2(join2(PROJECT_ROOT, WIREFRAMES_DIR), { recursive: true });
5820
- const existed = await stat2(join2(PROJECT_ROOT, relPath)).then(
5912
+ await mkdir2(join3(PROJECT_ROOT, WIREFRAMES_DIR), { recursive: true });
5913
+ const existed = await stat2(join3(PROJECT_ROOT, relPath)).then(
5821
5914
  () => true,
5822
5915
  () => false
5823
5916
  );
5824
- await writeFile2(join2(PROJECT_ROOT, relPath), content, "utf8");
5917
+ await writeFile2(join3(PROJECT_ROOT, relPath), content, "utf8");
5825
5918
  onLog?.(`Wrote ${relPath}, mirroring for preview...`);
5826
5919
  const mirror = await uploadMirror(context, slug, content);
5827
5920
  if (!mirror.ok) {
@@ -6092,7 +6185,7 @@ ${RENDER_TASK_BLOCK}`;
6092
6185
 
6093
6186
  // src/subagents/designExpert/validateWireframeRefs.ts
6094
6187
  import { stat as stat3 } from "fs/promises";
6095
- import { join as join3 } from "path";
6188
+ import { join as join4 } from "path";
6096
6189
  var WIREFRAME_REF_RE = /src\/\.wireframes\/[a-z0-9][a-z0-9-]*\.html/g;
6097
6190
  async function validateWireframeRefs(text) {
6098
6191
  const refs = [...new Set(text.match(WIREFRAME_REF_RE) ?? [])];
@@ -6101,7 +6194,7 @@ async function validateWireframeRefs(text) {
6101
6194
  }
6102
6195
  const missing = [];
6103
6196
  for (const ref of refs) {
6104
- const exists = await stat3(join3(PROJECT_ROOT, ref)).then(
6197
+ const exists = await stat3(join4(PROJECT_ROOT, ref)).then(
6105
6198
  (s) => s.isFile(),
6106
6199
  () => false
6107
6200
  );
@@ -9324,7 +9417,7 @@ async function runTurn(params) {
9324
9417
  // src/headless/attachments.ts
9325
9418
  import { mkdirSync, existsSync } from "fs";
9326
9419
  import { writeFile as writeFile3 } from "fs/promises";
9327
- import { basename as basename2, join as join4, extname as extname2 } from "path";
9420
+ import { basename as basename2, join as join5, extname as extname3 } from "path";
9328
9421
  var log16 = createLogger("headless:attachments");
9329
9422
  var UPLOADS_DIR = "src/.user-uploads";
9330
9423
  function filenameFromUrl(url) {
@@ -9337,11 +9430,11 @@ function filenameFromUrl(url) {
9337
9430
  }
9338
9431
  }
9339
9432
  function resolveUniqueFilename(name, claimed) {
9340
- const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join4(UPLOADS_DIR, candidate));
9433
+ const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join5(UPLOADS_DIR, candidate));
9341
9434
  if (isFree(name)) {
9342
9435
  return name;
9343
9436
  }
9344
- const ext = extname2(name);
9437
+ const ext = extname3(name);
9345
9438
  const base = name.slice(0, name.length - ext.length);
9346
9439
  let counter = 1;
9347
9440
  while (!isFree(`${base}-${counter}${ext}`)) {
@@ -9352,7 +9445,7 @@ function resolveUniqueFilename(name, claimed) {
9352
9445
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
9353
9446
  function isImageAttachment(att) {
9354
9447
  const name = att.filename || filenameFromUrl(att.url);
9355
- return IMAGE_EXTENSIONS.has(extname2(name).toLowerCase());
9448
+ return IMAGE_EXTENSIONS.has(extname3(name).toLowerCase());
9356
9449
  }
9357
9450
  async function persistAttachments(attachments) {
9358
9451
  const nonVoice = attachments.filter((a) => !a.isVoice);
@@ -9372,7 +9465,7 @@ async function persistAttachments(attachments) {
9372
9465
  const results = await Promise.allSettled(
9373
9466
  nonVoice.map(async (att, i) => {
9374
9467
  const name = names[i];
9375
- const localPath = join4(UPLOADS_DIR, name);
9468
+ const localPath = join5(UPLOADS_DIR, name);
9376
9469
  const res = await fetch(att.url, {
9377
9470
  signal: AbortSignal.timeout(3e4)
9378
9471
  });