@mindstudio-ai/remy 0.1.20 → 0.1.22

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.
@@ -43,7 +43,7 @@ Derive additional implementation colors (borders, focus states, hover states, di
43
43
 
44
44
  ### Typography block format
45
45
 
46
- A `` ```typography `` fenced block in a `type: design/typography` spec file declares fonts (with source URLs) and one or two anchor styles (typically Display and Body). Derive additional styles (labels, buttons, captions, overlines) from these anchors:
46
+ A `` ```typography `` fenced block in a `type: design/typography` spec file declares fonts (with source URLs) and one or two anchor styles (typically Display and Body). Styles can include an optional `case` field (`uppercase`, `lowercase`, `capitalize`) for text-transform. Derive additional styles (labels, buttons, captions, overlines) from these anchors:
47
47
 
48
48
  ```typography
49
49
  fonts:
@@ -59,6 +59,7 @@ styles:
59
59
  weight: 600
60
60
  letterSpacing: -0.03em
61
61
  lineHeight: 1.1
62
+ case: uppercase
62
63
  description: Page titles and hero text
63
64
  Body:
64
65
  font: Satoshi
@@ -181,6 +181,7 @@ styles:
181
181
  weight: 600
182
182
  letterSpacing: -0.03em
183
183
  lineHeight: 1.1
184
+ case: uppercase
184
185
  description: Page titles and hero text
185
186
  Body:
186
187
  font: Satoshi
package/dist/headless.js CHANGED
@@ -1242,8 +1242,32 @@ var confirmDestructiveActionTool = {
1242
1242
  }
1243
1243
  };
1244
1244
 
1245
- // src/subagents/sdkConsultant/index.ts
1245
+ // src/subagents/common/runCli.ts
1246
1246
  import { exec } from "child_process";
1247
+ function runCli(cmd, options) {
1248
+ return new Promise((resolve) => {
1249
+ exec(
1250
+ cmd,
1251
+ {
1252
+ timeout: options?.timeout ?? 6e4,
1253
+ maxBuffer: options?.maxBuffer ?? 1024 * 1024
1254
+ },
1255
+ (err, stdout, stderr) => {
1256
+ if (stdout.trim()) {
1257
+ resolve(stdout.trim());
1258
+ return;
1259
+ }
1260
+ if (err) {
1261
+ resolve(`Error: ${stderr.trim() || err.message}`);
1262
+ return;
1263
+ }
1264
+ resolve("(no response)");
1265
+ }
1266
+ );
1267
+ });
1268
+ }
1269
+
1270
+ // src/subagents/sdkConsultant/index.ts
1247
1271
  var askMindStudioSdkTool = {
1248
1272
  definition: {
1249
1273
  name: "askMindStudioSdk",
@@ -1261,28 +1285,13 @@ var askMindStudioSdkTool = {
1261
1285
  },
1262
1286
  async execute(input) {
1263
1287
  const query = input.query;
1264
- return new Promise((resolve) => {
1265
- exec(
1266
- `mindstudio ask ${JSON.stringify(query)}`,
1267
- { timeout: 6e4, maxBuffer: 512 * 1024 },
1268
- (err, stdout, stderr) => {
1269
- if (stdout.trim()) {
1270
- resolve(stdout.trim());
1271
- return;
1272
- }
1273
- if (err) {
1274
- resolve(`Error: ${stderr.trim() || err.message}`);
1275
- return;
1276
- }
1277
- resolve("(no response)");
1278
- }
1279
- );
1288
+ return runCli(`mindstudio ask ${JSON.stringify(query)}`, {
1289
+ maxBuffer: 512 * 1024
1280
1290
  });
1281
1291
  }
1282
1292
  };
1283
1293
 
1284
1294
  // src/tools/common/fetchUrl.ts
1285
- import { exec as exec2 } from "child_process";
1286
1295
  var fetchUrlTool = {
1287
1296
  definition: {
1288
1297
  name: "scapeWebUrl",
@@ -1309,29 +1318,13 @@ var fetchUrlTool = {
1309
1318
  if (screenshot) {
1310
1319
  pageOptions.screenshot = true;
1311
1320
  }
1312
- const cmd = `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`;
1313
- return new Promise((resolve) => {
1314
- exec2(
1315
- cmd,
1316
- { timeout: 6e4, maxBuffer: 1024 * 1024 },
1317
- (err, stdout, stderr) => {
1318
- if (stdout.trim()) {
1319
- resolve(stdout.trim());
1320
- return;
1321
- }
1322
- if (err) {
1323
- resolve(`Error: ${stderr.trim() || err.message}`);
1324
- return;
1325
- }
1326
- resolve("(no response)");
1327
- }
1328
- );
1329
- });
1321
+ return runCli(
1322
+ `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
1323
+ );
1330
1324
  }
1331
1325
  };
1332
1326
 
1333
1327
  // src/tools/common/searchGoogle.ts
1334
- import { exec as exec3 } from "child_process";
1335
1328
  var searchGoogleTool = {
1336
1329
  definition: {
1337
1330
  name: "searchGoogle",
@@ -1349,24 +1342,10 @@ var searchGoogleTool = {
1349
1342
  },
1350
1343
  async execute(input) {
1351
1344
  const query = input.query;
1352
- const cmd = `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`;
1353
- return new Promise((resolve) => {
1354
- exec3(
1355
- cmd,
1356
- { timeout: 6e4, maxBuffer: 512 * 1024 },
1357
- (err, stdout, stderr) => {
1358
- if (stdout.trim()) {
1359
- resolve(stdout.trim());
1360
- return;
1361
- }
1362
- if (err) {
1363
- resolve(`Error: ${stderr.trim() || err.message}`);
1364
- return;
1365
- }
1366
- resolve("(no response)");
1367
- }
1368
- );
1369
- });
1345
+ return runCli(
1346
+ `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`,
1347
+ { maxBuffer: 512 * 1024 }
1348
+ );
1370
1349
  }
1371
1350
  };
1372
1351
 
@@ -1691,7 +1670,7 @@ ${unifiedDiff(input.path, content, updated)}`;
1691
1670
  };
1692
1671
 
1693
1672
  // src/tools/code/bash.ts
1694
- import { exec as exec4 } from "child_process";
1673
+ import { exec as exec2 } from "child_process";
1695
1674
  var DEFAULT_TIMEOUT_MS = 12e4;
1696
1675
  var DEFAULT_MAX_LINES3 = 500;
1697
1676
  var bashTool = {
@@ -1725,7 +1704,7 @@ var bashTool = {
1725
1704
  const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
1726
1705
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
1727
1706
  return new Promise((resolve) => {
1728
- exec4(
1707
+ exec2(
1729
1708
  input.command,
1730
1709
  {
1731
1710
  timeout: timeoutMs,
@@ -1765,7 +1744,7 @@ var bashTool = {
1765
1744
  };
1766
1745
 
1767
1746
  // src/tools/code/grep.ts
1768
- import { exec as exec5 } from "child_process";
1747
+ import { exec as exec3 } from "child_process";
1769
1748
  var DEFAULT_MAX = 50;
1770
1749
  function formatResults(stdout, max) {
1771
1750
  const lines = stdout.trim().split("\n");
@@ -1812,12 +1791,12 @@ var grepTool = {
1812
1791
  const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
1813
1792
  const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
1814
1793
  return new Promise((resolve) => {
1815
- exec5(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
1794
+ exec3(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
1816
1795
  if (stdout?.trim()) {
1817
1796
  resolve(formatResults(stdout, max));
1818
1797
  return;
1819
1798
  }
1820
- exec5(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
1799
+ exec3(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
1821
1800
  if (grepStdout?.trim()) {
1822
1801
  resolve(formatResults(grepStdout, max));
1823
1802
  } else {
@@ -2049,17 +2028,38 @@ var runMethodTool = {
2049
2028
  };
2050
2029
 
2051
2030
  // src/tools/code/screenshot.ts
2031
+ var DEFAULT_PROMPT = "Describe this app screenshot for a developer who cannot see it. What is visible on screen: the layout, content, interactive elements, any loading or error states. Be concise and factual.";
2052
2032
  var screenshotTool = {
2053
2033
  definition: {
2054
2034
  name: "screenshot",
2055
- description: "Capture a screenshot of the app preview. Returns a CDN URL with dimensions. Useful for visually checking the current state after UI changes or when debugging layout issues.",
2035
+ description: "Capture a screenshot of the app preview and get a description of what's on screen. Optionally provide a specific question about what you're looking for.",
2056
2036
  inputSchema: {
2057
2037
  type: "object",
2058
- properties: {}
2038
+ properties: {
2039
+ prompt: {
2040
+ type: "string",
2041
+ description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
2042
+ }
2043
+ }
2059
2044
  }
2060
2045
  },
2061
- async execute() {
2062
- return "ok";
2046
+ async execute(input) {
2047
+ try {
2048
+ const { url } = await sidecarRequest(
2049
+ "/screenshot",
2050
+ {},
2051
+ { timeout: 3e4 }
2052
+ );
2053
+ const analysisPrompt = input.prompt || DEFAULT_PROMPT;
2054
+ const analysis = await runCli(
2055
+ `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
2056
+ );
2057
+ return `Screenshot: ${url}
2058
+
2059
+ ${analysis}`;
2060
+ } catch (err) {
2061
+ return `Error taking screenshot: ${err.message}`;
2062
+ }
2063
2063
  }
2064
2064
  };
2065
2065
 
@@ -2413,7 +2413,6 @@ var browserAutomationTool = {
2413
2413
  };
2414
2414
 
2415
2415
  // src/subagents/designExpert/tools.ts
2416
- import { exec as exec6 } from "child_process";
2417
2416
  var DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design reference. Assess:
2418
2417
  1) Mood/aesthetic
2419
2418
  2) Color palette with approximate hex values and palette strategy
@@ -2421,7 +2420,7 @@ var DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design r
2421
2420
  4) Layout composition (symmetric/asymmetric, grid structure, whitespace usage, content density)
2422
2421
  5) What makes it distinctive and interesting vs generic AI-generated interfaces
2423
2422
  Be specific and concise.`;
2424
- var DESIGN_RESEARCH_TOOLS = [
2423
+ var DESIGN_EXPERT_TOOLS = [
2425
2424
  {
2426
2425
  name: "searchGoogle",
2427
2426
  description: "Search Google for web results. Use for finding design inspiration, font recommendations, UI patterns, real products in a domain, and reference material.",
@@ -2496,7 +2495,7 @@ var DESIGN_RESEARCH_TOOLS = [
2496
2495
  },
2497
2496
  {
2498
2497
  name: "generateImages",
2499
- description: "Generate images using AI (Seedream). Returns CDN URLs. Produces high-quality results for both photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel.",
2498
+ description: "Generate images using AI (Seedream). Returns CDN URLs with a quality analysis for each image. Produces high-quality results for both 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.",
2500
2499
  inputSchema: {
2501
2500
  type: "object",
2502
2501
  properties: {
@@ -2520,27 +2519,26 @@ var DESIGN_RESEARCH_TOOLS = [
2520
2519
  }
2521
2520
  }
2522
2521
  ];
2523
- function runCli(cmd) {
2524
- return new Promise((resolve) => {
2525
- exec6(
2526
- cmd,
2527
- { timeout: 6e4, maxBuffer: 1024 * 1024 },
2528
- (err, stdout, stderr) => {
2529
- if (stdout.trim()) {
2530
- resolve(stdout.trim());
2531
- return;
2532
- }
2533
- if (err) {
2534
- resolve(`Error: ${stderr.trim() || err.message}`);
2535
- return;
2536
- }
2537
- resolve("(no response)");
2538
- }
2539
- );
2540
- });
2541
- }
2542
- async function executeDesignTool(name, input) {
2522
+ async function executeDesignExpertTool(name, input) {
2543
2523
  switch (name) {
2524
+ case "screenshot": {
2525
+ try {
2526
+ const { url } = await sidecarRequest(
2527
+ "/screenshot",
2528
+ {},
2529
+ { timeout: 3e4 }
2530
+ );
2531
+ const analysisPrompt = input.prompt || "Describe this app screenshot for a visual designer reviewing the current state. What is visible: layout, typography, colors, spacing, imagery. Note anything that looks broken or off. Be concise.";
2532
+ const analysis = await runCli(
2533
+ `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
2534
+ );
2535
+ return `Screenshot: ${url}
2536
+
2537
+ ${analysis}`;
2538
+ } catch (err) {
2539
+ return `Error taking screenshot: ${err.message}`;
2540
+ }
2541
+ }
2544
2542
  case "searchGoogle":
2545
2543
  return runCli(
2546
2544
  `mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`
@@ -2585,6 +2583,8 @@ ${analysis}`;
2585
2583
  const prompts = input.prompts;
2586
2584
  const width = input.width || 2048;
2587
2585
  const height = input.height || 2048;
2586
+ const ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical.";
2587
+ let imageUrls;
2588
2588
  if (prompts.length === 1) {
2589
2589
  const step = JSON.stringify({
2590
2590
  prompt: prompts[0],
@@ -2593,21 +2593,47 @@ ${analysis}`;
2593
2593
  config: { width, height }
2594
2594
  }
2595
2595
  });
2596
- return runCli(
2596
+ const url = await runCli(
2597
2597
  `mindstudio generate-image '${step}' --output-key imageUrl --no-meta`
2598
2598
  );
2599
- }
2600
- const steps = prompts.map((prompt) => ({
2601
- stepType: "generateImage",
2602
- step: {
2603
- prompt,
2604
- imageModelOverride: {
2605
- model: "seedream-4.5",
2606
- config: { width, height }
2599
+ imageUrls = [url];
2600
+ } else {
2601
+ const steps = prompts.map((prompt) => ({
2602
+ stepType: "generateImage",
2603
+ step: {
2604
+ prompt,
2605
+ imageModelOverride: {
2606
+ model: "seedream-4.5",
2607
+ config: { width, height }
2608
+ }
2607
2609
  }
2610
+ }));
2611
+ const batchResult = await runCli(
2612
+ `mindstudio batch '${JSON.stringify(steps)}' --no-meta`
2613
+ );
2614
+ try {
2615
+ const parsed = JSON.parse(batchResult);
2616
+ imageUrls = parsed.results.map(
2617
+ (r) => r.output?.imageUrl ?? `Error: ${r.error}`
2618
+ );
2619
+ } catch {
2620
+ return batchResult;
2608
2621
  }
2609
- }));
2610
- return runCli(`mindstudio batch '${JSON.stringify(steps)}' --no-meta`);
2622
+ }
2623
+ const analyses = await Promise.all(
2624
+ imageUrls.map(async (url, i) => {
2625
+ if (url.startsWith("Error")) {
2626
+ return `Image ${i + 1}: ${url}`;
2627
+ }
2628
+ const analysis = await runCli(
2629
+ `mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
2630
+ );
2631
+ return `**Image ${i + 1}:** ${url}
2632
+ Prompt: ${prompts[i]}
2633
+ Analysis: ${analysis}`;
2634
+ })
2635
+ );
2636
+ return analyses.join("\n\n");
2611
2637
  }
2612
2638
  default:
2613
2639
  return `Error: unknown tool "${name}"`;
@@ -2757,7 +2783,7 @@ function sample(arr, n) {
2757
2783
  }
2758
2784
  return copy.slice(0, n);
2759
2785
  }
2760
- function getDesignResearchPrompt() {
2786
+ function getDesignExpertPrompt() {
2761
2787
  const fonts = sample(fontData.fonts, 30);
2762
2788
  const pairings = sample(fontData.pairings, 20);
2763
2789
  const images = sample(inspirationImages, 15);
@@ -2811,7 +2837,7 @@ ${specContext}`;
2811
2837
 
2812
2838
  // src/subagents/designExpert/index.ts
2813
2839
  var DESCRIPTION = `
2814
- Visual design expert. Handles fonts, colors, palettes, gradients, layouts, imagery, icons, and visual direction. Can answer from expertise alone or research the web. Returns concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions. Include app context in your task \u2014 the agent cannot see your conversation with the user.
2840
+ Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job.
2815
2841
  `.trim();
2816
2842
  var designExpertTool = {
2817
2843
  definition: {
@@ -2830,17 +2856,17 @@ var designExpertTool = {
2830
2856
  },
2831
2857
  async execute(input, context) {
2832
2858
  if (!context) {
2833
- return "Error: design research requires execution context";
2859
+ return "Error: visual design expert requires execution context";
2834
2860
  }
2835
2861
  const result = await runSubAgent({
2836
- system: getDesignResearchPrompt(),
2862
+ system: getDesignExpertPrompt(),
2837
2863
  task: input.task,
2838
- tools: DESIGN_RESEARCH_TOOLS,
2839
- externalTools: /* @__PURE__ */ new Set(["screenshot"]),
2840
- executeTool: executeDesignTool,
2864
+ tools: DESIGN_EXPERT_TOOLS,
2865
+ externalTools: /* @__PURE__ */ new Set(),
2866
+ executeTool: executeDesignExpertTool,
2841
2867
  apiConfig: context.apiConfig,
2842
2868
  model: context.model,
2843
- subAgentId: "designExpert",
2869
+ subAgentId: "visualDesignExpert",
2844
2870
  signal: context.signal,
2845
2871
  parentToolId: context.toolCallId,
2846
2872
  onEvent: context.onEvent,
@@ -3709,7 +3735,6 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
3709
3735
  "runScenario",
3710
3736
  "runMethod",
3711
3737
  "browserCommand",
3712
- "screenshot",
3713
3738
  "setProjectName"
3714
3739
  ]);
3715
3740
  function createAgentState() {
@@ -3753,6 +3778,12 @@ async function runTurn(params) {
3753
3778
  });
3754
3779
  }
3755
3780
  state.messages.push(userMsg);
3781
+ const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
3782
+ "setProjectOnboardingState",
3783
+ "setProjectName",
3784
+ "clearSyncStatus",
3785
+ "editsFinished"
3786
+ ]);
3756
3787
  let lastCompletedTools = "";
3757
3788
  let lastCompletedResult = "";
3758
3789
  while (true) {
@@ -3841,7 +3872,7 @@ async function runTurn(params) {
3841
3872
  apiConfig,
3842
3873
  getContext: () => ({
3843
3874
  assistantText: getTextContent(contentBlocks).slice(-500),
3844
- lastToolName: getToolCalls(contentBlocks).at(-1)?.name || lastCompletedTools || void 0,
3875
+ lastToolName: getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
3845
3876
  lastToolResult: lastCompletedResult || void 0
3846
3877
  }),
3847
3878
  onStatus: (label) => onEvent({ type: "status", message: label }),
@@ -4000,6 +4031,28 @@ async function runTurn(params) {
4000
4031
  count: toolCalls.length,
4001
4032
  tools: toolCalls.map((tc) => tc.name)
4002
4033
  });
4034
+ let subAgentText = "";
4035
+ const origOnEvent = onEvent;
4036
+ const wrappedOnEvent = (e) => {
4037
+ if ("parentToolId" in e && e.parentToolId) {
4038
+ if (e.type === "text") {
4039
+ subAgentText = e.text;
4040
+ } else if (e.type === "tool_start") {
4041
+ subAgentText = `Using ${e.name}`;
4042
+ }
4043
+ }
4044
+ origOnEvent(e);
4045
+ };
4046
+ const toolStatusWatcher = startStatusWatcher({
4047
+ apiConfig,
4048
+ getContext: () => ({
4049
+ assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
4050
+ lastToolName: toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ") || void 0,
4051
+ lastToolResult: lastCompletedResult || void 0
4052
+ }),
4053
+ onStatus: (label) => origOnEvent({ type: "status", message: label }),
4054
+ signal
4055
+ });
4003
4056
  const subAgentMessages = /* @__PURE__ */ new Map();
4004
4057
  const results = await Promise.all(
4005
4058
  toolCalls.map(async (tc) => {
@@ -4025,7 +4078,7 @@ async function runTurn(params) {
4025
4078
  apiConfig,
4026
4079
  model,
4027
4080
  signal,
4028
- onEvent,
4081
+ onEvent: wrappedOnEvent,
4029
4082
  resolveExternalTool,
4030
4083
  toolCallId: tc.id,
4031
4084
  subAgentMessages
@@ -4059,15 +4112,21 @@ async function runTurn(params) {
4059
4112
  }
4060
4113
  })
4061
4114
  );
4062
- for (const [toolId, msgs] of subAgentMessages) {
4115
+ toolStatusWatcher.stop();
4116
+ for (const r of results) {
4063
4117
  const block = contentBlocks.find(
4064
- (b) => b.type === "tool" && b.id === toolId
4118
+ (b) => b.type === "tool" && b.id === r.id
4065
4119
  );
4066
4120
  if (block?.type === "tool") {
4067
- block.subAgentMessages = msgs;
4121
+ block.result = r.result;
4122
+ block.isError = r.isError;
4123
+ const msgs = subAgentMessages.get(r.id);
4124
+ if (msgs) {
4125
+ block.subAgentMessages = msgs;
4126
+ }
4068
4127
  }
4069
4128
  }
4070
- lastCompletedTools = toolCalls.map((tc) => tc.name).join(", ");
4129
+ lastCompletedTools = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
4071
4130
  lastCompletedResult = results.at(-1)?.result ?? "";
4072
4131
  for (const r of results) {
4073
4132
  state.messages.push({