@mindstudio-ai/remy 0.1.0
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/README.md +314 -0
- package/dist/actions/publish.md +12 -0
- package/dist/actions/sync.md +19 -0
- package/dist/compiled/README.md +100 -0
- package/dist/compiled/auth.md +77 -0
- package/dist/compiled/design.md +173 -0
- package/dist/compiled/dev-and-deploy.md +69 -0
- package/dist/compiled/interfaces.md +238 -0
- package/dist/compiled/manifest.md +107 -0
- package/dist/compiled/media-cdn.md +51 -0
- package/dist/compiled/methods.md +225 -0
- package/dist/compiled/msfm.md +133 -0
- package/dist/compiled/platform.md +101 -0
- package/dist/compiled/scenarios.md +103 -0
- package/dist/compiled/sdk-actions.md +152 -0
- package/dist/compiled/tables.md +192 -0
- package/dist/headless.d.ts +16 -0
- package/dist/headless.js +2515 -0
- package/dist/index.js +3164 -0
- package/dist/static/authoring.md +53 -0
- package/dist/static/identity.md +1 -0
- package/dist/static/instructions.md +21 -0
- package/dist/static/intake.md +44 -0
- package/dist/static/lsp.md +4 -0
- package/dist/static/projectContext.ts +155 -0
- package/package.json +52 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Methods
|
|
2
|
+
|
|
3
|
+
A method is a named async function that runs on the platform. It's the universal unit of backend logic — every interface (web, API, Discord, cron, webhook) invokes methods. Methods run in isolated sandboxes. One file per method, one named export.
|
|
4
|
+
|
|
5
|
+
## Writing a Method
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// dist/methods/src/submitVendorRequest.ts
|
|
9
|
+
|
|
10
|
+
import { db, auth } from '@mindstudio-ai/agent';
|
|
11
|
+
import { Vendors } from './tables/vendors';
|
|
12
|
+
|
|
13
|
+
export async function submitVendorRequest(input: {
|
|
14
|
+
name: string;
|
|
15
|
+
contactEmail: string;
|
|
16
|
+
taxId: string;
|
|
17
|
+
}) {
|
|
18
|
+
auth.requireRole('requester');
|
|
19
|
+
|
|
20
|
+
const vendor = await Vendors.push({
|
|
21
|
+
name: input.name,
|
|
22
|
+
contactEmail: input.contactEmail,
|
|
23
|
+
taxId: input.taxId,
|
|
24
|
+
status: 'pending',
|
|
25
|
+
requestedBy: auth.userId,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return { vendorId: vendor.id, status: vendor.status };
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Manifest Entry
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"id": "submit-vendor-request",
|
|
37
|
+
"name": "Submit Vendor Request",
|
|
38
|
+
"path": "dist/methods/src/submitVendorRequest.ts",
|
|
39
|
+
"export": "submitVendorRequest"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `id` — kebab-case, used in API URLs (the platform maps these internally)
|
|
44
|
+
- **Important:** the frontend `createClient` uses the camelCase `export` name, not the kebab-case `id`. Call `api.submitVendorRequest()`, not `api['submit-vendor-request']()`.
|
|
45
|
+
- `path` — relative to project root
|
|
46
|
+
- `export` — must match the function name
|
|
47
|
+
|
|
48
|
+
### Input and Output
|
|
49
|
+
|
|
50
|
+
Methods receive a single `input` parameter (an object) and return an object. Both are JSON-serializable. If no input is needed, the parameter can be omitted or typed as `{}`.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export async function getDashboard(input: {
|
|
54
|
+
period?: 'week' | 'month' | 'quarter';
|
|
55
|
+
}) {
|
|
56
|
+
const period = input.period || 'month';
|
|
57
|
+
// ...
|
|
58
|
+
return {
|
|
59
|
+
pendingApprovals,
|
|
60
|
+
recentOrders,
|
|
61
|
+
stats: { totalSpend, vendorCount },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Platform Capabilities
|
|
67
|
+
|
|
68
|
+
The `@mindstudio-ai/agent` SDK provides access to 200+ AI models and 1,000+ actions (email, SMS, web scraping, file uploads, third-party integrations, and more). Inside a method, create an instance and call actions directly. No constructor arguments needed — credentials are picked up automatically from the execution environment:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { MindStudioAgent } from '@mindstudio-ai/agent';
|
|
72
|
+
|
|
73
|
+
const agent = new MindStudioAgent();
|
|
74
|
+
|
|
75
|
+
// AI text generation
|
|
76
|
+
const { content } = await agent.generateText({
|
|
77
|
+
message: 'Summarize this invoice...',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// AI image generation
|
|
81
|
+
const { imageUrl } = await agent.generateImage({
|
|
82
|
+
prompt: 'A professional headshot placeholder',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Send email
|
|
86
|
+
await agent.sendEmail({
|
|
87
|
+
to: 'user@example.com',
|
|
88
|
+
subject: 'Your invoice',
|
|
89
|
+
body: content,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Upload files
|
|
93
|
+
const { url } = await agent.uploadFile({
|
|
94
|
+
data: buffer,
|
|
95
|
+
fileName: 'report.pdf',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Web scraping
|
|
99
|
+
const { markdown } = await agent.scrapeUrl({
|
|
100
|
+
url: 'https://example.com',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Resolve user display info
|
|
104
|
+
const { displayName, email } = await agent.resolveUser({
|
|
105
|
+
userId,
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
No separate API keys needed — the platform routes to the correct provider (OpenAI, Anthropic, Google, etc.) automatically.
|
|
110
|
+
|
|
111
|
+
## Error Handling
|
|
112
|
+
|
|
113
|
+
Throw errors with messages that make sense to end users — these may surface in the UI:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
export async function approveVendor(input: { vendorId: string }) {
|
|
117
|
+
auth.requireRole('admin', 'grc');
|
|
118
|
+
|
|
119
|
+
const vendor = await Vendors.get(input.vendorId);
|
|
120
|
+
if (!vendor) {
|
|
121
|
+
throw new Error('Vendor not found.');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (vendor.status !== 'pending') {
|
|
125
|
+
throw new Error('This vendor has already been reviewed.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ...
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`auth.requireRole()` throws a 403 automatically if the user doesn't have the required role.
|
|
133
|
+
|
|
134
|
+
## Common Patterns
|
|
135
|
+
|
|
136
|
+
### CRUD Method
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
export async function listVendors(input: {
|
|
140
|
+
status?: string;
|
|
141
|
+
search?: string;
|
|
142
|
+
}) {
|
|
143
|
+
const vendors = await Vendors
|
|
144
|
+
.filter(v => {
|
|
145
|
+
if (input.status && v.status !== input.status) return false;
|
|
146
|
+
if (input.search && !v.name.includes(input.search)) return false;
|
|
147
|
+
return true;
|
|
148
|
+
})
|
|
149
|
+
.sortBy(v => v.name);
|
|
150
|
+
|
|
151
|
+
return { vendors };
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Role-Gated Operation
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
export async function deleteVendor(input: { vendorId: string }) {
|
|
159
|
+
auth.requireRole('admin');
|
|
160
|
+
|
|
161
|
+
const vendor = await Vendors.get(input.vendorId);
|
|
162
|
+
if (!vendor) throw new Error('Vendor not found.');
|
|
163
|
+
|
|
164
|
+
await Vendors.remove(input.vendorId);
|
|
165
|
+
return { success: true };
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Multi-Table Transaction
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
export async function createPurchaseOrder(input: {
|
|
173
|
+
vendorId: string;
|
|
174
|
+
lineItems: Array<{ description: string; amount: number }>;
|
|
175
|
+
}) {
|
|
176
|
+
auth.requireRole('requester');
|
|
177
|
+
|
|
178
|
+
const vendor = await Vendors.get(input.vendorId);
|
|
179
|
+
if (!vendor || vendor.status !== 'approved') {
|
|
180
|
+
throw new Error('Vendor must be approved before creating a PO.');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const total = input.lineItems.reduce((sum, li) => sum + li.amount, 0);
|
|
184
|
+
|
|
185
|
+
const po = await PurchaseOrders.push({
|
|
186
|
+
vendorId: input.vendorId,
|
|
187
|
+
requestedBy: auth.userId,
|
|
188
|
+
lineItems: input.lineItems,
|
|
189
|
+
totalAmountCents: total,
|
|
190
|
+
status: 'pending_approval',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return { purchaseOrderId: po.id, total };
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Shared Helpers
|
|
198
|
+
|
|
199
|
+
Code shared between methods goes in `dist/methods/src/common/`. Helpers are not listed in the manifest — they're internal, imported by methods but not directly invocable.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// dist/methods/src/common/getApprovalState.ts
|
|
203
|
+
export function getApprovalState(approvals: Approval[]) {
|
|
204
|
+
const allApproved = approvals.every(a => a.status === 'approved');
|
|
205
|
+
const anyRejected = approvals.some(a => a.status === 'rejected');
|
|
206
|
+
// ...
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Streaming
|
|
211
|
+
|
|
212
|
+
Methods can stream token-by-token output (useful for AI-generated content):
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Frontend
|
|
216
|
+
const result = await api.generateReport(
|
|
217
|
+
{ month: 'march' },
|
|
218
|
+
{
|
|
219
|
+
stream: true,
|
|
220
|
+
onToken: (text) => setPreview(text),
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The platform handles the SSE transport. The method returns normally — streaming is managed by the SDK and platform, not by your method code.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# MSFM — MindStudio-Flavored Markdown
|
|
2
|
+
|
|
3
|
+
Specs are written in MSFM, a strict superset of Markdown that adds **block annotations** and **inline annotations**. Annotations attach precision to prose so the compiler produces consistent results. A bare spec with no annotations is valid — each annotation makes compilation more deterministic.
|
|
4
|
+
|
|
5
|
+
## Block Annotations
|
|
6
|
+
|
|
7
|
+
A tilde-fenced block (`~~~`) that attaches to the content immediately above it:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
When requesting a new vendor, there are three areas of review:
|
|
11
|
+
governance, legal, and accounting, with approvals flowing in that order.
|
|
12
|
+
|
|
13
|
+
~~~
|
|
14
|
+
The spec says "three areas" but lists four names. From the process
|
|
15
|
+
flowchart, it is three sequential stages:
|
|
16
|
+
|
|
17
|
+
1. Governance, Risk & Compliance (GRC) — one combined stage
|
|
18
|
+
2. Legal
|
|
19
|
+
3. Accounts Payable (AP)
|
|
20
|
+
|
|
21
|
+
These are sequential. Each stage must complete before the next is
|
|
22
|
+
notified. If any stage rejects, the entire request is rejected.
|
|
23
|
+
~~~
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Rules:**
|
|
27
|
+
- Starts and ends with `~~~` on its own line
|
|
28
|
+
- Attaches to the nearest preceding block element (paragraph, heading, table, list)
|
|
29
|
+
- Multiple annotations can follow the same element (read in order)
|
|
30
|
+
- Can contain any Markdown content (use backtick code fences inside — they coexist with tilde fences)
|
|
31
|
+
|
|
32
|
+
## Inline Annotations
|
|
33
|
+
|
|
34
|
+
Attach to a specific word or phrase with `[text]{content}`:
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
All invoices can be sent to the [Accounts Payable]{The AP team in
|
|
38
|
+
this context refers to the internal accounts payable department, not
|
|
39
|
+
a vendor's AP. Only users with the "ap" or "admin" role can process
|
|
40
|
+
invoices.} team for processing against the PO.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Good for definitions, units, and clarifications:
|
|
44
|
+
|
|
45
|
+
```markdown
|
|
46
|
+
The PO [amount]{Total across all line items, in USD cents (integer).
|
|
47
|
+
Does not include tax, shipping, or fees.} must not exceed the approved
|
|
48
|
+
budget for the [cost center]{The organizational budget code that this
|
|
49
|
+
purchase is charged to.}.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Pointers
|
|
53
|
+
|
|
54
|
+
When an annotation is too large for inline placement, use a pointer — an inline `{#id}` reference that points to a `~~~#id` block:
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
The AP team pays the vendor according to the [contract payment terms]{#payment-terms}.
|
|
58
|
+
|
|
59
|
+
~~~#payment-terms
|
|
60
|
+
We do not model payment terms (net 30, net 60, etc.) on the PO or
|
|
61
|
+
vendor. The dueDate on the invoice is the sole payment trigger.
|
|
62
|
+
|
|
63
|
+
A production implementation would:
|
|
64
|
+
- Store terms on the vendor record (net_30, net_60, due_on_receipt)
|
|
65
|
+
- Auto-calculate dueDate from invoiceDate + terms
|
|
66
|
+
- Support early payment discounts (2/10 net 30)
|
|
67
|
+
|
|
68
|
+
Default behavior when no terms are specified: due on receipt.
|
|
69
|
+
~~~
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Keep the block co-located (right after the paragraph containing the pointer). A single block annotation can be referenced by multiple pointers — useful for concepts like "amounts are in USD cents" that apply in several places.
|
|
73
|
+
|
|
74
|
+
## Authoring Conventions
|
|
75
|
+
|
|
76
|
+
**Specs are written in human language for humans.** The prose should describe what the app does the way you'd explain it to a colleague — not in code. No variable names, table names, column types, or function signatures in the prose. Technical details like column types, data representations, and implementation notes belong in annotations, where they serve as precision hints for the compiler without cluttering the readable spec.
|
|
77
|
+
|
|
78
|
+
Good: "The app remembers every greeting it generates, along with who it was for."
|
|
79
|
+
Bad: "One table: `greetings` with two columns: `name` (string) and `greeting` (string)."
|
|
80
|
+
|
|
81
|
+
The annotation for the good version might be: `~~~\nOne table. Columns: the person's name (string) and the generated greeting text (string).\n~~~`
|
|
82
|
+
|
|
83
|
+
**Use inline annotations liberally.** Inline annotations (`[text]{content}`) are the primary annotation tool — use them the way you'd use comments in Google Docs, attached to the specific word or phrase that needs clarification. When the prose says "the user submits a [request]{Must include vendor name, contact email, and tax ID. All fields required.} for review," the annotation is pinned to exactly the word that's ambiguous.
|
|
84
|
+
|
|
85
|
+
Block annotations (`~~~...~~~`) are for longer notes that don't attach to a single word — implementation notes, edge case lists, multi-line technical details. But most annotations should be inline. A spec where every annotation is a block annotation under a heading is under-annotated — it means the prose itself isn't being examined for ambiguity at the word level.
|
|
86
|
+
|
|
87
|
+
**Annotations support full markdown.** Use backticks for code and variable names, lists for enumerating options, code blocks for snippets. This keeps the prose clean while giving the compiler precise technical detail:
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
Each invoice has a [status]{One of: `pending_review`, `approved`, `rejected`, `paid`. Transitions:
|
|
91
|
+
- `pending_review` → `approved` or `rejected` (by AP)
|
|
92
|
+
- `approved` → `paid` (automatic on payment date)
|
|
93
|
+
- `rejected` → `pending_review` (if resubmitted)} that tracks where it is in the review process.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Annotate ambiguity, not the obvious.** If a statement has only one reasonable interpretation, leave it alone. Annotations resolve genuine ambiguity — places where two engineers might implement different things.
|
|
97
|
+
|
|
98
|
+
**Pin down edge cases.** The most valuable annotations answer "what happens when...":
|
|
99
|
+
- What happens when a reviewer rejects?
|
|
100
|
+
- What happens when the amount is exactly on the threshold?
|
|
101
|
+
- What happens when no user has the required role?
|
|
102
|
+
- What happens when this is called twice?
|
|
103
|
+
|
|
104
|
+
**Specify data when it matters.** When "amount" could mean integer cents or decimal dollars, annotate the representation. When "status" could be any string, list the valid values. These are perfect for inline annotations: `The PO [amount]{Total across all line items, in USD cents (integer). Does not include tax, shipping, or fees.} must not exceed the budget.`
|
|
105
|
+
|
|
106
|
+
**Let the spec breathe.** A spec with more annotation than prose is over-specified. Annotate the hard parts. Trust the compiler on the straightforward parts.
|
|
107
|
+
|
|
108
|
+
## Spec Structure
|
|
109
|
+
|
|
110
|
+
A spec starts with YAML frontmatter (`name`, `description`, `version`) followed by freeform Markdown. There's no mandated structure — use headings, prose, tables, lists, whatever makes sense for the domain.
|
|
111
|
+
|
|
112
|
+
```markdown
|
|
113
|
+
---
|
|
114
|
+
name: Expense Tracker
|
|
115
|
+
version: 1
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
# Expense Tracker
|
|
119
|
+
|
|
120
|
+
[High-level description]
|
|
121
|
+
|
|
122
|
+
~~~
|
|
123
|
+
[Clarifying annotations — roles, key constraints]
|
|
124
|
+
~~~
|
|
125
|
+
|
|
126
|
+
## [Domain Section]
|
|
127
|
+
|
|
128
|
+
[Prose describing the workflow]
|
|
129
|
+
|
|
130
|
+
~~~
|
|
131
|
+
[Edge cases, data representations, business rules]
|
|
132
|
+
~~~
|
|
133
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# MindStudio Platform
|
|
2
|
+
|
|
3
|
+
A MindStudio app has three layers: a **spec** (natural language in `src/`), a **backend contract** (methods, tables, roles in `dist/`), and **interfaces** (web, API, bots, cron, etc. — also in `dist/`). The spec is the source of truth; the code is a derivation.
|
|
4
|
+
|
|
5
|
+
`src/` is the authored source — natural language specs, brand guidelines, reference materials. No code. `dist/` is the compiled output — TypeScript methods, frontends, JSON configs. You can edit `dist/` directly, but `src/` is the reset point. Regenerate `dist/` from `src/` at any time.
|
|
6
|
+
|
|
7
|
+
## Directory Layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
my-app/
|
|
11
|
+
mindstudio.json ← manifest (declares everything)
|
|
12
|
+
|
|
13
|
+
src/ ← authored source (no code)
|
|
14
|
+
app.md backend spec (MSFM)
|
|
15
|
+
references/ supporting material (PDFs, notes, diagrams)
|
|
16
|
+
interfaces/
|
|
17
|
+
@brand/ shared brand identity
|
|
18
|
+
voice.md tone, terminology, error messages
|
|
19
|
+
visual.md colors, typography, components
|
|
20
|
+
assets/ logos, icons
|
|
21
|
+
web.md web UI spec
|
|
22
|
+
api.md API conventions
|
|
23
|
+
cron.md scheduled job descriptions
|
|
24
|
+
|
|
25
|
+
dist/ ← compiled output (code + config)
|
|
26
|
+
methods/ backend contract
|
|
27
|
+
src/
|
|
28
|
+
tables/ one file per table (TypeScript interfaces)
|
|
29
|
+
common/ shared helpers (imported by methods, not methods themselves)
|
|
30
|
+
*.ts one file per method (named async function export)
|
|
31
|
+
.scenarios/ seed data scripts (dev only, not deployed)
|
|
32
|
+
package.json backend dependencies
|
|
33
|
+
|
|
34
|
+
interfaces/ interface projections
|
|
35
|
+
web/ full project directory (Vite + React)
|
|
36
|
+
web.json dev server config
|
|
37
|
+
package.json
|
|
38
|
+
src/
|
|
39
|
+
api/interface.json REST API config
|
|
40
|
+
discord/interface.json Discord bot config
|
|
41
|
+
telegram/interface.json Telegram bot config
|
|
42
|
+
cron/interface.json cron config
|
|
43
|
+
webhook/interface.json webhook config
|
|
44
|
+
email/interface.json email config
|
|
45
|
+
mcp/interface.json MCP config
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What Goes Where
|
|
49
|
+
|
|
50
|
+
| What | Where | Notes |
|
|
51
|
+
|------|-------|-------|
|
|
52
|
+
| Method handlers | `dist/methods/src/*.ts` | One file per method, named export |
|
|
53
|
+
| Table definitions | `dist/methods/src/tables/*.ts` | One file per table |
|
|
54
|
+
| Shared helpers | `dist/methods/src/common/*.ts` | Imported by methods, not invokable directly |
|
|
55
|
+
| Scenarios | `dist/methods/.scenarios/*.ts` | Seed data for testing (not deployed) |
|
|
56
|
+
| Backend dependencies | `dist/methods/package.json` | Only declared packages are available at runtime |
|
|
57
|
+
| Web interface | `dist/interfaces/web/` | Full Vite + React project directory |
|
|
58
|
+
| Interface configs | `dist/interfaces/*/interface.json` | One per non-web interface type |
|
|
59
|
+
| Specs | `src/*.md` | Natural language, MSFM format |
|
|
60
|
+
| Brand identity | `src/interfaces/@brand/` | voice.md (tone, terminology), visual.md (colors, typography), assets/ |
|
|
61
|
+
| Reference material | `src/references/` | Context for the agent, not consumed by platform |
|
|
62
|
+
|
|
63
|
+
## The Two SDKs
|
|
64
|
+
|
|
65
|
+
**Backend: `@mindstudio-ai/agent`** — used inside methods. Provides `db` (database), `auth` (access control), and platform capabilities (AI, integrations, connectors). Pre-installed in the sandbox; must also be declared in `dist/methods/package.json`.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { db, auth } from '@mindstudio-ai/agent';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Frontend: `@mindstudio-ai/interface`** — used in the web interface. Typed RPC to backend methods.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { createClient } from '@mindstudio-ai/interface';
|
|
75
|
+
|
|
76
|
+
const api = createClient<{
|
|
77
|
+
approveVendor(input: { vendorId: string }): Promise<{ vendor: Vendor }>;
|
|
78
|
+
}>();
|
|
79
|
+
|
|
80
|
+
const { vendor } = await api.approveVendor({ vendorId: '...' });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## What the Platform Provides
|
|
84
|
+
|
|
85
|
+
- **Managed databases.** SQLite with typed schemas. Push a schema change and the platform diffs, migrates, and promotes atomically.
|
|
86
|
+
- **Built-in auth.** Define roles in the manifest, call `auth.requireRole('admin')` in methods. Platform handles sessions, tokens, user resolution.
|
|
87
|
+
- **Multiple interfaces, one codebase.** Web, API, Discord, Telegram, Cron, Webhook, Email, MCP — all invoke the same methods. Methods don't know which interface called them.
|
|
88
|
+
- **Sandboxed execution.** Methods run in isolated sandboxes with npm packages pre-installed.
|
|
89
|
+
- **Git-native deployment.** Push to default branch to deploy. Push to feature branch for preview. Rollback is a git revert.
|
|
90
|
+
|
|
91
|
+
## Minimum Viable App
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
my-app/
|
|
95
|
+
mindstudio.json
|
|
96
|
+
dist/methods/
|
|
97
|
+
src/hello.ts
|
|
98
|
+
package.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
One manifest, one method, one package.json. The method is accessible via API key.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Scenarios
|
|
2
|
+
|
|
3
|
+
Scenarios are seed scripts that set up the dev database into a specific state for testing. Instead of manually creating data through the app, run a scenario and get a repeatable starting point. A scenario is just an async function that uses the same `db.push()` calls as methods — no new API to learn.
|
|
4
|
+
|
|
5
|
+
## Defining Scenarios
|
|
6
|
+
|
|
7
|
+
In `mindstudio.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"scenarios": [
|
|
12
|
+
{
|
|
13
|
+
"id": "ap-overdue-invoices",
|
|
14
|
+
"name": "AP: Overdue Invoices",
|
|
15
|
+
"description": "AP user with two invoices past due date.",
|
|
16
|
+
"path": "dist/methods/.scenarios/apOverdueInvoices.ts",
|
|
17
|
+
"export": "apOverdueInvoices",
|
|
18
|
+
"roles": ["ap"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "empty-requester",
|
|
22
|
+
"name": "Empty Requester",
|
|
23
|
+
"description": "Brand new user, no data.",
|
|
24
|
+
"path": "dist/methods/.scenarios/emptyRequester.ts",
|
|
25
|
+
"export": "emptyRequester",
|
|
26
|
+
"roles": ["requester"]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Field | Description |
|
|
33
|
+
|-------|-------------|
|
|
34
|
+
| `id` | Kebab-case identifier |
|
|
35
|
+
| `name` | Display name (shown in dev panel) |
|
|
36
|
+
| `description` | What state this scenario creates |
|
|
37
|
+
| `path` | Path to the TypeScript file |
|
|
38
|
+
| `export` | Named export (the async function) |
|
|
39
|
+
| `roles` | Roles to impersonate after seeding |
|
|
40
|
+
|
|
41
|
+
## Writing a Scenario
|
|
42
|
+
|
|
43
|
+
Scenarios live at `dist/methods/.scenarios/` — inside the methods package scope. `@mindstudio-ai/agent` resolves from `dist/methods/node_modules/` and table imports are relative.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// dist/methods/.scenarios/apOverdueInvoices.ts
|
|
47
|
+
|
|
48
|
+
import { db } from '@mindstudio-ai/agent';
|
|
49
|
+
import { Vendors } from '../src/tables/vendors';
|
|
50
|
+
import { PurchaseOrders } from '../src/tables/purchase-orders';
|
|
51
|
+
import { Invoices } from '../src/tables/invoices';
|
|
52
|
+
|
|
53
|
+
export async function apOverdueInvoices() {
|
|
54
|
+
const vendor = await Vendors.push({
|
|
55
|
+
name: 'Acme Corp',
|
|
56
|
+
contactEmail: 'billing@acme.com',
|
|
57
|
+
status: 'approved',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const po = await PurchaseOrders.push({
|
|
61
|
+
vendorId: vendor.id,
|
|
62
|
+
requestedBy: 'user-requester-1',
|
|
63
|
+
totalAmountCents: 500000,
|
|
64
|
+
status: 'active',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await Invoices.push([
|
|
68
|
+
{
|
|
69
|
+
poId: po.id,
|
|
70
|
+
invoiceNumber: 'INV-001',
|
|
71
|
+
amountCents: 150000,
|
|
72
|
+
dueDate: db.ago(db.days(5)),
|
|
73
|
+
status: 'pending_review',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
poId: po.id,
|
|
77
|
+
invoiceNumber: 'INV-002',
|
|
78
|
+
amountCents: 100000,
|
|
79
|
+
dueDate: db.ago(db.days(2)),
|
|
80
|
+
status: 'approved',
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
An empty scenario is valid — it exists so you can switch to "clean slate" state:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
export async function emptyRequester() {
|
|
90
|
+
// No data — the truncate clears everything.
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Shared setup code can go in `dist/methods/.scenarios/_helpers/`.
|
|
95
|
+
|
|
96
|
+
## How Scenarios Run
|
|
97
|
+
|
|
98
|
+
When a scenario runs, the platform:
|
|
99
|
+
1. **Truncates** all tables (deletes all rows, preserves schema)
|
|
100
|
+
2. **Executes** the seed function (your `db.push()` calls populate the clean database)
|
|
101
|
+
3. **Impersonates** the roles from the scenario's `roles` field (the app renders from that user's perspective)
|
|
102
|
+
|
|
103
|
+
This is deterministic — same scenario always produces the same state.
|