@mindstudio-ai/remy 0.1.250 → 0.1.252

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
@@ -828,6 +828,10 @@ Current date: ${now}
828
828
  {{compiled/media-cdn.md}}
829
829
  </media_cdn>
830
830
 
831
+ <app_files>
832
+ {{compiled/files.md}}
833
+ </app_files>
834
+
831
835
  <interfaces>
832
836
  {{compiled/interfaces.md}}
833
837
  </interfaces>
@@ -2002,6 +2006,7 @@ var compactConversationTool = {
2002
2006
  // src/tools/code/readFile.ts
2003
2007
  import fs12 from "fs/promises";
2004
2008
  var DEFAULT_WINDOW = 500;
2009
+ var MAX_BYTES = 64 * 1024;
2005
2010
  function isBinary(buffer) {
2006
2011
  const sample = buffer.subarray(0, 8192);
2007
2012
  for (let i = 0; i < sample.length; i++) {
@@ -2015,7 +2020,7 @@ var readFileTool = {
2015
2020
  clearable: true,
2016
2021
  definition: {
2017
2022
  name: "readFile",
2018
- description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
2023
+ description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines, and at most 64KB \u2014 a file with very wide lines (a CSV, a minified bundle) comes back short of 500 lines, so read a narrower range or grep rather than paging through it. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
2019
2024
  inputSchema: {
2020
2025
  type: "object",
2021
2026
  properties: {
@@ -2072,12 +2077,35 @@ var readFileTool = {
2072
2077
  endIdxExclusive = Math.min(startIdx + DEFAULT_WINDOW, totalLines);
2073
2078
  }
2074
2079
  }
2075
- const sliced = allLines.slice(startIdx, endIdxExclusive);
2076
- const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
2077
- let result = numbered;
2080
+ const numberedLines = allLines.slice(startIdx, endIdxExclusive).map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`);
2081
+ let kept = numberedLines;
2082
+ let byteTruncated = false;
2083
+ if (Buffer.byteLength(numberedLines.join("\n"), "utf-8") > MAX_BYTES) {
2084
+ byteTruncated = true;
2085
+ kept = [];
2086
+ let used = 0;
2087
+ for (const line of numberedLines) {
2088
+ const cost = Buffer.byteLength(line, "utf-8") + (kept.length > 0 ? 1 : 0);
2089
+ if (used + cost > MAX_BYTES) {
2090
+ break;
2091
+ }
2092
+ kept.push(line);
2093
+ used += cost;
2094
+ }
2095
+ if (kept.length === 0) {
2096
+ kept = [
2097
+ Buffer.from(numberedLines[0], "utf-8").subarray(0, MAX_BYTES).toString("utf-8")
2098
+ ];
2099
+ }
2100
+ }
2101
+ let result = kept.join("\n");
2078
2102
  const displayStart = startIdx + 1;
2079
- const displayEnd = startIdx + sliced.length;
2080
- if (displayStart > 1 || displayEnd < totalLines) {
2103
+ const displayEnd = startIdx + kept.length;
2104
+ if (byteTruncated) {
2105
+ result += `
2106
+
2107
+ (showing lines ${displayStart}\u2013${displayEnd} of ${totalLines}, truncated at ${(MAX_BYTES / 1024).toFixed(0)}KB \u2014 this file's lines are wide. Read a narrower range with startLine/endLine, or grep for what you need, instead of paging through it.)`;
2108
+ } else if (displayStart > 1 || displayEnd < totalLines) {
2081
2109
  result += `
2082
2110
 
2083
2111
  (showing lines ${displayStart}\u2013${displayEnd} of ${totalLines} \u2014 pass startLine/endLine to read a different range)`;
@@ -3766,6 +3794,17 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3766
3794
  return { text: ack, messages: [], backgrounded: true };
3767
3795
  }
3768
3796
 
3797
+ // src/subagents/common/tools.ts
3798
+ var COMMON_READ_TOOLS = [
3799
+ readFileTool.definition,
3800
+ listDirTool.definition,
3801
+ grepTool.definition,
3802
+ globTool.definition
3803
+ ];
3804
+ var COMMON_READ_TOOL_NAMES = new Set(
3805
+ COMMON_READ_TOOLS.map((t) => t.name)
3806
+ );
3807
+
3769
3808
  // src/subagents/browserAutomation/tools.ts
3770
3809
  var BROWSER_TOOLS = [
3771
3810
  {
@@ -3909,23 +3948,193 @@ var BROWSER_TOOLS = [
3909
3948
  }
3910
3949
  }
3911
3950
  }
3912
- }
3951
+ },
3952
+ // Read tools so the QA agent can pull full spec detail on demand — the spec
3953
+ // context in its prompt is a lightweight index (see prompt.ts) that points
3954
+ // here. Routed to the global executeTool in index.ts, mirroring specSync.
3955
+ ...COMMON_READ_TOOLS,
3956
+ readSpecTool.definition
3913
3957
  ];
3914
3958
  var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
3915
3959
 
3916
- // src/subagents/browserAutomation/prompt.ts
3960
+ // src/subagents/common/context.ts
3917
3961
  import fs16 from "fs";
3918
- var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
3919
- function getBrowserAutomationPrompt() {
3962
+ import path9 from "path";
3963
+ function walkMdFiles2(dir, skip) {
3964
+ const files = [];
3920
3965
  try {
3921
- const appSpec = fs16.readFileSync("src/app.md", "utf-8").trim();
3922
- return `${BASE_PROMPT}
3966
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
3967
+ const full = path9.join(dir, entry.name);
3968
+ if (entry.isDirectory()) {
3969
+ if (!skip?.has(entry.name)) {
3970
+ files.push(...walkMdFiles2(full, skip));
3971
+ }
3972
+ } else if (entry.name.endsWith(".md")) {
3973
+ files.push(full);
3974
+ }
3975
+ }
3976
+ } catch {
3977
+ }
3978
+ return files.sort();
3979
+ }
3980
+ function parseFrontmatter2(filePath) {
3981
+ try {
3982
+ const content = fs16.readFileSync(filePath, "utf-8");
3983
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
3984
+ if (!match) {
3985
+ return {};
3986
+ }
3987
+ const fm = {};
3988
+ for (const line of match[1].split("\n")) {
3989
+ const sep = line.indexOf(":");
3990
+ if (sep > 0) {
3991
+ const key = line.slice(0, sep).trim();
3992
+ const val = line.slice(sep + 1).trim();
3993
+ fm[key] = val;
3994
+ }
3995
+ }
3996
+ return fm;
3997
+ } catch {
3998
+ return {};
3999
+ }
4000
+ }
4001
+ function loadSpecIndex() {
4002
+ const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
4003
+ if (files.length === 0) {
4004
+ return "";
4005
+ }
4006
+ const lines = files.map((f) => {
4007
+ const fm = parseFrontmatter2(f);
4008
+ let line = `- ${f}`;
4009
+ if (fm.name) {
4010
+ line += ` \u2014 "${fm.name}"`;
4011
+ }
4012
+ if (fm.description) {
4013
+ line += ` \u2014 ${fm.description}`;
4014
+ }
4015
+ return line;
4016
+ });
4017
+ return `<spec_files>
4018
+ ## Project Spec Files
4019
+ Use readFile to access full contents.
3923
4020
 
3924
- <!-- cache_breakpoint -->
4021
+ ${lines.join("\n")}
4022
+ </spec_files>`;
4023
+ }
4024
+ function loadRoadmapIndex() {
4025
+ const parts = [];
4026
+ try {
4027
+ const indexJson = JSON.parse(
4028
+ fs16.readFileSync("src/roadmap/index.json", "utf-8")
4029
+ );
4030
+ if (indexJson.lanes?.length > 0) {
4031
+ const laneLines = indexJson.lanes.map(
4032
+ (l) => `- **${l.name}**: ${l.narrative || ""} (${l.items?.length || 0} items)`
4033
+ );
4034
+ parts.push(`### Lanes
4035
+ ${laneLines.join("\n")}`);
4036
+ }
4037
+ if (indexJson.standalone?.length > 0) {
4038
+ parts.push(
4039
+ `### Standalone
4040
+ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
4041
+ );
4042
+ }
4043
+ } catch {
4044
+ }
4045
+ const files = walkMdFiles2("src/roadmap");
4046
+ if (files.length > 0) {
4047
+ const lines = files.map((f) => {
4048
+ const fm = parseFrontmatter2(f);
4049
+ let line = `- ${f}`;
4050
+ if (fm.name) {
4051
+ line += ` \u2014 "${fm.name}"`;
4052
+ }
4053
+ if (fm.status) {
4054
+ line += ` (${fm.status})`;
4055
+ }
4056
+ if (fm.description) {
4057
+ line += ` \u2014 ${fm.description}`;
4058
+ }
4059
+ return line;
4060
+ });
4061
+ parts.push(`### Items
4062
+ ${lines.join("\n")}`);
4063
+ }
4064
+ if (parts.length === 0) {
4065
+ return "";
4066
+ }
4067
+ return `<current_roadmap>
4068
+ ## Roadmap
4069
+ Use readFile to access full contents.
4070
+
4071
+ ${parts.join("\n\n")}
4072
+ </current_roadmap>`;
4073
+ }
4074
+ function loadPlatformBrief() {
4075
+ return `<platform_brief>
4076
+ ## What is a Remy app?
4077
+
4078
+ A Remy app is a managed full-stack TypeScript project with three layers: a spec (natural language in src/), a backend contract (methods, tables, roles in dist/), and one or more interfaces (web, API, bots, cron, etc.). The spec is the source of truth; code is derived from it.
4079
+
4080
+ This is a capable, stable platform used in production by 100k+ users. Build with confidence \u2014 you're building production-grade apps, not fragile prototypes.
4081
+
4082
+ ## What people build
4083
+
4084
+ - Business tools \u2014 client portals, approval workflows, admin panels with role-based access
4085
+ - AI-powered apps \u2014 document processors, image/video tools, content generators, conversational agents that take actions
4086
+ - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
4087
+ - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
4088
+ - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
4089
+ - Agent tools \u2014 MCP tool servers for AI assistants
4090
+ - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
4091
+ - API services \u2014 backend logic exposed as REST endpoints
4092
+ - Simple static sites \u2014 no backend needed, just a web interface with a build step
4093
+
4094
+ An app can be any combination of these.
4095
+
4096
+ ## Interfaces
4097
+
4098
+ Each interface type invokes the same backend methods. Methods don't know which interface called them.
4099
+
4100
+ - Web \u2014 any TypeScript project with a build command. Framework-agnostic (React, Vue, Svelte, vanilla, anything). The frontend SDK provides typed RPC to backend methods.
4101
+ - API \u2014 auto-generated REST endpoints for every method
4102
+ - Cron \u2014 scheduled jobs on a configurable interval
4103
+ - Webhook \u2014 HTTP endpoints that trigger methods
4104
+ - Email \u2014 inbound email processing
4105
+ - MCP \u2014 tool servers for AI assistants
4106
+ - Agent \u2014 conversational LLM interface with tool access to backend methods
4107
+
4108
+ ## Backend
4109
+
4110
+ TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
4111
+
4112
+ - Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
4113
+ - Built-in app-managed auth. Opt-in via manifest \u2014 developer builds login UI, platform handles verification codes (email-code, sms-code) and cookie sessions. API key auth for programmatic access. No OAuth, no social login (no Apple, Google, Facebook, or GitHub sign-in). Backend methods use auth.requireRole() for access control.
4114
+ - Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
4115
+ - Git-native deployment. Push to default branch to deploy.
4116
+
4117
+ ## MindStudio SDK
4118
+
4119
+ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (OpenAI, Anthropic, Google, Meta, Mistral, and more) and 1000+ integrations (email, SMS, Slack, HubSpot, Google Workspace, web scraping, image/video generation, media processing, and much more) with zero configuration \u2014 credentials are handled automatically in the execution environment. No API keys needed. This SDK is robust and battle-tested in production.
3925
4120
 
3926
- <app_context>
3927
- ${appSpec}
3928
- </app_context>`;
4121
+ ## What Remy apps are NOT good for
4122
+
4123
+ - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
4124
+ - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
4125
+ </platform_brief>`;
4126
+ }
4127
+
4128
+ // src/subagents/browserAutomation/prompt.ts
4129
+ var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
4130
+ function getBrowserAutomationPrompt() {
4131
+ try {
4132
+ const specIndex = loadSpecIndex();
4133
+ const parts = [BASE_PROMPT, "<!-- cache_breakpoint -->"];
4134
+ if (specIndex) {
4135
+ parts.push(specIndex);
4136
+ }
4137
+ return parts.join("\n\n");
3929
4138
  } catch {
3930
4139
  return BASE_PROMPT;
3931
4140
  }
@@ -3982,6 +4191,9 @@ async function runBrowserAutomation(task, context, opts) {
3982
4191
  return `Error taking screenshot: ${err.message}`;
3983
4192
  }
3984
4193
  }
4194
+ if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
4195
+ return executeTool(name, _input, context);
4196
+ }
3985
4197
  return `Error: unknown local tool "${name}"`;
3986
4198
  },
3987
4199
  apiConfig: context.apiConfig,
@@ -4190,17 +4402,6 @@ var screenshotTool = {
4190
4402
  }
4191
4403
  };
4192
4404
 
4193
- // src/subagents/common/tools.ts
4194
- var COMMON_READ_TOOLS = [
4195
- readFileTool.definition,
4196
- listDirTool.definition,
4197
- grepTool.definition,
4198
- globTool.definition
4199
- ];
4200
- var COMMON_READ_TOOL_NAMES = new Set(
4201
- COMMON_READ_TOOLS.map((t) => t.name)
4202
- );
4203
-
4204
4405
  // src/subagents/designExpert/tools/searchGoogle.ts
4205
4406
  var searchGoogle_exports = {};
4206
4407
  __export(searchGoogle_exports, {
@@ -4802,174 +5003,6 @@ __export(polishCopy_exports, {
4802
5003
  execute: () => execute8
4803
5004
  });
4804
5005
 
4805
- // src/subagents/common/context.ts
4806
- import fs17 from "fs";
4807
- import path9 from "path";
4808
- function walkMdFiles2(dir, skip) {
4809
- const files = [];
4810
- try {
4811
- for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
4812
- const full = path9.join(dir, entry.name);
4813
- if (entry.isDirectory()) {
4814
- if (!skip?.has(entry.name)) {
4815
- files.push(...walkMdFiles2(full, skip));
4816
- }
4817
- } else if (entry.name.endsWith(".md")) {
4818
- files.push(full);
4819
- }
4820
- }
4821
- } catch {
4822
- }
4823
- return files.sort();
4824
- }
4825
- function parseFrontmatter2(filePath) {
4826
- try {
4827
- const content = fs17.readFileSync(filePath, "utf-8");
4828
- const match = content.match(/^---\n([\s\S]*?)\n---/);
4829
- if (!match) {
4830
- return {};
4831
- }
4832
- const fm = {};
4833
- for (const line of match[1].split("\n")) {
4834
- const sep = line.indexOf(":");
4835
- if (sep > 0) {
4836
- const key = line.slice(0, sep).trim();
4837
- const val = line.slice(sep + 1).trim();
4838
- fm[key] = val;
4839
- }
4840
- }
4841
- return fm;
4842
- } catch {
4843
- return {};
4844
- }
4845
- }
4846
- function loadSpecIndex() {
4847
- const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
4848
- if (files.length === 0) {
4849
- return "";
4850
- }
4851
- const lines = files.map((f) => {
4852
- const fm = parseFrontmatter2(f);
4853
- let line = `- ${f}`;
4854
- if (fm.name) {
4855
- line += ` \u2014 "${fm.name}"`;
4856
- }
4857
- if (fm.description) {
4858
- line += ` \u2014 ${fm.description}`;
4859
- }
4860
- return line;
4861
- });
4862
- return `<spec_files>
4863
- ## Project Spec Files
4864
- Use readFile to access full contents.
4865
-
4866
- ${lines.join("\n")}
4867
- </spec_files>`;
4868
- }
4869
- function loadRoadmapIndex() {
4870
- const parts = [];
4871
- try {
4872
- const indexJson = JSON.parse(
4873
- fs17.readFileSync("src/roadmap/index.json", "utf-8")
4874
- );
4875
- if (indexJson.lanes?.length > 0) {
4876
- const laneLines = indexJson.lanes.map(
4877
- (l) => `- **${l.name}**: ${l.narrative || ""} (${l.items?.length || 0} items)`
4878
- );
4879
- parts.push(`### Lanes
4880
- ${laneLines.join("\n")}`);
4881
- }
4882
- if (indexJson.standalone?.length > 0) {
4883
- parts.push(
4884
- `### Standalone
4885
- ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
4886
- );
4887
- }
4888
- } catch {
4889
- }
4890
- const files = walkMdFiles2("src/roadmap");
4891
- if (files.length > 0) {
4892
- const lines = files.map((f) => {
4893
- const fm = parseFrontmatter2(f);
4894
- let line = `- ${f}`;
4895
- if (fm.name) {
4896
- line += ` \u2014 "${fm.name}"`;
4897
- }
4898
- if (fm.status) {
4899
- line += ` (${fm.status})`;
4900
- }
4901
- if (fm.description) {
4902
- line += ` \u2014 ${fm.description}`;
4903
- }
4904
- return line;
4905
- });
4906
- parts.push(`### Items
4907
- ${lines.join("\n")}`);
4908
- }
4909
- if (parts.length === 0) {
4910
- return "";
4911
- }
4912
- return `<current_roadmap>
4913
- ## Roadmap
4914
- Use readFile to access full contents.
4915
-
4916
- ${parts.join("\n\n")}
4917
- </current_roadmap>`;
4918
- }
4919
- function loadPlatformBrief() {
4920
- return `<platform_brief>
4921
- ## What is a Remy app?
4922
-
4923
- A Remy app is a managed full-stack TypeScript project with three layers: a spec (natural language in src/), a backend contract (methods, tables, roles in dist/), and one or more interfaces (web, API, bots, cron, etc.). The spec is the source of truth; code is derived from it.
4924
-
4925
- This is a capable, stable platform used in production by 100k+ users. Build with confidence \u2014 you're building production-grade apps, not fragile prototypes.
4926
-
4927
- ## What people build
4928
-
4929
- - Business tools \u2014 client portals, approval workflows, admin panels with role-based access
4930
- - AI-powered apps \u2014 document processors, image/video tools, content generators, conversational agents that take actions
4931
- - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
4932
- - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
4933
- - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
4934
- - Agent tools \u2014 MCP tool servers for AI assistants
4935
- - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
4936
- - API services \u2014 backend logic exposed as REST endpoints
4937
- - Simple static sites \u2014 no backend needed, just a web interface with a build step
4938
-
4939
- An app can be any combination of these.
4940
-
4941
- ## Interfaces
4942
-
4943
- Each interface type invokes the same backend methods. Methods don't know which interface called them.
4944
-
4945
- - Web \u2014 any TypeScript project with a build command. Framework-agnostic (React, Vue, Svelte, vanilla, anything). The frontend SDK provides typed RPC to backend methods.
4946
- - API \u2014 auto-generated REST endpoints for every method
4947
- - Cron \u2014 scheduled jobs on a configurable interval
4948
- - Webhook \u2014 HTTP endpoints that trigger methods
4949
- - Email \u2014 inbound email processing
4950
- - MCP \u2014 tool servers for AI assistants
4951
- - Agent \u2014 conversational LLM interface with tool access to backend methods
4952
-
4953
- ## Backend
4954
-
4955
- TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
4956
-
4957
- - Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
4958
- - Built-in app-managed auth. Opt-in via manifest \u2014 developer builds login UI, platform handles verification codes (email-code, sms-code) and cookie sessions. API key auth for programmatic access. No OAuth, no social login (no Apple, Google, Facebook, or GitHub sign-in). Backend methods use auth.requireRole() for access control.
4959
- - Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
4960
- - Git-native deployment. Push to default branch to deploy.
4961
-
4962
- ## MindStudio SDK
4963
-
4964
- The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (OpenAI, Anthropic, Google, Meta, Mistral, and more) and 1000+ integrations (email, SMS, Slack, HubSpot, Google Workspace, web scraping, image/video generation, media processing, and much more) with zero configuration \u2014 credentials are handled automatically in the execution environment. No API keys needed. This SDK is robust and battle-tested in production.
4965
-
4966
- ## What Remy apps are NOT good for
4967
-
4968
- - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
4969
- - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
4970
- </platform_brief>`;
4971
- }
4972
-
4973
5006
  // src/subagents/copyEditor/tools.ts
4974
5007
  var COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
4975
5008
 
@@ -5068,7 +5101,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
5068
5101
  }
5069
5102
 
5070
5103
  // src/subagents/designExpert/data/sampleCache.ts
5071
- import fs18 from "fs";
5104
+ import fs17 from "fs";
5072
5105
  var SAMPLE_FILE = ".remy-design-sample.json";
5073
5106
  var cached2 = null;
5074
5107
  function generateIndices(poolSize, sampleSize) {
@@ -5082,14 +5115,14 @@ function generateIndices(poolSize, sampleSize) {
5082
5115
  }
5083
5116
  function load() {
5084
5117
  try {
5085
- return JSON.parse(fs18.readFileSync(SAMPLE_FILE, "utf-8"));
5118
+ return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
5086
5119
  } catch {
5087
5120
  return null;
5088
5121
  }
5089
5122
  }
5090
5123
  function save(indices) {
5091
5124
  try {
5092
- fs18.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5125
+ fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5093
5126
  } catch {
5094
5127
  }
5095
5128
  }
@@ -5451,7 +5484,7 @@ var VISION_TOOLS = [
5451
5484
  ];
5452
5485
 
5453
5486
  // src/subagents/productVision/executor.ts
5454
- import fs19 from "fs";
5487
+ import fs18 from "fs";
5455
5488
  import path10 from "path";
5456
5489
  var ROADMAP_DIR = "src/roadmap";
5457
5490
  var PITCH_DECK_SHELL = readAsset(
@@ -5466,13 +5499,13 @@ async function executeVisionTool(name, input, context) {
5466
5499
  case "writeFile": {
5467
5500
  const filePath = resolve(input.path);
5468
5501
  try {
5469
- fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
5502
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5470
5503
  let oldContent = null;
5471
5504
  try {
5472
- oldContent = fs19.readFileSync(filePath, "utf-8");
5505
+ oldContent = fs18.readFileSync(filePath, "utf-8");
5473
5506
  } catch {
5474
5507
  }
5475
- fs19.writeFileSync(filePath, input.content, "utf-8");
5508
+ fs18.writeFileSync(filePath, input.content, "utf-8");
5476
5509
  const lineCount = input.content.split("\n").length;
5477
5510
  const label = oldContent !== null ? "Wrote" : "Created";
5478
5511
  return `${label} ${filePath} (${lineCount} lines)
@@ -5484,11 +5517,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
5484
5517
  case "deleteFile": {
5485
5518
  const filePath = resolve(input.path);
5486
5519
  try {
5487
- if (!fs19.existsSync(filePath)) {
5520
+ if (!fs18.existsSync(filePath)) {
5488
5521
  return `Error: ${filePath} does not exist`;
5489
5522
  }
5490
- const oldContent = fs19.readFileSync(filePath, "utf-8");
5491
- fs19.unlinkSync(filePath);
5523
+ const oldContent = fs18.readFileSync(filePath, "utf-8");
5524
+ fs18.unlinkSync(filePath);
5492
5525
  return `Deleted ${filePath}
5493
5526
  ${unifiedDiff(filePath, oldContent, "")}`;
5494
5527
  } catch (err) {
@@ -5501,9 +5534,9 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5501
5534
  }
5502
5535
  const filePath = resolve("pitch.html");
5503
5536
  try {
5504
- fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
5505
- const exists = fs19.existsSync(filePath);
5506
- const before = exists ? fs19.statSync(filePath).mtimeMs : null;
5537
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5538
+ const exists = fs18.existsSync(filePath);
5539
+ const before = exists ? fs18.statSync(filePath).mtimeMs : null;
5507
5540
  const delivery = exists ? `### Your deliverable
5508
5541
  The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
5509
5542
 
@@ -5533,11 +5566,11 @@ Maintain the bones of the presentation scaffolding. Always keep the progress bar
5533
5566
  ${delivery}`;
5534
5567
  const result = await runDesignExpertRender({ task }, context);
5535
5568
  context.subAgentMessages?.set(context.toolCallId, result.messages);
5536
- if (!fs19.existsSync(filePath)) {
5569
+ if (!fs18.existsSync(filePath)) {
5537
5570
  return `Error: the design expert did not write ${filePath}. Its reply was:
5538
5571
  ${result.text}`;
5539
5572
  }
5540
- if (before !== null && fs19.statSync(filePath).mtimeMs === before) {
5573
+ if (before !== null && fs18.statSync(filePath).mtimeMs === before) {
5541
5574
  return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
5542
5575
  ${result.text}`;
5543
5576
  }
@@ -5871,7 +5904,7 @@ var scrapeWebUrlTool = {
5871
5904
  };
5872
5905
 
5873
5906
  // src/tools/spec/writeBuildOverview.ts
5874
- import fs20 from "fs";
5907
+ import fs19 from "fs";
5875
5908
  var OVERVIEW_FILE = "src/overview.html";
5876
5909
  var DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
5877
5910
 
@@ -5951,7 +5984,7 @@ var buildOverviewTool = {
5951
5984
  if (!content) {
5952
5985
  return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
5953
5986
  }
5954
- const exists = fs20.existsSync(OVERVIEW_FILE);
5987
+ const exists = fs19.existsSync(OVERVIEW_FILE);
5955
5988
  const task = `<overview_copy>${content}</overview_copy>
5956
5989
 
5957
5990
  ${DESIGN_BRIEF}
@@ -5969,7 +6002,7 @@ ${exists ? refreshDelivery() : initialDelivery()}`;
5969
6002
  }
5970
6003
  const result = await runDesignExpertRender({ task }, context);
5971
6004
  context.subAgentMessages?.set(context.toolCallId, result.messages);
5972
- if (!fs20.existsSync(OVERVIEW_FILE)) {
6005
+ if (!fs19.existsSync(OVERVIEW_FILE)) {
5973
6006
  return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
5974
6007
  ${result.text}`;
5975
6008
  }
@@ -6393,7 +6426,7 @@ Write the summary of the conversation above, following your instructions.`;
6393
6426
  }
6394
6427
 
6395
6428
  // src/session.ts
6396
- import fs21 from "fs";
6429
+ import fs20 from "fs";
6397
6430
  import path11 from "path";
6398
6431
  var log9 = createLogger("session");
6399
6432
  var SESSION_FILE = ".remy-session.json";
@@ -6412,7 +6445,7 @@ var ARCHIVE_MSG_CACHE_MAX = 3;
6412
6445
  function loadSession(state) {
6413
6446
  pruneArchives();
6414
6447
  try {
6415
- const raw = fs21.readFileSync(SESSION_FILE, "utf-8");
6448
+ const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
6416
6449
  const data = JSON.parse(raw);
6417
6450
  if (data.models && typeof data.models === "object") {
6418
6451
  state.models = data.models;
@@ -6485,19 +6518,19 @@ function buildPayload(state) {
6485
6518
  return payload;
6486
6519
  }
6487
6520
  function archiveMessages(messages, label, models) {
6488
- fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
6521
+ fs20.mkdirSync(ARCHIVE_DIR, { recursive: true });
6489
6522
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6490
6523
  const count = messages.length;
6491
6524
  let dest = path11.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
6492
6525
  let n = 1;
6493
- while (fs21.existsSync(dest)) {
6526
+ while (fs20.existsSync(dest)) {
6494
6527
  dest = path11.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
6495
6528
  }
6496
6529
  const payload = { messages };
6497
6530
  if (models && Object.keys(models).length > 0) {
6498
6531
  payload.models = models;
6499
6532
  }
6500
- fs21.writeFileSync(dest, JSON.stringify(payload), "utf-8");
6533
+ fs20.writeFileSync(dest, JSON.stringify(payload), "utf-8");
6501
6534
  archiveCountCache.set(path11.basename(dest), count);
6502
6535
  log9.info("Session archived", { label, dest, messageCount: count });
6503
6536
  pruneArchives();
@@ -6505,13 +6538,13 @@ function archiveMessages(messages, label, models) {
6505
6538
  }
6506
6539
  function pruneArchives() {
6507
6540
  try {
6508
- const entries = fs21.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
6541
+ const entries = fs20.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
6509
6542
  if (entries.length <= 1) {
6510
6543
  return;
6511
6544
  }
6512
6545
  const archives = entries.map((name) => ({
6513
6546
  name,
6514
- size: fs21.statSync(path11.join(ARCHIVE_DIR, name)).size
6547
+ size: fs20.statSync(path11.join(ARCHIVE_DIR, name)).size
6515
6548
  })).sort(
6516
6549
  (a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
6517
6550
  );
@@ -6529,7 +6562,7 @@ function pruneArchives() {
6529
6562
  let freed = 0;
6530
6563
  for (let i = cut; i < archives.length; i++) {
6531
6564
  try {
6532
- fs21.unlinkSync(path11.join(ARCHIVE_DIR, archives[i].name));
6565
+ fs20.unlinkSync(path11.join(ARCHIVE_DIR, archives[i].name));
6533
6566
  freed += archives[i].size;
6534
6567
  removed++;
6535
6568
  } catch {
@@ -6553,7 +6586,7 @@ function parseArchive(name) {
6553
6586
  return cached3;
6554
6587
  }
6555
6588
  try {
6556
- const raw = fs21.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
6589
+ const raw = fs20.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
6557
6590
  const data = JSON.parse(raw);
6558
6591
  const messages = Array.isArray(data?.messages) ? data.messages : [];
6559
6592
  archiveCountCache.set(name, messages.length);
@@ -6591,7 +6624,7 @@ function readArchiveMessages(name) {
6591
6624
  function listConversationArchives() {
6592
6625
  let names;
6593
6626
  try {
6594
- names = fs21.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
6627
+ names = fs20.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
6595
6628
  } catch {
6596
6629
  return { slots: [], archivedCount: 0 };
6597
6630
  }
@@ -6702,7 +6735,7 @@ function saveSession(state) {
6702
6735
  if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
6703
6736
  serialized = JSON.stringify(buildPayload(state));
6704
6737
  }
6705
- fs21.writeFileSync(SESSION_FILE, serialized, "utf-8");
6738
+ fs20.writeFileSync(SESSION_FILE, serialized, "utf-8");
6706
6739
  log9.info("Session saved", { messageCount: state.messages.length });
6707
6740
  } catch (err) {
6708
6741
  log9.warn("Session save failed", { error: err.message });
@@ -6718,8 +6751,8 @@ function clearSession(state) {
6718
6751
  }
6719
6752
  state.messages = [];
6720
6753
  try {
6721
- if (fs21.existsSync(SESSION_FILE)) {
6722
- fs21.unlinkSync(SESSION_FILE);
6754
+ if (fs20.existsSync(SESSION_FILE)) {
6755
+ fs20.unlinkSync(SESSION_FILE);
6723
6756
  }
6724
6757
  } catch (err) {
6725
6758
  log9.warn("Session clear: could not remove live file", {
@@ -6771,7 +6804,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
6771
6804
  }
6772
6805
 
6773
6806
  // src/brandExtraction/index.ts
6774
- import fs22 from "fs";
6807
+ import fs21 from "fs";
6775
6808
  import path12 from "path";
6776
6809
  import { createHash } from "crypto";
6777
6810
  var log11 = createLogger("brandExtraction");
@@ -6795,13 +6828,16 @@ async function runExtraction(apiConfig, model) {
6795
6828
  log11.info("Brand persisted", { inputHash });
6796
6829
  return brand;
6797
6830
  }
6798
- function isBrandRelevant(filePath) {
6799
- if (filePath === path12.join("src", "app.md")) {
6831
+ function isDedicatedBrandFile(filePath) {
6832
+ if (filePath.split(path12.sep).includes("@brand")) {
6800
6833
  return true;
6801
6834
  }
6802
6835
  const { type } = parseFrontmatter3(filePath);
6803
6836
  return type.startsWith("design/color") || type.startsWith("design/typography");
6804
6837
  }
6838
+ function isBrandRelevant(filePath) {
6839
+ return filePath === path12.join("src", "app.md") || isDedicatedBrandFile(filePath);
6840
+ }
6805
6841
  function computeInputHash() {
6806
6842
  const entries = [];
6807
6843
  for (const filePath of walkMdFiles3("src")) {
@@ -6809,7 +6845,7 @@ function computeInputHash() {
6809
6845
  entries.push({ path: filePath, content: readSafe(filePath) });
6810
6846
  }
6811
6847
  }
6812
- const manifest = readSafe("mindstudio.json");
6848
+ const manifest = readBrandManifest();
6813
6849
  if (manifest) {
6814
6850
  entries.push({ path: "mindstudio.json", content: manifest });
6815
6851
  }
@@ -6822,7 +6858,25 @@ function sha256(input) {
6822
6858
  }
6823
6859
  function readSafe(filePath) {
6824
6860
  try {
6825
- return fs22.readFileSync(filePath, "utf-8");
6861
+ return fs21.readFileSync(filePath, "utf-8");
6862
+ } catch {
6863
+ return "";
6864
+ }
6865
+ }
6866
+ function readBrandManifest() {
6867
+ const raw = readSafe("mindstudio.json");
6868
+ if (!raw) {
6869
+ return "";
6870
+ }
6871
+ try {
6872
+ const parsed = JSON.parse(raw);
6873
+ const projected = {};
6874
+ for (const key of ["name", "description", "iconUrl"]) {
6875
+ if (parsed[key] !== void 0) {
6876
+ projected[key] = parsed[key];
6877
+ }
6878
+ }
6879
+ return Object.keys(projected).length > 0 ? JSON.stringify(projected, null, 2) : "";
6826
6880
  } catch {
6827
6881
  return "";
6828
6882
  }
@@ -6830,7 +6884,7 @@ function readSafe(filePath) {
6830
6884
  function walkMdFiles3(dir) {
6831
6885
  const results = [];
6832
6886
  try {
6833
- const entries = fs22.readdirSync(dir, { withFileTypes: true });
6887
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
6834
6888
  for (const entry of entries) {
6835
6889
  const full = path12.join(dir, entry.name);
6836
6890
  if (entry.isDirectory()) {
@@ -6845,7 +6899,7 @@ function walkMdFiles3(dir) {
6845
6899
  }
6846
6900
  function parseFrontmatter3(filePath) {
6847
6901
  try {
6848
- const content = fs22.readFileSync(filePath, "utf-8");
6902
+ const content = fs21.readFileSync(filePath, "utf-8");
6849
6903
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6850
6904
  if (!match) {
6851
6905
  return { type: "" };
@@ -6908,6 +6962,7 @@ async function extractBrand(apiConfig, model) {
6908
6962
  }
6909
6963
  return validateBrand(parsed);
6910
6964
  }
6965
+ var HEAD_SLICE_CHARS = 2e3;
6911
6966
  var BRAND_CORPUS_CHAR_LIMIT = 24e5;
6912
6967
  function buildCorpus() {
6913
6968
  const all = walkMdFiles3("src");
@@ -6916,14 +6971,17 @@ function buildCorpus() {
6916
6971
  ...all.filter((f) => !isBrandRelevant(f))
6917
6972
  ];
6918
6973
  const files = [];
6919
- const manifest = readSafe("mindstudio.json");
6974
+ const manifest = readBrandManifest();
6920
6975
  if (manifest) {
6921
6976
  files.push({ path: "mindstudio.json", content: manifest });
6922
6977
  }
6923
6978
  for (const filePath of ordered) {
6924
6979
  const content = readSafe(filePath);
6925
6980
  if (content) {
6926
- files.push({ path: filePath, content });
6981
+ files.push({
6982
+ path: filePath,
6983
+ content: isDedicatedBrandFile(filePath) ? content : headSlice(content)
6984
+ });
6927
6985
  }
6928
6986
  }
6929
6987
  const sep = "\n\n---\n\n";
@@ -6945,6 +7003,14 @@ ${content}`;
6945
7003
  }
6946
7004
  return sections.join(sep);
6947
7005
  }
7006
+ function headSlice(content) {
7007
+ if (content.length <= HEAD_SLICE_CHARS) {
7008
+ return content;
7009
+ }
7010
+ return content.slice(0, HEAD_SLICE_CHARS) + `
7011
+
7012
+ (head slice of a ${(content.length / 1024).toFixed(0)}KB spec \u2014 not a dedicated brand file, so only its opening is included)`;
7013
+ }
6948
7014
  function parseJsonResponse(text) {
6949
7015
  const trimmed = text.trim();
6950
7016
  const fenceMatch = trimmed.match(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/);
@@ -7043,14 +7109,14 @@ function pickFont(raw) {
7043
7109
  }
7044
7110
  function persistBrand(brand, inputHash) {
7045
7111
  const tmp = `${BRAND_FILE}.tmp`;
7046
- fs22.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7047
- fs22.renameSync(tmp, BRAND_FILE);
7112
+ fs21.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7113
+ fs21.renameSync(tmp, BRAND_FILE);
7048
7114
  const cache = { inputHash, generatedAt: Date.now() };
7049
- fs22.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7115
+ fs21.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7050
7116
  }
7051
7117
  function readCache() {
7052
7118
  try {
7053
- const raw = fs22.readFileSync(CACHE_FILE, "utf-8");
7119
+ const raw = fs21.readFileSync(CACHE_FILE, "utf-8");
7054
7120
  const parsed = JSON.parse(raw);
7055
7121
  if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
7056
7122
  return parsed;