@mindstudio-ai/remy 0.1.298 → 0.1.300
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 +42 -17
- package/dist/index.js +42 -17
- package/dist/prompt/compiled/auth.md +1 -1
- package/dist/prompt/compiled/dev-and-deploy.md +3 -3
- package/dist/prompt/compiled/interfaces.md +1 -1
- package/dist/prompt/skills/auth.md +9 -9
- package/dist/prompt/skills/dataSources.md +10 -10
- package/dist/prompt/skills/files.md +4 -4
- package/dist/prompt/skills/inboundEmail.md +21 -3
- package/dist/prompt/skills/jewels.md +5 -5
- package/dist/prompt/skills/publishing.md +3 -3
- package/dist/prompt/skills/scenarios.md +1 -1
- package/dist/prompt/skills/scheduledJobs.md +11 -0
- package/dist/prompt/skills/secrets.md +1 -1
- package/dist/prompt/skills/voiceInterfaces.md +8 -8
- package/dist/prompt/static/coding.md +1 -1
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/subagents/codeSanityCheck/prompt.md +3 -2
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -942,9 +942,9 @@ function buildBackgroundResultsMessage(results) {
|
|
|
942
942
|
${r.result}
|
|
943
943
|
</tool_result>`
|
|
944
944
|
).join("\n\n");
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
945
|
+
const body = `<background_results>
|
|
946
|
+
Automated delivery of results from background tool calls that completed while other work was in progress. This block is not from the user \u2014 anything outside it is.
|
|
947
|
+
|
|
948
948
|
${xml}
|
|
949
949
|
</background_results>`;
|
|
950
950
|
return automatedMessage("background_results", body);
|
|
@@ -1848,7 +1848,7 @@ var askMindStudioSdkTool = {
|
|
|
1848
1848
|
async execute(input, context) {
|
|
1849
1849
|
const query = input.query;
|
|
1850
1850
|
const result = await runCli("mindstudio", ["ask", query], {
|
|
1851
|
-
timeout:
|
|
1851
|
+
timeout: 48e4,
|
|
1852
1852
|
maxBuffer: 512 * 1024,
|
|
1853
1853
|
onLog: context?.onLog
|
|
1854
1854
|
});
|
|
@@ -4570,7 +4570,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4570
4570
|
task,
|
|
4571
4571
|
tools: BROWSER_TOOLS,
|
|
4572
4572
|
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
4573
|
-
executeTool: async (name, _input,
|
|
4573
|
+
executeTool: async (name, _input, toolCallId, onLog) => {
|
|
4574
4574
|
if (name === "setupBrowser") {
|
|
4575
4575
|
try {
|
|
4576
4576
|
const result2 = await sidecarRequest(
|
|
@@ -4587,7 +4587,11 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4587
4587
|
}
|
|
4588
4588
|
}
|
|
4589
4589
|
if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
|
|
4590
|
-
return executeTool(
|
|
4590
|
+
return executeTool(
|
|
4591
|
+
name,
|
|
4592
|
+
_input,
|
|
4593
|
+
toolCallId ? deriveContext(context, toolCallId, onLog) : context
|
|
4594
|
+
);
|
|
4591
4595
|
}
|
|
4592
4596
|
return `Error: unknown local tool "${name}"`;
|
|
4593
4597
|
},
|
|
@@ -5556,7 +5560,13 @@ var copyEditorTool = {
|
|
|
5556
5560
|
task: input.task,
|
|
5557
5561
|
tools: COPY_EDITOR_TOOLS,
|
|
5558
5562
|
externalTools: /* @__PURE__ */ new Set(),
|
|
5559
|
-
executeTool: (name, toolInput
|
|
5563
|
+
executeTool: (name, toolInput, toolCallId, onLog, sams) => {
|
|
5564
|
+
const childCtx = toolCallId ? {
|
|
5565
|
+
...deriveContext(context, toolCallId, onLog),
|
|
5566
|
+
subAgentMessages: sams
|
|
5567
|
+
} : context;
|
|
5568
|
+
return executeTool(name, toolInput, childCtx);
|
|
5569
|
+
},
|
|
5560
5570
|
apiConfig: context.apiConfig,
|
|
5561
5571
|
model: resolveModel("copyEditor", context.models, context.model),
|
|
5562
5572
|
subAgentId: "copyEditor",
|
|
@@ -5824,7 +5834,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
|
|
|
5824
5834
|
if (!tool) {
|
|
5825
5835
|
return `Error: unknown tool "${name}"`;
|
|
5826
5836
|
}
|
|
5827
|
-
const childContext = context && toolCallId ? deriveContext(context, toolCallId) : context;
|
|
5837
|
+
const childContext = context && toolCallId ? deriveContext(context, toolCallId, onLog) : context;
|
|
5828
5838
|
return tool.execute(input, onLog, childContext);
|
|
5829
5839
|
}
|
|
5830
5840
|
|
|
@@ -6112,7 +6122,10 @@ async function runDesignExpert(opts, context) {
|
|
|
6112
6122
|
tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
|
|
6113
6123
|
externalTools: /* @__PURE__ */ new Set(),
|
|
6114
6124
|
executeTool: (name, input, toolCallId, onLog, sams) => {
|
|
6115
|
-
const childCtx = toolCallId ? {
|
|
6125
|
+
const childCtx = toolCallId ? {
|
|
6126
|
+
...deriveContext(context, toolCallId, onLog),
|
|
6127
|
+
subAgentMessages: sams
|
|
6128
|
+
} : { ...context, subAgentMessages: sams };
|
|
6116
6129
|
if (COMMON_READ_TOOL_NAMES.has(name)) {
|
|
6117
6130
|
return executeTool(name, input, childCtx);
|
|
6118
6131
|
}
|
|
@@ -6380,8 +6393,11 @@ var productVisionTool = {
|
|
|
6380
6393
|
cachePolicy: "conversation",
|
|
6381
6394
|
tools: VISION_TOOLS,
|
|
6382
6395
|
externalTools: /* @__PURE__ */ new Set(),
|
|
6383
|
-
executeTool: (name, input2, toolCallId,
|
|
6384
|
-
const childCtx = toolCallId ? {
|
|
6396
|
+
executeTool: (name, input2, toolCallId, onLog, sams) => {
|
|
6397
|
+
const childCtx = toolCallId ? {
|
|
6398
|
+
...deriveContext(context, toolCallId, onLog),
|
|
6399
|
+
subAgentMessages: sams
|
|
6400
|
+
} : { ...context, subAgentMessages: sams };
|
|
6385
6401
|
if (COMMON_READ_TOOL_NAMES.has(name)) {
|
|
6386
6402
|
return executeTool(name, input2, childCtx);
|
|
6387
6403
|
}
|
|
@@ -6493,7 +6509,13 @@ var codeSanityCheckTool = {
|
|
|
6493
6509
|
task: input.task,
|
|
6494
6510
|
tools: SANITY_CHECK_TOOLS,
|
|
6495
6511
|
externalTools: /* @__PURE__ */ new Set(),
|
|
6496
|
-
executeTool: (name, toolInput
|
|
6512
|
+
executeTool: (name, toolInput, toolCallId, onLog, sams) => {
|
|
6513
|
+
const childCtx = toolCallId ? {
|
|
6514
|
+
...deriveContext(context, toolCallId, onLog),
|
|
6515
|
+
subAgentMessages: sams
|
|
6516
|
+
} : context;
|
|
6517
|
+
return executeTool(name, toolInput, childCtx);
|
|
6518
|
+
},
|
|
6497
6519
|
apiConfig: context.apiConfig,
|
|
6498
6520
|
model: resolveModel("codeSanityCheck", context.models, context.model),
|
|
6499
6521
|
subAgentId: "codeSanityCheck",
|
|
@@ -6703,16 +6725,19 @@ After reconciling the spec, refresh the Build Overview: author the complete upda
|
|
|
6703
6725
|
task,
|
|
6704
6726
|
tools: tools2,
|
|
6705
6727
|
externalTools: /* @__PURE__ */ new Set(),
|
|
6706
|
-
executeTool: (name, toolInput, toolCallId,
|
|
6728
|
+
executeTool: (name, toolInput, toolCallId, onLog, sams) => {
|
|
6729
|
+
const childCtx = toolCallId ? {
|
|
6730
|
+
...deriveContext(context, toolCallId, onLog),
|
|
6731
|
+
subAgentMessages: sams
|
|
6732
|
+
} : { ...context, subAgentMessages: sams };
|
|
6707
6733
|
if (name === "writeBuildOverview") {
|
|
6708
|
-
const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
|
|
6709
6734
|
return renderBuildOverview(
|
|
6710
6735
|
String(toolInput.content ?? "").trim(),
|
|
6711
6736
|
childCtx,
|
|
6712
6737
|
{ background: false }
|
|
6713
6738
|
);
|
|
6714
6739
|
}
|
|
6715
|
-
return executeTool(name, toolInput,
|
|
6740
|
+
return executeTool(name, toolInput, childCtx);
|
|
6716
6741
|
},
|
|
6717
6742
|
apiConfig: context.apiConfig,
|
|
6718
6743
|
model: resolveModel("specSync", context.models, context.model),
|
|
@@ -6786,8 +6811,8 @@ var scrapeWebUrlTool = {
|
|
|
6786
6811
|
};
|
|
6787
6812
|
|
|
6788
6813
|
// src/tools/index.ts
|
|
6789
|
-
function deriveContext(parent, toolCallId) {
|
|
6790
|
-
return { ...parent, toolCallId };
|
|
6814
|
+
function deriveContext(parent, toolCallId, onLog) {
|
|
6815
|
+
return { ...parent, toolCallId, onLog };
|
|
6791
6816
|
}
|
|
6792
6817
|
var ALL_TOOLS = [
|
|
6793
6818
|
// Common
|
package/dist/index.js
CHANGED
|
@@ -1379,7 +1379,7 @@ var init_sdkConsultant = __esm({
|
|
|
1379
1379
|
async execute(input, context) {
|
|
1380
1380
|
const query = input.query;
|
|
1381
1381
|
const result = await runCli("mindstudio", ["ask", query], {
|
|
1382
|
-
timeout:
|
|
1382
|
+
timeout: 48e4,
|
|
1383
1383
|
maxBuffer: 512 * 1024,
|
|
1384
1384
|
onLog: context?.onLog
|
|
1385
1385
|
});
|
|
@@ -1660,9 +1660,9 @@ function buildBackgroundResultsMessage(results) {
|
|
|
1660
1660
|
${r.result}
|
|
1661
1661
|
</tool_result>`
|
|
1662
1662
|
).join("\n\n");
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1663
|
+
const body = `<background_results>
|
|
1664
|
+
Automated delivery of results from background tool calls that completed while other work was in progress. This block is not from the user \u2014 anything outside it is.
|
|
1665
|
+
|
|
1666
1666
|
${xml}
|
|
1667
1667
|
</background_results>`;
|
|
1668
1668
|
return automatedMessage("background_results", body);
|
|
@@ -5648,7 +5648,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5648
5648
|
task,
|
|
5649
5649
|
tools: BROWSER_TOOLS,
|
|
5650
5650
|
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
5651
|
-
executeTool: async (name, _input,
|
|
5651
|
+
executeTool: async (name, _input, toolCallId, onLog) => {
|
|
5652
5652
|
if (name === "setupBrowser") {
|
|
5653
5653
|
try {
|
|
5654
5654
|
const result2 = await sidecarRequest(
|
|
@@ -5665,7 +5665,11 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5665
5665
|
}
|
|
5666
5666
|
}
|
|
5667
5667
|
if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
|
|
5668
|
-
return executeTool(
|
|
5668
|
+
return executeTool(
|
|
5669
|
+
name,
|
|
5670
|
+
_input,
|
|
5671
|
+
toolCallId ? deriveContext(context, toolCallId, onLog) : context
|
|
5672
|
+
);
|
|
5669
5673
|
}
|
|
5670
5674
|
return `Error: unknown local tool "${name}"`;
|
|
5671
5675
|
},
|
|
@@ -6752,7 +6756,13 @@ var init_copyEditor = __esm({
|
|
|
6752
6756
|
task: input.task,
|
|
6753
6757
|
tools: COPY_EDITOR_TOOLS,
|
|
6754
6758
|
externalTools: /* @__PURE__ */ new Set(),
|
|
6755
|
-
executeTool: (name, toolInput
|
|
6759
|
+
executeTool: (name, toolInput, toolCallId, onLog, sams) => {
|
|
6760
|
+
const childCtx = toolCallId ? {
|
|
6761
|
+
...deriveContext(context, toolCallId, onLog),
|
|
6762
|
+
subAgentMessages: sams
|
|
6763
|
+
} : context;
|
|
6764
|
+
return executeTool(name, toolInput, childCtx);
|
|
6765
|
+
},
|
|
6756
6766
|
apiConfig: context.apiConfig,
|
|
6757
6767
|
model: resolveModel("copyEditor", context.models, context.model),
|
|
6758
6768
|
subAgentId: "copyEditor",
|
|
@@ -7034,7 +7044,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
|
|
|
7034
7044
|
if (!tool) {
|
|
7035
7045
|
return `Error: unknown tool "${name}"`;
|
|
7036
7046
|
}
|
|
7037
|
-
const childContext = context && toolCallId ? deriveContext(context, toolCallId) : context;
|
|
7047
|
+
const childContext = context && toolCallId ? deriveContext(context, toolCallId, onLog) : context;
|
|
7038
7048
|
return tool.execute(input, onLog, childContext);
|
|
7039
7049
|
}
|
|
7040
7050
|
var tools, DESIGN_EXPERT_TOOLS;
|
|
@@ -7466,7 +7476,10 @@ async function runDesignExpert(opts, context) {
|
|
|
7466
7476
|
tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
|
|
7467
7477
|
externalTools: /* @__PURE__ */ new Set(),
|
|
7468
7478
|
executeTool: (name, input, toolCallId, onLog, sams) => {
|
|
7469
|
-
const childCtx = toolCallId ? {
|
|
7479
|
+
const childCtx = toolCallId ? {
|
|
7480
|
+
...deriveContext(context, toolCallId, onLog),
|
|
7481
|
+
subAgentMessages: sams
|
|
7482
|
+
} : { ...context, subAgentMessages: sams };
|
|
7470
7483
|
if (COMMON_READ_TOOL_NAMES.has(name)) {
|
|
7471
7484
|
return executeTool(name, input, childCtx);
|
|
7472
7485
|
}
|
|
@@ -7794,8 +7807,11 @@ var init_productVision = __esm({
|
|
|
7794
7807
|
cachePolicy: "conversation",
|
|
7795
7808
|
tools: VISION_TOOLS,
|
|
7796
7809
|
externalTools: /* @__PURE__ */ new Set(),
|
|
7797
|
-
executeTool: (name, input2, toolCallId,
|
|
7798
|
-
const childCtx = toolCallId ? {
|
|
7810
|
+
executeTool: (name, input2, toolCallId, onLog, sams) => {
|
|
7811
|
+
const childCtx = toolCallId ? {
|
|
7812
|
+
...deriveContext(context, toolCallId, onLog),
|
|
7813
|
+
subAgentMessages: sams
|
|
7814
|
+
} : { ...context, subAgentMessages: sams };
|
|
7799
7815
|
if (COMMON_READ_TOOL_NAMES.has(name)) {
|
|
7800
7816
|
return executeTool(name, input2, childCtx);
|
|
7801
7817
|
}
|
|
@@ -7926,7 +7942,13 @@ var init_codeSanityCheck = __esm({
|
|
|
7926
7942
|
task: input.task,
|
|
7927
7943
|
tools: SANITY_CHECK_TOOLS,
|
|
7928
7944
|
externalTools: /* @__PURE__ */ new Set(),
|
|
7929
|
-
executeTool: (name, toolInput
|
|
7945
|
+
executeTool: (name, toolInput, toolCallId, onLog, sams) => {
|
|
7946
|
+
const childCtx = toolCallId ? {
|
|
7947
|
+
...deriveContext(context, toolCallId, onLog),
|
|
7948
|
+
subAgentMessages: sams
|
|
7949
|
+
} : context;
|
|
7950
|
+
return executeTool(name, toolInput, childCtx);
|
|
7951
|
+
},
|
|
7930
7952
|
apiConfig: context.apiConfig,
|
|
7931
7953
|
model: resolveModel("codeSanityCheck", context.models, context.model),
|
|
7932
7954
|
subAgentId: "codeSanityCheck",
|
|
@@ -8173,16 +8195,19 @@ After reconciling the spec, refresh the Build Overview: author the complete upda
|
|
|
8173
8195
|
task,
|
|
8174
8196
|
tools: tools2,
|
|
8175
8197
|
externalTools: /* @__PURE__ */ new Set(),
|
|
8176
|
-
executeTool: (name, toolInput, toolCallId,
|
|
8198
|
+
executeTool: (name, toolInput, toolCallId, onLog, sams) => {
|
|
8199
|
+
const childCtx = toolCallId ? {
|
|
8200
|
+
...deriveContext(context, toolCallId, onLog),
|
|
8201
|
+
subAgentMessages: sams
|
|
8202
|
+
} : { ...context, subAgentMessages: sams };
|
|
8177
8203
|
if (name === "writeBuildOverview") {
|
|
8178
|
-
const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
|
|
8179
8204
|
return renderBuildOverview(
|
|
8180
8205
|
String(toolInput.content ?? "").trim(),
|
|
8181
8206
|
childCtx,
|
|
8182
8207
|
{ background: false }
|
|
8183
8208
|
);
|
|
8184
8209
|
}
|
|
8185
|
-
return executeTool(name, toolInput,
|
|
8210
|
+
return executeTool(name, toolInput, childCtx);
|
|
8186
8211
|
},
|
|
8187
8212
|
apiConfig: context.apiConfig,
|
|
8188
8213
|
model: resolveModel("specSync", context.models, context.model),
|
|
@@ -8266,8 +8291,8 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
8266
8291
|
});
|
|
8267
8292
|
|
|
8268
8293
|
// src/tools/index.ts
|
|
8269
|
-
function deriveContext(parent, toolCallId) {
|
|
8270
|
-
return { ...parent, toolCallId };
|
|
8294
|
+
function deriveContext(parent, toolCallId, onLog) {
|
|
8295
|
+
return { ...parent, toolCallId, onLog };
|
|
8271
8296
|
}
|
|
8272
8297
|
function getToolDefinitions() {
|
|
8273
8298
|
return ALL_TOOLS.map((t) => t.definition);
|
|
@@ -13,7 +13,7 @@ Four auth methods, combinable per app in the manifest:
|
|
|
13
13
|
|
|
14
14
|
**Before writing any auth code — the manifest `auth` config, the user table, login/signup UI, frontend `auth.*` calls, API keys — load the `auth` skill.** It has the full contract: config schema, platform-managed columns, the frontend SDK (flows, auth state, error codes, phone/email helpers), delegated sign-in implementation, worked examples, and the auth-screen design rules.
|
|
15
15
|
|
|
16
|
-
**Restricting who can sign up is a platform setting, not app code.** Limiting sign-ups to specific domains or addresses ("only @acme.com emails"), blocking disposable emails, and fixed-code test accounts for app-store reviewers are all enforced platform-side before any verification code is sent, and managed via `
|
|
16
|
+
**Restricting who can sign up is a platform setting, not app code.** Limiting sign-ups to specific domains or addresses ("only @acme.com emails"), blocking disposable emails, and fixed-code test accounts for app-store reviewers are all enforced platform-side before any verification code is sent, and managed via `remy-admin settings`. Never build an app-level equivalent (a client-side email check, a backend gate) — it's weaker than the real thing. Load the `auth` skill before acting on any of these requests.
|
|
17
17
|
|
|
18
18
|
## Backend Enforcement
|
|
19
19
|
|
|
@@ -40,13 +40,13 @@ The platform builds and deploys automatically:
|
|
|
40
40
|
5. **Compute effects** — roles diff, cron diff, bot command diffs, table DDL
|
|
41
41
|
6. **Apply** — create/update roles, sync bot commands, apply DDL to a staging database copy, swap the live pointer
|
|
42
42
|
|
|
43
|
-
All deployed apps are available on `<uuid>.madewithremy.com` where uuid is their app ID. Apps can also be served on a custom platform subdomain (`<subdomain>.madewithremy.com`) or on a fully custom domain the user owns (pointed at the platform via CNAME or A records). Configure either via the `
|
|
43
|
+
All deployed apps are available on `<uuid>.madewithremy.com` where uuid is their app ID. Apps can also be served on a custom platform subdomain (`<subdomain>.madewithremy.com`) or on a fully custom domain the user owns (pointed at the platform via CNAME or A records). Configure either via the `remy-admin` CLI.
|
|
44
44
|
|
|
45
|
-
An app meant to be embedded in an iframe on a customer's own site needs that site's origin added to the app's frame-ancestors setting (`
|
|
45
|
+
An app meant to be embedded in an iframe on a customer's own site needs that site's origin added to the app's frame-ancestors setting (`remy-admin settings frame-ancestors add`) — without it the platform's CSP blocks the embed on the deployed app. Don't debug a blocked embed in app code; it's this setting.
|
|
46
46
|
|
|
47
47
|
### Post-Deploy Diagnostics
|
|
48
48
|
|
|
49
|
-
Every live deploy runs an automated Lighthouse audit of the app. Pull it via `
|
|
49
|
+
Every live deploy runs an automated Lighthouse audit of the app. Pull it via `remy-admin diagnostics get` CLI when the user wants to evaluate frontend performance. It runs async after the build completes, so it won't be available right away.
|
|
50
50
|
|
|
51
51
|
### Database Migrations on Deploy
|
|
52
52
|
|
|
@@ -147,7 +147,7 @@ import { prerender } from '@mindstudio-ai/agent';
|
|
|
147
147
|
await prerender.invalidate(['/u/abc']); // omit arg to purge all
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
`
|
|
150
|
+
`remy-admin prerender` can help you verify/manage snapshots during development.
|
|
151
151
|
|
|
152
152
|
### Mounting other apps
|
|
153
153
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Auth & User Accounts
|
|
3
|
-
what: Apps manage their own users — opt-in via manifest config over a developer-owned user table, with the platform handling verification codes, cookie sessions, and role sync. Covers email/SMS code login (the platform sends real 6-digit codes; the developer builds the UI), per-user API keys that resolve to full RBAC over Bearer auth, org-delegated "Sign in with Remy" for internal apps (redirect/popup handshake, platform-managed identity), the platform signup-restriction settings (domain/email allowlist, disposable-email blocking, fixed-code test accounts for app-store review — all platform settings via `
|
|
3
|
+
what: Apps manage their own users — opt-in via manifest config over a developer-owned user table, with the platform handling verification codes, cookie sessions, and role sync. Covers email/SMS code login (the platform sends real 6-digit codes; the developer builds the UI), per-user API keys that resolve to full RBAC over Bearer auth, org-delegated "Sign in with Remy" for internal apps (redirect/popup handshake, platform-managed identity), the platform signup-restriction settings (domain/email allowlist, disposable-email blocking, fixed-code test accounts for app-store review — all platform settings via `remy-admin settings`, never app code), the full frontend SDK (auth state and onAuthStateChanged, flows, email/phone changes, phone and email helpers, error codes), backend enforcement (requireRole/hasRole/userId, the system role), auth-screen design rules, and the dev test bypasses.
|
|
4
4
|
when: Before writing ANY auth code — the manifest `auth` config, the user table, login/signup UI, frontend `auth.*` calls, API keys, delegated sign-in — and before testing or debugging auth flows.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -51,14 +51,14 @@ Remy apps can have and manage their own users. Auth is opt-in: configure it in t
|
|
|
51
51
|
|
|
52
52
|
## Restricting Who Can Sign Up (Platform Settings)
|
|
53
53
|
|
|
54
|
-
When the user wants to limit who can register — "only allow sign-ins from our domain", "block throwaway emails", "give the app-store reviewer a working login" — these are **platform settings, enforced at code-send time before any email goes out, not app code**. An app-level equivalent (a client-side email check, a backend gate) is both redundant and weaker: the platform sends the verification code, so app code can't actually stop a signup. Manage them with the `
|
|
54
|
+
When the user wants to limit who can register — "only allow sign-ins from our domain", "block throwaway emails", "give the app-store reviewer a working login" — these are **platform settings, enforced at code-send time before any email goes out, not app code**. An app-level equivalent (a client-side email check, a backend gate) is both redundant and weaker: the platform sends the verification code, so app code can't actually stop a signup. Manage them with the `remy-admin settings` CLI (run `remy-admin settings --help` for the full surface).
|
|
55
55
|
|
|
56
56
|
**Signup allowlist ("only @acme.com emails"):**
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
remy-admin settings allowlist add '*@acme.com' # whole domain
|
|
60
|
+
remy-admin settings allowlist add 'cfo@other.com' # one address
|
|
61
|
+
remy-admin settings allowlist enable # start enforcing
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
- Entries are explicit globs: `*@domain.com` (any address at that exact domain — no subdomains; add `*@sub.domain.com` separately) or `user@domain.com` (one address).
|
|
@@ -67,15 +67,15 @@ mindstudio-prod settings allowlist enable # start enforcing
|
|
|
67
67
|
- Blocked signups fail at `auth.sendEmailCode` with error code `email_not_allowed` — handle it in the login UI with a clear "this app is restricted" message.
|
|
68
68
|
- The dev test login (`remy@mindstudio.ai`) bypasses the allowlist in dev sessions, so you can still smoke-test auth in the preview after enabling it. Don't add it to the allowlist.
|
|
69
69
|
|
|
70
|
-
**Disposable-email blocking:** on by default — email-code signups from known burner domains are rejected at code-send with error code `disposable_email_blocked`. Per-app opt-out: `
|
|
70
|
+
**Disposable-email blocking:** on by default — email-code signups from known burner domains are rejected at code-send with error code `disposable_email_blocked`. Per-app opt-out: `remy-admin settings set blockDisposableEmails false`.
|
|
71
71
|
|
|
72
72
|
**Test accounts (fixed-code login):** the mechanism for handing app-store reviewers working credentials for a wrapped app — never hand-roll a fake-auth backdoor for this. A listed email or E.164 phone signs in with a pre-set 6-digit code instead of a delivered one, bypassing the disposable and allowlist gates. Works in production; max 5; disable after review.
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
remy-admin settings test-accounts add reviewer@example.com 246810
|
|
76
|
+
remy-admin settings test-accounts enable
|
|
77
77
|
# after review:
|
|
78
|
-
|
|
78
|
+
remy-admin settings test-accounts disable
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
## Auth Table
|
|
@@ -50,11 +50,11 @@ Search is deterministic for a fixed corpus and configuration, so eval sets and r
|
|
|
50
50
|
## Loading documents — normally at build time, from the CLI
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
remy-admin datasources add --source policies --wait docs/*.pdf
|
|
54
|
+
remy-admin datasources add --source policies --metadata department=legal,year=2026 contract.pdf
|
|
55
|
+
remy-admin datasources search --source policies "what are the payment terms?" # sanity-check
|
|
56
|
+
remy-admin datasources search --source policies --filter department=legal --mode lexical "ERR-7741X"
|
|
57
|
+
remy-admin datasources delete --source policies # whole source; --source is required, never defaulted
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
`--wait` blocks until processing finishes and exits non-zero on failure. Also `datasources list`, `status` (per-document state + ingest errors), `rm --document <id>`. `--help` for flags.
|
|
@@ -91,16 +91,16 @@ Images inside documents are described by a vision model and the description subs
|
|
|
91
91
|
`rerank` and `hybrid` default on and are usually right — reranking is the biggest quality lever, and hybrid is what finds part numbers, error codes and proper nouns a semantic model never learned. Both are also per-query (`search(q, { rerank: false })`) for a latency-sensitive path.
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
remy-admin datasources config --source policies # show
|
|
95
|
+
remy-admin datasources config --source policies --top-k 8 # free, immediate
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
**A rebuild-class change on a populated corpus is rejected** — you're told what it would invalidate and what it costs. To make it, build a new version alongside the live one:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
remy-admin datasources revectorize --source policies --max-chars 900 --wait
|
|
102
|
+
remy-admin datasources search --source policies --candidate "payment terms" # compare
|
|
103
|
+
remy-admin datasources promote --source policies # go live
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
Search serves the current version throughout, so nothing degrades while the new one builds. `datasources drop` discards an unwanted candidate.
|
|
@@ -96,7 +96,7 @@ Lightweight-config pattern: a public store + a stable `key` is a file the fronte
|
|
|
96
96
|
Need an image on the site (hero, logo, OG image)? **Never commit binaries to the repo** — it bloats git. Upload once and embed the returned URL:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
|
|
99
|
+
remy-admin files put --public ./hero.jpg # → { url, key } — content-addressed, immutable
|
|
100
100
|
```
|
|
101
101
|
Write that URL into your JSX/HTML. The full CLI surface: `put` (any size up to 5 GiB — bytes go directly to storage), `get` (download an object to disk), `sign` (mint a link), `stat` (metadata / existence), `ls` (objects in a store), `list` (store summary), `rm` (`--help` for flags).
|
|
102
102
|
|
|
@@ -105,9 +105,9 @@ Write that URL into your JSX/HTML. The full CLI surface: `put` (any size up to 5
|
|
|
105
105
|
The sandbox is ephemeral; when a user needs a file out of it (an export, an archive, a handoff), the channel is a **private store + a signed link** — private, expiring, and revocable:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
remy-admin files put --private --store handoff ./export.tar.gz # → { key }
|
|
109
|
+
remy-admin files sign --private --store handoff --key <key> --ttl 86400 # → { url, expiresAt }
|
|
110
|
+
remy-admin files rm --store handoff --key <key> --private # revoke when confirmed received
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
**Never use the account media CDN (`mindstudio upload`) for sensitive material — those URLs are public and permanent, with no delete.** That includes probing: don't test an upload channel with the real payload; use throwaway bytes.
|
|
@@ -8,7 +8,7 @@ when: Before writing an email-handler method, adding an `email` interface, or pr
|
|
|
8
8
|
|
|
9
9
|
Inbound email triggers. Each app has **one** email-handler method; the platform routes all inbound mail destined for the app — across any of its address tiers — to that method.
|
|
10
10
|
|
|
11
|
-
The addresses themselves are configured at the project level
|
|
11
|
+
The addresses themselves are configured at the project level. Your job in the app code is the `interface.json` and the method that handles the mail; custom domains (both directions) are provisioned outside the repo, via the `remy-admin email` CLI (see "Custom domains" below).
|
|
12
12
|
|
|
13
13
|
## Address tiers
|
|
14
14
|
|
|
@@ -17,12 +17,30 @@ Three tiers, all delivered to the same handler method. The new tiers are catchal
|
|
|
17
17
|
| Tier | Address | How it's set up |
|
|
18
18
|
|---|---|---|
|
|
19
19
|
| Platform subdomain (default) | `*@<custom_subdomain>.madewithremy.com` | Automatic the moment the app has a `custom_subdomain` set. Every address on that subdomain delivers to the handler. |
|
|
20
|
-
| Custom domain | `*@<their-domain>` |
|
|
20
|
+
| Custom domain | `*@<their-domain>` | Registered via `remy-admin email inbound-domains add <domain>`; the user points one MX record at the platform (the command returns it). See "Custom domains" below. |
|
|
21
21
|
| Legacy `mindstudio-hooks.com` | `<name>@mindstudio-hooks.com` | Existing apps only — frozen for new apps. Don't recommend it; treat as read-only history. |
|
|
22
22
|
|
|
23
23
|
Because the new tiers are catchall, `to` carries an arbitrary localpart. Methods that need to branch on it should read `input.to` (e.g. `if (input.to.startsWith('support@')) ...`). This is what makes per-purpose addresses free: you don't register `support@` anywhere, you just check for it.
|
|
24
24
|
|
|
25
|
-
A verified custom domain (and the app's `madewithremy.com` subdomain) also **sends** outbound mail, not just receives — `sendEmail` picks the app's own-brand sender automatically
|
|
25
|
+
A verified custom domain (and the app's `madewithremy.com` subdomain) also **sends** outbound mail, not just receives — `sendEmail` picks the app's own-brand sender automatically once a sending domain is verified.
|
|
26
|
+
|
|
27
|
+
## Custom domains (agent-provisioned, via CLI)
|
|
28
|
+
|
|
29
|
+
Both directions are set up with `remy-admin email`, and both follow the same loop: **add → hand the user the DNS records → verify → check status**. The user's only job is creating records at their DNS host.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Sending (governs the From address; SES identity + DKIM):
|
|
33
|
+
remy-admin email domains add mail.acme.com # → dnsInstructions: 3 DKIM CNAMEs (required) + recommended SPF TXT
|
|
34
|
+
remy-admin email domains verify mail.acme.com # re-check now; uiStatus: pending | verified | action_needed
|
|
35
|
+
remy-admin email domains list # all sending domains + effectiveSender (what the app sends as today)
|
|
36
|
+
|
|
37
|
+
# Receiving (mail to *@their-domain reaches the email handler; one MX record):
|
|
38
|
+
remy-admin email inbound-domains check acme.com # returns the MX record before registering anything
|
|
39
|
+
remy-admin email inbound-domains add acme.com
|
|
40
|
+
remy-admin email inbound-domains verify acme.com
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Give the user the exact records from the command output — never paraphrase DNS values. Verification is asynchronous after the records resolve (sending can take minutes; `action_needed` means check `verificationErrors` — sending verification expires unstarted after ~72h, fix and `verify` again).
|
|
26
44
|
|
|
27
45
|
## Config (`interface.json`)
|
|
28
46
|
|
|
@@ -233,11 +233,11 @@ on; default all history), `epochs` (1-10, default 3), `rank` (LoRA rank, 4-64, d
|
|
|
233
233
|
changing a knob is a commit + deploy before the next training run.
|
|
234
234
|
|
|
235
235
|
Once a method has accumulated graded pairs, train from the prod CLI:
|
|
236
|
-
`
|
|
236
|
+
`remy-admin jewels train <methodId>` (see `--help`). It returns immediately with
|
|
237
237
|
a run id and the dataset report; a run takes minutes to tens of minutes, so never use
|
|
238
238
|
`--wait` (that flag is for humans at a terminal — it would block your whole loop).
|
|
239
239
|
Start the run, tell the user it's training, keep working on other things, and check in
|
|
240
|
-
with `
|
|
240
|
+
with `remy-admin jewels run <runId>` between tasks — the `progress` field shows
|
|
241
241
|
the live phase and training percent, the run's `log` narrates the whole story as
|
|
242
242
|
timestamped status events (queued, GPU acquired, training, grading — loss points are
|
|
243
243
|
compacted to a count in CLI output), and `status` goes `complete` or `failed` with the
|
|
@@ -253,7 +253,7 @@ snapshots, plus a DPO pass over the team's corrections when enough have accumula
|
|
|
253
253
|
and the platform promotes whichever scores highest on that grader — `report.selected`
|
|
254
254
|
names the serving checkpoint; this is automatic, never something you configure.
|
|
255
255
|
A completed run without a `grading` block just hasn't been graded yet
|
|
256
|
-
(`
|
|
256
|
+
(`remy-admin jewels grade <runId>` fills it). A completed run registers the
|
|
257
257
|
app's tuned model as a real model id — `tuned/{appId}/{methodId}`, one stable id per
|
|
258
258
|
method that retraining advances in place — and the latest complete run per method is
|
|
259
259
|
automatically served on the platform's GPU pool, so that id works like any other
|
|
@@ -336,7 +336,7 @@ What makes this safe with no extra machinery:
|
|
|
336
336
|
- **Dismissal is not consumption**: the decision moment stays open for other verbs (a dismissed draft doesn't block a later merge). Unresolved items expire at the method's `attributionWindow`, so there's no infinite backlog.
|
|
337
337
|
- `propose` returns `queueItemId` on `queued`, so the app can badge its UI or notify its own way the moment a draft lands.
|
|
338
338
|
|
|
339
|
-
Before the app has its own review UI (or when the user asks you to act), the same queue is reachable from `
|
|
339
|
+
Before the app has its own review UI (or when the user asks you to act), the same queue is reachable from `remy-admin jewels queue` / `jewels resolve`; approving there applies the method as the user through the identical machinery.
|
|
340
340
|
|
|
341
341
|
## Verifying a Jewel: the `testJewel` Tool
|
|
342
342
|
|
|
@@ -351,4 +351,4 @@ For cases with no known right answer, pass `subject` instead of `humanInput` for
|
|
|
351
351
|
|
|
352
352
|
Each run also lands in `.logs/requests.ndjson` as a `type: 'jewel'` record if you need the trail.
|
|
353
353
|
|
|
354
|
-
Once deployed, the prod-side view lives in `
|
|
354
|
+
Once deployed, the prod-side view lives in `remy-admin jewels` (run `--help` for commands): agreement stats, pair records, the approval queue, and `jewels dryrun`, the prod twin of `testJewel`. It runs the LIVE jewel against a real subject inside a disposable database mirror and records nothing.
|
|
@@ -18,9 +18,9 @@ If dismissed, acknowledge and do nothing — no commit, no push.
|
|
|
18
18
|
|
|
19
19
|
## 2. Ship (on approval)
|
|
20
20
|
|
|
21
|
-
- Stage and commit any uncommitted changes with a clean, descriptive commit message. If the committed work resolves any open issues (`
|
|
21
|
+
- Stage and commit any uncommitted changes with a clean, descriptive commit message. If the committed work resolves any open issues (`remy-admin issues`), reference them in the commit message with a closing keyword — `fixes #42`, `closes #7` — so the deploy closes them automatically once it goes live.
|
|
22
22
|
- Push to main.
|
|
23
|
-
- Use `
|
|
23
|
+
- Use `remy-admin releases wait` to poll the build until it completes. Let the user know it's deploying, then report back when it's live.
|
|
24
24
|
|
|
25
25
|
## 3. Close out — scaled to what shipped
|
|
26
26
|
|
|
@@ -31,6 +31,6 @@ The changelog you just wrote is the measure.
|
|
|
31
31
|
|
|
32
32
|
## 4. Offer next steps
|
|
33
33
|
|
|
34
|
-
Once deployed, offer to help with what comes next. This includes technical steps like setting up a custom domain (`
|
|
34
|
+
Once deployed, offer to help with what comes next. This includes technical steps like setting up a custom domain (`remy-admin domains`), checking for errors (`remy-admin requests stats`), seeding production data (`remy-admin db`), managing env vars/secrets, or anything else they need for launch. It also includes going above and beyond and helping holistically: if it's the initial deploy, offer to help create collateral to announce the launch (e.g., an image for sharing on social media, text copy for a post); if it's a meaningful incremental update, an announcement post or something similar. Refer to the design guidance in the spec for how to talk about the product, and consider consulting the design expert to generate images or other marketing collateral — help the user see that you care about the product from end-to-end, not just the code.
|
|
35
35
|
|
|
36
36
|
After everything is done, if this was a meaningful release, call `compactConversation` to summarize the session and free up context for the next phase of work. After a hotfix, don't bother.
|
|
@@ -119,7 +119,7 @@ Scenarios are useful for seeding initial app state after build for testing, as w
|
|
|
119
119
|
Don't try to seed documents into a data source from a scenario, and don't write `clear()`-style reset helpers for one. Load a test corpus once from the CLI instead:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
|
|
122
|
+
remy-admin datasources add --source policies --wait fixtures/*.pdf
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
Re-running it is free (documents are content-addressed), so it's safe to keep in a setup script beside your scenarios.
|
|
@@ -44,3 +44,14 @@ Declare it in `mindstudio.json`:
|
|
|
44
44
|
Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that should only be reachable on a schedule. The auth reference in your system prompt covers the system role in full.
|
|
45
45
|
|
|
46
46
|
A scheduled job that needs to act on user data acts as the system, not as any user, so it reaches everything. Scope what it touches in the method itself rather than relying on role checks to narrow it.
|
|
47
|
+
|
|
48
|
+
## Operating jobs in production
|
|
49
|
+
|
|
50
|
+
The `remy-admin cron` group covers the live app's schedule without opening the dashboard:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
remy-admin cron list # every job: schedule, status (active|paused|blocked), next run, recent runs
|
|
54
|
+
remy-admin cron run <methodId> # trigger a run now (202 { requestId }; doesn't affect the schedule)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A run's id is its request-log id — debug a failing job with `remy-admin requests get <runId>`. Jobs that fail repeatedly become `blocked` (see `statusReason`) and stay stopped until fixed and redeployed.
|
|
@@ -37,7 +37,7 @@ Each secret has a dev value and a prod value. The same code (`process.env.STRIPE
|
|
|
37
37
|
|
|
38
38
|
## Management
|
|
39
39
|
|
|
40
|
-
Secrets are managed through the app's dashboard or the `
|
|
40
|
+
Secrets are managed through the app's dashboard or the `remy-admin secrets` CLI.
|
|
41
41
|
|
|
42
42
|
## What NOT to Store
|
|
43
43
|
|
|
@@ -27,7 +27,7 @@ Beyond the mechanics, the persona itself should be *of the ear*: pacing, warmth,
|
|
|
27
27
|
|
|
28
28
|
### The shape of `system.md`
|
|
29
29
|
|
|
30
|
-
Structure the compiled prompt as short **labeled sections** — Role & Objective, Personality & Tone, Rules, and (when the app has a real call flow) Conversation Flow — with bullets over paragraphs; realtime models find and follow sectioned rules far more reliably than prose. Scope rules precisely; blanket `always`/`never` makes the agent rigid and unable to handle reasonable exceptions. And start minimal: state the role, the boundaries, and the voice mechanics above, then add rules only for behaviors that actually misfire in test calls (the transcripts in the call log are the feedback loop — `
|
|
30
|
+
Structure the compiled prompt as short **labeled sections** — Role & Objective, Personality & Tone, Rules, and (when the app has a real call flow) Conversation Flow — with bullets over paragraphs; realtime models find and follow sectioned rules far more reliably than prose. Scope rules precisely; blanket `always`/`never` makes the agent rigid and unable to handle reasonable exceptions. And start minimal: state the role, the boundaries, and the voice mechanics above, then add rules only for behaviors that actually misfire in test calls (the transcripts in the call log are the feedback loop — `remy-admin voice sessions get` reads a call verbatim) rather than front-loading a policy manual.
|
|
31
31
|
|
|
32
32
|
### The latency classes
|
|
33
33
|
|
|
@@ -211,7 +211,7 @@ export async function callMeAboutMyOrder(input: { phone: string }) {
|
|
|
211
211
|
|
|
212
212
|
- **The method is the authorization gate.** The voice interface's `auth` block does not apply to calls the backend places deliberately — gate the *method* with `auth.requireRole(...)` exactly as you would any sensitive action.
|
|
213
213
|
- **`assumeIdentity: true`** runs the call as the user who invoked the method: the agent knows who it's talking to (Current User block) and every tool call carries their roles — regardless of which number was dialed (the user types any number into a field; identity comes from their session, not the phone). Omitted/false → anonymous call; role-gated tools decline. System/cron invocations have no human identity and always run anonymously. Anonymous outbound calls (deployed) get the same in-call verification flow as inbound — the callee proves possession of the number that was dialed, or verifies by email — so an anonymous call can still upgrade to a known user mid-conversation.
|
|
214
|
-
- **Production needs a dedicated phone number.** The app owner attaches one ($1/month) via the dashboard or `
|
|
214
|
+
- **Production needs a dedicated phone number.** The app owner attaches one ($1/month) via the dashboard or `remy-admin voice numbers` (see "The voice CLI" below) — it becomes the caller ID for every call, in dev sessions too, so users always see the same number. Without one, deployed calls throw `phone_out_requires_dedicated_number`, and dev sessions fall back to a shared platform test number that varies per call (tighter limits apply on the shared pool).
|
|
215
215
|
- **Outcome is on the call record**, not the return value: `voice.call` returns as soon as dialing starts (`{ sessionId, status: 'dialing', from, to }`); answered/busy/no-answer land on the session in the app's call log (`voice.listSessions()` / the dashboard).
|
|
216
216
|
- **Limits**: the app's concurrent-session policy, a daily outbound-call cap, a per-call duration ceiling, and one active call per callee number (`voice_callee_busy`).
|
|
217
217
|
- **Compliance**: automated calls require prior consent. Call your own users who opted in to calls from this app, honor reasonable calling hours, never dial purchased or cold lists — design the consent moment into the product (a "call me" button IS consent; a scraped list is not).
|
|
@@ -242,14 +242,14 @@ A caller whose number exactly matches an app user's phone starts the call alread
|
|
|
242
242
|
|
|
243
243
|
## The voice CLI
|
|
244
244
|
|
|
245
|
-
The `
|
|
245
|
+
The `remy-admin voice` family covers numbers, the call log, and voice policy:
|
|
246
246
|
|
|
247
247
|
```bash
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
remy-admin voice numbers search --area-code 310 # available numbers to offer the user
|
|
249
|
+
remy-admin voice numbers buy +13105551234 # buy + attach ($1/month — see below)
|
|
250
|
+
remy-admin voice numbers release +13105551234 # permanent; no refund, ~15-day quarantine
|
|
251
|
+
remy-admin voice sessions list --limit 10 # call log: web / phone-out / phone-in
|
|
252
|
+
remy-admin voice sessions get <sessionId> # full transcript + cost breakdown
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
Also `voice numbers list`, `voice numbers set-name` (outbound caller-ID display name; 12-72h carrier propagation), `voice settings get`/`set` (concurrency, per-visitor, max duration — `set` merges: only the settings you pass change). `--help` for flags.
|
|
@@ -80,7 +80,7 @@ Before installing a package you haven't used in this project, do a quick web sea
|
|
|
80
80
|
You have access to the `mindstudio` CLI, which exposes every SDK action as a command-line tool. Use it via bash for one-off tasks: generating images, video, or audio, scraping URLs, sending emails, running AI completions, or anything else the SDK can do. Every JavaScript SDK method has a corresponding CLI command. Run `askMindStudioSdk` to discover commands for CLI usage.
|
|
81
81
|
|
|
82
82
|
### Production App Management
|
|
83
|
-
You have access to `
|
|
83
|
+
You have access to `remy-admin`, a CLI for managing the user's production app. Use it via your bash tool. All output is JSON. Run `remy-admin --help` or `remy-admin <command> --help` to discover usage and available options.
|
|
84
84
|
|
|
85
85
|
Available commands: `requests` (server logs, errors, latency), `crashes` (frontend browser errors), `analytics` (traffic queries — lifetime metrics, sources, live counters), `releases`, `diagnostics` (Lighthouse audit), `domains`, `users` (list, set roles), `db` (query production sql), `data` (live db operations like lift-from-dev), `methods` (list, invoke), `secrets`, `files` (CDN files), `datasources` (document corpora), `prerender` (crawler snapshots), `voice` (phone numbers, call logs, voice policy), `issues` (externally-reported bugs), `settings` (app settings: signup restrictions, app-store-reviewer test accounts, embedding origins, toggles).
|
|
86
86
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- After two failed attempts at the same approach, tell the user what's going wrong.
|
|
16
16
|
- Never estimate how long something will take or how much it will cost. Just do it. If the user asks, politely refuse — any number would be a guess. Never quote concrete time units for work you're about to do. You can describe scope qualitatively (small change, large refactor, etc.), but never estimate the time it will take you to do work.
|
|
17
17
|
- Pushing to main branch will trigger a production deploy. Publishing happens at the user's ask — the Publish button or an explicit request in chat — and the release has its own playbook: load the `publishing` skill before presenting a changelog or pushing to main.
|
|
18
|
-
- Pushing any OTHER branch builds a preview instead of deploying: a private copy of the app at its own URL, with its own copy of the data, that anyone who can open the app in Remy can visit. `
|
|
18
|
+
- Pushing any OTHER branch builds a preview instead of deploying: a private copy of the app at its own URL, with its own copy of the data, that anyone who can open the app in Remy can visit. `remy-admin releases wait` returns its `previewUrl`. Use this to show the user working software before it goes live — a branch push plus the link is often a better answer than describing what you built.
|
|
19
19
|
|
|
20
20
|
### Build Notes
|
|
21
21
|
For complex tasks — especially an initial buildout from a spec or making multiple changes in a single turn — write a `.remy-notes.md` scratchpad in the project root. Use it to track progress: a checklist of what's been built and what's remaining. Do not include implementation details or other decisions in the notes - it is solely for keeping track of tasks. Read the spec files directly when you need design details, implementation decisions, or other reference materials - never write them to the notes file. Delete the notes file when your work is done. When implementing an approved plan, `.remy-plan.md` serves as your reference. Delete it when all planned work is complete.
|
|
@@ -38,7 +38,8 @@ These are things we already know about and have decided to accept:
|
|
|
38
38
|
- Preferences:
|
|
39
39
|
- use [wouter](https://github.com/molefrog/wouter) for React routing instead of reaching for react-router
|
|
40
40
|
- uploading user files should always happen via `platform.uploadFile()` from `@mindstudio-ai/interface` — not custom S3 code, not FormData to a method endpoint
|
|
41
|
-
- for
|
|
41
|
+
- for build-time prerendering of purely static sites (marketing pages with no dynamic content — distinct from the platform's crawler prerendering, below), roll your own with a post-build `renderToString` script — do not use `vite-prerender-plugin` (it bundles the prerender script as a client chunk, adding ~800KB to the user-facing bundle with no way to prevent it)
|
|
42
|
+
- **Prerendering for crawlers/unfurlers is a platform feature — don't design around it.** For SEO / link-unfurl / AI-crawler visibility on a non-static SPA, the platform already handles it: routes opt in via `web.json` (`{ "web": { "prerender": { "paths": ["/blog/*"] } } }`), the SPA signals readiness by setting `data-prerender-ready` on the html element, deploys invalidate the snapshot cache automatically, and content that changes outside deploys is invalidated at runtime with `await prerender.invalidate([...])` from `@mindstudio-ai/agent` (called from the mutating method). It serves cached headless snapshots of the live SPA to bots — it is NOT build-time rendering. Do not recommend post-build render scripts, rebuild-on-content-change, or third-party prerender services for this. The developer's main context carries the full interfaces reference with exact semantics — tell them to consult it rather than improvising.
|
|
42
43
|
|
|
43
44
|
### Common pitfalls (always flag these)
|
|
44
45
|
|
|
@@ -93,7 +94,7 @@ When a plan includes multiple screens/API calls, always note this item for the d
|
|
|
93
94
|
|
|
94
95
|
If you get a whiff of any of the following, make a note for the developer:
|
|
95
96
|
|
|
96
|
-
- **Stripe**: The developer has access to the Stripe CLI via bash (they'll need to get the secret key from
|
|
97
|
+
- **Stripe**: The developer has access to the Stripe CLI via bash (they'll need to get the secret key from remy-admin CLI first to set it up - it's not available in env directly). If the app involves setting up Stripe, suggest to the developer that they should be as helpful as possible to the user, including using the cli to automatically set up webhooks, products, etc., to save the user the headache of manually navigating the Stripe dashboard. Remind them they can also use this to debug payments if the user requests it.
|
|
97
98
|
|
|
98
99
|
- **Long-running AI jobs.** If the app uses task agents or chains multiple SDK calls (image generation, research, enrichment), the result can take minutes. Never leave the user staring at a spinner. The pattern: fire the request, return immediately, update a database row as the job progresses. At minimum, track status on the relevant record (`processing` → `done` / `failed`). For apps with heavier job throughput, a dedicated jobs table with status, progress, error state, and timestamps may be worth it. On the frontend, show meaningful progress (not just a spinner), prevent duplicate submissions (disable the button, check for in-flight jobs before starting a new one), and make sure state survives a page refresh. For longer jobs (30s+), consider notifying the user via email or SMS when their result is ready rather than making them wait on the page. Store errors visibly so failed jobs don't just silently disappear.
|
|
99
100
|
|