@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 CHANGED
@@ -942,9 +942,9 @@ function buildBackgroundResultsMessage(results) {
942
942
  ${r.result}
943
943
  </tool_result>`
944
944
  ).join("\n\n");
945
- const plural = results.length > 1 ? "s" : "";
946
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
947
- <background_results>
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: 2e5,
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, _toolCallId, onLog) => {
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(name, _input, context);
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) => executeTool(name, toolInput, context),
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 ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
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, _onLog, sams) => {
6384
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
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) => executeTool(name, toolInput, context),
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, _onLog, sams) => {
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, context);
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: 2e5,
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 plural = results.length > 1 ? "s" : "";
1664
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
1665
- <background_results>
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, _toolCallId, onLog) => {
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(name, _input, context);
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) => executeTool(name, toolInput, context),
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 ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
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, _onLog, sams) => {
7798
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
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) => executeTool(name, toolInput, context),
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, _onLog, sams) => {
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, context);
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 `mindstudio-prod 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.
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 `mindstudio-prod` CLI.
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 (`mindstudio-prod 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.
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 `mindstudio-prod 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.
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
- `mindstudio-prod prerender` can help you verify/manage snapshots during development.
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 `mindstudio-prod 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.
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 `mindstudio-prod settings` CLI (run `mindstudio-prod settings --help` for the full surface).
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
- mindstudio-prod settings allowlist add '*@acme.com' # whole domain
60
- mindstudio-prod settings allowlist add 'cfo@other.com' # one address
61
- mindstudio-prod settings allowlist enable # start enforcing
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: `mindstudio-prod settings set blockDisposableEmails false`.
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
- mindstudio-prod settings test-accounts add reviewer@example.com 246810
76
- mindstudio-prod settings test-accounts enable
75
+ remy-admin settings test-accounts add reviewer@example.com 246810
76
+ remy-admin settings test-accounts enable
77
77
  # after review:
78
- mindstudio-prod settings test-accounts disable
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
- mindstudio-prod datasources add --source policies --wait docs/*.pdf
54
- mindstudio-prod datasources add --source policies --metadata department=legal,year=2026 contract.pdf
55
- mindstudio-prod datasources search --source policies "what are the payment terms?" # sanity-check
56
- mindstudio-prod datasources search --source policies --filter department=legal --mode lexical "ERR-7741X"
57
- mindstudio-prod datasources delete --source policies # whole source; --source is required, never defaulted
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
- mindstudio-prod datasources config --source policies # show
95
- mindstudio-prod datasources config --source policies --top-k 8 # free, immediate
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
- mindstudio-prod datasources revectorize --source policies --max-chars 900 --wait
102
- mindstudio-prod datasources search --source policies --candidate "payment terms" # compare
103
- mindstudio-prod datasources promote --source policies # go live
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
- mindstudio-prod files put --public ./hero.jpg # → { url, key } — content-addressed, immutable
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
- mindstudio-prod files put --private --store handoff ./export.tar.gz # → { key }
109
- mindstudio-prod files sign --private --store handoff --key <key> --ttl 86400 # → { url, expiresAt }
110
- mindstudio-prod files rm --store handoff --key <key> --private # revoke when confirmed received
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 by the user through the Remy platform. Your job is the `interface.json` and the method that handles the mail, not domain registration or MX records.
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>` | The user adds a domain in the dashboard's email-domains settings and points one MX record at `mx.msagent.ai`. Not something the agent provisions. |
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, configured in the dashboard's **Email** settings.
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
- `mindstudio-prod jewels train <methodId>` (see `--help`). It returns immediately with
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 `mindstudio-prod jewels run <runId>` between tasks — the `progress` field shows
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
- (`mindstudio-prod jewels grade <runId>` fills it). A completed run registers the
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 `mindstudio-prod jewels queue` / `jewels resolve`; approving there applies the method as the user through the identical machinery.
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 `mindstudio-prod 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.
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 (`mindstudio-prod 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.
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 `mindstudio-prod releases wait` to poll the build until it completes. Let the user know it's deploying, then report back when it's live.
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 (`mindstudio-prod domains`), checking for errors (`mindstudio-prod requests stats`), seeding production data (`mindstudio-prod 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.
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
- mindstudio-prod datasources add --source policies --wait fixtures/*.pdf
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 `mindstudio-prod secrets` CLI.
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 — `mindstudio-prod voice sessions get` reads a call verbatim) rather than front-loading a policy manual.
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 `mindstudio-prod 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).
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 `mindstudio-prod voice` family covers numbers, the call log, and voice policy:
245
+ The `remy-admin voice` family covers numbers, the call log, and voice policy:
246
246
 
247
247
  ```bash
248
- mindstudio-prod voice numbers search --area-code 310 # available numbers to offer the user
249
- mindstudio-prod voice numbers buy +13105551234 # buy + attach ($1/month — see below)
250
- mindstudio-prod voice numbers release +13105551234 # permanent; no refund, ~15-day quarantine
251
- mindstudio-prod voice sessions list --limit 10 # call log: web / phone-out / phone-in
252
- mindstudio-prod voice sessions get <sessionId> # full transcript + cost breakdown
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 `mindstudio-prod`, a CLI for managing the user's production app. Use it via your bash tool. All output is JSON. Run `mindstudio-prod --help` or `mindstudio-prod <command> --help` to discover usage and available options.
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. `mindstudio-prod 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.
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 static prerendering of Vite + React sites, 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)
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 mindstudio-prod 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
+ - **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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.298",
3
+ "version": "0.1.300",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",