@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/index.js CHANGED
@@ -2755,16 +2755,17 @@ function isBinary(buffer) {
2755
2755
  }
2756
2756
  return false;
2757
2757
  }
2758
- var DEFAULT_WINDOW, readFileTool;
2758
+ var DEFAULT_WINDOW, MAX_BYTES, readFileTool;
2759
2759
  var init_readFile = __esm({
2760
2760
  "src/tools/code/readFile.ts"() {
2761
2761
  "use strict";
2762
2762
  DEFAULT_WINDOW = 500;
2763
+ MAX_BYTES = 64 * 1024;
2763
2764
  readFileTool = {
2764
2765
  clearable: true,
2765
2766
  definition: {
2766
2767
  name: "readFile",
2767
- 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.",
2768
+ 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.",
2768
2769
  inputSchema: {
2769
2770
  type: "object",
2770
2771
  properties: {
@@ -2821,12 +2822,35 @@ var init_readFile = __esm({
2821
2822
  endIdxExclusive = Math.min(startIdx + DEFAULT_WINDOW, totalLines);
2822
2823
  }
2823
2824
  }
2824
- const sliced = allLines.slice(startIdx, endIdxExclusive);
2825
- const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
2826
- let result = numbered;
2825
+ const numberedLines = allLines.slice(startIdx, endIdxExclusive).map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`);
2826
+ let kept = numberedLines;
2827
+ let byteTruncated = false;
2828
+ if (Buffer.byteLength(numberedLines.join("\n"), "utf-8") > MAX_BYTES) {
2829
+ byteTruncated = true;
2830
+ kept = [];
2831
+ let used = 0;
2832
+ for (const line of numberedLines) {
2833
+ const cost = Buffer.byteLength(line, "utf-8") + (kept.length > 0 ? 1 : 0);
2834
+ if (used + cost > MAX_BYTES) {
2835
+ break;
2836
+ }
2837
+ kept.push(line);
2838
+ used += cost;
2839
+ }
2840
+ if (kept.length === 0) {
2841
+ kept = [
2842
+ Buffer.from(numberedLines[0], "utf-8").subarray(0, MAX_BYTES).toString("utf-8")
2843
+ ];
2844
+ }
2845
+ }
2846
+ let result = kept.join("\n");
2827
2847
  const displayStart = startIdx + 1;
2828
- const displayEnd = startIdx + sliced.length;
2829
- if (displayStart > 1 || displayEnd < totalLines) {
2848
+ const displayEnd = startIdx + kept.length;
2849
+ if (byteTruncated) {
2850
+ result += `
2851
+
2852
+ (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.)`;
2853
+ } else if (displayStart > 1 || displayEnd < totalLines) {
2830
2854
  result += `
2831
2855
 
2832
2856
  (showing lines ${displayStart}\u2013${displayEnd} of ${totalLines} \u2014 pass startLine/endLine to read a different range)`;
@@ -4479,11 +4503,34 @@ var init_runner = __esm({
4479
4503
  }
4480
4504
  });
4481
4505
 
4506
+ // src/subagents/common/tools.ts
4507
+ var COMMON_READ_TOOLS, COMMON_READ_TOOL_NAMES;
4508
+ var init_tools = __esm({
4509
+ "src/subagents/common/tools.ts"() {
4510
+ "use strict";
4511
+ init_readFile();
4512
+ init_listDir();
4513
+ init_grep();
4514
+ init_glob();
4515
+ COMMON_READ_TOOLS = [
4516
+ readFileTool.definition,
4517
+ listDirTool.definition,
4518
+ grepTool.definition,
4519
+ globTool.definition
4520
+ ];
4521
+ COMMON_READ_TOOL_NAMES = new Set(
4522
+ COMMON_READ_TOOLS.map((t) => t.name)
4523
+ );
4524
+ }
4525
+ });
4526
+
4482
4527
  // src/subagents/browserAutomation/tools.ts
4483
4528
  var BROWSER_TOOLS, BROWSER_EXTERNAL_TOOLS;
4484
- var init_tools = __esm({
4529
+ var init_tools2 = __esm({
4485
4530
  "src/subagents/browserAutomation/tools.ts"() {
4486
4531
  "use strict";
4532
+ init_tools();
4533
+ init_readSpec();
4487
4534
  BROWSER_TOOLS = [
4488
4535
  {
4489
4536
  clearable: false,
@@ -4626,24 +4673,199 @@ var init_tools = __esm({
4626
4673
  }
4627
4674
  }
4628
4675
  }
4629
- }
4676
+ },
4677
+ // Read tools so the QA agent can pull full spec detail on demand — the spec
4678
+ // context in its prompt is a lightweight index (see prompt.ts) that points
4679
+ // here. Routed to the global executeTool in index.ts, mirroring specSync.
4680
+ ...COMMON_READ_TOOLS,
4681
+ readSpecTool.definition
4630
4682
  ];
4631
4683
  BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
4632
4684
  }
4633
4685
  });
4634
4686
 
4635
- // src/subagents/browserAutomation/prompt.ts
4687
+ // src/subagents/common/context.ts
4636
4688
  import fs15 from "fs";
4637
- function getBrowserAutomationPrompt() {
4689
+ import path8 from "path";
4690
+ function walkMdFiles(dir, skip) {
4691
+ const files = [];
4692
+ try {
4693
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4694
+ const full = path8.join(dir, entry.name);
4695
+ if (entry.isDirectory()) {
4696
+ if (!skip?.has(entry.name)) {
4697
+ files.push(...walkMdFiles(full, skip));
4698
+ }
4699
+ } else if (entry.name.endsWith(".md")) {
4700
+ files.push(full);
4701
+ }
4702
+ }
4703
+ } catch {
4704
+ }
4705
+ return files.sort();
4706
+ }
4707
+ function parseFrontmatter(filePath) {
4638
4708
  try {
4639
- const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
4640
- return `${BASE_PROMPT}
4709
+ const content = fs15.readFileSync(filePath, "utf-8");
4710
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
4711
+ if (!match) {
4712
+ return {};
4713
+ }
4714
+ const fm = {};
4715
+ for (const line of match[1].split("\n")) {
4716
+ const sep = line.indexOf(":");
4717
+ if (sep > 0) {
4718
+ const key = line.slice(0, sep).trim();
4719
+ const val = line.slice(sep + 1).trim();
4720
+ fm[key] = val;
4721
+ }
4722
+ }
4723
+ return fm;
4724
+ } catch {
4725
+ return {};
4726
+ }
4727
+ }
4728
+ function loadSpecIndex() {
4729
+ const files = walkMdFiles("src", /* @__PURE__ */ new Set(["roadmap"]));
4730
+ if (files.length === 0) {
4731
+ return "";
4732
+ }
4733
+ const lines = files.map((f) => {
4734
+ const fm = parseFrontmatter(f);
4735
+ let line = `- ${f}`;
4736
+ if (fm.name) {
4737
+ line += ` \u2014 "${fm.name}"`;
4738
+ }
4739
+ if (fm.description) {
4740
+ line += ` \u2014 ${fm.description}`;
4741
+ }
4742
+ return line;
4743
+ });
4744
+ return `<spec_files>
4745
+ ## Project Spec Files
4746
+ Use readFile to access full contents.
4641
4747
 
4642
- <!-- cache_breakpoint -->
4748
+ ${lines.join("\n")}
4749
+ </spec_files>`;
4750
+ }
4751
+ function loadRoadmapIndex() {
4752
+ const parts = [];
4753
+ try {
4754
+ const indexJson = JSON.parse(
4755
+ fs15.readFileSync("src/roadmap/index.json", "utf-8")
4756
+ );
4757
+ if (indexJson.lanes?.length > 0) {
4758
+ const laneLines = indexJson.lanes.map(
4759
+ (l) => `- **${l.name}**: ${l.narrative || ""} (${l.items?.length || 0} items)`
4760
+ );
4761
+ parts.push(`### Lanes
4762
+ ${laneLines.join("\n")}`);
4763
+ }
4764
+ if (indexJson.standalone?.length > 0) {
4765
+ parts.push(
4766
+ `### Standalone
4767
+ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
4768
+ );
4769
+ }
4770
+ } catch {
4771
+ }
4772
+ const files = walkMdFiles("src/roadmap");
4773
+ if (files.length > 0) {
4774
+ const lines = files.map((f) => {
4775
+ const fm = parseFrontmatter(f);
4776
+ let line = `- ${f}`;
4777
+ if (fm.name) {
4778
+ line += ` \u2014 "${fm.name}"`;
4779
+ }
4780
+ if (fm.status) {
4781
+ line += ` (${fm.status})`;
4782
+ }
4783
+ if (fm.description) {
4784
+ line += ` \u2014 ${fm.description}`;
4785
+ }
4786
+ return line;
4787
+ });
4788
+ parts.push(`### Items
4789
+ ${lines.join("\n")}`);
4790
+ }
4791
+ if (parts.length === 0) {
4792
+ return "";
4793
+ }
4794
+ return `<current_roadmap>
4795
+ ## Roadmap
4796
+ Use readFile to access full contents.
4797
+
4798
+ ${parts.join("\n\n")}
4799
+ </current_roadmap>`;
4800
+ }
4801
+ function loadPlatformBrief() {
4802
+ return `<platform_brief>
4803
+ ## What is a Remy app?
4804
+
4805
+ 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.
4806
+
4807
+ 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.
4808
+
4809
+ ## What people build
4810
+
4811
+ - Business tools \u2014 client portals, approval workflows, admin panels with role-based access
4812
+ - AI-powered apps \u2014 document processors, image/video tools, content generators, conversational agents that take actions
4813
+ - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
4814
+ - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
4815
+ - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
4816
+ - Agent tools \u2014 MCP tool servers for AI assistants
4817
+ - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
4818
+ - API services \u2014 backend logic exposed as REST endpoints
4819
+ - Simple static sites \u2014 no backend needed, just a web interface with a build step
4820
+
4821
+ An app can be any combination of these.
4822
+
4823
+ ## Interfaces
4824
+
4825
+ Each interface type invokes the same backend methods. Methods don't know which interface called them.
4643
4826
 
4644
- <app_context>
4645
- ${appSpec}
4646
- </app_context>`;
4827
+ - 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.
4828
+ - API \u2014 auto-generated REST endpoints for every method
4829
+ - Cron \u2014 scheduled jobs on a configurable interval
4830
+ - Webhook \u2014 HTTP endpoints that trigger methods
4831
+ - Email \u2014 inbound email processing
4832
+ - MCP \u2014 tool servers for AI assistants
4833
+ - Agent \u2014 conversational LLM interface with tool access to backend methods
4834
+
4835
+ ## Backend
4836
+
4837
+ TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
4838
+
4839
+ - Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
4840
+ - 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.
4841
+ - Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
4842
+ - Git-native deployment. Push to default branch to deploy.
4843
+
4844
+ ## MindStudio SDK
4845
+
4846
+ 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.
4847
+
4848
+ ## What Remy apps are NOT good for
4849
+
4850
+ - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
4851
+ - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
4852
+ </platform_brief>`;
4853
+ }
4854
+ var init_context = __esm({
4855
+ "src/subagents/common/context.ts"() {
4856
+ "use strict";
4857
+ }
4858
+ });
4859
+
4860
+ // src/subagents/browserAutomation/prompt.ts
4861
+ function getBrowserAutomationPrompt() {
4862
+ try {
4863
+ const specIndex = loadSpecIndex();
4864
+ const parts = [BASE_PROMPT, "<!-- cache_breakpoint -->"];
4865
+ if (specIndex) {
4866
+ parts.push(specIndex);
4867
+ }
4868
+ return parts.join("\n\n");
4647
4869
  } catch {
4648
4870
  return BASE_PROMPT;
4649
4871
  }
@@ -4653,6 +4875,7 @@ var init_prompt = __esm({
4653
4875
  "src/subagents/browserAutomation/prompt.ts"() {
4654
4876
  "use strict";
4655
4877
  init_assets();
4878
+ init_context();
4656
4879
  BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
4657
4880
  }
4658
4881
  });
@@ -4707,6 +4930,9 @@ async function runBrowserAutomation(task, context, opts) {
4707
4930
  return `Error taking screenshot: ${err.message}`;
4708
4931
  }
4709
4932
  }
4933
+ if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
4934
+ return executeTool(name, _input, context);
4935
+ }
4710
4936
  return `Error: unknown local tool "${name}"`;
4711
4937
  },
4712
4938
  apiConfig: context.apiConfig,
@@ -4794,8 +5020,11 @@ var log8, browserAutomationTool;
4794
5020
  var init_browserAutomation = __esm({
4795
5021
  "src/subagents/browserAutomation/index.ts"() {
4796
5022
  "use strict";
5023
+ init_tools8();
4797
5024
  init_runner();
5025
+ init_tools2();
4798
5026
  init_tools();
5027
+ init_readSpec();
4799
5028
  init_prompt();
4800
5029
  init_sidecar();
4801
5030
  init_browserLock();
@@ -4941,27 +5170,6 @@ var init_screenshot2 = __esm({
4941
5170
  }
4942
5171
  });
4943
5172
 
4944
- // src/subagents/common/tools.ts
4945
- var COMMON_READ_TOOLS, COMMON_READ_TOOL_NAMES;
4946
- var init_tools2 = __esm({
4947
- "src/subagents/common/tools.ts"() {
4948
- "use strict";
4949
- init_readFile();
4950
- init_listDir();
4951
- init_grep();
4952
- init_glob();
4953
- COMMON_READ_TOOLS = [
4954
- readFileTool.definition,
4955
- listDirTool.definition,
4956
- grepTool.definition,
4957
- globTool.definition
4958
- ];
4959
- COMMON_READ_TOOL_NAMES = new Set(
4960
- COMMON_READ_TOOLS.map((t) => t.name)
4961
- );
4962
- }
4963
- });
4964
-
4965
5173
  // src/subagents/designExpert/tools/searchGoogle.ts
4966
5174
  var searchGoogle_exports = {};
4967
5175
  __export(searchGoogle_exports, {
@@ -5631,185 +5839,12 @@ var init_editImages = __esm({
5631
5839
  }
5632
5840
  });
5633
5841
 
5634
- // src/subagents/common/context.ts
5635
- import fs16 from "fs";
5636
- import path8 from "path";
5637
- function walkMdFiles(dir, skip) {
5638
- const files = [];
5639
- try {
5640
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
5641
- const full = path8.join(dir, entry.name);
5642
- if (entry.isDirectory()) {
5643
- if (!skip?.has(entry.name)) {
5644
- files.push(...walkMdFiles(full, skip));
5645
- }
5646
- } else if (entry.name.endsWith(".md")) {
5647
- files.push(full);
5648
- }
5649
- }
5650
- } catch {
5651
- }
5652
- return files.sort();
5653
- }
5654
- function parseFrontmatter(filePath) {
5655
- try {
5656
- const content = fs16.readFileSync(filePath, "utf-8");
5657
- const match = content.match(/^---\n([\s\S]*?)\n---/);
5658
- if (!match) {
5659
- return {};
5660
- }
5661
- const fm = {};
5662
- for (const line of match[1].split("\n")) {
5663
- const sep = line.indexOf(":");
5664
- if (sep > 0) {
5665
- const key = line.slice(0, sep).trim();
5666
- const val = line.slice(sep + 1).trim();
5667
- fm[key] = val;
5668
- }
5669
- }
5670
- return fm;
5671
- } catch {
5672
- return {};
5673
- }
5674
- }
5675
- function loadSpecIndex() {
5676
- const files = walkMdFiles("src", /* @__PURE__ */ new Set(["roadmap"]));
5677
- if (files.length === 0) {
5678
- return "";
5679
- }
5680
- const lines = files.map((f) => {
5681
- const fm = parseFrontmatter(f);
5682
- let line = `- ${f}`;
5683
- if (fm.name) {
5684
- line += ` \u2014 "${fm.name}"`;
5685
- }
5686
- if (fm.description) {
5687
- line += ` \u2014 ${fm.description}`;
5688
- }
5689
- return line;
5690
- });
5691
- return `<spec_files>
5692
- ## Project Spec Files
5693
- Use readFile to access full contents.
5694
-
5695
- ${lines.join("\n")}
5696
- </spec_files>`;
5697
- }
5698
- function loadRoadmapIndex() {
5699
- const parts = [];
5700
- try {
5701
- const indexJson = JSON.parse(
5702
- fs16.readFileSync("src/roadmap/index.json", "utf-8")
5703
- );
5704
- if (indexJson.lanes?.length > 0) {
5705
- const laneLines = indexJson.lanes.map(
5706
- (l) => `- **${l.name}**: ${l.narrative || ""} (${l.items?.length || 0} items)`
5707
- );
5708
- parts.push(`### Lanes
5709
- ${laneLines.join("\n")}`);
5710
- }
5711
- if (indexJson.standalone?.length > 0) {
5712
- parts.push(
5713
- `### Standalone
5714
- ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
5715
- );
5716
- }
5717
- } catch {
5718
- }
5719
- const files = walkMdFiles("src/roadmap");
5720
- if (files.length > 0) {
5721
- const lines = files.map((f) => {
5722
- const fm = parseFrontmatter(f);
5723
- let line = `- ${f}`;
5724
- if (fm.name) {
5725
- line += ` \u2014 "${fm.name}"`;
5726
- }
5727
- if (fm.status) {
5728
- line += ` (${fm.status})`;
5729
- }
5730
- if (fm.description) {
5731
- line += ` \u2014 ${fm.description}`;
5732
- }
5733
- return line;
5734
- });
5735
- parts.push(`### Items
5736
- ${lines.join("\n")}`);
5737
- }
5738
- if (parts.length === 0) {
5739
- return "";
5740
- }
5741
- return `<current_roadmap>
5742
- ## Roadmap
5743
- Use readFile to access full contents.
5744
-
5745
- ${parts.join("\n\n")}
5746
- </current_roadmap>`;
5747
- }
5748
- function loadPlatformBrief() {
5749
- return `<platform_brief>
5750
- ## What is a Remy app?
5751
-
5752
- 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.
5753
-
5754
- 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.
5755
-
5756
- ## What people build
5757
-
5758
- - Business tools \u2014 client portals, approval workflows, admin panels with role-based access
5759
- - AI-powered apps \u2014 document processors, image/video tools, content generators, conversational agents that take actions
5760
- - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
5761
- - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
5762
- - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
5763
- - Agent tools \u2014 MCP tool servers for AI assistants
5764
- - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
5765
- - API services \u2014 backend logic exposed as REST endpoints
5766
- - Simple static sites \u2014 no backend needed, just a web interface with a build step
5767
-
5768
- An app can be any combination of these.
5769
-
5770
- ## Interfaces
5771
-
5772
- Each interface type invokes the same backend methods. Methods don't know which interface called them.
5773
-
5774
- - 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.
5775
- - API \u2014 auto-generated REST endpoints for every method
5776
- - Cron \u2014 scheduled jobs on a configurable interval
5777
- - Webhook \u2014 HTTP endpoints that trigger methods
5778
- - Email \u2014 inbound email processing
5779
- - MCP \u2014 tool servers for AI assistants
5780
- - Agent \u2014 conversational LLM interface with tool access to backend methods
5781
-
5782
- ## Backend
5783
-
5784
- TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
5785
-
5786
- - Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
5787
- - 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.
5788
- - Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
5789
- - Git-native deployment. Push to default branch to deploy.
5790
-
5791
- ## MindStudio SDK
5792
-
5793
- 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.
5794
-
5795
- ## What Remy apps are NOT good for
5796
-
5797
- - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
5798
- - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
5799
- </platform_brief>`;
5800
- }
5801
- var init_context = __esm({
5802
- "src/subagents/common/context.ts"() {
5803
- "use strict";
5804
- }
5805
- });
5806
-
5807
5842
  // src/subagents/copyEditor/tools.ts
5808
5843
  var COPY_EDITOR_TOOLS;
5809
5844
  var init_tools3 = __esm({
5810
5845
  "src/subagents/copyEditor/tools.ts"() {
5811
5846
  "use strict";
5812
- init_tools2();
5847
+ init_tools();
5813
5848
  COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
5814
5849
  }
5815
5850
  });
@@ -5922,7 +5957,7 @@ var init_tools4 = __esm({
5922
5957
  "src/subagents/designExpert/tools/index.ts"() {
5923
5958
  "use strict";
5924
5959
  init_tools8();
5925
- init_tools2();
5960
+ init_tools();
5926
5961
  init_searchGoogle2();
5927
5962
  init_scrapeWebUrl();
5928
5963
  init_analyzeDesign();
@@ -6012,7 +6047,7 @@ var init_orgContext = __esm({
6012
6047
  });
6013
6048
 
6014
6049
  // src/subagents/designExpert/data/sampleCache.ts
6015
- import fs17 from "fs";
6050
+ import fs16 from "fs";
6016
6051
  function generateIndices(poolSize, sampleSize) {
6017
6052
  const n = Math.min(sampleSize, poolSize);
6018
6053
  const indices = Array.from({ length: poolSize }, (_, i) => i);
@@ -6024,14 +6059,14 @@ function generateIndices(poolSize, sampleSize) {
6024
6059
  }
6025
6060
  function load() {
6026
6061
  try {
6027
- return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
6062
+ return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
6028
6063
  } catch {
6029
6064
  return null;
6030
6065
  }
6031
6066
  }
6032
6067
  function save(indices) {
6033
6068
  try {
6034
- fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
6069
+ fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
6035
6070
  } catch {
6036
6071
  }
6037
6072
  }
@@ -6353,7 +6388,7 @@ var init_designExpert = __esm({
6353
6388
  init_tools8();
6354
6389
  init_runner();
6355
6390
  init_tools4();
6356
- init_tools2();
6391
+ init_tools();
6357
6392
  init_prompt2();
6358
6393
  init_history();
6359
6394
  init_surfaces();
@@ -6411,7 +6446,7 @@ var VISION_TOOLS;
6411
6446
  var init_tools5 = __esm({
6412
6447
  "src/subagents/productVision/tools.ts"() {
6413
6448
  "use strict";
6414
- init_tools2();
6449
+ init_tools();
6415
6450
  VISION_TOOLS = [
6416
6451
  ...COMMON_READ_TOOLS,
6417
6452
  {
@@ -6465,7 +6500,7 @@ var init_tools5 = __esm({
6465
6500
  });
6466
6501
 
6467
6502
  // src/subagents/productVision/executor.ts
6468
- import fs18 from "fs";
6503
+ import fs17 from "fs";
6469
6504
  import path9 from "path";
6470
6505
  function resolve(filePath) {
6471
6506
  return path9.join(ROADMAP_DIR, filePath);
@@ -6475,13 +6510,13 @@ async function executeVisionTool(name, input, context) {
6475
6510
  case "writeFile": {
6476
6511
  const filePath = resolve(input.path);
6477
6512
  try {
6478
- fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
6513
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
6479
6514
  let oldContent = null;
6480
6515
  try {
6481
- oldContent = fs18.readFileSync(filePath, "utf-8");
6516
+ oldContent = fs17.readFileSync(filePath, "utf-8");
6482
6517
  } catch {
6483
6518
  }
6484
- fs18.writeFileSync(filePath, input.content, "utf-8");
6519
+ fs17.writeFileSync(filePath, input.content, "utf-8");
6485
6520
  const lineCount = input.content.split("\n").length;
6486
6521
  const label = oldContent !== null ? "Wrote" : "Created";
6487
6522
  return `${label} ${filePath} (${lineCount} lines)
@@ -6493,11 +6528,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
6493
6528
  case "deleteFile": {
6494
6529
  const filePath = resolve(input.path);
6495
6530
  try {
6496
- if (!fs18.existsSync(filePath)) {
6531
+ if (!fs17.existsSync(filePath)) {
6497
6532
  return `Error: ${filePath} does not exist`;
6498
6533
  }
6499
- const oldContent = fs18.readFileSync(filePath, "utf-8");
6500
- fs18.unlinkSync(filePath);
6534
+ const oldContent = fs17.readFileSync(filePath, "utf-8");
6535
+ fs17.unlinkSync(filePath);
6501
6536
  return `Deleted ${filePath}
6502
6537
  ${unifiedDiff(filePath, oldContent, "")}`;
6503
6538
  } catch (err) {
@@ -6510,9 +6545,9 @@ ${unifiedDiff(filePath, oldContent, "")}`;
6510
6545
  }
6511
6546
  const filePath = resolve("pitch.html");
6512
6547
  try {
6513
- fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
6514
- const exists = fs18.existsSync(filePath);
6515
- const before = exists ? fs18.statSync(filePath).mtimeMs : null;
6548
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
6549
+ const exists = fs17.existsSync(filePath);
6550
+ const before = exists ? fs17.statSync(filePath).mtimeMs : null;
6516
6551
  const delivery = exists ? `### Your deliverable
6517
6552
  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.
6518
6553
 
@@ -6542,11 +6577,11 @@ Maintain the bones of the presentation scaffolding. Always keep the progress bar
6542
6577
  ${delivery}`;
6543
6578
  const result = await runDesignExpertRender({ task }, context);
6544
6579
  context.subAgentMessages?.set(context.toolCallId, result.messages);
6545
- if (!fs18.existsSync(filePath)) {
6580
+ if (!fs17.existsSync(filePath)) {
6546
6581
  return `Error: the design expert did not write ${filePath}. Its reply was:
6547
6582
  ${result.text}`;
6548
6583
  }
6549
- if (before !== null && fs18.statSync(filePath).mtimeMs === before) {
6584
+ if (before !== null && fs17.statSync(filePath).mtimeMs === before) {
6550
6585
  return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
6551
6586
  ${result.text}`;
6552
6587
  }
@@ -6566,7 +6601,6 @@ var init_executor = __esm({
6566
6601
  init_diff();
6567
6602
  init_designExpert();
6568
6603
  init_assets();
6569
- init_context();
6570
6604
  ROADMAP_DIR = "src/roadmap";
6571
6605
  PITCH_DECK_SHELL = readAsset(
6572
6606
  "subagents/productVision",
@@ -6594,7 +6628,6 @@ var init_prompt3 = __esm({
6594
6628
  "src/subagents/productVision/prompt.ts"() {
6595
6629
  "use strict";
6596
6630
  init_assets();
6597
- init_executor();
6598
6631
  init_context();
6599
6632
  BASE_PROMPT3 = readAsset("subagents/productVision", "prompt.md");
6600
6633
  }
@@ -6608,7 +6641,7 @@ var init_productVision = __esm({
6608
6641
  init_tools8();
6609
6642
  init_runner();
6610
6643
  init_tools5();
6611
- init_tools2();
6644
+ init_tools();
6612
6645
  init_executor();
6613
6646
  init_prompt3();
6614
6647
  init_history();
@@ -6682,7 +6715,7 @@ var SANITY_CHECK_TOOLS;
6682
6715
  var init_tools6 = __esm({
6683
6716
  "src/subagents/codeSanityCheck/tools.ts"() {
6684
6717
  "use strict";
6685
- init_tools2();
6718
+ init_tools();
6686
6719
  SANITY_CHECK_TOOLS = [
6687
6720
  ...COMMON_READ_TOOLS,
6688
6721
  {
@@ -6800,7 +6833,7 @@ var SPEC_SYNC_TOOLS;
6800
6833
  var init_tools7 = __esm({
6801
6834
  "src/subagents/specSync/tools.ts"() {
6802
6835
  "use strict";
6803
- init_tools2();
6836
+ init_tools();
6804
6837
  init_readSpec();
6805
6838
  init_editSpec();
6806
6839
  init_writeSpec();
@@ -6974,7 +7007,7 @@ var init_scrapeWebUrl2 = __esm({
6974
7007
  });
6975
7008
 
6976
7009
  // src/tools/spec/writeBuildOverview.ts
6977
- import fs19 from "fs";
7010
+ import fs18 from "fs";
6978
7011
  function initialDelivery() {
6979
7012
  return `### Your deliverable
6980
7013
  Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
@@ -7059,7 +7092,7 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
7059
7092
  if (!content) {
7060
7093
  return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
7061
7094
  }
7062
- const exists = fs19.existsSync(OVERVIEW_FILE);
7095
+ const exists = fs18.existsSync(OVERVIEW_FILE);
7063
7096
  const task = `<overview_copy>${content}</overview_copy>
7064
7097
 
7065
7098
  ${DESIGN_BRIEF}
@@ -7077,7 +7110,7 @@ ${exists ? refreshDelivery() : initialDelivery()}`;
7077
7110
  }
7078
7111
  const result = await runDesignExpertRender({ task }, context);
7079
7112
  context.subAgentMessages?.set(context.toolCallId, result.messages);
7080
- if (!fs19.existsSync(OVERVIEW_FILE)) {
7113
+ if (!fs18.existsSync(OVERVIEW_FILE)) {
7081
7114
  return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
7082
7115
  ${result.text}`;
7083
7116
  }
@@ -7416,7 +7449,7 @@ var init_errors = __esm({
7416
7449
  });
7417
7450
 
7418
7451
  // src/brandExtraction/index.ts
7419
- import fs20 from "fs";
7452
+ import fs19 from "fs";
7420
7453
  import path10 from "path";
7421
7454
  import { createHash } from "crypto";
7422
7455
  async function runExtraction(apiConfig, model) {
@@ -7436,13 +7469,16 @@ async function runExtraction(apiConfig, model) {
7436
7469
  log10.info("Brand persisted", { inputHash });
7437
7470
  return brand;
7438
7471
  }
7439
- function isBrandRelevant(filePath) {
7440
- if (filePath === path10.join("src", "app.md")) {
7472
+ function isDedicatedBrandFile(filePath) {
7473
+ if (filePath.split(path10.sep).includes("@brand")) {
7441
7474
  return true;
7442
7475
  }
7443
7476
  const { type } = parseFrontmatter2(filePath);
7444
7477
  return type.startsWith("design/color") || type.startsWith("design/typography");
7445
7478
  }
7479
+ function isBrandRelevant(filePath) {
7480
+ return filePath === path10.join("src", "app.md") || isDedicatedBrandFile(filePath);
7481
+ }
7446
7482
  function computeInputHash() {
7447
7483
  const entries = [];
7448
7484
  for (const filePath of walkMdFiles2("src")) {
@@ -7450,7 +7486,7 @@ function computeInputHash() {
7450
7486
  entries.push({ path: filePath, content: readSafe(filePath) });
7451
7487
  }
7452
7488
  }
7453
- const manifest = readSafe("mindstudio.json");
7489
+ const manifest = readBrandManifest();
7454
7490
  if (manifest) {
7455
7491
  entries.push({ path: "mindstudio.json", content: manifest });
7456
7492
  }
@@ -7463,7 +7499,25 @@ function sha256(input) {
7463
7499
  }
7464
7500
  function readSafe(filePath) {
7465
7501
  try {
7466
- return fs20.readFileSync(filePath, "utf-8");
7502
+ return fs19.readFileSync(filePath, "utf-8");
7503
+ } catch {
7504
+ return "";
7505
+ }
7506
+ }
7507
+ function readBrandManifest() {
7508
+ const raw = readSafe("mindstudio.json");
7509
+ if (!raw) {
7510
+ return "";
7511
+ }
7512
+ try {
7513
+ const parsed = JSON.parse(raw);
7514
+ const projected = {};
7515
+ for (const key of ["name", "description", "iconUrl"]) {
7516
+ if (parsed[key] !== void 0) {
7517
+ projected[key] = parsed[key];
7518
+ }
7519
+ }
7520
+ return Object.keys(projected).length > 0 ? JSON.stringify(projected, null, 2) : "";
7467
7521
  } catch {
7468
7522
  return "";
7469
7523
  }
@@ -7471,7 +7525,7 @@ function readSafe(filePath) {
7471
7525
  function walkMdFiles2(dir) {
7472
7526
  const results = [];
7473
7527
  try {
7474
- const entries = fs20.readdirSync(dir, { withFileTypes: true });
7528
+ const entries = fs19.readdirSync(dir, { withFileTypes: true });
7475
7529
  for (const entry of entries) {
7476
7530
  const full = path10.join(dir, entry.name);
7477
7531
  if (entry.isDirectory()) {
@@ -7486,7 +7540,7 @@ function walkMdFiles2(dir) {
7486
7540
  }
7487
7541
  function parseFrontmatter2(filePath) {
7488
7542
  try {
7489
- const content = fs20.readFileSync(filePath, "utf-8");
7543
+ const content = fs19.readFileSync(filePath, "utf-8");
7490
7544
  const match = content.match(/^---\n([\s\S]*?)\n---/);
7491
7545
  if (!match) {
7492
7546
  return { type: "" };
@@ -7556,14 +7610,17 @@ function buildCorpus() {
7556
7610
  ...all.filter((f) => !isBrandRelevant(f))
7557
7611
  ];
7558
7612
  const files = [];
7559
- const manifest = readSafe("mindstudio.json");
7613
+ const manifest = readBrandManifest();
7560
7614
  if (manifest) {
7561
7615
  files.push({ path: "mindstudio.json", content: manifest });
7562
7616
  }
7563
7617
  for (const filePath of ordered) {
7564
7618
  const content = readSafe(filePath);
7565
7619
  if (content) {
7566
- files.push({ path: filePath, content });
7620
+ files.push({
7621
+ path: filePath,
7622
+ content: isDedicatedBrandFile(filePath) ? content : headSlice(content)
7623
+ });
7567
7624
  }
7568
7625
  }
7569
7626
  const sep = "\n\n---\n\n";
@@ -7585,6 +7642,14 @@ ${content}`;
7585
7642
  }
7586
7643
  return sections.join(sep);
7587
7644
  }
7645
+ function headSlice(content) {
7646
+ if (content.length <= HEAD_SLICE_CHARS) {
7647
+ return content;
7648
+ }
7649
+ return content.slice(0, HEAD_SLICE_CHARS) + `
7650
+
7651
+ (head slice of a ${(content.length / 1024).toFixed(0)}KB spec \u2014 not a dedicated brand file, so only its opening is included)`;
7652
+ }
7588
7653
  function parseJsonResponse(text) {
7589
7654
  const trimmed = text.trim();
7590
7655
  const fenceMatch = trimmed.match(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/);
@@ -7683,14 +7748,14 @@ function pickFont(raw) {
7683
7748
  }
7684
7749
  function persistBrand(brand, inputHash) {
7685
7750
  const tmp = `${BRAND_FILE}.tmp`;
7686
- fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7687
- fs20.renameSync(tmp, BRAND_FILE);
7751
+ fs19.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7752
+ fs19.renameSync(tmp, BRAND_FILE);
7688
7753
  const cache = { inputHash, generatedAt: Date.now() };
7689
- fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7754
+ fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7690
7755
  }
7691
7756
  function readCache() {
7692
7757
  try {
7693
- const raw = fs20.readFileSync(CACHE_FILE, "utf-8");
7758
+ const raw = fs19.readFileSync(CACHE_FILE, "utf-8");
7694
7759
  const parsed = JSON.parse(raw);
7695
7760
  if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
7696
7761
  return parsed;
@@ -7700,7 +7765,7 @@ function readCache() {
7700
7765
  return null;
7701
7766
  }
7702
7767
  }
7703
- var log10, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, BRAND_CORPUS_CHAR_LIMIT;
7768
+ var log10, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, HEAD_SLICE_CHARS, BRAND_CORPUS_CHAR_LIMIT;
7704
7769
  var init_brandExtraction = __esm({
7705
7770
  "src/brandExtraction/index.ts"() {
7706
7771
  "use strict";
@@ -7712,6 +7777,7 @@ var init_brandExtraction = __esm({
7712
7777
  EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
7713
7778
  BRAND_FILE = ".remy-brand.json";
7714
7779
  CACHE_FILE = ".remy-brand.cache.json";
7780
+ HEAD_SLICE_CHARS = 2e3;
7715
7781
  BRAND_CORPUS_CHAR_LIMIT = 24e5;
7716
7782
  }
7717
7783
  });
@@ -8388,7 +8454,7 @@ var init_agent = __esm({
8388
8454
  });
8389
8455
 
8390
8456
  // src/prompt/static/projectContext.ts
8391
- import fs21 from "fs";
8457
+ import fs20 from "fs";
8392
8458
  import path11 from "path";
8393
8459
  function loadProjectRoot() {
8394
8460
  return `
@@ -8399,7 +8465,7 @@ File paths are relative to this directory. Every tool operates here and bash com
8399
8465
  }
8400
8466
  function loadProjectManifest() {
8401
8467
  try {
8402
- const manifest = fs21.readFileSync("mindstudio.json", "utf-8");
8468
+ const manifest = fs20.readFileSync("mindstudio.json", "utf-8");
8403
8469
  return `
8404
8470
  ## Project Manifest (mindstudio.json)
8405
8471
  \`\`\`json
@@ -8440,7 +8506,7 @@ ${entries.join("\n")}`;
8440
8506
  function walkMdFiles3(dir) {
8441
8507
  const results = [];
8442
8508
  try {
8443
- const entries = fs21.readdirSync(dir, { withFileTypes: true });
8509
+ const entries = fs20.readdirSync(dir, { withFileTypes: true });
8444
8510
  for (const entry of entries) {
8445
8511
  const full = path11.join(dir, entry.name);
8446
8512
  if (entry.isDirectory()) {
@@ -8455,7 +8521,7 @@ function walkMdFiles3(dir) {
8455
8521
  }
8456
8522
  function parseFrontmatter3(filePath) {
8457
8523
  try {
8458
- const content = fs21.readFileSync(filePath, "utf-8");
8524
+ const content = fs20.readFileSync(filePath, "utf-8");
8459
8525
  const match = content.match(/^---\n([\s\S]*?)\n---/);
8460
8526
  if (!match) {
8461
8527
  return { name: "", description: "", type: "" };
@@ -8471,7 +8537,7 @@ function parseFrontmatter3(filePath) {
8471
8537
  }
8472
8538
  function loadPlanStatus(onboardingState) {
8473
8539
  try {
8474
- const content = fs21.readFileSync(".remy-plan.md", "utf-8");
8540
+ const content = fs20.readFileSync(".remy-plan.md", "utf-8");
8475
8541
  const match = content.match(/^---\n([\s\S]*?)\n---/);
8476
8542
  const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
8477
8543
  if (status === "pending" && onboardingState === "intake") {
@@ -8499,7 +8565,7 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
8499
8565
  }
8500
8566
  function loadProjectFileListing() {
8501
8567
  try {
8502
- const entries = fs21.readdirSync(".", { withFileTypes: true });
8568
+ const entries = fs20.readdirSync(".", { withFileTypes: true });
8503
8569
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
8504
8570
  if (a.isDirectory() && !b.isDirectory()) {
8505
8571
  return -1;
@@ -8591,6 +8657,10 @@ Current date: ${now}
8591
8657
  {{compiled/media-cdn.md}}
8592
8658
  </media_cdn>
8593
8659
 
8660
+ <app_files>
8661
+ {{compiled/files.md}}
8662
+ </app_files>
8663
+
8594
8664
  <interfaces>
8595
8665
  {{compiled/interfaces.md}}
8596
8666
  </interfaces>
@@ -8682,12 +8752,12 @@ var init_prompt4 = __esm({
8682
8752
  });
8683
8753
 
8684
8754
  // src/config.ts
8685
- import fs22 from "fs";
8755
+ import fs21 from "fs";
8686
8756
  import path12 from "path";
8687
8757
  import os from "os";
8688
8758
  function loadConfigFile() {
8689
8759
  try {
8690
- const raw = fs22.readFileSync(CONFIG_PATH, "utf-8");
8760
+ const raw = fs21.readFileSync(CONFIG_PATH, "utf-8");
8691
8761
  log13.debug("Loaded config file", { path: CONFIG_PATH });
8692
8762
  return JSON.parse(raw);
8693
8763
  } catch (err) {
@@ -10008,7 +10078,7 @@ var init_headless = __esm({
10008
10078
  // src/index.tsx
10009
10079
  import { render } from "ink";
10010
10080
  import os2 from "os";
10011
- import fs23 from "fs";
10081
+ import fs22 from "fs";
10012
10082
  import path13 from "path";
10013
10083
 
10014
10084
  // src/tui/App.tsx
@@ -10330,7 +10400,7 @@ for (let i = 0; i < args.length; i++) {
10330
10400
  var startupLog = createLogger("startup");
10331
10401
  function printDebugInfo(config) {
10332
10402
  const pkg = JSON.parse(
10333
- fs23.readFileSync(
10403
+ fs22.readFileSync(
10334
10404
  path13.join(import.meta.dirname, "..", "package.json"),
10335
10405
  "utf-8"
10336
10406
  )