@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 +233 -228
- package/dist/index.js +258 -249
- package/dist/prompt/compiled/agent-interfaces.md +2 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -1
- package/dist/prompt/compiled/task-agents.md +40 -9
- package/dist/prompt/static/coding.md +1 -1
- package/dist/prompt/static/intake.md +1 -1
- package/dist/subagents/codeSanityCheck/prompt.md +9 -1
- package/package.json +1 -1
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/
|
|
3932
|
+
// src/subagents/common/context.ts
|
|
3917
3933
|
import fs16 from "fs";
|
|
3918
|
-
|
|
3919
|
-
function
|
|
3934
|
+
import path9 from "path";
|
|
3935
|
+
function walkMdFiles2(dir, skip) {
|
|
3936
|
+
const files = [];
|
|
3920
3937
|
try {
|
|
3921
|
-
const
|
|
3922
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5474
|
+
fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5470
5475
|
let oldContent = null;
|
|
5471
5476
|
try {
|
|
5472
|
-
oldContent =
|
|
5477
|
+
oldContent = fs18.readFileSync(filePath, "utf-8");
|
|
5473
5478
|
} catch {
|
|
5474
5479
|
}
|
|
5475
|
-
|
|
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 (!
|
|
5492
|
+
if (!fs18.existsSync(filePath)) {
|
|
5488
5493
|
return `Error: ${filePath} does not exist`;
|
|
5489
5494
|
}
|
|
5490
|
-
const oldContent =
|
|
5491
|
-
|
|
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
|
-
|
|
5505
|
-
const exists =
|
|
5506
|
-
const before = exists ?
|
|
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 (!
|
|
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 &&
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
6722
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
7047
|
-
|
|
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
|
-
|
|
7054
|
+
fs21.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7050
7055
|
}
|
|
7051
7056
|
function readCache() {
|
|
7052
7057
|
try {
|
|
7053
|
-
const raw =
|
|
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;
|