@mindstudio-ai/remy 0.1.250 → 0.1.252

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,95 @@
1
+ # Files & Storage
2
+
3
+ Per-app blob storage — the twin of `db` (`db` stores rows; `files` stores files: user uploads,
4
+ generated documents, images, marketing assets). **Private by default.** Files serve on the app's own
5
+ domain.
6
+
7
+ **File stores are always live — there is no dev copy.** Every `put`/`delete`/overwrite hits
8
+ production storage immediately and irreversibly. And unlike the database, **scenarios never reset file
9
+ stores** — a scenario truncates DB tables but leaves files untouched, so files are not a "clean slate"
10
+ you can re-seed, and orphaned files accumulate across runs. Delete deliberately.
11
+
12
+ ## Defining a store
13
+
14
+ Like `db.defineTable`, define at module scope and import into methods. Access is pinned at define
15
+ time.
16
+
17
+ ```typescript
18
+ import { files } from '@mindstudio-ai/agent';
19
+
20
+ export const Uploads = files.defineStore('uploads'); // private (default)
21
+ export const Assets = files.defineStore('assets', { access: 'public' }); // world-readable + CDN
22
+ // optional upload policy: files.defineStore('uploads', { maxSize, contentTypes })
23
+ ```
24
+
25
+ Store names: lowercase `[a-z0-9_-]`, ≤ 64 chars. Keys are paths within the store (`reports/q1.pdf`).
26
+
27
+ ## Backend (`@mindstudio-ai/agent`)
28
+
29
+ ```typescript
30
+ import { Uploads } from './files/uploads';
31
+
32
+ // Store bytes the backend produced; hand file.url to the frontend.
33
+ const file = await Reports.put(pdfBuffer, { contentType: 'application/pdf', filename: 'q1.pdf' });
34
+ file.url; // stable, on-domain URL for <img>/<a>/fetch (private → app-session-authed)
35
+
36
+ const bytes = await Uploads.get(key); // Buffer — backend-side read (parse it, feed a model)
37
+ await Uploads.head(key); // metadata; .exists(key) → boolean
38
+ const { files, cursor } = await Uploads.list({ prefix: 'reports/', limit: 100 });
39
+ await Uploads.delete(key);
40
+ ```
41
+
42
+ - `put(content, { key?, contentType?, filename?, contentAddressed? })` → `StoredFile`
43
+ (`{ key, url, size?, contentType?, updatedAt?, shareUrl() }`). Omit `key` → UUID;
44
+ `contentAddressed: true` → a `<sha256>.<ext>` key (immutable/idempotent, for baked-in public assets).
45
+ - **`file.url`** is a plain relative string — don't await it. To display a user *their own* file, hand
46
+ them `file.url`; don't `get()` the bytes and stream them yourself.
47
+ - **`await file.shareUrl({ expiresIn })`** → an absolute signed link that works with **no session**
48
+ (email / cross-site embed). Private stores only; default 24h.
49
+
50
+ ## User uploads (client-direct — bytes never go through the backend)
51
+
52
+ Backend mints a token; the browser uploads straight to storage:
53
+
54
+ ```typescript
55
+ // backend method
56
+ export async function getUploadSlot(input: { filename: string; contentType: string }) {
57
+ return Uploads.createUploadToken({ contentType: input.contentType, maxSize: 25 * 1024 * 1024 });
58
+ }
59
+ ```
60
+ ```typescript
61
+ // frontend (@mindstudio-ai/interface)
62
+ import { createClient, platform } from '@mindstudio-ai/interface';
63
+ const api = createClient();
64
+ const token = await api.getUploadSlot({ filename: file.name, contentType: file.type });
65
+ const { key, url } = await platform.upload(token, file, { onProgress: (f) => setProgress(f) });
66
+ ```
67
+
68
+ ## Public assets + image resizing
69
+
70
+ Public files are world-readable, on the app's domain, and **images resize via query params**
71
+ (`?w=&h=&fit=&crop=&fm=&dpr=&q=&blur=&sharpen=` — same vocabulary as the image CDN; set `dpr=2/3` for
72
+ retina). Request the size you need rather than CSS-scaling a full-res original.
73
+
74
+ Lightweight-config pattern: a public store + a stable `key` is a file the frontend can `fetch` with no
75
+ DB hit and the backend can overwrite (`Config.put(json, { key: 'config/latest.json' })`).
76
+
77
+ ## Build-time / marketing assets
78
+
79
+ Need an image on the site (hero, logo, OG image)? **Never commit binaries to the repo** — it bloats
80
+ git. Upload once and embed the returned URL:
81
+
82
+ ```bash
83
+ mindstudio-prod files put --public ./hero.jpg # → { url, key } — content-addressed, immutable
84
+ ```
85
+ Write that URL into your JSX/HTML. Also: `files list`, `files rm --store … --key …` (`--help` for flags).
86
+
87
+ ## When public vs private
88
+
89
+ - **Private (default):** user uploads, generated docs, anything not world-readable. Reads are authed
90
+ (the app session) or a short-lived `shareUrl`.
91
+ - **Public:** marketing images, resizable media, config the frontend reads. Deliberate `access:
92
+ 'public'`.
93
+
94
+ Per-user access is the app's job — key files per user (`{userId}/…`) and hand each user only their own
95
+ URLs; the platform authorizes at the app level, not per file.
@@ -88,6 +88,8 @@ When a plan includes multiple screens/API calls, always note this item for the d
88
88
 
89
89
  - **Hardcoded credentials.** If the plan or code contains API keys, tokens, or connection strings inline, flag it — these should be `process.env` secrets managed via the dashboard. Also flag if the plan uses `process.env` for something the MindStudio SDK already handles (AI model keys, email/SMS sending, etc.).
90
90
 
91
+ - **Don't use `agent.uploadFile()` from @mindstudio-ai/agent SDK** — that's the legacy v1 public CDN. Use `files`.
92
+
91
93
  ### Other things to note
92
94
 
93
95
  If you get a whiff of any of the following, make a note for the developer:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.250",
3
+ "version": "0.1.252",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",