@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.
@@ -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.