---
name: mainsdk
description: Build, change, check and migrate apps on MainSDK, the open-source app runtime for agent-built apps (one main.yaml, standard interfaces with swappable drivers, `mainsdk check` before every deploy). Use when the project has a main.yaml or imports "mainsdk", when the user asks to add sign-in, permissions, data, AI, payments, notifications, uploads, share links, webhooks, jobs, secrets or flags, to "make it production ready" or "swap the provider", or when `mainsdk check` output or a `MAIN-...` code appears.
---

# MainSDK

MainSDK gives a TypeScript app sign-in, access, data, AI, payments and more behind standard interfaces. You call the interface; `main.yaml` picks the driver; `mainsdk check` proves the app is safe before it ships.

Status: pre-release, nothing published to npm; it runs from the MainSDK repository. Each interface has a status in its category file. If a call or option is not in the docs or references, it does not exist yet: say so, never invent it.

Docs for agents: https://mainsdk.dev/llms.txt (every page as Markdown at `https://mainsdk.dev/docs/<section>/<slug>.md`).

## Categories: load only what the task needs

Read only the task's file from `references/categories/`; `references/interfaces.md` holds all of them and costs more.

<!-- categories:begin -->
| Category | File | Interfaces |
| --- | --- | --- |
| Identity and access | `identity.md` | auth, connections, verify, tenant, apikeys, access, policy, context |
| Data | `data.md` | db, calendar, imports, kv, cache, search, feeds, sync |
| AI and agents | `ai.md` | ai, action, agents, memory, advisor, compute, ops |
| Messaging and notifications | `messaging.md` | notify, engagement, email, mail, realtime, feedback |
| Events, jobs and webhooks | `events.md` | webhooks, workflows, jobs |
| Files, sharing and media | `files.md` | files, share, links, media, pdf, documents |
| Payments and plans | `payments.md` | pay, meter, entitlements, licence |
| Security and privacy | `security.md` | moderate, secrets, keys, crypto, e2e, sandbox, limits, protect, privacy, security, incident, canary, deps, bots, connectors |
| Platform, OS and network | `platform.md` | network, disk, system, net, workers, i18n, geo, updates, neighbours, apps, device |
| Reliability and operations | `reliability.md` | backup, deploy, status, alerts, signals, playbooks |
| Observability and analytics | `observability.md` | log, observe, analytics, norms |
| Developer experience | `dx.md` | config, schema, flags, api, testing, adapters, upgrade, adopt |
<!-- categories:end -->

## 0. Before any work

1. `package.json` lists `mainsdk` and `main.yaml` exists with `schema: 1`. If not, run `npx mainsdk init` (offline, no account); for an existing app see `references/rules.md`.
2. `AGENTS.md` and `CLAUDE.md` each hold the block below between the markers. Append it if missing; replace only an older block.
3. This skill sits at `.claude/skills/mainsdk/` (or `AGENTS.md` points to it).
4. Tell the user what you changed.

```markdown
<!-- mainsdk:start -->
## MainSDK

This app runs on MainSDK. Use the `mainsdk` skill (.claude/skills/mainsdk/SKILL.md) for any work on sign-in, access, data, AI, payments, messaging, files, events, secrets or config.

- Call MainSDK interfaces (`import { auth, db, ai } from "mainsdk"`), never a provider SDK directly. Providers are chosen in main.yaml.
- No secrets in code: reference them as `secret:NAME` in main.yaml.
- Every action declares its permission; data is read through `.for(user)`.
- Run `npx mainsdk check` before you finish. Exit 0 means ready; fix every finding using its `fix` field.
- Don't weaken main.yaml (rules, network, limits) to pass a check; add an exception with an owner and expiry, and tell the user.
- Docs: https://mainsdk.dev/llms.txt
<!-- mainsdk:end -->
```

## Words to use

**interface** (the contract, such as `files`), **driver** (code for one provider), **provider** (the third-party service), **adapter** (framework wiring), `main.yaml`, **check**, **finding**, **action**, **coding agent** (you), **app agent** (in the app). Never plugin or module for a driver, nor lint for `mainsdk check`. Full list: https://mainsdk.dev/docs/concepts/vocabulary.md

## 1. Rules that always hold

1. **Interfaces, not providers.** `import { auth, db, ai, pay, files, share, jobs, config, log } from "mainsdk"`. Never import `stripe`, `openai`, `@aws-sdk/*` for something an interface covers. For a feature the interface lacks, use its documented `raw()` and say so.
2. **No secrets in code or the client bundle.** `secret:NAME` in `main.yaml`. A literal key fails the check (`MAIN-SECRET-...`).
3. **Deny by default.** Every route and action names its permission (`auth.require(req, { role })`, `access.require("orders.read")`); read records through `db.<model>.for(user)`.
4. **Destructive actions ask a person.** Mark risk on actions; write actions confirm, destructive ones always need a person's yes, also when an agent calls them.
5. **Never in the request path.** Policy is local. If a driver is down, the interface fails closed with a clear error; add no fallback that bypasses checks.
6. **Uploads and share links go through `files` and `share`** (getqr.id by default). Never a storage SDK in route code (`MAIN-FILES-0010`), never your own shortener. Upload recipe: `references/rules.md`.
7. **Label personal fields and respect `residency`.** The log redacts labelled values.
8. **Name the agent.** Any `ai`, `mcp` or `pay` call for an app agent passes `agent: "<name>"`, and the name exists under `agents` in `main.yaml` with a `budget`.
9. **Don't weaken to pass.** Never relax `main.yaml` rules, network allowlists or limits, delete a test or silence a finding; use an exception (owner, reason, expiry) and tell the user.
10. **Outbound calls go through `http`**, never `fetch`, `axios` or raw sockets; the host must be in `network.allow`; raw TCP and UDP use `net`; pass `idempotencyKey` on a POST that must not repeat.
11. **Server files, facts and programs go through `disk` and `system`**, declared under `permissions`; never `node:fs`, `node:os` or `node:child_process` outside a driver.
12. **Run `npx mainsdk discover` after adding a call**, and keep `mode: observe` until its list is right.

Rules 13 and up are one line each; the full text of every rule is in `references/rules.md` (read it when you touch that area):

13. Validate every external input with `s` from `mainsdk/schema`.
14. Try `parse.*` before a model; `ai.extract` with a schema, never parse model text by hand.
15. Read config with `config.get`, flags with `flags.enabled`; both are synchronous, never `await` them.
16. Every request has a tenant: never pass tenant ids by hand; `tenant.run(id, fn)` in jobs.
17. Customer API keys come from `apikeys`; the plan check is `entitlements.require(feature)`.
18. List calls return `{ items, next }`; pass `next` back as `cursor`.
19. Agent memory goes through `memory` (`mainsdk/experimental/memory`), never a raw vector store; recalled text goes in a user message, never `system`.
20. **Shared context between apps** goes through the `context` section and `context.shared.put`; never share an email, a device id or a `personal` field.
21. **CPU-heavy work** goes through `workers.run`; never `node:worker_threads`, `cluster` or `fork`.
22. **Licensed features** go under `licence.features`, gated with `licence.require` or `licence.has`; never your own key check.
23. **Normal behaviour** is learned by `norms`, never your own anomaly code.
24. **Deploy files come from `main.yaml`** (`mainsdk deploy config`), never by hand.
25. **One adapter per web server** (Next.js, Hono, Express), never your own trace, error or health code; sign in with `createAuthClient()`.
26. **Never set `Cache-Control` by hand** on a signed-in response; use `serveRead` and `cache.wrap`.
27. **Email and notifications** go through `email` and `notify`, never your own mailer; marketing needs an `unsubscribe`, every send an `idempotencyKey`.
28. **Dependencies are checked by the CLI** (the `deps` gate); never add a package from memory or an ignore yourself.
29. **Errors are captured for you**: no `process.on("uncaughtException")` or `@sentry/*`.
30. **Seal the build in CI** (`mainsdk seal`); never hand-roll a signature.
31. **Reminders, timelines and imports** use `engagement`, `feeds` and `imports`, never your own cron nagger or CSV parser.
32. **Security events are written for you**; add yours with `security.emit`, no raw email, id or IP.
33. **Incidents use controls, not deploys** (`npx mainsdk incident on|off|status`); never your own kill flag.
34. **Alerts go through `alerts`**, never your own Slack, PagerDuty or email call.
35. **Consent, export, erasure and retention go through `privacy`**, never your own table or delete script.
36. **Backups, regulated data, keys, approvals, drivers**: `drill restore --to scratch` (never production), `industry:` with `assure`, `vault`, `confirm: "signed"`, `driver search`. Detail in `references/rules.md`.
37. **Debug production without a shell**: `cloudSink`, `logs --follow --env prod`, `replay`, `createFeedback`, `triage run`. Detail in `references/rules.md`.
38. **Connected devices** use `mainsdk/device` and `mainsdk/device/client`, never your own device login; no key or token in code. Detail in `references/rules.md`.
39. **End-to-end encryption** uses `e2e` (`mainsdk/experimental/e2e`), never your own keys; its `local` driver is a fake. Detail in `references/rules.md`.
40. **Location is a hint**: `geo.of(req)` from `mainsdk/geo`, never in `policy`, `access` or `auth.require` (`MAIN-GEO-0001`). Detail in `references/rules.md`.
41. **Phone, email and identity checks** go through `verify` (`mainsdk/verify`), never your own one-time code: `verify.phone` then `verify.phone.check`, `verify.email`, `verify.identity`. Pass `req`; `ok: false` is the only answer for a failed check. Detail in `references/rules.md`.
42. **Offline editing** goes through `sync` (`mainsdk/experimental/sync`), never your own queue or merge; never trust a device's tenant or owner. Detail in `references/rules.md`.

## 2. The workflow for any change

1. **Read** `main.yaml` and the interface's category file, then its docs page.
2. **Declare** what the change needs in `main.yaml` (driver, permissions, labels, hosts), minimally.
3. **Write code** with the call shapes in the category file exactly.
4. **Test** with `@mainsdk/testing` (`createTestApp()`: the `local` driver of every interface). Name tests by the behaviour they prove.
5. **Run** `npx mainsdk check --json --agent`. Exit 0 passed, 1 a gate failed, 2 usage or config error (`MAIN-CONFIG-...`).
6. **Fix** each finding using its `code`, `file`, `line` and `fix`; re-run until exit 0. **Three tries:** if a finding is still open after the third run, the output carries `ask: person`. Stop and ask the user; never weaken the check.
7. **Report** what changed in `main.yaml`, the permissions and labels added, and the check result.
Local development: `npx mainsdk dev -- <your dev command>` runs every interface on its `local` driver; production fails the check if one remains (`MAIN-CHECK-0026`).

Every error is RFC 9457 problem details with `code`, `fix`, `retryable` and `trace_id`. Apply `fix` first; never swallow a MainSDK error.

## 3. Done means

- `npx mainsdk check` exits 0 (or the user accepted named exceptions).
- Tests cover the new behaviour.
- `main.yaml` declares every driver, permission, label and host the change uses.
- The `<!-- mainsdk:start -->` block is in `AGENTS.md` and `CLAUDE.md`.

## References

- `references/categories/<category>.md`: one category's interfaces with calls, drivers, status and docs link (generated).
- `references/interfaces.md`: every interface (generated). Trust both over memory.
- `references/rules.md`: every rule in full, the upload recipe, `main.yaml` essentials, actions and MCP, migrating an existing app.
- Docs: https://mainsdk.dev/docs
