@mindstudio-ai/remy 0.1.276 → 0.1.277
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 +112 -33
- package/dist/index.js +135 -35
- package/dist/prompt/skills/agentInterfaces.md +13 -39
- package/dist/prompt/skills/auth.md +1 -7
- package/dist/prompt/skills/dataSources.md +20 -66
- package/dist/prompt/skills/files.md +17 -47
- package/dist/prompt/skills/inboundEmail.md +11 -32
- package/dist/prompt/skills/mcpInterfaces.md +21 -63
- package/dist/prompt/skills/restApi.md +7 -21
- package/dist/prompt/skills/scenarios.md +3 -7
- package/dist/prompt/skills/scheduledJobs.md +3 -8
- package/dist/prompt/skills/voiceInterfaces.md +117 -366
- package/dist/prompt/skills/webhooks.md +10 -31
- package/dist/prompt/static/authoring.md +1 -2
- package/dist/subagents/browserAutomation/prompt.md +6 -22
- package/dist/subagents/codeSanityCheck/prompt.md +1 -2
- package/dist/subagents/designExpert/prompt.md +0 -3
- package/dist/subagents/designExpert/prompts/ui-patterns.md +7 -21
- package/dist/subagents/designExpert/skills/authExperience.md +60 -0
- package/dist/subagents/designExpert/skills/chatExperience.md +59 -0
- package/dist/subagents/designExpert/skills/dataViz.md +82 -0
- package/dist/subagents/designExpert/{prompts → skills}/images.md +6 -0
- package/dist/subagents/designExpert/skills/voiceExperience.md +103 -0
- package/dist/subagents/productVision/prompt.md +2 -0
- package/package.json +1 -1
|
@@ -6,33 +6,20 @@ when: Only for unstructured documents queried by meaning — "find the clause ab
|
|
|
6
6
|
|
|
7
7
|
# Data Sources (Search Over Documents)
|
|
8
8
|
|
|
9
|
-
Per-app searchable document corpora: upload documents, ask in plain language, get back the passages
|
|
10
|
-
that answer it with a citation to the source.
|
|
9
|
+
Per-app searchable document corpora: upload documents, ask in plain language, get back the passages that answer it with a citation to the source.
|
|
11
10
|
|
|
12
|
-
**Most apps should not use one — check this before reaching for it.** Structured data (rows with
|
|
13
|
-
fields you filter on) belongs in `db`; a `WHERE` clause is faster, cheaper and exact. A data source
|
|
14
|
-
earns its cost only for **unstructured documents queried by meaning**. "Find the clause about early
|
|
15
|
-
termination" is a data source. "Find contracts signed after March" is a `db` query. If the question
|
|
16
|
-
can be expressed as a filter, it is not a search problem.
|
|
11
|
+
**Most apps should not use one — check this before reaching for it.** Structured data (rows with fields you filter on) belongs in `db`; a `WHERE` clause is faster, cheaper and exact. A data source earns its cost only for **unstructured documents queried by meaning**. "Find the clause about early termination" is a data source. "Find contracts signed after March" is a `db` query. If the question can be expressed as a filter, it is not a search problem.
|
|
17
12
|
|
|
18
|
-
Don't use one when: the data is structured (`db`), you only need to store files (`files` — nobody is
|
|
19
|
-
searching the contents), the requirement is exact lookup by identifier, or the corpus is a handful of
|
|
20
|
-
short docs that fit in a prompt.
|
|
13
|
+
Don't use one when: the data is structured (`db`), you only need to store files (`files` — nobody is searching the contents), the requirement is exact lookup by identifier, or the corpus is a handful of short docs that fit in a prompt.
|
|
21
14
|
|
|
22
15
|
## Behaviour (read before the API)
|
|
23
16
|
|
|
24
|
-
- **One corpus shared across dev and prod** — like a file store, not a table. No dev copy, no
|
|
25
|
-
per-release isolation. A document added while building is already live.
|
|
17
|
+
- **One corpus shared across dev and prod** — like a file store, not a table. No dev copy, no per-release isolation. A document added while building is already live.
|
|
26
18
|
- **Scenarios never reset a data source.** Don't write `clear()`-style reset helpers.
|
|
27
19
|
- **Re-adding the same bytes is free** — content-addressed, so ingest scripts are safe to re-run.
|
|
28
20
|
- **Ingest is async.** `add()` returns once queued; poll `documents()`, or use `--wait` from the CLI.
|
|
29
21
|
- **Reprocessing costs real money**, so changing how a corpus is built is always explicit.
|
|
30
|
-
- **Limits apply**: 25 data sources per app, 5,000 documents per source, 10,000 chunks per
|
|
31
|
-
document, 300 searches/minute. Well clear of normal use — but **source names must be fixed, not
|
|
32
|
-
computed per user or per request**, since referencing one creates it. Partition inside a source
|
|
33
|
-
with document metadata instead: tag at add time
|
|
34
|
-
(`add(bytes, { filename, metadata: { userId } })`), narrow at search time
|
|
35
|
-
(`search(q, { filter: { metadata: { userId } } })`).
|
|
22
|
+
- **Limits apply**: 25 data sources per app, 5,000 documents per source, 10,000 chunks per document, 300 searches/minute. Well clear of normal use — but **source names must be fixed, not computed per user or per request**, since referencing one creates it. Partition inside a source with document metadata instead: tag at add time (`add(bytes, { filename, metadata: { userId } })`), narrow at search time (`search(q, { filter: { metadata: { userId } } })`).
|
|
36
23
|
|
|
37
24
|
## Defining and searching
|
|
38
25
|
|
|
@@ -44,43 +31,21 @@ const { results } = await Policies.search('what are the payment terms?', { topK:
|
|
|
44
31
|
const context = results.map((r) => r.text).join('\n\n');
|
|
45
32
|
```
|
|
46
33
|
|
|
47
|
-
Hits are `{ score, text, citation }` with
|
|
48
|
-
`citation: { documentId, filename, pageNumber, chunkIndex, headingPath, boundingBox?, url }`, plus
|
|
49
|
-
`retrievalRank`/`retrievalScore` — the position before reranking, so you can show what reranking did.
|
|
34
|
+
Hits are `{ score, text, citation }` with `citation: { documentId, filename, pageNumber, chunkIndex, headingPath, boundingBox?, url }`, plus `retrievalRank`/`retrievalScore` — the position before reranking, so you can show what reranking did.
|
|
50
35
|
|
|
51
|
-
**Always render the citation.** `citation.url` is a stable on-domain link — put it in an `<a href>`
|
|
52
|
-
beside the answer. Retrieval is approximate; a user who can click through can judge for themselves.
|
|
53
|
-
An answer with no citation is an assertion.
|
|
36
|
+
**Always render the citation.** `citation.url` is a stable on-domain link — put it in an `<a href>` beside the answer. Retrieval is approximate; a user who can click through can judge for themselves. An answer with no citation is an assertion.
|
|
54
37
|
|
|
55
|
-
Created on first use, so searching a source the build hasn't populated returns no results rather than
|
|
56
|
-
throwing. `search` options: `topK` (default 5, max 50), `scoreThreshold`, `filter`, `mode`,
|
|
57
|
-
`maxPerDocument`, `highlight`, `rerank`, `hybrid`.
|
|
38
|
+
Created on first use, so searching a source the build hasn't populated returns no results rather than throwing. `search` options: `topK` (default 5, max 50), `scoreThreshold`, `filter`, `mode`, `maxPerDocument`, `highlight`, `rerank`, `hybrid`.
|
|
58
39
|
|
|
59
|
-
**Filtering** narrows a search before ranking, and every condition only narrows:
|
|
60
|
-
`filter: { metadata: { department: 'legal', year: [2025, 2026] }, filename, documentIds,
|
|
61
|
-
pages: { min?, max? }, contains: 'all these words', phrase: 'exact adjacent sequence' }`.
|
|
62
|
-
Metadata is tagged at add time (scalars only, ≤16 keys); re-adding the same bytes with different
|
|
63
|
-
metadata updates the tags in place, free. Filters are the right tool for scoping retrieval
|
|
64
|
-
(per-user, per-category); they are NOT a substitute for a `db` query over structured data.
|
|
40
|
+
**Filtering** narrows a search before ranking, and every condition only narrows: `filter: { metadata: { department: 'legal', year: [2025, 2026] }, filename, documentIds, pages: { min?, max? }, contains: 'all these words', phrase: 'exact adjacent sequence' }`. Metadata is tagged at add time (scalars only, ≤16 keys); re-adding the same bytes with different metadata updates the tags in place, free. Filters are the right tool for scoping retrieval (per-user, per-category); they are NOT a substitute for a `db` query over structured data.
|
|
65
41
|
|
|
66
|
-
**Modes**: `mode: 'hybrid'` (default) fuses semantic and keyword retrieval; `'semantic'` is the
|
|
67
|
-
embedding alone; `'lexical'` is keyword-only with **no query embedding** — cheapest and fastest,
|
|
68
|
-
right when the query is an identifier (an error code, a SKU, a name) rather than a meaning.
|
|
69
|
-
`maxPerDocument: 2` stops one document monopolizing the results when the answer should draw on
|
|
70
|
-
several. `highlight: true` adds `matches` (`{start, end}` offsets into `text`) for rendering
|
|
71
|
-
highlighted excerpts.
|
|
42
|
+
**Modes**: `mode: 'hybrid'` (default) fuses semantic and keyword retrieval; `'semantic'` is the embedding alone; `'lexical'` is keyword-only with **no query embedding** — cheapest and fastest, right when the query is an identifier (an error code, a SKU, a name) rather than a meaning. `maxPerDocument: 2` stops one document monopolizing the results when the answer should draw on several. `highlight: true` adds `matches` (`{start, end}` offsets into `text`) for rendering highlighted excerpts.
|
|
72
43
|
|
|
73
|
-
Search is deterministic for a fixed corpus and configuration, so eval sets and regression checks are
|
|
74
|
-
meaningful — key them on `(documentId, chunkIndex)` rather than on chunk text.
|
|
44
|
+
Search is deterministic for a fixed corpus and configuration, so eval sets and regression checks are meaningful — key them on `(documentId, chunkIndex)` rather than on chunk text.
|
|
75
45
|
|
|
76
|
-
**Debugging retrieval.** Two opt-in options, neither of which changes the results or their order:
|
|
77
|
-
`explain: true` adds `explain.{dense, lexical, matchedVia}` (which half of hybrid found each hit;
|
|
78
|
-
costs two extra round trips), and `expand: 1` adds `neighbors.{before, after}` for surrounding
|
|
79
|
-
context. When a document never comes back at all, `Policies.stats()` reports the config actually in
|
|
80
|
-
effect and `Policies.chunks(documentId)` shows exactly how it was split.
|
|
46
|
+
**Debugging retrieval.** Two opt-in options, neither of which changes the results or their order: `explain: true` adds `explain.{dense, lexical, matchedVia}` (which half of hybrid found each hit; costs two extra round trips), and `expand: 1` adds `neighbors.{before, after}` for surrounding context. When a document never comes back at all, `Policies.stats()` reports the config actually in effect and `Policies.chunks(documentId)` shows exactly how it was split.
|
|
81
47
|
|
|
82
|
-
**Configuration is not declared in code** — chunking and embedding settings live on the corpus and are
|
|
83
|
-
set with the CLI, so code and reality can't drift.
|
|
48
|
+
**Configuration is not declared in code** — chunking and embedding settings live on the corpus and are set with the CLI, so code and reality can't drift.
|
|
84
49
|
|
|
85
50
|
## Loading documents — normally at build time, from the CLI
|
|
86
51
|
|
|
@@ -92,12 +57,9 @@ mindstudio-prod datasources search --source policies --filter department=legal -
|
|
|
92
57
|
mindstudio-prod datasources delete --source policies # whole source; --source is required, never defaulted
|
|
93
58
|
```
|
|
94
59
|
|
|
95
|
-
`--wait` blocks until processing finishes and exits non-zero on failure. Also `datasources list`,
|
|
96
|
-
`status` (per-document state + ingest errors), `rm --document <id>`. `--help` for flags.
|
|
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.
|
|
97
61
|
|
|
98
|
-
**Seeding a test corpus:** scenarios don't touch data sources, so load fixtures with the same command
|
|
99
|
-
in a setup script — `datasources add --source <slug> --wait fixtures/*.pdf`. Re-running is free, so
|
|
100
|
-
it needs no guard.
|
|
62
|
+
**Seeding a test corpus:** scenarios don't touch data sources, so load fixtures with the same command in a setup script — `datasources add --source <slug> --wait fixtures/*.pdf`. Re-running is free, so it needs no guard.
|
|
101
63
|
|
|
102
64
|
Use the SDK's `add()` only when *users* upload documents that must become searchable:
|
|
103
65
|
|
|
@@ -115,9 +77,7 @@ Formats: pdf, docx, pptx, xlsx, odt, rtf, epub, images, txt, md, json, csv, tsv,
|
|
|
115
77
|
|
|
116
78
|
## Answering from results
|
|
117
79
|
|
|
118
|
-
Retrieve → join passages as context → have a model answer *from that context* → render citations.
|
|
119
|
-
Never paste raw chunks at the user; they're fragments. For agentic flows, give the model `search` as a
|
|
120
|
-
tool so it can query repeatedly and refine, rather than retrieving once up front.
|
|
80
|
+
Retrieve → join passages as context → have a model answer *from that context* → render citations. Never paste raw chunks at the user; they're fragments. For agentic flows, give the model `search` as a tool so it can query repeatedly and refine, rather than retrieving once up front.
|
|
121
81
|
|
|
122
82
|
## Tuning — two kinds of setting
|
|
123
83
|
|
|
@@ -126,21 +86,16 @@ tool so it can query repeatedly and refine, rather than retrieving once up front
|
|
|
126
86
|
| **Free** (ranking) | `--rerank`, `--rerank-model`, `--hybrid`, `--top-k` | none, next search |
|
|
127
87
|
| **Rebuild** (how docs become vectors) | `--max-chars`, `--min-chars`, `--drop-blocks`, `--contextual`, `--describe-images`, `--embedding-model`, `--extraction-model` | every document reprocessed |
|
|
128
88
|
|
|
129
|
-
Images inside documents are described by a vision model and the description substituted into the
|
|
130
|
-
searchable text (`--describe-images`, on by default) — without it a chart contributes nothing to
|
|
131
|
-
search at all. Documents with no images cost nothing.
|
|
89
|
+
Images inside documents are described by a vision model and the description substituted into the searchable text (`--describe-images`, on by default) — without it a chart contributes nothing to search at all. Documents with no images cost nothing.
|
|
132
90
|
|
|
133
|
-
`rerank` and `hybrid` default on and are usually right — reranking is the biggest quality lever, and
|
|
134
|
-
hybrid is what finds part numbers, error codes and proper nouns a semantic model never learned. Both
|
|
135
|
-
are also per-query (`search(q, { rerank: false })`) for a latency-sensitive path.
|
|
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.
|
|
136
92
|
|
|
137
93
|
```bash
|
|
138
94
|
mindstudio-prod datasources config --source policies # show
|
|
139
95
|
mindstudio-prod datasources config --source policies --top-k 8 # free, immediate
|
|
140
96
|
```
|
|
141
97
|
|
|
142
|
-
**A rebuild-class change on a populated corpus is rejected** — you're told what it would invalidate
|
|
143
|
-
and what it costs. To make it, build a new version alongside the live one:
|
|
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:
|
|
144
99
|
|
|
145
100
|
```bash
|
|
146
101
|
mindstudio-prod datasources revectorize --source policies --max-chars 900 --wait
|
|
@@ -148,7 +103,6 @@ mindstudio-prod datasources search --source policies --candidate "payment terms"
|
|
|
148
103
|
mindstudio-prod datasources promote --source policies # go live
|
|
149
104
|
```
|
|
150
105
|
|
|
151
|
-
Search serves the current version throughout, so nothing degrades while the new one builds.
|
|
152
|
-
`datasources drop` discards an unwanted candidate.
|
|
106
|
+
Search serves the current version throughout, so nothing degrades while the new one builds. `datasources drop` discards an unwanted candidate.
|
|
153
107
|
|
|
154
108
|
For anything deeper on the SDK, ask `askMindStudioSdk` rather than guessing at an API.
|
|
@@ -6,37 +6,19 @@ when: Before defining a file store or writing any upload, download, share-link,
|
|
|
6
6
|
|
|
7
7
|
# Files & Storage
|
|
8
8
|
|
|
9
|
-
Per-app blob storage: user uploads, generated documents, images, marketing assets. **Think of a store
|
|
10
|
-
as a CDN-backed bucket the app talks to — not app-defined state like the database.** You declare the
|
|
11
|
-
store; what lands in it is arbitrary durable blobs the app doesn't model — no schema, nothing to
|
|
12
|
-
migrate, no dev/prod sync. **Private by default**, served on the app's own domain. (The API is
|
|
13
|
-
*shaped* like `db` — `defineStore` at module scope, import the handle, like `defineTable` — but the
|
|
14
|
-
mental model is a bucket, not rows.)
|
|
9
|
+
Per-app blob storage: user uploads, generated documents, images, marketing assets. **Think of a store as a CDN-backed bucket the app talks to — not app-defined state like the database.** You declare the store; what lands in it is arbitrary durable blobs the app doesn't model — no schema, nothing to migrate, no dev/prod sync. **Private by default**, served on the app's own domain. (The API is *shaped* like `db` — `defineStore` at module scope, import the handle, like `defineTable` — but the mental model is a bucket, not rows.)
|
|
15
10
|
|
|
16
11
|
## How a store behaves (read before the API)
|
|
17
12
|
|
|
18
|
-
- **One store, shared across dev and prod — on purpose.** There's no dev copy: a file you upload in
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **
|
|
22
|
-
|
|
23
|
-
keys and coexist. A collision only happens when you *choose* a fixed key.
|
|
24
|
-
- **Care goes on the destructive / fixed-key operations**, not on writing in general: `delete(key)`
|
|
25
|
-
and overwriting a **stable key** (e.g. `config/latest.json`) reach the one live store, so a dev run
|
|
26
|
-
can clobber what prod serves. A `put()` with a default key can't. (There's intentionally no bulk
|
|
27
|
-
"clear the store".)
|
|
28
|
-
- **Scenarios don't touch files — and that's correct.** A scenario truncates DB tables to seed test
|
|
29
|
-
*rows*; files are durable and left alone. A store isn't a "clean slate" you re-seed each run — upload
|
|
30
|
-
a test file once in dev and it stays. Accumulation is normal for an asset store; don't write
|
|
31
|
-
`clear()`-style reset helpers.
|
|
32
|
-
- **Need dev and prod to *not* share** something (mutable fixed-key state, or sensitive uploads a
|
|
33
|
-
developer shouldn't see)? There's no per-store isolation switch — scope the key yourself (e.g.
|
|
34
|
-
`config/${env}/…`). Rare; the shared default is right almost always.
|
|
13
|
+
- **One store, shared across dev and prod — on purpose.** There's no dev copy: a file you upload in the dev editor (a marketing image, a corpus to vectorize) is *already there in prod* at the same stable URL. That continuity is a feature — don't fork buckets per environment.
|
|
14
|
+
- **Creates are safe by default**, because keys default to unique — `put()` mints a UUID (or a content-addressed hash) when you don't pass one, so a dev write and a prod write land at different keys and coexist. A collision only happens when you *choose* a fixed key.
|
|
15
|
+
- **Care goes on the destructive / fixed-key operations**, not on writing in general: `delete(key)` and overwriting a **stable key** (e.g. `config/latest.json`) reach the one live store, so a dev run can clobber what prod serves. A `put()` with a default key can't. (There's intentionally no bulk "clear the store".)
|
|
16
|
+
- **Scenarios don't touch files — and that's correct.** A scenario truncates DB tables to seed test *rows*; files are durable and left alone. A store isn't a "clean slate" you re-seed each run — upload a test file once in dev and it stays. Accumulation is normal for an asset store; don't write `clear()`-style reset helpers.
|
|
17
|
+
- **Need dev and prod to *not* share** something (mutable fixed-key state, or sensitive uploads a developer shouldn't see)? There's no per-store isolation switch — scope the key yourself (e.g. `config/${env}/…`). Rare; the shared default is right almost always.
|
|
35
18
|
|
|
36
19
|
## Defining a store
|
|
37
20
|
|
|
38
|
-
Like `db.defineTable`, define at module scope and import into methods. Access is pinned at define
|
|
39
|
-
time.
|
|
21
|
+
Like `db.defineTable`, define at module scope and import into methods. Access is pinned at define time.
|
|
40
22
|
|
|
41
23
|
```typescript
|
|
42
24
|
import { files } from '@mindstudio-ai/agent';
|
|
@@ -63,13 +45,9 @@ const { files, cursor } = await Uploads.list({ prefix: 'reports/', limit: 100 })
|
|
|
63
45
|
await Uploads.delete(key);
|
|
64
46
|
```
|
|
65
47
|
|
|
66
|
-
- `put(content, { key?, contentType?, filename?, contentAddressed? })` → `StoredFile`
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **`file.url`** is a plain relative string — don't await it. To display a user *their own* file, hand
|
|
70
|
-
them `file.url`; don't `get()` the bytes and stream them yourself.
|
|
71
|
-
- **`await file.shareUrl({ expiresIn })`** → an absolute signed link that works with **no session**
|
|
72
|
-
(email / cross-site embed). Private stores only; default 24h.
|
|
48
|
+
- `put(content, { key?, contentType?, filename?, contentAddressed? })` → `StoredFile` (`{ key, url, size?, contentType?, updatedAt?, shareUrl() }`). Omit `key` → UUID; `contentAddressed: true` → a `<sha256>.<ext>` key (immutable/idempotent, for baked-in public assets).
|
|
49
|
+
- **`file.url`** is a plain relative string — don't await it. To display a user *their own* file, hand them `file.url`; don't `get()` the bytes and stream them yourself.
|
|
50
|
+
- **`await file.shareUrl({ expiresIn })`** → an absolute signed link that works with **no session** (email / cross-site embed). Private stores only; default 24h.
|
|
73
51
|
|
|
74
52
|
## User uploads (client-direct — bytes never go through the backend)
|
|
75
53
|
|
|
@@ -91,9 +69,7 @@ const { key, url } = await platform.upload(token, file, { onProgress: (f) => set
|
|
|
91
69
|
|
|
92
70
|
## Public assets + image resizing
|
|
93
71
|
|
|
94
|
-
Public files are world-readable, served on the app's own domain, and **images resize via query
|
|
95
|
-
params** — request the size you need rather than CSS-scaling a full-res original. Always set `dpr=2`
|
|
96
|
-
or `3` when sizing so images stay crisp on Retina displays.
|
|
72
|
+
Public files are world-readable, served on the app's own domain, and **images resize via query params** — request the size you need rather than CSS-scaling a full-res original. Always set `dpr=2` or `3` when sizing so images stay crisp on Retina displays.
|
|
97
73
|
|
|
98
74
|
| Param | Example | Effect |
|
|
99
75
|
|-------|---------|--------|
|
|
@@ -109,13 +85,11 @@ or `3` when sizing so images stay crisp on Retina displays.
|
|
|
109
85
|
|
|
110
86
|
Combine freely: `…/hero.jpg?w=200&h=200&fit=crop&fm=avif`.
|
|
111
87
|
|
|
112
|
-
Lightweight-config pattern: a public store + a stable `key` is a file the frontend can `fetch` with no
|
|
113
|
-
DB hit and the backend can overwrite (`Config.put(json, { key: 'config/latest.json' })`).
|
|
88
|
+
Lightweight-config pattern: a public store + a stable `key` is a file the frontend can `fetch` with no DB hit and the backend can overwrite (`Config.put(json, { key: 'config/latest.json' })`).
|
|
114
89
|
|
|
115
90
|
## Build-time / marketing assets
|
|
116
91
|
|
|
117
|
-
Need an image on the site (hero, logo, OG image)? **Never commit binaries to the repo** — it bloats
|
|
118
|
-
git. Upload once and embed the returned URL:
|
|
92
|
+
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:
|
|
119
93
|
|
|
120
94
|
```bash
|
|
121
95
|
mindstudio-prod files put --public ./hero.jpg # → { url, key } — content-addressed, immutable
|
|
@@ -124,8 +98,7 @@ Write that URL into your JSX/HTML. Also: `files list`, `files rm --store … --k
|
|
|
124
98
|
|
|
125
99
|
## Generated assets
|
|
126
100
|
|
|
127
|
-
MindStudio SDK Actions that produce a file (`generateImage`, `generateVideo`, `generateSpeech`, `generatePdf`,
|
|
128
|
-
`upscaleImage`, …) can optionally write straight into a store — pass the handle as `store` in the options object:
|
|
101
|
+
MindStudio SDK Actions that produce a file (`generateImage`, `generateVideo`, `generateSpeech`, `generatePdf`, `upscaleImage`, …) can optionally write straight into a store — pass the handle as `store` in the options object:
|
|
129
102
|
|
|
130
103
|
```typescript
|
|
131
104
|
const { imageUrl } = await mindstudio.generateImage({ prompt }, { store: Assets });
|
|
@@ -135,10 +108,7 @@ If omitted, files are written to the default global, public MindStudio CDN.
|
|
|
135
108
|
|
|
136
109
|
## When public vs private
|
|
137
110
|
|
|
138
|
-
- **Private (default):** user uploads, generated docs, anything not world-readable. Reads are authed
|
|
139
|
-
|
|
140
|
-
- **Public:** marketing images, resizable media, config the frontend reads. Deliberate `access:
|
|
141
|
-
'public'`.
|
|
111
|
+
- **Private (default):** user uploads, generated docs, anything not world-readable. Reads are authed (the app session) or a short-lived `shareUrl`.
|
|
112
|
+
- **Public:** marketing images, resizable media, config the frontend reads. Deliberate `access: 'public'`.
|
|
142
113
|
|
|
143
|
-
Per-user access is the app's job — key files per user (`{userId}/…`) and hand each user only their own
|
|
144
|
-
URLs; the platform authorizes at the app level, not per file.
|
|
114
|
+
Per-user access is the app's job — key files per user (`{userId}/…`) and hand each user only their own URLs; the platform authorizes at the app level, not per file.
|
|
@@ -6,17 +6,13 @@ when: Before writing an email-handler method, adding an `email` interface, or pr
|
|
|
6
6
|
|
|
7
7
|
# Inbound Email Interfaces
|
|
8
8
|
|
|
9
|
-
Inbound email triggers. Each app has **one** email-handler method; the platform routes all inbound
|
|
10
|
-
mail destined for the app — across any of its address tiers — to that method.
|
|
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.
|
|
11
10
|
|
|
12
|
-
The addresses themselves are configured at the project level by the user through the Remy platform.
|
|
13
|
-
Your job is the `interface.json` and the method that handles the mail, not domain registration or MX
|
|
14
|
-
records.
|
|
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.
|
|
15
12
|
|
|
16
13
|
## Address tiers
|
|
17
14
|
|
|
18
|
-
Three tiers, all delivered to the same handler method. The new tiers are catchall (no localpart
|
|
19
|
-
registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
15
|
+
Three tiers, all delivered to the same handler method. The new tiers are catchall (no localpart registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
20
16
|
|
|
21
17
|
| Tier | Address | How it's set up |
|
|
22
18
|
|---|---|---|
|
|
@@ -24,13 +20,9 @@ registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
|
24
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. |
|
|
25
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. |
|
|
26
22
|
|
|
27
|
-
Because the new tiers are catchall, `to` carries an arbitrary localpart. Methods that need to branch on
|
|
28
|
-
it should read `input.to` (e.g. `if (input.to.startsWith('support@')) ...`). This is what makes
|
|
29
|
-
per-purpose addresses free: you don't register `support@` anywhere, you just check for it.
|
|
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.
|
|
30
24
|
|
|
31
|
-
A verified custom domain (and the app's `madewithremy.com` subdomain) also **sends** outbound mail, not
|
|
32
|
-
just receives — `sendEmail` picks the app's own-brand sender automatically, configured in the
|
|
33
|
-
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, configured in the dashboard's **Email** settings.
|
|
34
26
|
|
|
35
27
|
## Config (`interface.json`)
|
|
36
28
|
|
|
@@ -45,10 +37,7 @@ The top-level key must match the interface type (`email`):
|
|
|
45
37
|
}
|
|
46
38
|
```
|
|
47
39
|
|
|
48
|
-
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com`
|
|
49
|
-
wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before
|
|
50
|
-
the method runs (silently — the sender isn't bounced). Matching is case-insensitive. The same list
|
|
51
|
-
applies uniformly across all three address tiers.
|
|
40
|
+
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com` wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before the method runs (silently — the sender isn't bounced). Matching is case-insensitive. The same list applies uniformly across all three address tiers.
|
|
52
41
|
|
|
53
42
|
Declare it in `mindstudio.json`:
|
|
54
43
|
|
|
@@ -78,9 +67,7 @@ Declare it in `mindstudio.json`:
|
|
|
78
67
|
|
|
79
68
|
## Replying in thread
|
|
80
69
|
|
|
81
|
-
Replies go out through the SDK's `sendEmail` action (its full parameter list is in the SDK actions
|
|
82
|
-
reference in your system prompt). Set `inReplyTo` to the incoming `messageId` and `references` to
|
|
83
|
-
`[...references, messageId]`. Send to `replyTo` when it's set, otherwise `from`.
|
|
70
|
+
Replies go out through the SDK's `sendEmail` action (its full parameter list is in the SDK actions reference in your system prompt). Set `inReplyTo` to the incoming `messageId` and `references` to `[...references, messageId]`. Send to `replyTo` when it's set, otherwise `from`.
|
|
84
71
|
|
|
85
72
|
```typescript
|
|
86
73
|
await mindstudio.sendEmail({
|
|
@@ -95,22 +82,14 @@ await mindstudio.sendEmail({
|
|
|
95
82
|
});
|
|
96
83
|
```
|
|
97
84
|
|
|
98
|
-
`sendEmail` returns `{ recipients, cc, bcc, from }` — who it sent to and the sender used. It does
|
|
99
|
-
**not** return the sent message's own `Message-ID`, so thread off *inbound* mail, never off messages
|
|
100
|
-
you sent.
|
|
85
|
+
`sendEmail` returns `{ recipients, cc, bcc, from }` — who it sent to and the sender used. It does **not** return the sent message's own `Message-ID`, so thread off *inbound* mail, never off messages you sent.
|
|
101
86
|
|
|
102
87
|
## Attachments and size limits
|
|
103
88
|
|
|
104
|
-
`attachments[]` is an array of CDN URLs — the platform has already received and uploaded the files.
|
|
105
|
-
Fetch them server-side via the URL when you need the bytes; pass them through as URLs to UI or
|
|
106
|
-
downstream services.
|
|
89
|
+
`attachments[]` is an array of CDN URLs — the platform has already received and uploaded the files. Fetch them server-side via the URL when you need the bytes; pass them through as URLs to UI or downstream services.
|
|
107
90
|
|
|
108
|
-
Max inbound message size is 25 MB total (including all attachments). Oversized messages are rejected by
|
|
109
|
-
the platform before the method runs.
|
|
91
|
+
Max inbound message size is 25 MB total (including all attachments). Oversized messages are rejected by the platform before the method runs.
|
|
110
92
|
|
|
111
93
|
## Auth
|
|
112
94
|
|
|
113
|
-
Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not
|
|
114
|
-
a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that
|
|
115
|
-
should only be reachable via email. The auth reference in your system prompt covers the system role in
|
|
116
|
-
full.
|
|
95
|
+
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 via email. The auth reference in your system prompt covers the system role in full.
|
|
@@ -6,10 +6,7 @@ when: Before authoring `src/interfaces/mcp.md`, deciding which of the app's meth
|
|
|
6
6
|
|
|
7
7
|
# MCP Interfaces
|
|
8
8
|
|
|
9
|
-
Exposing an app as an MCP server — a tool / resource / prompt surface for *external* AI agents (Claude
|
|
10
|
-
Desktop, Cursor, anyone's agent). Unlike the agent interface, which *is* an agent with its own LLM,
|
|
11
|
-
personality and chat UI, MCP has no model of its own: it's the app projected as a server for an outside
|
|
12
|
-
AI to drive.
|
|
9
|
+
Exposing an app as an MCP server — a tool / resource / prompt surface for *external* AI agents (Claude Desktop, Cursor, anyone's agent). Unlike the agent interface, which *is* an agent with its own LLM, personality and chat UI, MCP has no model of its own: it's the app projected as a server for an outside AI to drive.
|
|
13
10
|
|
|
14
11
|
It supports the full MCP surface:
|
|
15
12
|
|
|
@@ -18,18 +15,11 @@ It supports the full MCP surface:
|
|
|
18
15
|
- **Prompts** — reusable, parameterized prompt templates the server offers.
|
|
19
16
|
- **Instructions** — server-level guidance shown to the calling agent (the toolset's "system prompt").
|
|
20
17
|
|
|
21
|
-
The platform hosts the server, handles auth, and derives every tool's input schema from the method
|
|
22
|
-
contract. So there's no protocol code to write, and the whole job is authorship plus a config file.
|
|
23
|
-
Authorship first, since that's what decides whether the toolset actually works.
|
|
18
|
+
The platform hosts the server, handles auth, and derives every tool's input schema from the method contract. So there's no protocol code to write, and the whole job is authorship plus a config file. Authorship first, since that's what decides whether the toolset actually works.
|
|
24
19
|
|
|
25
20
|
## The descriptions are the product
|
|
26
21
|
|
|
27
|
-
The calling agent is a stranger with no knowledge of your app. It decides what to invoke entirely from
|
|
28
|
-
the names, descriptions, and annotations you ship. Follow the same principles as the agent interface's
|
|
29
|
-
tool descriptions (load the `agentInterfaces` skill for those — when to use and when not, parameter
|
|
30
|
-
guidance beyond the schema, what the tool returns) — but write them **self-contained**. An in-app agent
|
|
31
|
-
tool can lean on the app's framing; an MCP tool can't, because the caller has no context. Spell out what
|
|
32
|
-
an outsider wouldn't know.
|
|
22
|
+
The calling agent is a stranger with no knowledge of your app. It decides what to invoke entirely from the names, descriptions, and annotations you ship. Follow the same principles as the agent interface's tool descriptions (load the `agentInterfaces` skill for those — when to use and when not, parameter guidance beyond the schema, what the tool returns) — but write them **self-contained**. An in-app agent tool can lean on the app's framing; an MCP tool can't, because the caller has no context. Spell out what an outsider wouldn't know.
|
|
33
23
|
|
|
34
24
|
What that looks like in practice — the same method, described twice:
|
|
35
25
|
|
|
@@ -47,32 +37,22 @@ full updated vendor. Editors and admins only; other roles are rejected. For a
|
|
|
47
37
|
vendor that doesn't exist yet, use `createVendor`.
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
The weak one is what a schema already tells the caller. The strong one carries the three things a schema
|
|
51
|
-
can't: the prerequisite, the partial-update semantics, and the alternative when this isn't the right
|
|
52
|
-
tool.
|
|
40
|
+
The weak one is what a schema already tells the caller. The strong one carries the three things a schema can't: the prerequisite, the partial-update semantics, and the alternative when this isn't the right tool.
|
|
53
41
|
|
|
54
42
|
## Curate — not every method is a tool
|
|
55
43
|
|
|
56
|
-
Expose what an outside agent would actually use. Skip internal helpers, admin-only methods, and batch
|
|
57
|
-
operations. A focused set of well-described tools beats a large set of thin ones. Note role restrictions
|
|
58
|
-
in the description — gated tools are listed but reject unauthorized calls at runtime, so set
|
|
59
|
-
expectations rather than surfacing a raw error.
|
|
44
|
+
Expose what an outside agent would actually use. Skip internal helpers, admin-only methods, and batch operations. A focused set of well-described tools beats a large set of thin ones. Note role restrictions in the description — gated tools are listed but reject unauthorized calls at runtime, so set expectations rather than surfacing a raw error.
|
|
60
45
|
|
|
61
46
|
## Annotations
|
|
62
47
|
|
|
63
|
-
Annotations are machine-readable hints clients use to decide whether to auto-call a tool or ask the user
|
|
64
|
-
first. Set them honestly:
|
|
48
|
+
Annotations are machine-readable hints clients use to decide whether to auto-call a tool or ask the user first. Set them honestly:
|
|
65
49
|
|
|
66
|
-
- `readOnly` — the tool only reads, never mutates. The highest-value hint: clients auto-call reads
|
|
67
|
-
without prompting, so set it on every pure read.
|
|
50
|
+
- `readOnly` — the tool only reads, never mutates. The highest-value hint: clients auto-call reads without prompting, so set it on every pure read.
|
|
68
51
|
- `destructive` — the tool can delete or overwrite. Clients gate these behind confirmation.
|
|
69
52
|
- `idempotent` — calling twice with the same arguments has the same effect as calling once.
|
|
70
|
-
- `openWorld` — the tool reaches outside the app (external web/services) rather than operating only on
|
|
71
|
-
app data.
|
|
53
|
+
- `openWorld` — the tool reaches outside the app (external web/services) rather than operating only on app data.
|
|
72
54
|
|
|
73
|
-
The judgement is per tool, and getting `readOnly` right is what makes a toolset feel responsive rather
|
|
74
|
-
than nagging. Abbreviated to just the annotations (a real entry also carries `name`, `title` and
|
|
75
|
-
`description` — the Config section has the full shape):
|
|
55
|
+
The judgement is per tool, and getting `readOnly` right is what makes a toolset feel responsive rather than nagging. Abbreviated to just the annotations (a real entry also carries `name`, `title` and `description` — the Config section has the full shape):
|
|
76
56
|
|
|
77
57
|
```jsonc
|
|
78
58
|
"tools": [
|
|
@@ -84,32 +64,21 @@ than nagging. Abbreviated to just the annotations (a real entry also carries `na
|
|
|
84
64
|
]
|
|
85
65
|
```
|
|
86
66
|
|
|
87
|
-
Set them honestly rather than defensively. Marking a read `destructive` to be safe means the caller's
|
|
88
|
-
user gets a confirmation prompt for looking something up, and they will stop reading the prompts.
|
|
67
|
+
Set them honestly rather than defensively. Marking a read `destructive` to be safe means the caller's user gets a confirmation prompt for looking something up, and they will stop reading the prompts.
|
|
89
68
|
|
|
90
69
|
## Tools vs. resources
|
|
91
70
|
|
|
92
|
-
A **tool** is an action the agent *invokes*; a **resource** is data the agent *reads into context*. A
|
|
93
|
-
read-only method can be either — expose it as a tool if the agent will call it as a step, as a resource
|
|
94
|
-
if it's reference data the agent should pull in, and as both when both fit.
|
|
71
|
+
A **tool** is an action the agent *invokes*; a **resource** is data the agent *reads into context*. A read-only method can be either — expose it as a tool if the agent will call it as a step, as a resource if it's reference data the agent should pull in, and as both when both fit.
|
|
95
72
|
|
|
96
|
-
Resources are method-backed: a read invokes the method. Use a static `uri` for a fixed collection
|
|
97
|
-
(`app://vendors`) and a `uriTemplate` when the read takes parameters (`app://vendors/{id}`, where `{id}`
|
|
98
|
-
maps to the method's input). Keep URIs stable and human-legible.
|
|
73
|
+
Resources are method-backed: a read invokes the method. Use a static `uri` for a fixed collection (`app://vendors`) and a `uriTemplate` when the read takes parameters (`app://vendors/{id}`, where `{id}` maps to the method's input). Keep URIs stable and human-legible.
|
|
99
74
|
|
|
100
75
|
## Prompts
|
|
101
76
|
|
|
102
|
-
Prompts are reusable, parameterized templates the server offers to clients — e.g. a "draft a vendor
|
|
103
|
-
email" starter. Author the template body with `{{arg}}` placeholders and declare its arguments. Offer a
|
|
104
|
-
prompt when there's a recurring task worth packaging; skip it if a tool already covers the need.
|
|
77
|
+
Prompts are reusable, parameterized templates the server offers to clients — e.g. a "draft a vendor email" starter. Author the template body with `{{arg}}` placeholders and declare its arguments. Offer a prompt when there's a recurring task worth packaging; skip it if a tool already covers the need.
|
|
105
78
|
|
|
106
79
|
## Server instructions
|
|
107
80
|
|
|
108
|
-
The spec's intro prose becomes the server `instructions` — toolset-level guidance returned to the
|
|
109
|
-
calling agent at connect time (its "system prompt"). Put *cross-cutting* guidance here: how the tools
|
|
110
|
-
fit together, ordering or prerequisites ("read a vendor before updating it"), and norms that apply
|
|
111
|
-
across the whole toolset. Keep per-tool specifics in the tool descriptions; instructions are for the
|
|
112
|
-
toolset as a whole.
|
|
81
|
+
The spec's intro prose becomes the server `instructions` — toolset-level guidance returned to the calling agent at connect time (its "system prompt"). Put *cross-cutting* guidance here: how the tools fit together, ordering or prerequisites ("read a vendor before updating it"), and norms that apply across the whole toolset. Keep per-tool specifics in the tool descriptions; instructions are for the toolset as a whole.
|
|
113
82
|
|
|
114
83
|
```markdown
|
|
115
84
|
This server exposes a procurement app. Vendors are the central record and
|
|
@@ -119,8 +88,7 @@ search tool before calling anything that takes one. Search results are capped
|
|
|
119
88
|
at 50; page with the returned cursor rather than broadening the query.
|
|
120
89
|
```
|
|
121
90
|
|
|
122
|
-
That's four sentences doing what no individual tool description could: it explains the shape of the
|
|
123
|
-
domain, so the calling agent's first move is a reasonable one.
|
|
91
|
+
That's four sentences doing what no individual tool description could: it explains the shape of the domain, so the calling agent's first move is a reasonable one.
|
|
124
92
|
|
|
125
93
|
---
|
|
126
94
|
|
|
@@ -128,8 +96,7 @@ domain, so the calling agent's first move is a reasonable one.
|
|
|
128
96
|
|
|
129
97
|
## Spec: `src/interfaces/mcp.md`
|
|
130
98
|
|
|
131
|
-
Frontmatter declares the server. In the body, the intro prose becomes the server `instructions`, and
|
|
132
|
-
`## Tools`, `## Resources` and `## Prompts` headings declare the rest.
|
|
99
|
+
Frontmatter declares the server. In the body, the intro prose becomes the server `instructions`, and `## Tools`, `## Resources` and `## Prompts` headings declare the rest.
|
|
133
100
|
|
|
134
101
|
```yaml
|
|
135
102
|
---
|
|
@@ -252,8 +219,7 @@ The top-level key must match the interface type (`mcp`):
|
|
|
252
219
|
| `prompts[].arguments` | `[{ name, description?, required? }]` |
|
|
253
220
|
| `prompts[].template` | Relative path to the template body (`{{arg}}` placeholders) |
|
|
254
221
|
|
|
255
|
-
There is no `inputSchema` field — the platform derives each tool's schema from the method's input
|
|
256
|
-
contract.
|
|
222
|
+
There is no `inputSchema` field — the platform derives each tool's schema from the method's input contract.
|
|
257
223
|
|
|
258
224
|
Declare it in `mindstudio.json`:
|
|
259
225
|
|
|
@@ -263,18 +229,10 @@ Declare it in `mindstudio.json`:
|
|
|
263
229
|
|
|
264
230
|
## Platform Behavior
|
|
265
231
|
|
|
266
|
-
- The platform hosts the MCP server and exposes it to external clients. Clients connect at
|
|
267
|
-
|
|
268
|
-
`custom_subdomain` host (e.g. `myapp.madewithremy.com`), a custom domain if configured, or the UUID
|
|
269
|
-
host (`<appId>.madewithremy.com` / `.msagent.ai`).
|
|
270
|
-
- **Auth is optional.** A `Bearer` key resolves to a user with full RBAC, so the method's own
|
|
271
|
-
`auth.requireRole(...)`/`hasRole(...)` checks apply as they would for that user. With no key, calls run
|
|
272
|
-
anonymously — no user, no roles. The method is the boundary: gate sensitive tools, and understand that
|
|
273
|
-
a public (keyless) server effectively exposes only the un-gated ones.
|
|
232
|
+
- The platform hosts the MCP server and exposes it to external clients. Clients connect at `POST https://{app-host}/_/mcp`, where `{app-host}` is any host the app is served on: its `custom_subdomain` host (e.g. `myapp.madewithremy.com`), a custom domain if configured, or the UUID host (`<appId>.madewithremy.com` / `.msagent.ai`).
|
|
233
|
+
- **Auth is optional.** A `Bearer` key resolves to a user with full RBAC, so the method's own `auth.requireRole(...)`/`hasRole(...)` checks apply as they would for that user. With no key, calls run anonymously — no user, no roles. The method is the boundary: gate sensitive tools, and understand that a public (keyless) server effectively exposes only the un-gated ones.
|
|
274
234
|
- Input schemas are derived automatically from each method's input contract.
|
|
275
|
-
- `tools/list` is static; access is enforced per-method at call time (a gated tool is listed but rejects
|
|
276
|
-
|
|
277
|
-
- A resource read invokes the backing method (template `{param}`s come from the URI) and returns its
|
|
278
|
-
output as the resource contents.
|
|
235
|
+
- `tools/list` is static; access is enforced per-method at call time (a gated tool is listed but rejects an unauthorized call).
|
|
236
|
+
- A resource read invokes the backing method (template `{param}`s come from the URI) and returns its output as the resource contents.
|
|
279
237
|
- `prompts/get` fills the template with the provided arguments.
|
|
280
238
|
- `instructions` is returned in the `initialize` response.
|
|
@@ -6,24 +6,15 @@ when: Before authoring `src/interfaces/api.md` or adding an `api` interface with
|
|
|
6
6
|
|
|
7
7
|
# REST API Interfaces
|
|
8
8
|
|
|
9
|
-
REST endpoints for external consumers — other services, mobile apps, integrations. This is separate
|
|
10
|
-
from the web frontend's internal RPC (`@mindstudio-ai/interface` calls `/_/methods` directly and does
|
|
11
|
-
not use the API interface). The API interface lives at `/_/api/` and exposes only the methods you
|
|
12
|
-
choose to route.
|
|
9
|
+
REST endpoints for external consumers — other services, mobile apps, integrations. This is separate from the web frontend's internal RPC (`@mindstudio-ai/interface` calls `/_/methods` directly and does not use the API interface). The API interface lives at `/_/api/` and exposes only the methods you choose to route.
|
|
13
10
|
|
|
14
|
-
Use it for sync endpoints for other services, a public REST API, batch tools — anything where
|
|
15
|
-
something outside the app's own frontend needs to call a method over HTTP.
|
|
11
|
+
Use it for sync endpoints for other services, a public REST API, batch tools — anything where something outside the app's own frontend needs to call a method over HTTP.
|
|
16
12
|
|
|
17
|
-
**For provider webhooks (Stripe, GitHub, Shopify) there are two native paths**, and they are easy to
|
|
18
|
-
confuse. This interface handles them with bearer auth and `input._request.rawBody`. The Webhook
|
|
19
|
-
interface handles them with secret-in-URL routing and a top-level `input.rawBody` — usually the better
|
|
20
|
-
fit for provider callbacks, since providers can't send a bearer token. Load the `webhooks` skill before
|
|
21
|
-
choosing.
|
|
13
|
+
**For provider webhooks (Stripe, GitHub, Shopify) there are two native paths**, and they are easy to confuse. This interface handles them with bearer auth and `input._request.rawBody`. The Webhook interface handles them with secret-in-URL routing and a top-level `input.rawBody` — usually the better fit for provider callbacks, since providers can't send a bearer token. Load the `webhooks` skill before choosing.
|
|
22
14
|
|
|
23
15
|
## Spec: `src/interfaces/api.md`
|
|
24
16
|
|
|
25
|
-
The human-readable spec. Frontmatter declares the API name and description; the body maps methods to
|
|
26
|
-
REST routes using MSFM.
|
|
17
|
+
The human-readable spec. Frontmatter declares the API name and description; the body maps methods to REST routes using MSFM.
|
|
27
18
|
|
|
28
19
|
```yaml
|
|
29
20
|
---
|
|
@@ -33,8 +24,7 @@ type: interface/api
|
|
|
33
24
|
---
|
|
34
25
|
```
|
|
35
26
|
|
|
36
|
-
Routes are declared as `VERB /path → methodExportName` under resource headings, with annotations for
|
|
37
|
-
params and descriptions:
|
|
27
|
+
Routes are declared as `VERB /path → methodExportName` under resource headings, with annotations for params and descriptions:
|
|
38
28
|
|
|
39
29
|
```markdown
|
|
40
30
|
## Vendors
|
|
@@ -140,10 +130,6 @@ Routes are mounted at `/_/api{path}` (e.g. `DELETE /_/api/vendors/abc123`).
|
|
|
140
130
|
- **Query params** are merged into input for GET requests: `?status=approved` → `{ status: "approved" }`
|
|
141
131
|
- **Request body** for POST/PUT/PATCH is the input directly (no `{ input: {...} }` wrapper)
|
|
142
132
|
- **Response** is the method output directly (no `{ output: {...} }` wrapper)
|
|
143
|
-
- **Auth** via `Authorization: Bearer sk_...` — an API key resolves to a user with full RBAC, so the
|
|
144
|
-
method's own `auth.requireRole(...)`/`hasRole(...)` checks apply exactly as they would for that user
|
|
133
|
+
- **Auth** via `Authorization: Bearer sk_...` — an API key resolves to a user with full RBAC, so the method's own `auth.requireRole(...)`/`hasRole(...)` checks apply exactly as they would for that user
|
|
145
134
|
- **Streaming**: `Accept: text/event-stream` header returns SSE chunks
|
|
146
|
-
- **Raw request context**: Every API method receives `input._request` with `{ method, headers, rawBody }`.
|
|
147
|
-
`rawBody` is the original unparsed body as a UTF-8 string — needed for signature verification, since
|
|
148
|
-
providers HMAC the raw payload and a re-serialized body won't match. For most methods you don't need
|
|
149
|
-
`_request` at all.
|
|
135
|
+
- **Raw request context**: Every API method receives `input._request` with `{ method, headers, rawBody }`. `rawBody` is the original unparsed body as a UTF-8 string — needed for signature verification, since providers HMAC the raw payload and a re-serialized body won't match. For most methods you don't need `_request` at all.
|
|
@@ -112,19 +112,15 @@ Scenarios are useful for seeding initial app state after build for testing, as w
|
|
|
112
112
|
|
|
113
113
|
## What scenarios don't touch
|
|
114
114
|
|
|
115
|
-
**Scenarios seed database tables and nothing else.** They do not touch file stores or data sources —
|
|
116
|
-
deliberately: both are durable and shared across dev and prod, with no per-release copy to reset, so
|
|
117
|
-
there is nothing to truncate.
|
|
115
|
+
**Scenarios seed database tables and nothing else.** They do not touch file stores or data sources — deliberately: both are durable and shared across dev and prod, with no per-release copy to reset, so there is nothing to truncate.
|
|
118
116
|
|
|
119
|
-
Don't try to seed documents into a data source from a scenario, and don't write `clear()`-style reset
|
|
120
|
-
helpers for one. Load a test corpus once from the CLI instead:
|
|
117
|
+
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:
|
|
121
118
|
|
|
122
119
|
```bash
|
|
123
120
|
mindstudio-prod datasources add --source policies --wait fixtures/*.pdf
|
|
124
121
|
```
|
|
125
122
|
|
|
126
|
-
Re-running it is free (documents are content-addressed), so it's safe to keep in a setup script
|
|
127
|
-
beside your scenarios.
|
|
123
|
+
Re-running it is free (documents are content-addressed), so it's safe to keep in a setup script beside your scenarios.
|
|
128
124
|
|
|
129
125
|
## Scenario Data
|
|
130
126
|
|
|
@@ -31,8 +31,7 @@ The top-level key must match the interface type (`cron`):
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Standard cron expression format. `method` is the id of a method in `methods[]`. Jobs are synced to the
|
|
35
|
-
platform on deploy.
|
|
34
|
+
Standard cron expression format. `method` is the id of a method in `methods[]`. Jobs are synced to the platform on deploy.
|
|
36
35
|
|
|
37
36
|
Declare it in `mindstudio.json`:
|
|
38
37
|
|
|
@@ -42,10 +41,6 @@ Declare it in `mindstudio.json`:
|
|
|
42
41
|
|
|
43
42
|
## Auth
|
|
44
43
|
|
|
45
|
-
Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not
|
|
46
|
-
a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that
|
|
47
|
-
should only be reachable on a schedule. The auth reference in your system prompt covers the system role
|
|
48
|
-
in full.
|
|
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.
|
|
49
45
|
|
|
50
|
-
A scheduled job that needs to act on user data acts as the system, not as any user, so it reaches
|
|
51
|
-
everything. Scope what it touches in the method itself rather than relying on role checks to narrow it.
|
|
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.
|