@mortar-ai/skill 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 ADDED
@@ -0,0 +1,61 @@
1
+ # @mortar-ai/skill
2
+
3
+ [Claude Code](https://claude.ai/code) skill package for building on
4
+ **Mortar** (the BaaS that AI bundles talk to). Drop it into your skills
5
+ directory + Claude becomes instantly Mortar-fluent: knows the SDK
6
+ accessors, CLI commands, MCP tools, and — most importantly — the
7
+ tenant-isolation + `auth.tenant_id()` RLS rules that are the easiest
8
+ thing to get wrong.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm i -g @mortar-ai/skill
14
+ mortar-skill-install
15
+ # → installs to ~/.claude/skills/mortar/SKILL.md
16
+ ```
17
+
18
+ Then restart Claude Code (or `/skill reload`).
19
+
20
+ Pairs naturally with the [Mortar MCP server](../mcp) — the skill teaches Claude
21
+ *how* to think about Mortar; the MCP gives it live tools to *act* on a
22
+ project. Install both for the full Supabase-plugin-equivalent
23
+ experience.
24
+
25
+ ## What's inside
26
+
27
+ The skill teaches Claude:
28
+
29
+ - **SDK surface** — the `mortar.from()` / `auth` / `storage` /
30
+ `realtime` / `compute` / `usage` / `cron` / `queue` / `comms`
31
+ accessors + the account-scope `AccountClient`; which one to reach for
32
+ - **CLI commands** — `mortar login / use / db / storage / compute /
33
+ cron / queue / domains / comms / keys / tokens / topup / usage`
34
+ - **MCP tools** — the 40 `mortar_*` tools and when an agent should use
35
+ them vs the SDK
36
+ - **Tenant isolation + RLS** — `auth.tenant_id()` (Mortar's analog of
37
+ Supabase's `auth.uid()`), the `tenant_id = auth.tenant_id()` policy
38
+ shape, and why every per-project table carries a `tenant_id`
39
+ - **Architecture** — the 9 primitive × (5 core + 3 extension) feature
40
+ model, and why the SDK shape ≠ the billing shape
41
+ - **Common patterns** — auth (email+password / phone-OTP), data CRUD,
42
+ file upload + signed URLs, realtime subscribe, queue + cron
43
+ - **Anti-patterns** — assuming Fabric runs on Mortar; using `namespace`
44
+ instead of `tenant_id`; hand-rolling RLS the SDK already enforces
45
+
46
+ ## Custom install location
47
+
48
+ ```bash
49
+ mortar-skill-install --dest=/path/to/your/skills/dir/mortar.md
50
+ ```
51
+
52
+ Useful for team-shared skill repositories or in-repo skill packs.
53
+
54
+ ## Updating
55
+
56
+ Re-run `mortar-skill-install` after `npm update @mortar-ai/skill`.
57
+ Idempotent — overwrites in place.
58
+
59
+ ## License
60
+
61
+ Apache-2.0 (same as the rest of Mortar).
package/bin/install.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mortar-skill-install — copies mortar-skill.md into the user's Claude
4
+ * Code skills directory.
5
+ *
6
+ * Default destination: `~/.claude/skills/mortar/SKILL.md`. Override
7
+ * via `--dest=<path>` for non-default Claude installs.
8
+ *
9
+ * Idempotent: re-running overwrites the existing file in place.
10
+ */
11
+ 'use strict';
12
+
13
+ const { copyFileSync, existsSync, mkdirSync } = require('node:fs');
14
+ const { homedir } = require('node:os');
15
+ const { dirname, resolve } = require('node:path');
16
+
17
+ function parseFlags(argv) {
18
+ const out = { dest: undefined };
19
+ for (let i = 2; i < argv.length; i++) {
20
+ const a = argv[i];
21
+ if (a.startsWith('--dest=')) out.dest = a.slice('--dest='.length);
22
+ else if (a === '--dest') out.dest = argv[++i];
23
+ }
24
+ return out;
25
+ }
26
+
27
+ function main() {
28
+ const flags = parseFlags(process.argv);
29
+ const src = resolve(__dirname, '..', 'mortar-skill.md');
30
+ if (!existsSync(src)) {
31
+ console.error(`mortar-skill: source missing at ${src}`);
32
+ process.exit(1);
33
+ }
34
+ const dest = flags.dest
35
+ ? resolve(flags.dest)
36
+ : resolve(homedir(), '.claude', 'skills', 'mortar', 'SKILL.md');
37
+
38
+ mkdirSync(dirname(dest), { recursive: true });
39
+ copyFileSync(src, dest);
40
+ console.log(`✓ Installed Mortar skill → ${dest}`);
41
+ console.log('');
42
+ console.log('Restart Claude Code (or run `/skill reload`) to activate.');
43
+ }
44
+
45
+ main();
package/index.js ADDED
@@ -0,0 +1,24 @@
1
+ // @mortar-ai/skill — exports the skill markdown for programmatic use.
2
+ //
3
+ // Consumers (e.g. a future `mortar install --self-update` flow) can
4
+ // require this package + push the markdown content into wherever they
5
+ // want; the bin/install.js script is the standard CLI path.
6
+
7
+ 'use strict';
8
+
9
+ const { readFileSync } = require('node:fs');
10
+ const { resolve } = require('node:path');
11
+
12
+ const SKILL_PATH = resolve(__dirname, 'mortar-skill.md');
13
+
14
+ module.exports = {
15
+ /**
16
+ * skillContents() returns the raw markdown. Useful when a
17
+ * higher-level CLI wants to embed it into another package's skill
18
+ * output (symmetric with @keel-ai/skill's skillContents()).
19
+ */
20
+ skillContents() {
21
+ return readFileSync(SKILL_PATH, 'utf8');
22
+ },
23
+ SKILL_PATH,
24
+ };
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: Mortar
3
+ description: Required guidance for Fabric apps that need a database, backend, authentication, persistence, file storage, realtime collaboration, cloud functions, or product analytics.
4
+ activation: auto
5
+ priority: high
6
+ triggers:
7
+ - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, storage, upload, 文件, realtime, 实时, collaboration, compute, cloud, 云端]
8
+ ---
9
+
10
+ # Mortar for Fabric applications
11
+
12
+ Mortar is an independent Backend-as-a-Service used by Fabric-built apps; Fabric
13
+ itself does not run on Mortar. Do not infer its setup from another BaaS.
14
+
15
+ ## Fabric integration contract
16
+
17
+ - Bundler projects (Web, Desktop renderer, Keel/React Native) import the
18
+ platform singleton only:
19
+
20
+ ```ts
21
+ import { mortar } from '@/integrations/mortar/client';
22
+ ```
23
+
24
+ - WeChat has no `@/` alias. A page normally imports:
25
+
26
+ ```ts
27
+ import { mortar } from '../../utils/mortar/client';
28
+ ```
29
+
30
+ - `src/integrations/mortar/client.ts`, its `types.ts`, and the WeChat equivalents
31
+ under `utils/mortar/` are platform-owned. Never create, overwrite, or replace
32
+ them.
33
+ - Never add `VITE_MORTAR_*`, `MORTAR_URL`, or client-side API-key environment
34
+ variables. Never hand-write `createClient(...)`, `src/lib/mortar.ts`, or a
35
+ second wrapper/client in a Fabric project.
36
+ - Fabric seeds an import-safe singleton, then injects the tenant URL and PUBLIC
37
+ bundle key after provisioning. The public key belongs in app code; it is not
38
+ a server/admin secret.
39
+ - Add `@mortar-ai/client` to `dependencies` with npm when the app first needs
40
+ Mortar. The agent owns `package.json`, `package-lock.json`, and `node_modules`.
41
+ Run the install before starting the dev server.
42
+ - WeChat's injected singleton uses `@mortar-ai/client/wechat`, which supplies
43
+ `wx.request`, `wx.*StorageSync`, and chunked-request realtime adapters. Do not
44
+ replace it with raw `wx.request` calls to Mortar.
45
+
46
+ If the singleton reports that the backend is not connected, do not invent a
47
+ fallback URL or ask the user for keys. Use the platform schema/provisioning flow
48
+ below; if platform provisioning fails, report that platform failure.
49
+
50
+ ## Required workflow
51
+
52
+ 1. Call `mortar_schema` before designing or changing backend data. It returns
53
+ the current live schema, including console changes.
54
+ 2. For each schema change, call `mortar_migrate` once with one operation. It
55
+ applies the live change first; only after success does Fabric append a
56
+ migration record under `mortar/migrations/`.
57
+ 3. `mortar/migrations/` is a platform-owned output journal. Never create or edit
58
+ SQL there: a file in that directory does not execute or change the live
59
+ tenant. Do not edit or create `mortar.config.json`. Do not edit generated
60
+ `integrations/mortar/types.ts`; Fabric regenerates it from the live schema.
61
+ 4. Run `npm install @mortar-ai/client` if the dependency is absent, then write app
62
+ code using the injected singleton.
63
+ 5. Type-check and build. Handle every `{ data, error }` result in UI code.
64
+
65
+ Example schema operations (the tool schema is authoritative for all fields):
66
+
67
+ ```json
68
+ {"kind":"create_table","table":"todos","access":"private","columns":[{"name":"title","type":"text","required":true},{"name":"done","type":"bool"},{"name":"user_id","type":"uuid","required":true}]}
69
+ ```
70
+
71
+ Supported migration kinds are `create_table`, `drop_table`, `set_table_access`,
72
+ `add_column`, `drop_column`, `create_index`, `set_bucket_access`, `deploy_function`, `delete_function`,
73
+ `upsert_cron`, and `delete_cron`. Destructive and backend-changing operations
74
+ must respect the workspace approval gate. This list is exhaustive: arbitrary
75
+ PostgreSQL, data backfills, PL/pgSQL functions, transactions, and triggers are
76
+ not migration inputs. If required behavior cannot be expressed by one of these
77
+ operations, report the unsupported Mortar capability instead of authoring a SQL
78
+ file that only looks executable.
79
+
80
+ To change an existing table's access policy, use `set_table_access`. Access is
81
+ metadata and must be updated in place. Never use `drop_table` followed by
82
+ `create_table` for a permission change: dropping a table deletes all of its
83
+ rows and columns.
84
+
85
+ For a public form, event registration, evidence upload, or similar operator-
86
+ reviewed intake, use `access: "public_insert"` on the destination table and
87
+ `set_bucket_access` with `access: "public_insert"` on any upload bucket. This
88
+ allows a public bundle client to create a new row/object while denying public
89
+ list, read, update, delete, and overwrite. Never use `public` merely to make an
90
+ anonymous form submit successfully; `public` exposes all rows/files for public
91
+ read and mutation. Generate an unguessable object key for every upload.
92
+
93
+ ## Database SDK
94
+
95
+ The query surface is Supabase-shaped. Async row/auth/storage methods normally
96
+ return `{ data, error }`; check `error` rather than assuming success.
97
+
98
+ ```ts
99
+ const { data: todos, error } = await mortar
100
+ .from('todos')
101
+ .select('*')
102
+ .eq('done', false)
103
+ .order('created_at', { ascending: false })
104
+ .limit(20);
105
+ if (error) throw new Error(error.message);
106
+
107
+ const { data: created, error: insertError } = await mortar
108
+ .from('todos')
109
+ .insert({ title: 'Ship', done: false, user_id: userId })
110
+ .select()
111
+ .single();
112
+
113
+ const { data: updated, error: updateError } = await mortar
114
+ .from('todos')
115
+ .update({ done: true })
116
+ .eq('id', todoId)
117
+ .select();
118
+
119
+ const { error: deleteError } = await mortar
120
+ .from('todos')
121
+ .delete()
122
+ .eq('id', todoId);
123
+ ```
124
+
125
+ Useful chaining includes `eq`, `neq`, `gt/gte`, `lt/lte`, `like/ilike`, `in`,
126
+ `is`, `order`, `range`, `limit`, `single`, and `maybeSingle`.
127
+
128
+ ## App-user authentication
129
+
130
+ Auth sessions are persisted and attached to later database, storage, realtime,
131
+ and compute calls by the SDK.
132
+
133
+ ```ts
134
+ const { data, error } = await mortar.auth.signUp({ email, password });
135
+ const signedIn = await mortar.auth.signInWithPassword({ email, password });
136
+
137
+ const sent = await mortar.auth.signInWithOtp({ email }); // or { phone }
138
+ const verified = await mortar.auth.verifyOtp({ email, token });
139
+
140
+ const { data: sessionData } = await mortar.auth.getSession();
141
+ const { data: userData, error: userError } = await mortar.auth.getUser();
142
+
143
+ const { data: listener } = mortar.auth.onAuthStateChange((event, session) => {
144
+ // INITIAL_SESSION | SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED
145
+ });
146
+ listener.subscription.unsubscribe();
147
+
148
+ await mortar.auth.signOut();
149
+ ```
150
+
151
+ Password reset starts with `mortar.auth.resetPasswordForEmail(email)`; confirm via
152
+ `passwordResetConfirm(email, token, newPassword)`. Never store or pass session
153
+ tokens manually unless the user explicitly needs a server integration.
154
+
155
+ For private user rows, include the authenticated user's ID in the schema/data
156
+ model and choose the correct table access policy. Client-side filters improve
157
+ UX but do not replace server-side access rules.
158
+
159
+ ## File storage
160
+
161
+ ```ts
162
+ const bucket = mortar.storage.from('avatars');
163
+
164
+ const { data: uploaded, error: uploadError } = await bucket.upload(path, file, {
165
+ contentType: file.type,
166
+ upsert: true,
167
+ });
168
+ const { data: blob, error: downloadError } = await bucket.download(path);
169
+ const { data: files, error: listError } = await bucket.list('users/');
170
+ const { data: signed, error: signError } = await bucket.createSignedUrl(path, 3600);
171
+ const { error: removeError } = await bucket.remove([path]);
172
+ ```
173
+
174
+ Downloads mint a short-lived URL and fetch the object directly from the storage
175
+ backend. Reuse `signed.signedUrl` until it nears expiry when rendering media.
176
+ Prefer the signed URL directly in `<img src>`, `<video src>`, or `<audio src>`;
177
+ this avoids an unnecessary blob copy and does not require CORS merely to render
178
+ the media. Signed-URL minting is excluded from the project's API-concurrency
179
+ budget, but clients should still lazy-load visible media and cache/reuse each
180
+ URL instead of signing again on every render. Signing authorizes a possible
181
+ download; it does not prove bytes were served and must never be described or
182
+ modeled as an object-egress charge. Only provider-observed access-log or billing
183
+ reconciliation may record actual object egress. Use the third
184
+ `createSignedUrl` argument to bound only the signing request when the UI needs
185
+ a deadline:
186
+
187
+ ```ts
188
+ const { data: signed, error } = await bucket.createSignedUrl(path, 900, {
189
+ signal: controller.signal,
190
+ timeoutMs: 10_000,
191
+ });
192
+ ```
193
+
194
+ `bucket.download(path)` uses JavaScript `fetch()` against the object store and
195
+ therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
196
+ storage provides that contract; self-hosted object storage must allow the app
197
+ origin for credential-free `GET` / `HEAD`. Never attach Mortar Authorization or
198
+ browser cookies to a signed object URL.
199
+
200
+ ## Realtime, broadcast, and presence
201
+
202
+ ```ts
203
+ const room = mortar
204
+ .channel('todos-room', { presence: { key: userId } })
205
+ .on(
206
+ 'postgres_changes',
207
+ { event: '*', schema: 'public', table: 'todos' },
208
+ (payload) => refreshFromChange(payload),
209
+ )
210
+ .on('broadcast', { event: 'cursor' }, ({ payload }) => drawCursor(payload))
211
+ .on('presence', { event: 'sync' }, () => renderRoster(room.presenceState()))
212
+ .subscribe((status) => {
213
+ if (status === 'SUBSCRIBED') void room.track({ online: true });
214
+ });
215
+
216
+ await room.send({ type: 'broadcast', event: 'cursor', payload: { x, y } });
217
+ // On component/page cleanup:
218
+ mortar.removeChannel(room);
219
+ ```
220
+
221
+ Clean up channels with their page/component. Persist through `from(table)`;
222
+ realtime only notifies peers.
223
+
224
+ ## Compute and analytics
225
+
226
+ Invoke only functions already deployed through `mortar_migrate`:
227
+
228
+ ```ts
229
+ const response = await mortar.compute.invoke({
230
+ name: 'summarize',
231
+ method: 'POST',
232
+ body: JSON.stringify({ text }),
233
+ contentType: 'application/json',
234
+ });
235
+ if (!response.ok) throw new Error(await response.text());
236
+ ```
237
+
238
+ Analytics calls are fire-and-forget except `flush`:
239
+
240
+ ```ts
241
+ mortar.analytics.track('pageview', { path: location.pathname });
242
+ mortar.analytics.identify(user.id, { plan: 'free' });
243
+ mortar.analytics.captureError(error, { screen: 'checkout' });
244
+ await mortar.analytics.flush();
245
+ ```
246
+
247
+ ## Boundary for non-Fabric development
248
+
249
+ For Mortar itself or a standalone non-Fabric integration, inspect the current SDK
250
+ README. Never copy its standalone `createClient` setup into a Fabric project.
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@mortar-ai/skill",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code skill package for building on Mortar (BaaS). Drop `mortar-skill.md` into ~/.claude/skills/mortar/ to give Claude expert-level guidance about the Mortar SDK / CLI / MCP, tenant isolation + auth.tenant_id() RLS, and the 9 primitive × feature model.",
5
+ "main": "index.js",
6
+ "files": ["mortar-skill.md", "index.js", "README.md"],
7
+ "bin": {
8
+ "mortar-skill-install": "bin/install.js"
9
+ },
10
+ "publishConfig": { "access": "public" },
11
+ "license": "Apache-2.0"
12
+ }