@mindstudio-ai/remy 0.1.249 → 0.1.251

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
@@ -3766,6 +3766,17 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3766
3766
  return { text: ack, messages: [], backgrounded: true };
3767
3767
  }
3768
3768
 
3769
+ // src/subagents/common/tools.ts
3770
+ var COMMON_READ_TOOLS = [
3771
+ readFileTool.definition,
3772
+ listDirTool.definition,
3773
+ grepTool.definition,
3774
+ globTool.definition
3775
+ ];
3776
+ var COMMON_READ_TOOL_NAMES = new Set(
3777
+ COMMON_READ_TOOLS.map((t) => t.name)
3778
+ );
3779
+
3769
3780
  // src/subagents/browserAutomation/tools.ts
3770
3781
  var BROWSER_TOOLS = [
3771
3782
  {
@@ -3909,23 +3920,193 @@ var BROWSER_TOOLS = [
3909
3920
  }
3910
3921
  }
3911
3922
  }
3912
- }
3923
+ },
3924
+ // Read tools so the QA agent can pull full spec detail on demand — the spec
3925
+ // context in its prompt is a lightweight index (see prompt.ts) that points
3926
+ // here. Routed to the global executeTool in index.ts, mirroring specSync.
3927
+ ...COMMON_READ_TOOLS,
3928
+ readSpecTool.definition
3913
3929
  ];
3914
3930
  var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
3915
3931
 
3916
- // src/subagents/browserAutomation/prompt.ts
3932
+ // src/subagents/common/context.ts
3917
3933
  import fs16 from "fs";
3918
- var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
3919
- function getBrowserAutomationPrompt() {
3934
+ import path9 from "path";
3935
+ function walkMdFiles2(dir, skip) {
3936
+ const files = [];
3920
3937
  try {
3921
- const appSpec = fs16.readFileSync("src/app.md", "utf-8").trim();
3922
- return `${BASE_PROMPT}
3938
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
3939
+ const full = path9.join(dir, entry.name);
3940
+ if (entry.isDirectory()) {
3941
+ if (!skip?.has(entry.name)) {
3942
+ files.push(...walkMdFiles2(full, skip));
3943
+ }
3944
+ } else if (entry.name.endsWith(".md")) {
3945
+ files.push(full);
3946
+ }
3947
+ }
3948
+ } catch {
3949
+ }
3950
+ return files.sort();
3951
+ }
3952
+ function parseFrontmatter2(filePath) {
3953
+ try {
3954
+ const content = fs16.readFileSync(filePath, "utf-8");
3955
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
3956
+ if (!match) {
3957
+ return {};
3958
+ }
3959
+ const fm = {};
3960
+ for (const line of match[1].split("\n")) {
3961
+ const sep = line.indexOf(":");
3962
+ if (sep > 0) {
3963
+ const key = line.slice(0, sep).trim();
3964
+ const val = line.slice(sep + 1).trim();
3965
+ fm[key] = val;
3966
+ }
3967
+ }
3968
+ return fm;
3969
+ } catch {
3970
+ return {};
3971
+ }
3972
+ }
3973
+ function loadSpecIndex() {
3974
+ const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
3975
+ if (files.length === 0) {
3976
+ return "";
3977
+ }
3978
+ const lines = files.map((f) => {
3979
+ const fm = parseFrontmatter2(f);
3980
+ let line = `- ${f}`;
3981
+ if (fm.name) {
3982
+ line += ` \u2014 "${fm.name}"`;
3983
+ }
3984
+ if (fm.description) {
3985
+ line += ` \u2014 ${fm.description}`;
3986
+ }
3987
+ return line;
3988
+ });
3989
+ return `<spec_files>
3990
+ ## Project Spec Files
3991
+ Use readFile to access full contents.
3923
3992
 
3924
- <!-- cache_breakpoint -->
3993
+ ${lines.join("\n")}
3994
+ </spec_files>`;
3995
+ }
3996
+ function loadRoadmapIndex() {
3997
+ const parts = [];
3998
+ try {
3999
+ const indexJson = JSON.parse(
4000
+ fs16.readFileSync("src/roadmap/index.json", "utf-8")
4001
+ );
4002
+ if (indexJson.lanes?.length > 0) {
4003
+ const laneLines = indexJson.lanes.map(
4004
+ (l) => `- **${l.name}**: ${l.narrative || ""} (${l.items?.length || 0} items)`
4005
+ );
4006
+ parts.push(`### Lanes
4007
+ ${laneLines.join("\n")}`);
4008
+ }
4009
+ if (indexJson.standalone?.length > 0) {
4010
+ parts.push(
4011
+ `### Standalone
4012
+ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
4013
+ );
4014
+ }
4015
+ } catch {
4016
+ }
4017
+ const files = walkMdFiles2("src/roadmap");
4018
+ if (files.length > 0) {
4019
+ const lines = files.map((f) => {
4020
+ const fm = parseFrontmatter2(f);
4021
+ let line = `- ${f}`;
4022
+ if (fm.name) {
4023
+ line += ` \u2014 "${fm.name}"`;
4024
+ }
4025
+ if (fm.status) {
4026
+ line += ` (${fm.status})`;
4027
+ }
4028
+ if (fm.description) {
4029
+ line += ` \u2014 ${fm.description}`;
4030
+ }
4031
+ return line;
4032
+ });
4033
+ parts.push(`### Items
4034
+ ${lines.join("\n")}`);
4035
+ }
4036
+ if (parts.length === 0) {
4037
+ return "";
4038
+ }
4039
+ return `<current_roadmap>
4040
+ ## Roadmap
4041
+ Use readFile to access full contents.
4042
+
4043
+ ${parts.join("\n\n")}
4044
+ </current_roadmap>`;
4045
+ }
4046
+ function loadPlatformBrief() {
4047
+ return `<platform_brief>
4048
+ ## What is a Remy app?
3925
4049
 
3926
- <app_context>
3927
- ${appSpec}
3928
- </app_context>`;
4050
+ 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.
4051
+
4052
+ 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.
4053
+
4054
+ ## What people build
4055
+
4056
+ - Business tools \u2014 client portals, approval workflows, admin panels with role-based access
4057
+ - AI-powered apps \u2014 document processors, image/video tools, content generators, conversational agents that take actions
4058
+ - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
4059
+ - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
4060
+ - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
4061
+ - Agent tools \u2014 MCP tool servers for AI assistants
4062
+ - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
4063
+ - API services \u2014 backend logic exposed as REST endpoints
4064
+ - Simple static sites \u2014 no backend needed, just a web interface with a build step
4065
+
4066
+ An app can be any combination of these.
4067
+
4068
+ ## Interfaces
4069
+
4070
+ Each interface type invokes the same backend methods. Methods don't know which interface called them.
4071
+
4072
+ - 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.
4073
+ - API \u2014 auto-generated REST endpoints for every method
4074
+ - Cron \u2014 scheduled jobs on a configurable interval
4075
+ - Webhook \u2014 HTTP endpoints that trigger methods
4076
+ - Email \u2014 inbound email processing
4077
+ - MCP \u2014 tool servers for AI assistants
4078
+ - Agent \u2014 conversational LLM interface with tool access to backend methods
4079
+
4080
+ ## Backend
4081
+
4082
+ TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
4083
+
4084
+ - Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
4085
+ - 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.
4086
+ - Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
4087
+ - Git-native deployment. Push to default branch to deploy.
4088
+
4089
+ ## MindStudio SDK
4090
+
4091
+ 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.
4092
+
4093
+ ## What Remy apps are NOT good for
4094
+
4095
+ - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
4096
+ - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
4097
+ </platform_brief>`;
4098
+ }
4099
+
4100
+ // src/subagents/browserAutomation/prompt.ts
4101
+ var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
4102
+ function getBrowserAutomationPrompt() {
4103
+ try {
4104
+ const specIndex = loadSpecIndex();
4105
+ const parts = [BASE_PROMPT, "<!-- cache_breakpoint -->"];
4106
+ if (specIndex) {
4107
+ parts.push(specIndex);
4108
+ }
4109
+ return parts.join("\n\n");
3929
4110
  } catch {
3930
4111
  return BASE_PROMPT;
3931
4112
  }
@@ -3982,6 +4163,9 @@ async function runBrowserAutomation(task, context, opts) {
3982
4163
  return `Error taking screenshot: ${err.message}`;
3983
4164
  }
3984
4165
  }
4166
+ if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
4167
+ return executeTool(name, _input, context);
4168
+ }
3985
4169
  return `Error: unknown local tool "${name}"`;
3986
4170
  },
3987
4171
  apiConfig: context.apiConfig,
@@ -4190,17 +4374,6 @@ var screenshotTool = {
4190
4374
  }
4191
4375
  };
4192
4376
 
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
4377
  // src/subagents/designExpert/tools/searchGoogle.ts
4205
4378
  var searchGoogle_exports = {};
4206
4379
  __export(searchGoogle_exports, {
@@ -4802,174 +4975,6 @@ __export(polishCopy_exports, {
4802
4975
  execute: () => execute8
4803
4976
  });
4804
4977
 
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
4978
  // src/subagents/copyEditor/tools.ts
4974
4979
  var COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
4975
4980
 
@@ -5068,7 +5073,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
5068
5073
  }
5069
5074
 
5070
5075
  // src/subagents/designExpert/data/sampleCache.ts
5071
- import fs18 from "fs";
5076
+ import fs17 from "fs";
5072
5077
  var SAMPLE_FILE = ".remy-design-sample.json";
5073
5078
  var cached2 = null;
5074
5079
  function generateIndices(poolSize, sampleSize) {
@@ -5082,14 +5087,14 @@ function generateIndices(poolSize, sampleSize) {
5082
5087
  }
5083
5088
  function load() {
5084
5089
  try {
5085
- return JSON.parse(fs18.readFileSync(SAMPLE_FILE, "utf-8"));
5090
+ return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
5086
5091
  } catch {
5087
5092
  return null;
5088
5093
  }
5089
5094
  }
5090
5095
  function save(indices) {
5091
5096
  try {
5092
- fs18.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5097
+ fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5093
5098
  } catch {
5094
5099
  }
5095
5100
  }
@@ -5451,7 +5456,7 @@ var VISION_TOOLS = [
5451
5456
  ];
5452
5457
 
5453
5458
  // src/subagents/productVision/executor.ts
5454
- import fs19 from "fs";
5459
+ import fs18 from "fs";
5455
5460
  import path10 from "path";
5456
5461
  var ROADMAP_DIR = "src/roadmap";
5457
5462
  var PITCH_DECK_SHELL = readAsset(
@@ -5466,13 +5471,13 @@ async function executeVisionTool(name, input, context) {
5466
5471
  case "writeFile": {
5467
5472
  const filePath = resolve(input.path);
5468
5473
  try {
5469
- fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
5474
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5470
5475
  let oldContent = null;
5471
5476
  try {
5472
- oldContent = fs19.readFileSync(filePath, "utf-8");
5477
+ oldContent = fs18.readFileSync(filePath, "utf-8");
5473
5478
  } catch {
5474
5479
  }
5475
- fs19.writeFileSync(filePath, input.content, "utf-8");
5480
+ fs18.writeFileSync(filePath, input.content, "utf-8");
5476
5481
  const lineCount = input.content.split("\n").length;
5477
5482
  const label = oldContent !== null ? "Wrote" : "Created";
5478
5483
  return `${label} ${filePath} (${lineCount} lines)
@@ -5484,11 +5489,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
5484
5489
  case "deleteFile": {
5485
5490
  const filePath = resolve(input.path);
5486
5491
  try {
5487
- if (!fs19.existsSync(filePath)) {
5492
+ if (!fs18.existsSync(filePath)) {
5488
5493
  return `Error: ${filePath} does not exist`;
5489
5494
  }
5490
- const oldContent = fs19.readFileSync(filePath, "utf-8");
5491
- fs19.unlinkSync(filePath);
5495
+ const oldContent = fs18.readFileSync(filePath, "utf-8");
5496
+ fs18.unlinkSync(filePath);
5492
5497
  return `Deleted ${filePath}
5493
5498
  ${unifiedDiff(filePath, oldContent, "")}`;
5494
5499
  } catch (err) {
@@ -5501,9 +5506,9 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5501
5506
  }
5502
5507
  const filePath = resolve("pitch.html");
5503
5508
  try {
5504
- fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
5505
- const exists = fs19.existsSync(filePath);
5506
- const before = exists ? fs19.statSync(filePath).mtimeMs : null;
5509
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5510
+ const exists = fs18.existsSync(filePath);
5511
+ const before = exists ? fs18.statSync(filePath).mtimeMs : null;
5507
5512
  const delivery = exists ? `### Your deliverable
5508
5513
  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
5514
 
@@ -5533,11 +5538,11 @@ Maintain the bones of the presentation scaffolding. Always keep the progress bar
5533
5538
  ${delivery}`;
5534
5539
  const result = await runDesignExpertRender({ task }, context);
5535
5540
  context.subAgentMessages?.set(context.toolCallId, result.messages);
5536
- if (!fs19.existsSync(filePath)) {
5541
+ if (!fs18.existsSync(filePath)) {
5537
5542
  return `Error: the design expert did not write ${filePath}. Its reply was:
5538
5543
  ${result.text}`;
5539
5544
  }
5540
- if (before !== null && fs19.statSync(filePath).mtimeMs === before) {
5545
+ if (before !== null && fs18.statSync(filePath).mtimeMs === before) {
5541
5546
  return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
5542
5547
  ${result.text}`;
5543
5548
  }
@@ -5871,7 +5876,7 @@ var scrapeWebUrlTool = {
5871
5876
  };
5872
5877
 
5873
5878
  // src/tools/spec/writeBuildOverview.ts
5874
- import fs20 from "fs";
5879
+ import fs19 from "fs";
5875
5880
  var OVERVIEW_FILE = "src/overview.html";
5876
5881
  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
5882
 
@@ -5951,7 +5956,7 @@ var buildOverviewTool = {
5951
5956
  if (!content) {
5952
5957
  return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
5953
5958
  }
5954
- const exists = fs20.existsSync(OVERVIEW_FILE);
5959
+ const exists = fs19.existsSync(OVERVIEW_FILE);
5955
5960
  const task = `<overview_copy>${content}</overview_copy>
5956
5961
 
5957
5962
  ${DESIGN_BRIEF}
@@ -5969,7 +5974,7 @@ ${exists ? refreshDelivery() : initialDelivery()}`;
5969
5974
  }
5970
5975
  const result = await runDesignExpertRender({ task }, context);
5971
5976
  context.subAgentMessages?.set(context.toolCallId, result.messages);
5972
- if (!fs20.existsSync(OVERVIEW_FILE)) {
5977
+ if (!fs19.existsSync(OVERVIEW_FILE)) {
5973
5978
  return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
5974
5979
  ${result.text}`;
5975
5980
  }
@@ -6393,7 +6398,7 @@ Write the summary of the conversation above, following your instructions.`;
6393
6398
  }
6394
6399
 
6395
6400
  // src/session.ts
6396
- import fs21 from "fs";
6401
+ import fs20 from "fs";
6397
6402
  import path11 from "path";
6398
6403
  var log9 = createLogger("session");
6399
6404
  var SESSION_FILE = ".remy-session.json";
@@ -6412,7 +6417,7 @@ var ARCHIVE_MSG_CACHE_MAX = 3;
6412
6417
  function loadSession(state) {
6413
6418
  pruneArchives();
6414
6419
  try {
6415
- const raw = fs21.readFileSync(SESSION_FILE, "utf-8");
6420
+ const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
6416
6421
  const data = JSON.parse(raw);
6417
6422
  if (data.models && typeof data.models === "object") {
6418
6423
  state.models = data.models;
@@ -6485,19 +6490,19 @@ function buildPayload(state) {
6485
6490
  return payload;
6486
6491
  }
6487
6492
  function archiveMessages(messages, label, models) {
6488
- fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
6493
+ fs20.mkdirSync(ARCHIVE_DIR, { recursive: true });
6489
6494
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6490
6495
  const count = messages.length;
6491
6496
  let dest = path11.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
6492
6497
  let n = 1;
6493
- while (fs21.existsSync(dest)) {
6498
+ while (fs20.existsSync(dest)) {
6494
6499
  dest = path11.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
6495
6500
  }
6496
6501
  const payload = { messages };
6497
6502
  if (models && Object.keys(models).length > 0) {
6498
6503
  payload.models = models;
6499
6504
  }
6500
- fs21.writeFileSync(dest, JSON.stringify(payload), "utf-8");
6505
+ fs20.writeFileSync(dest, JSON.stringify(payload), "utf-8");
6501
6506
  archiveCountCache.set(path11.basename(dest), count);
6502
6507
  log9.info("Session archived", { label, dest, messageCount: count });
6503
6508
  pruneArchives();
@@ -6505,13 +6510,13 @@ function archiveMessages(messages, label, models) {
6505
6510
  }
6506
6511
  function pruneArchives() {
6507
6512
  try {
6508
- const entries = fs21.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
6513
+ const entries = fs20.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
6509
6514
  if (entries.length <= 1) {
6510
6515
  return;
6511
6516
  }
6512
6517
  const archives = entries.map((name) => ({
6513
6518
  name,
6514
- size: fs21.statSync(path11.join(ARCHIVE_DIR, name)).size
6519
+ size: fs20.statSync(path11.join(ARCHIVE_DIR, name)).size
6515
6520
  })).sort(
6516
6521
  (a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
6517
6522
  );
@@ -6529,7 +6534,7 @@ function pruneArchives() {
6529
6534
  let freed = 0;
6530
6535
  for (let i = cut; i < archives.length; i++) {
6531
6536
  try {
6532
- fs21.unlinkSync(path11.join(ARCHIVE_DIR, archives[i].name));
6537
+ fs20.unlinkSync(path11.join(ARCHIVE_DIR, archives[i].name));
6533
6538
  freed += archives[i].size;
6534
6539
  removed++;
6535
6540
  } catch {
@@ -6553,7 +6558,7 @@ function parseArchive(name) {
6553
6558
  return cached3;
6554
6559
  }
6555
6560
  try {
6556
- const raw = fs21.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
6561
+ const raw = fs20.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
6557
6562
  const data = JSON.parse(raw);
6558
6563
  const messages = Array.isArray(data?.messages) ? data.messages : [];
6559
6564
  archiveCountCache.set(name, messages.length);
@@ -6591,7 +6596,7 @@ function readArchiveMessages(name) {
6591
6596
  function listConversationArchives() {
6592
6597
  let names;
6593
6598
  try {
6594
- names = fs21.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
6599
+ names = fs20.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
6595
6600
  } catch {
6596
6601
  return { slots: [], archivedCount: 0 };
6597
6602
  }
@@ -6702,7 +6707,7 @@ function saveSession(state) {
6702
6707
  if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
6703
6708
  serialized = JSON.stringify(buildPayload(state));
6704
6709
  }
6705
- fs21.writeFileSync(SESSION_FILE, serialized, "utf-8");
6710
+ fs20.writeFileSync(SESSION_FILE, serialized, "utf-8");
6706
6711
  log9.info("Session saved", { messageCount: state.messages.length });
6707
6712
  } catch (err) {
6708
6713
  log9.warn("Session save failed", { error: err.message });
@@ -6718,8 +6723,8 @@ function clearSession(state) {
6718
6723
  }
6719
6724
  state.messages = [];
6720
6725
  try {
6721
- if (fs21.existsSync(SESSION_FILE)) {
6722
- fs21.unlinkSync(SESSION_FILE);
6726
+ if (fs20.existsSync(SESSION_FILE)) {
6727
+ fs20.unlinkSync(SESSION_FILE);
6723
6728
  }
6724
6729
  } catch (err) {
6725
6730
  log9.warn("Session clear: could not remove live file", {
@@ -6771,7 +6776,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
6771
6776
  }
6772
6777
 
6773
6778
  // src/brandExtraction/index.ts
6774
- import fs22 from "fs";
6779
+ import fs21 from "fs";
6775
6780
  import path12 from "path";
6776
6781
  import { createHash } from "crypto";
6777
6782
  var log11 = createLogger("brandExtraction");
@@ -6822,7 +6827,7 @@ function sha256(input) {
6822
6827
  }
6823
6828
  function readSafe(filePath) {
6824
6829
  try {
6825
- return fs22.readFileSync(filePath, "utf-8");
6830
+ return fs21.readFileSync(filePath, "utf-8");
6826
6831
  } catch {
6827
6832
  return "";
6828
6833
  }
@@ -6830,7 +6835,7 @@ function readSafe(filePath) {
6830
6835
  function walkMdFiles3(dir) {
6831
6836
  const results = [];
6832
6837
  try {
6833
- const entries = fs22.readdirSync(dir, { withFileTypes: true });
6838
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
6834
6839
  for (const entry of entries) {
6835
6840
  const full = path12.join(dir, entry.name);
6836
6841
  if (entry.isDirectory()) {
@@ -6845,7 +6850,7 @@ function walkMdFiles3(dir) {
6845
6850
  }
6846
6851
  function parseFrontmatter3(filePath) {
6847
6852
  try {
6848
- const content = fs22.readFileSync(filePath, "utf-8");
6853
+ const content = fs21.readFileSync(filePath, "utf-8");
6849
6854
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6850
6855
  if (!match) {
6851
6856
  return { type: "" };
@@ -7043,14 +7048,14 @@ function pickFont(raw) {
7043
7048
  }
7044
7049
  function persistBrand(brand, inputHash) {
7045
7050
  const tmp = `${BRAND_FILE}.tmp`;
7046
- fs22.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7047
- fs22.renameSync(tmp, BRAND_FILE);
7051
+ fs21.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7052
+ fs21.renameSync(tmp, BRAND_FILE);
7048
7053
  const cache = { inputHash, generatedAt: Date.now() };
7049
- fs22.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7054
+ fs21.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7050
7055
  }
7051
7056
  function readCache() {
7052
7057
  try {
7053
- const raw = fs22.readFileSync(CACHE_FILE, "utf-8");
7058
+ const raw = fs21.readFileSync(CACHE_FILE, "utf-8");
7054
7059
  const parsed = JSON.parse(raw);
7055
7060
  if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
7056
7061
  return parsed;