@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 +305 -239
- package/dist/index.js +332 -262
- package/dist/prompt/compiled/files.md +95 -0
- package/dist/subagents/codeSanityCheck/prompt.md +2 -0
- package/package.json +1 -1
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
|
|
2825
|
-
|
|
2826
|
-
let
|
|
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 +
|
|
2829
|
-
if (
|
|
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
|
|
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/
|
|
4687
|
+
// src/subagents/common/context.ts
|
|
4636
4688
|
import fs15 from "fs";
|
|
4637
|
-
|
|
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
|
|
4640
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6513
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
6479
6514
|
let oldContent = null;
|
|
6480
6515
|
try {
|
|
6481
|
-
oldContent =
|
|
6516
|
+
oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
6482
6517
|
} catch {
|
|
6483
6518
|
}
|
|
6484
|
-
|
|
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 (!
|
|
6531
|
+
if (!fs17.existsSync(filePath)) {
|
|
6497
6532
|
return `Error: ${filePath} does not exist`;
|
|
6498
6533
|
}
|
|
6499
|
-
const oldContent =
|
|
6500
|
-
|
|
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
|
-
|
|
6514
|
-
const exists =
|
|
6515
|
-
const before = exists ?
|
|
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 (!
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
|
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
|
|
7440
|
-
if (filePath
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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({
|
|
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
|
-
|
|
7687
|
-
|
|
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
|
-
|
|
7754
|
+
fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7690
7755
|
}
|
|
7691
7756
|
function readCache() {
|
|
7692
7757
|
try {
|
|
7693
|
-
const raw =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
10403
|
+
fs22.readFileSync(
|
|
10334
10404
|
path13.join(import.meta.dirname, "..", "package.json"),
|
|
10335
10405
|
"utf-8"
|
|
10336
10406
|
)
|