everyapp 0.2.2 → 0.3.0-rc.2

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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -77
  3. package/bin/everyapp.js +235 -0
  4. package/dist/access-LGEN3ABJ.js +1 -0
  5. package/dist/activity-RSD4D66F.js +12 -0
  6. package/dist/apps-4RCFXT74.js +13 -0
  7. package/dist/chunk-23HJ5I2A.js +2 -0
  8. package/dist/chunk-2Y5Q6A5K.js +6 -0
  9. package/dist/chunk-4JRIS6S6.js +1 -0
  10. package/dist/chunk-5PTNYZK6.js +4 -0
  11. package/dist/chunk-6OGVO7QK.js +6 -0
  12. package/dist/chunk-6PGUCTJ6.js +11 -0
  13. package/dist/chunk-AGTCKFRB.js +22 -0
  14. package/dist/chunk-BHMRY53V.js +2 -0
  15. package/dist/chunk-CB4U7H2G.js +4 -0
  16. package/dist/chunk-CCWBPAKH.js +4 -0
  17. package/dist/chunk-CZTEDWZ2.js +22 -0
  18. package/dist/chunk-CZXHKPRZ.js +2 -0
  19. package/dist/chunk-E3EPK27P.js +10 -0
  20. package/dist/chunk-EGEWRFVY.js +1 -0
  21. package/dist/chunk-ELK7QZ2M.js +1 -0
  22. package/dist/chunk-FJJSSVET.js +33 -0
  23. package/dist/chunk-GKBPYVP3.js +25 -0
  24. package/dist/chunk-H6QQMY7B.js +18 -0
  25. package/dist/chunk-H7PDL3X5.js +1 -0
  26. package/dist/chunk-HOUHG236.js +20 -0
  27. package/dist/chunk-IFCPALVC.js +46 -0
  28. package/dist/chunk-IUOBIB5M.js +30 -0
  29. package/dist/chunk-J35DDCNU.js +1 -0
  30. package/dist/chunk-J455FAEN.js +7 -0
  31. package/dist/chunk-JYKRUS5C.js +1 -0
  32. package/dist/chunk-LXYMDZER.js +1 -0
  33. package/dist/chunk-LYNBZ7RV.js +57 -0
  34. package/dist/chunk-M2QQ2UD7.js +132 -0
  35. package/dist/chunk-N4E5YE4X.js +5 -0
  36. package/dist/chunk-N5GQLGEC.js +2 -0
  37. package/dist/chunk-NFU4YFXB.js +1 -0
  38. package/dist/chunk-OM2HDWVP.js +1 -0
  39. package/dist/chunk-ONLNJXVQ.js +32 -0
  40. package/dist/chunk-OP4DYXYI.js +2 -0
  41. package/dist/chunk-PNLU5U3P.js +1 -0
  42. package/dist/chunk-PS7S74DG.js +6 -0
  43. package/dist/chunk-RMF4YGD6.js +98 -0
  44. package/dist/chunk-T2GD7RHS.js +10 -0
  45. package/dist/chunk-TLRM3P7V.js +15 -0
  46. package/dist/chunk-W3JCQHHU.js +1 -0
  47. package/dist/chunk-WOVS3GV5.js +10 -0
  48. package/dist/chunk-YHJZMJYZ.js +1 -0
  49. package/dist/chunk-ZECBN24L.js +15 -0
  50. package/dist/chunk-ZEXVHB22.js +2 -0
  51. package/dist/chunk-ZFCUH5PK.js +7 -0
  52. package/dist/configure-F7UZCJUG.js +8 -0
  53. package/dist/create-2E5HVWYK.js +22 -0
  54. package/dist/db-HEQVTDTM.js +9 -0
  55. package/dist/delete-XCSIQIRS.js +3 -0
  56. package/dist/deploy-3QOZEZHC.js +1 -0
  57. package/dist/dev-JL3ZP5BT.js +86 -0
  58. package/dist/email-RLZFUFUN.js +10 -0
  59. package/dist/events-2V5VRBWP.js +15 -0
  60. package/dist/groups-HDG5DVFY.js +8 -0
  61. package/dist/index.js +97 -1
  62. package/dist/install-Z5FYXUYM.js +1 -0
  63. package/dist/login-KHJBC64Z.js +1 -0
  64. package/dist/mcp-EFDUKJ5V.js +1 -0
  65. package/dist/provider-EOI5PTFH.js +13 -0
  66. package/dist/release-74J5ZIL3.js +1 -0
  67. package/dist/restore-FFGYV7NO.js +3 -0
  68. package/dist/secret-7FKKTLHB.js +1 -0
  69. package/dist/setup-PAKRCAFO.js +1 -0
  70. package/dist/signin-AP3D4XM4.js +12 -0
  71. package/dist/status-RMWNHOEK.js +4 -0
  72. package/dist/teardown-E4JNKAZJ.js +22 -0
  73. package/dist/update-DWVZFNPS.js +1 -0
  74. package/dist/workspace-CJC5I66L.js +21 -0
  75. package/package.json +19 -16
  76. package/skills/build-every-app/SKILL.md +199 -0
  77. package/skills/build-every-app/patterns.md +140 -0
  78. package/skills/everyapp/SKILL.md +292 -0
  79. package/skills/everyapp/mcp.md +350 -0
  80. package/skills/everyapp/security-review.md +61 -0
  81. package/skills/everyapp/setup/README.md +274 -0
  82. package/skills/everyapp/setup/access.md +178 -0
  83. package/skills/everyapp/setup/context.md +117 -0
  84. package/skills/everyapp/setup/dataforseo.md +287 -0
  85. package/skills/everyapp/setup/email.md +366 -0
  86. package/skills/everyapp/setup/gateway.md +243 -0
  87. package/skills/everyapp/setup/google.md +360 -0
  88. package/skills/everyapp/setup/microsoft-365.md +281 -0
  89. package/skills/everyapp/setup/openai.md +318 -0
  90. package/skills/everyapp/setup/openrouter.md +309 -0
  91. package/skills/everyapp/setup/providers.md +1253 -0
  92. package/skills/everyapp/setup/remote-mcp.md +199 -0
  93. package/skills/everyapp/setup/signin-apple.md +178 -0
  94. package/skills/everyapp/setup/signin-cloudflare.md +148 -0
  95. package/skills/everyapp/setup/signin-github.md +139 -0
  96. package/skills/everyapp/setup/signin-google.md +178 -0
  97. package/skills/everyapp/setup/signin-microsoft.md +183 -0
  98. package/skills/everyapp/setup/signin-oidc.md +163 -0
  99. package/skills/everyapp/setup/slack.md +344 -0
  100. package/skills/everyapp/setup/workers-ai.md +138 -0
  101. package/bin/__everyapp_bash_complete.js +0 -12
  102. package/dist/bash-complete.js +0 -3
  103. package/dist/chunk-7BOQJFI5.js +0 -8
  104. package/dist/chunk-7YVJFS4A.js +0 -1
  105. package/dist/chunk-BIXKPCDH.js +0 -2
  106. package/dist/chunk-DLGPLHBH.js +0 -47
  107. package/dist/chunk-FD6K2YA7.js +0 -9
  108. package/dist/chunk-GAJEQYJH.js +0 -39
  109. package/dist/chunk-O2KDOP5R.js +0 -13
  110. package/dist/chunk-OI2MBGBG.js +0 -4
  111. package/dist/chunk-POSDLD3G.js +0 -8
  112. package/dist/chunk-RB5D2R45.js +0 -1
  113. package/dist/chunk-U4LIQ5VR.js +0 -1
  114. package/dist/chunk-VEYSNPDH.js +0 -3
  115. package/dist/chunk-VP6SKW44.js +0 -7
  116. package/dist/chunk-WO3NE7ZU.js +0 -7
  117. package/dist/chunk-XTO2VTPY.js +0 -6
  118. package/dist/chunk-YJ2DKIZS.js +0 -1
  119. package/dist/create-DYQ3NBWJ.js +0 -22
  120. package/dist/deploy-PYTCMDGV.js +0 -7
  121. package/dist/deploy-RMAHOA4Z.js +0 -55
  122. package/dist/dev-SAE237FS.js +0 -0
  123. package/dist/generateConfig-46Z3ERF3.js +0 -1
  124. package/dist/login-B6EF3SMX.js +0 -1
  125. package/dist/logout-XMKZBLNV.js +0 -1
  126. package/dist/remoteD1Shell-A67JMO3H.js +0 -6
  127. package/dist/setupLocal-TZJUW5ES.js +0 -6
  128. package/gateway/every-app-gateway-build.tar.gz +0 -0
@@ -0,0 +1,140 @@
1
+ # Example patterns
2
+
3
+ How the apps on Every App do each thing, and the file to copy from. `starter/` is what `pnpm everyapp create` copies (in the Every App checkout); in an app `everyapp create` made, a `starter/` path is that path in the app. The four apps are open, at `https://github.com/every-app/<name>`, cited by their repository names: `open-crm/` (OpenCRM), `open-analytics/` (OpenAnalytics), `open-seo/` (OpenSEO) and `agentic-inbox/` (Agentic Inbox); a path is relative to that repository's root. Read a file before copying from it: each is short where it matters, and its comments say why. When a cited file is out of reach, the "What it is" and "Copy this" columns are the pattern; start from the starter's file when the row names one.
4
+
5
+ ## Identity
6
+
7
+ | Pattern | What it is | Where | Copy this |
8
+ | --- | --- | --- | --- |
9
+ | Identity in a server function | `requireEveryAppUser(getRequest(), env)` in a TanStack Start middleware; every server function lists it and reads `context.user` | `starter/src/middleware/ensureUser.ts`, `starter/src/serverFunctions/todos.ts` | `.middleware([ensureUserMiddleware])` on every server function, and `context.user.id` as the first argument of every query |
10
+ | Identity resolved once, with the organization | `getEveryAppUser()` over the request's headers; the deploy's `EVERY_APP_ORG_ID` pin; the role from the token, never from a member row; one context `{ userId, userEmail, organizationId, role }` every repository filters by | `open-crm/src/middleware/ensure-user/everyApp.ts`, `open-crm/src/middleware/ensureUser.ts`; the same shape in `open-seo/src/middleware/ensure-user/everyApp.ts` | `resolveEveryAppUserContext(user)`: the pin check, `ROLES`, and that a server function accepts only a credential holding every scope (`hasScope(user, "*")`) |
11
+ | Identity at the Worker entry | `everyApp()`'s handler gets `user` as its fourth argument; the app refuses another organization, keeps the user in a `WeakMap` by request, and its routes read it from there | `agentic-inbox/workers/app.ts`, `agentic-inbox/workers/everyapp/identity.ts` | `requireOrganization(env, user)` before routing; `roleOf()`; the `requireAdmin` middleware on the routes only owners and admins may use |
12
+ | Identity mirrored into the app's tables | A `Viewer` from `getEveryAppUser()`; the person, their organization and their membership are mirrored into rows so other rows can join on them, and access still comes from the signed identity alone | `open-analytics/src/lib/edge/auth/viewer.ts` | `mirror()` writes at most once an hour per person and is never consulted for access |
13
+ | Identity in a tool handler | The handler's second argument carries the verified caller (`user.id`, `user.orgId`, `user.orgRole`, `user.scopes`); the starter passes `user.id` to the same queries the page uses | `starter/src/tools.ts`; `open-crm/src/server/everyapp/crm-tool.ts` | `crmTool()`: one wrapper that resolves the context, scopes the handler to the organization, and turns an `AppError` into a `ToolError` |
14
+ | Identity on a public route | A public route is open to anyone, so it never assumes a member: `getEveryAppUser(request, env)` is the member when the Gateway signed one in (a member's session still travels on a public route) and `null` otherwise. The four apps' public routes read no identity at all: OpenAnalytics' collector checks the token its tracker was issued, OpenCRM's tracker takes the site from the id in the URL | `open-analytics/src/lib/edge/collector/collect.ts`, `open-analytics/src/lib/hono/routes/collect.ts`; `open-crm/src/server/features/tracking/trackingRoutes.ts` | A public route exists only when the app needs one (a webhook, a collector, an OAuth callback); it checks what it must itself and answers nothing that is anyone's without that check |
15
+ | Organization-wide rows | Sharing by design: every table keys on the organization in the signed identity, every member sees the organization's rows, owners and admins manage. OpenCRM hangs every table off `organization_id`; OpenAnalytics' `teamId` is the organization; Agentic Inbox is one organization per deploy, every mailbox shared | `open-crm/docs/maintainers/every-app.md`, `open-crm/src/middleware/ensure-user/everyApp.ts`; `open-analytics/src/lib/edge/auth/viewer.ts`; `agentic-inbox/workers/everyapp/identity.ts` | The rule in words: authorization reads the organization and the role from the token on every request, never from a row the app wrote |
16
+ | Roles from the Gateway | `owner`, `admin` and `member` from `user.orgRole`; anything else is a member | `open-crm/src/server/auth/org-gate.ts`; `agentic-inbox/workers/everyapp/identity.ts` | `requireOrgPermission(context, permissions)` throwing `FORBIDDEN`; `isAdmin(user)` |
17
+ | The way back | A link to the Gateway. The starter's `launcher` is the identity issuer (`EVERYAPP_IDENTITY_ISSUER`, the Gateway's URL), or the dev host one label up under `everyapp dev`; the apps read `GATEWAY_URL` (set by the deploy, absent under `everyapp dev`) first, then the issuer. Where: the starter's header ("All apps"), OpenCRM's and OpenSEO's sidebar footer, Agentic Inbox's signed-in line ("Back to Every App"). The convention the identity page states for a new app: the first sidebar row, muted "‹ Every App", aria-label "Back to Every App" | `starter/src/serverFunctions/home.ts`, `starter/src/routes/index.tsx`; `open-crm/src/client/components/Sidebar.tsx`, `open-crm/src/serverFunctions/everyApp.ts`; `open-seo/src/client/components/Sidebar.tsx`; `agentic-inbox/app/routes/home.tsx`, `agentic-inbox/workers/everyapp/identity.ts` | `launcher` (the starter, dev-aware); `getEveryAppGatewayUrl()`: `GATEWAY_URL`, then `EVERYAPP_IDENTITY_ISSUER` |
18
+
19
+ ## Public routes with their own checks
20
+
21
+ | Pattern | What it is | Where | Copy this |
22
+ | --- | --- | --- | --- |
23
+ | A public route with its own token | OpenAnalytics' tracker: `/script.js` (GET) issues a signed collect token (an HMAC over the site id and the client's address, 12 hours, derived from `MAIN_SECRET`); `/collect` (POST) verifies it, then runs the site's blocking rules and bot checks before anything is ingested | `open-analytics/everyapp.config.ts` (`public`), `open-analytics/src/lib/edge/collector/script-endpoint.ts`, `open-analytics/src/lib/edge/auth/collect-token.ts`, `open-analytics/src/lib/edge/collector/collect.ts`, `open-analytics/src/lib/hono/routes/collect.ts` | `verifyCollectToken()` before anything is stored; the tracker posts `text/plain`, so the browser sends no preflight and `OPTIONS` stays private |
24
+ | A public tracker for another website | OpenCRM's `/t/*`: the loader, a per-site tracker with its config baked in, and a collector that always answers 204; the organization comes from the site id, the client's address only counts requests | `open-crm/src/server/features/tracking/trackingRoutes.ts`, `open-crm/everyapp.config.ts` | The route sets its own CORS and resource-policy headers; `withinAddressLimit()` before any work |
25
+ | A public route armed by a secret | An operator endpoint that answers 404 until `ERASURE_SECRET` is set, and then only to a request signed with it in the last five minutes | `open-crm/src/server/features/erasure/erasureRoute.ts` | Compare signatures in constant time; keep the secret `optional` and unset it after use |
26
+ | Share links and assets | Public pages, their data and the built assets they load, each listed with its method | `open-analytics/everyapp.config.ts` | `/share/**`, `/api/public/share/**`, `/assets/**`: list the assets a public page loads, or it renders bare |
27
+ | No public routes | `public: []` with a comment saying why nothing is open; the Gateway's own tool calls carry their own tokens | `open-seo/everyapp.config.ts`, `agentic-inbox/everyapp.config.ts` | Say why in the manifest comment |
28
+
29
+ ## The manifest, field by field
30
+
31
+ | Pattern | What it is | Where | Copy this |
32
+ | --- | --- | --- | --- |
33
+ | `id`, `name`, `description`, `icon` | The id names the hostname and the Worker; the description is the app library's one line; the icon an SVG the app serves | `open-crm/everyapp.config.ts` | `icon: "/everyapp-icon.svg"` |
34
+ | `resources.d1` | The database, bound as `env.DB`; the deploy creates it | `starter/everyapp.config.ts` | `resources: { d1: ["DB"] }` |
35
+ | `resources.kv` | Per-site settings read on the hot path | `open-analytics/everyapp.config.ts` (`SITE_SETTINGS_KV`), `open-seo/everyapp.config.ts` (`KV`) | One namespace per purpose, named for it |
36
+ | `resources.durableObjects` | `{ name, className }`, the class exported from the Worker entry; `worker` when a helper Worker defines it | `agentic-inbox/everyapp.config.ts` (`MAILBOX`, `EMAIL_AGENT`), `open-crm/everyapp.config.ts` (`CRM_AGENT`), `open-seo/everyapp.config.ts` (`AUDIT_SCRATCHPAD` on the `audit` Worker) | The comment above each: what one instance is (a mailbox, a research session) |
37
+ | `resources.workflows` and `workers` | Workflows by binding and class, one of them on a helper Worker; the helper declared with its service binding | `open-seo/everyapp.config.ts` | `workers: [{ name: "audit", binding: "AUDIT_ENGINE" }]` with `SITE_AUDIT_WORKFLOW` on it |
38
+ | An R2 bucket | Added in `cloudflare.config.ts` beside the manifest's bindings, named after the Worker (`bindings.r2({ name: worker.name })`); `resources.r2` in the manifest does the same. The deploy creates the bucket; an account that never enabled R2 stops with `R2_NOT_ENABLED` until the person enables it once in the Cloudflare dashboard | `open-crm/cloudflare.config.ts`, `open-seo/cloudflare.config.ts`, `agentic-inbox/cloudflare.config.ts` | Spread `worker.env` and add to it; import nothing the Worker imports there |
39
+ | `migrations` with drizzle | `{ engine: "drizzle", dir, config, binding }`, and a drizzle config that opens the local D1 `pnpm dev` serves | `open-crm/everyapp.config.ts`, `open-crm/drizzle/sqlite.config.ts`, `open-crm/drizzle/sqlite/0000_init.sql`; the starter's default is `starter/drizzle.config.ts` with no `migrations` field | `localD1Path()` from `@every-app/sdk/cloudflare` for drizzle-kit studio |
40
+ | `migrations` with hand-written SQL | `{ engine: "d1-sql", dir: "migrations", binding: "DB" }` and plain SQL files, one `;`-terminated statement after another | `open-analytics/everyapp.config.ts`, `open-analytics/migrations/0001_baseline.sql` | For an app whose schema drizzle doesn't own |
41
+ | `public` | `{ path, methods }` per route, `GET` by default; a route is listed only when something other than a signed-in member must reach it: a webhook, a collector, an OAuth callback. The starter lists none (its manifest's comment shows a webhook); OpenAnalytics opens its tracker, collector and share pages, OpenCRM its tracker; OpenSEO and Agentic Inbox nothing | `starter/everyapp.config.ts` (the comment), `open-analytics/everyapp.config.ts`, `open-crm/everyapp.config.ts` | Comment each route with why it is open; `/mcp` can't be one |
42
+ | `providers` with `why` | The AI provider the Gateway calls for the app; required unless `optional: true` | `open-crm/everyapp.config.ts` (`openrouter`), `agentic-inbox/everyapp.config.ts` (`openrouter` required, `workers-ai` optional) | One sentence for whoever sets the key |
43
+ | `capabilities`: an API key | `dataforseo` and `context` with `access: "read"` and a `why`; `optional` where a feature can say what is missing | `open-seo/everyapp.config.ts` | `context: { access: "read", optional: true, why }` |
44
+ | `capabilities`: an OAuth client with scopes | `google-workspace`, `microsoft-365` and `slack` with `access: "write"`, `scopes: [...]` (the least that does the job) and `optional: true`; the Gateway holds the client, the app keeps each member's tokens | `open-crm/everyapp.config.ts` | `scopes: ["gmail.read", "calendar.read"]` |
45
+ | `capabilities.email` and `capabilities.members` | Email as `access: "write"` (required for an inbox, optional for reports); members as `access: "read"` to know who can open the app | `agentic-inbox/everyapp.config.ts`, `open-analytics/everyapp.config.ts`, `starter/everyapp.config.ts` | The starter's `members` and `listMembers()` in `starter/src/serverFunctions/home.ts` |
46
+ | `secrets`, required | The app's own root secret, with a `why` that says how to make one | `open-analytics/everyapp.config.ts` (`MAIN_SECRET`) | "32 or more random characters, e.g. openssl rand -base64 32" |
47
+ | `secrets`, optional | Keys one feature needs, each with a `why`, and `docs` where a page exists (`GITHUB_TOKEN`); the feature says so where it is used | `open-crm/everyapp.config.ts` (`TOKEN_ENCRYPTION_KEY`, `GITHUB_TOKEN`, `OPENROUTER_MODEL`, `ERASURE_SECRET`) | `{ name, optional: true, why }`, plus `docs` when there is a page |
48
+ | `access: "everyone"` | Every member of the organization can open the app; owners and admins manage inside it. OpenSEO leaves the default | `open-crm/everyapp.config.ts`, `open-analytics/everyapp.config.ts`, `agentic-inbox/everyapp.config.ts` | The default is `admins`: say `everyone` when the app is for the team |
49
+ | `ai.models` | The model ids the Gateway accepts from the app; any other is `model_not_allowed` | `agentic-inbox/everyapp.config.ts` | List every id the code can send, per provider |
50
+ | `email.inbound` | The Worker exports an `email()` handler; the addresses are set after the deploy with `everyapp email set --inbound` | `agentic-inbox/everyapp.config.ts` | `email: { inbound: true }` |
51
+ | `build` and `dev` | Commands when the app's build is more than `cf build` (bundles to build first, an environment variable to set) | `open-analytics/everyapp.config.ts`, `open-seo/everyapp.config.ts` | `dev` must listen on `PORT` |
52
+ | `compatibilityDate` | The runtime date every build runs on | `open-analytics/everyapp.config.ts`, `open-seo/everyapp.config.ts` | Pin it when porting an app that ran under Wrangler |
53
+ | `cloudflare.config.ts` beyond the manifest | Crons, compatibility flags and plain text bindings read when the Worker is built | `open-crm/cloudflare.config.ts` (`crons: ["* * * * *"]`, `global_fetch_strictly_public`), `agentic-inbox/cloudflare.config.ts` (`AI_PROVIDER`, `AGENT_MODEL`), `open-seo/cloudflare.config.ts` | `everyAppWorker({ manifest, entrypoint, ctx, crons })`, then spread and extend |
54
+
55
+ ## Cloudflare resources
56
+
57
+ | Pattern | What it is | Where | Copy this |
58
+ | --- | --- | --- | --- |
59
+ | Workers AI as a binding | `resources.ai` binds `env.AI` on every Worker of the app: `env.AI.run()` directly (the Agents SDK, models the OpenAI-compatible routes don't carry, an AI Gateway by id), billed to the account with no Gateway budget or cap. Under `pnpm dev` every call throws `Binding AI needs to be run remotely` until `vite.config.ts` sets `remoteBindings: true` and `cf auth login` has run. For calls the Gateway caps and counts, list the `workers-ai` provider instead | `packages/sdk/src/cloudflare/worker.ts` (`addAiBindings()`); no app uses it yet: `agentic-inbox/workers/everyapp/gateway.ts` (`gatewayAi()`) is the provider path for code written against `env.AI.run()` | `resources: { ai: ["AI"] }`, then `await env.AI.run("@cf/meta/llama-3.1-8b-instruct", { prompt })` |
60
+
61
+ ## Sending and receiving email
62
+
63
+ | Pattern | What it is | Where | Copy this |
64
+ | --- | --- | --- | --- |
65
+ | Sending email | `sendEmail({ env, for, message })` through the Gateway's `email` capability; the Gateway checks the sender against what `everyapp email set --sender` allowed, and the budgets | `agentic-inbox/workers/everyapp/email.ts`; `open-analytics/src/lib/notifications/edge/delivery.ts` | `gatewayEmail()`: the SDK call shaped as the `send_email` binding upstream's code expects, every refusal an `EmailSendError` with the Gateway's code; OpenAnalytics skips the mail, and says so, on a Gateway without email |
66
+ | Receiving email | An `email()` export beside `everyApp()`'s `fetch`; the deploy makes the Email Routing rules from `email.inbound` and the addresses `everyapp email set --inbound` names | `agentic-inbox/workers/app.ts`, `agentic-inbox/workers/everyapp/inbound.ts`, `agentic-inbox/test/receive-email.test.ts` | `export default { ...everyApp(handler, { tools }), async email(event, env, ctx) { … } }`; rethrow so Email Routing retries or bounces |
67
+
68
+ ## AI through the Gateway
69
+
70
+ | Pattern | What it is | Where | Copy this |
71
+ | --- | --- | --- | --- |
72
+ | AI through the Gateway | `createGatewayFetch({ env, for })` as the `fetch` of an OpenAI-compatible client, with a base URL on the Gateway's `/api/ai/<provider>/…` path; the Gateway holds the key, so the client's own key is a placeholder | `open-crm/src/server/lib/openrouter.ts`; `agentic-inbox/workers/everyapp/gateway.ts` | OpenCRM: `createOpenRouter` over the Gateway fetch, the model from the optional secret `OPENROUTER_MODEL`; Agentic Inbox: `gatewayModel()` with `@ai-sdk/openai-compatible`, and `gatewayAi()` as a `run()` shim for code written against Workers AI |
73
+ | Every AI failure as a sentence | `ConfigurationError` (no Gateway and no dev key), `ai_provider_not_configured` (the key isn't set on the Gateway) and every other refusal read with `gatewayRefusal()`, each turned into one sentence that names the setting and who sets it | `agentic-inbox/workers/everyapp/agent-ai.ts`, `agentic-inbox/workers/everyapp/gateway.ts` | `aiSetupProblem()` before the first call; `aiProviderNotSetUpMessage()` names the command, never a value |
74
+ | Whose call it is | Attribution: while serving a request, `for: request`; for work no request started (a cron, a Workflow, an agent's turn), `for: { background: true, organizationId }` | `open-crm/src/server/lib/gateway-attribution.ts`, `open-seo/src/server/lib/gateway-attribution.ts`; `agentic-inbox/workers/everyapp/agent-ai.ts` (`forMailbox()`) | `serveEveryAppRequest(request, user, fn)` at the entry and in tools; `forOrganization(id, fn)` around background work |
75
+
76
+ ## Agents, Workflows and a second Worker
77
+
78
+ | Pattern | What it is | Where | Copy this |
79
+ | --- | --- | --- | --- |
80
+ | A Durable Object agent | One Durable Object per conversation or task, named for its organization; authorized in the Worker before the request reaches the object; the model through the Gateway | `open-crm/src/server/features/agent/CrmAgent.ts` (on `@cloudflare/think`), `open-crm/src/server/features/agent/agentRoutes.ts`; `agentic-inbox/workers/agent/index.ts` (on `@cloudflare/ai-chat`), `agentic-inbox/workers/app.ts`; `open-seo/src/server/features/sam/SamChatAgent.ts`, `open-seo/src/server.ts` | `routeAgentRequest(request, env, { onBeforeConnect, onBeforeRequest })` with a guard that checks the session is the caller's organization's |
81
+ | Workflows | A `WorkflowEntrypoint` a cron starts; the organization named once around `run()` and carried into every step | `open-seo/src/server/workflows/RankCheckWorkflow.ts`, `open-seo/src/server/workflows/pgStep.ts`, `open-seo/cloudflare.config.ts` (the cron) | `forOrganization()` around `run()`; `carryOrganization()` inside `pgStep()` |
82
+ | A second Worker | A helper Worker for work that should not share the app's memory (a crawler, Lighthouse); it exports its Workflow and Durable Object classes and an RPC `WorkerEntrypoint`, and only the app's Worker can reach it | `open-seo/src/audit-worker.ts`, `open-seo/vite-plugin-every-app.ts`, `open-seo/everyapp.config.ts` (`workers`) | `everyAppAuxiliaryWorker({ manifest, worker: "audit", entrypoint, ctx })` under the Cloudflare Vite plugin's `auxiliaryWorkers` |
83
+ | The lean Worker bundle | A Vite plugin that stubs dead-weight packages, swaps zod's locales for English alone, and fails the build when a denylisted module reaches the Worker's eager import graph | `open-seo/vite-plugin-lean-worker-bundle.ts` | `EAGER_DENYLIST` with an `expected` note per entry; the `generateBundle` assertion |
84
+
85
+ ## Tools
86
+
87
+ | Pattern | What it is | Where | Copy this |
88
+ | --- | --- | --- | --- |
89
+ | The starter's catalog | Four tools over the same queries as the server functions, the zod schemas shared; `instructions` for the set; `ToolError` for a message the model may read | `starter/src/tools.ts`, `starter/src/server/todos.ts` | `risk` per tool, `idempotent` where true, `handler: (input, { user }) => todos.listTodos(user.id)` |
90
+ | Tools per feature, with output schemas | One module per feature exporting a map, spread into one `defineTools()`; a `crmTool()` wrapper that adds `output` schemas, with the shapes several tools share (`personOutput`, `recordRefOutput`, `pageOf()`) | `open-crm/src/server/everyapp/tools.ts`, `open-crm/src/server/everyapp/tools/companies.ts`, `open-crm/src/server/everyapp/crm-tool.ts`, `open-crm/docs/maintainers/mcp.md` | People named by email, never by id, in what a model reads; "Never guess ids" in `INSTRUCTIONS` |
91
+ | Wrapping an existing tool registry | An app that had an MCP server keeps its registry and adapts each entry: `risk` from the annotations and the name, `objectSchema()` for input and output, `toolResult({ text, structured })` from the old `CallToolResult` | `open-seo/src/server/everyapp/tools.ts` | `riskOf()`; the old MCP server is off: the Gateway's `/mcp` serves the catalog (an app's host answers `/mcp` only with the manifest's `mcp: { by, why }`, which none of the four sets) |
92
+ | Tools over the private API, in-process | Each tool calls the same Hono API the dashboard uses, with the call's identity, so permissions and validation live in one place | `open-analytics/src/lib/mcp/tools.ts` | `resolveViewer()` from the call; `MAX_RESULT_BYTES` asking for a narrower question |
93
+ | Tools loaded on first use | The tools module behind a dynamic import on `/__everyapp/*`, so the app's eager bundle stays small | `open-crm/src/server.ts` | `if (pathname.startsWith("/__everyapp/")) { const { everyAppTools } = await import(...) }` |
94
+ | Schemas without zod 4.2 | `{ jsonSchema, validate }` from an older zod's `toJSONSchema` and Standard Schema | `agentic-inbox/workers/everyapp/tools.ts` | `plainSchema(schema)` |
95
+ | Testing the catalog | The catalog read the way the Gateway reads it (`POST /__everyapp/tools/list` with a system token), the risk of every tool checked, and calls as a read-only and a read-write caller | `open-crm/src/server/everyapp/catalog.test.ts`, `open-crm/src/server/everyapp/toolTestSupport.ts`; `agentic-inbox/test/tools.test.ts` | `reads` equals every `list_`, `get_` and `search_` tool |
96
+
97
+ ## Needs setup inside the app
98
+
99
+ Every app says, inside itself, what the Gateway hasn't got yet: the setting's name and who sets it, never a value, never the Gateway's own refusal sentence.
100
+
101
+ | Pattern | What it is | Where | Copy this |
102
+ | --- | --- | --- | --- |
103
+ | The starter's first screen | Who the Gateway signed in and what the app was allowed, read from the inlined manifest; a `GatewayRefusalError` from `listMembers()` is a state the page shows | `starter/src/routes/index.tsx`, `starter/src/serverFunctions/home.ts` | `whoCanOpen()`: catch the refusal, return its code |
104
+ | A paused agent | A checklist of the Gateway's keys for the agent (`OPENROUTER_API_KEY`, `CONTEXT_API_KEY`): done, checking or missing, each with what being unset costs and the command an owner or admin runs; members are told whom to ask | `open-crm/src/server/features/setup/services/SetupService.ts`, `open-crm/src/client/features/agent/AgentPaused.tsx`, `open-crm/src/serverFunctions/setup.ts` | `pausedResearchMessage(canManage)`: two sentences, by role |
105
+ | A required secret | Without the root secret every page redirects to one that names the secret and the command (`pnpm everyapp secret set MAIN_SECRET --app analytics`), or `.dev.vars` under `everyapp dev` | `open-analytics/src/middleware.ts`, `open-analytics/src/routes/$locale.runtime-config-error.tsx` | `loadRuntimeSetupMode()` asks the Worker whether it runs under `everyapp dev` |
106
+ | A capability | `integrationStatus({ env, provider }).ready` without calling the provider, driving the "set up DataForSEO" prompt | `open-seo/src/server/lib/provider-access.ts`, `open-seo/src/serverFunctions/config.ts` | `hasProviderAccess()` |
107
+ | Email | A banner before the person writes: no sender allowed yet (`everyapp email set --sender`) or no `EMAIL_FROM`; owners and admins get the command with the mailbox's domain filled in, members whom to ask; Send stays enabled, the server decides | `agentic-inbox/app/components/SendingOffBanner.tsx` | `config.ready === false` from `GET /api/v1/config` |
108
+
109
+ ## Cookies, headers and the bundle
110
+
111
+ | Pattern | What it is | Where | Copy this |
112
+ | --- | --- | --- | --- |
113
+ | App-host session cookies | The Gateway strips every `Cookie` header before a request reaches the app and refuses requests whose cookies repeat its own session cookie's name, so an app can never read back a cookie it set; `withoutCookies()` drops any `Set-Cookie` the app's framework adds (a WebSocket upgrade passes through) | `open-crm/src/server/everyapp/cookies.ts`, `open-seo/src/server/everyapp/cookies.ts`; `open-analytics/src/server.ts` | Wrap the response of `everyApp()`'s `fetch` in `withoutCookies()`; keep preferences in the browser |
114
+ | Security headers the app sets itself | `frame-ancestors 'self'`, an `img-src` of only what the app shows, a per-response nonce for inline scripts, on HTML documents without a policy of their own. OpenAnalytics sets one static policy on every page instead (`withPageHeaders` in `open-analytics/src/server.ts`: `frame-ancestors 'none'`, no nonce) | `open-crm/src/server.ts` | `APP_CSP` |
115
+
116
+ ## Tests
117
+
118
+ | Pattern | What it is | Where | Copy this |
119
+ | --- | --- | --- | --- |
120
+ | Tests over queries with an in-memory SQLite | libsql `:memory:` with the app's migrations applied by drizzle's migrator, `@/db` mocked to it; one test that two people's rows stay apart | `starter/src/server/todos.test.ts` | `vi.hoisted` and `vi.mock("@/db", …)`; `migrate(db, { migrationsFolder })` |
121
+ | A test database with every migration | `createSqliteTestDb()` applies every production migration so repositories run their real queries; `seedTwoOrganizations()` and tests that one organization never sees another's rows | `open-crm/src/server/testing/testDb.ts`, `open-crm/src/server/features/agent/repositories/isolation.test.ts` | Mock `cloudflare:workers`, `@/db` and `@/db/runBatch` from the hoisted database |
122
+ | A fake Gateway binding | A `fetch` that records every call's URL, body and identity header, standing in for `EVERY_APP_GATEWAY` | `agentic-inbox/test/with-gateway.test.ts`, `agentic-inbox/test/gateway-email.test.ts` | `fakeGateway(reply)` |
123
+ | Public routes under test | The collect token's issue and verify, and the collector's refusals | `open-analytics/src/lib/edge/__tests__/collect-token.test.ts`, `open-analytics/src/lib/edge/__tests__/collect.test.ts` | Test the refusal paths first |
124
+
125
+ ## Dev vs production
126
+
127
+ | Pattern | What it is | Where | Copy this |
128
+ | --- | --- | --- | --- |
129
+ | `.dev.vars` | Locally there is no Gateway: the app's own secrets and the Gateway settings its manifest names (a provider key, `EMAIL_FROM`, `EVERYAPP_EMAIL_SENDERS`) come from `.dev.vars`, gitignored; `pnpm dev` names the ones missing | `agentic-inbox/.dev.vars.example`, `open-analytics/.dev.vars.example`, `open-crm/.dev.vars.example`; `starter/src/env.d.ts` | Ship a `.dev.vars.example` that names each variable and where it comes from, with no values |
130
+ | The manifest inlined for the page | The Worker can't import `everyapp.config.ts`; Vite's `define` inlines it as `__EVERYAPP_MANIFEST__` for the first screen | `starter/vite.config.ts`, `starter/src/serverFunctions/home.ts`, `starter/src/env.d.ts` | `remoteBindings: false`: local dev never touches remote resources |
131
+ | Dev or deployed, in the app | `env.EVERYAPP_DEV` says which. The starter's `launcher` is `EVERYAPP_IDENTITY_ISSUER` (the Gateway's URL) when deployed and the dev host one label up under `everyapp dev`; the apps read `GATEWAY_URL` (set by the deploy) first, then the issuer | `starter/src/serverFunctions/home.ts`; `open-crm/src/serverFunctions/everyApp.ts`, `agentic-inbox/workers/everyapp/identity.ts`; `open-analytics/src/routes/$locale.runtime-config-error.tsx` | Name `.dev.vars` in dev and `pnpm everyapp configure <app>` when deployed |
132
+ | What a port changes | What the Gateway replaces in an app that ran elsewhere (sign-in, keys, MCP, email), in one table | `open-crm/docs/maintainers/every-app.md`, `open-seo/docs/maintainers/every-app.md`, `agentic-inbox/docs/EVERY_APP.md` | Keep such a document in the app |
133
+
134
+ ## Release and install
135
+
136
+ | Pattern | What it is | Where | Copy this |
137
+ | --- | --- | --- | --- |
138
+ | Release and install | `pnpm everyapp release` packs the production build, the migrations and a `release.json` into `everyapp-release.tar.gz`; from a GitHub release of the app's repository, anyone installs it with `pnpm everyapp install <repository url>`, no clone. `pnpm pack-for-app <app dir> --workflow` from an Every App checkout vendors the CLI and the SDK into the app's repository (`file:` tarballs under `vendor/`) and adds the Release workflow that publishes the tarball on every push. The vendored CLI runs `dev`, `db migrate` and `release`, not `deploy`: deploys run from an Every App checkout (`pnpm everyapp install <url>` there, or that checkout's CLI from the app's directory). OpenCRM, OpenSEO and Agentic Inbox bind an R2 bucket, so R2 is enabled once on the account before their first install (`R2_NOT_ENABLED` otherwise); OpenAnalytics needs nothing | `docs/runbooks/app-releases.md`, `scripts/pack-for-app.mjs`, `scripts/pack-for-app/release.yml` and `landing-page/content/agent/deploy.md` ("Release installs") in the checkout; `open-crm/docs/maintainers/releases.md` (tags, notes) | `pnpm pack-for-app <app dir> --workflow` once, then commit `vendor/`, `package.json`, `pnpm-lock.yaml` and the workflow; after a platform change the same command without `--workflow`; the version in `package.json` names the release |
139
+
140
+ A pattern missing here: read the four manifests first, then search the app for the SDK export you need (`createGatewayFetch`, `integrationFetch`, `sendEmail`, `listMembers`, `defineTools`).
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: everyapp
3
+ description: Create, run, deploy and manage private web apps on Cloudflare with everyapp (Every App). Use before running any everyapp command, to deploy an app, set a secret, query its database, check the Gateway, or answer any "how do I" question about Every App.
4
+ ---
5
+
6
+ everyapp: self-hosted platform for private web apps on Cloudflare. While
7
+ publishing is paused the command is `pnpm everyapp` (never `npx everyapp`),
8
+ run from the Every App checkout or from an app's directory (its package.json
9
+ links the CLI).
10
+
11
+ Docs: two worlds. The user docs (https://everyapp.com/docs) are short and
12
+ for the person. The agent docs are yours: https://everyapp.com/llms.txt
13
+ lists every page with a one-line summary; a page is Markdown at its URL
14
+ plus .md (https://everyapp.com/docs/agent/manifest.md); in a checkout they
15
+ are landing-page/content/agent/*.md. Pages: setup (driving setup for a
16
+ person, every handoff), cli (every command and flag), json (the --json
17
+ protocol), errors (every code and its fix), troubleshooting (by symptom),
18
+ status (the report, every line, and its fixes in order), manifest
19
+ (everyapp.config.ts), identity (headers, token, env, SDK), secrets
20
+ (secrets vs Gateway settings vs capabilities), dev (dev vs production),
21
+ email, events (webhooks, email alerts, the signature, every event type,
22
+ emit()), mcp (tools, connector, OAuth), deploy (the pipeline, what the policy
23
+ check refuses), providers (API providers: the catalog, registering one,
24
+ a spec's fields), access (Cloudflare Access), workspace (the workspace from
25
+ the terminal and from an AI client: every command and every tool), gateway
26
+ (everything a Gateway offers: apps vs MCP apps vs the compact MCP surface,
27
+ people, groups and caps, the CLI by job, what to suggest next, other MCP
28
+ servers), build (the build-every-app skill as a page), patterns (its
29
+ example-patterns index), repo/* (security model, capabilities, runbooks).
30
+ Step-by-step setup of each
31
+ integration (the Gateway, Access, email, DataForSEO, Context, OpenRouter,
32
+ OpenAI, Workers AI, Google, Microsoft 365, Slack; every catalog provider by
33
+ id in setup/providers.md): setup/README.md beside this file.
34
+ Answering "how do I": fetch the page for the subject and answer from it
35
+ with the exact command or manifest field, naming the page. A failed
36
+ command: look its code up on errors (anchor: the code, lowercased) before
37
+ trying anything else. Never guess a flag: cli has them all.
38
+ Building an app: the Gateway signs people in, so never build a login page
39
+ or auth; read the user with the SDK (identity page), and scope every query
40
+ by user.id: the Gateway admits every member of the organization. A new app
41
+ edits everyapp.config.ts for name, resources, public, providers,
42
+ capabilities, secrets and email (manifest page), and the Gateway gives it
43
+ nothing it doesn't declare there. AI: list the provider, providers:
44
+ ["openai"] (or openrouter, workers-ai), and call it with the SDK's
45
+ createGatewayFetch({ env, for: request }), a fetch for
46
+ http://every-app-gateway.internal/api/ai/<provider>/... (an OpenAI client
47
+ takes it as fetch, with http://every-app-gateway.internal/api/ai/openai/v1
48
+ as baseURL; the other two base URLs and the code are on the identity
49
+ page); the Gateway holds the key, never the app, and a provider the
50
+ manifest doesn't list is refused provider_not_allowed 403. A capability
51
+ (members, email, an OAuth client, DataForSEO) is listed under
52
+ capabilities with access and a why and called with listMembers(),
53
+ sendEmail() or integrationFetch() (secrets page); an OAuth client with
54
+ scope sets (google-workspace, microsoft-365, slack) is listed with
55
+ scopes: [...] and held to them; after a schema change run pnpm run
56
+ db:generate; a value only dev needs goes in .dev.vars, never in git (dev
57
+ page). The way back: the first row of the app's sidebar (or of its page
58
+ header when it has no sidebar) is a muted "‹ Every App" link to
59
+ GATEWAY_URL (aria-label "Back to Every App"), above the app's own name,
60
+ so every app on the Gateway reads the same; put nothing else about the
61
+ Gateway in the footer.
62
+
63
+ Pass --json. One JSON event per line; "done" = success; exit 2 = show the
64
+ user the last action_required (url, code), then run its data.rerun (the same
65
+ command, unless it names another); exit 1 = show the error; if data.authorize
66
+ is set, ask before using data.rerun. waiting: true = keep reading (5 min
67
+ for the device login, up to 15 for the owner link).
68
+
69
+ State: pnpm everyapp status --json (data.fixes says what to run when something is off;
70
+ data.next says what to do next: at most four { code, command | url, note, why }
71
+ from the Gateway's state, never repeating a fix; tell the person
72
+ each why and offer the command or page; data.apps[].setup says what each
73
+ app needs set: ready, settings, unmet (each with why and docs), and
74
+ command, pnpm everyapp configure <app>)
75
+ Gateway: pnpm everyapp setup --domain <host> --json (install, upgrade, repair;
76
+ a fresh install runs 5-20 min: build, certificate, then up to 15 min
77
+ waiting for the owner link, so give it a long timeout or background
78
+ it and keep reading; run data.rerun as given (a needsTerminal rerun
79
+ goes to the person))
80
+ New app: pnpm everyapp create ../<id> --json (from the every-app folder: the
81
+ app is its own repository beside it, linked to the checkout; a bare
82
+ <id> makes apps/<id> inside the checkout instead) then cd into it and
83
+ edit src/; the manifest is everyapp.config.ts
84
+ Run: pnpm everyapp dev (long-running, human-facing: start it in the
85
+ background and read the printed URL; no --json; --port when 8787 is
86
+ taken; stop it with Ctrl-C/SIGINT to the everyapp process, never a
87
+ pattern kill of vite or workerd; a pnpm install or deploy meanwhile
88
+ rebuilds the SDK and breaks dev for a few seconds)
89
+ Ship: pnpm everyapp deploy --json (from the app directory; safe to repeat;
90
+ also upgrades the Gateway to this checkout when it has to rebind).
91
+ No Gateway yet: action_required install_gateway; ask the user, run
92
+ its data.rerun (setup), then deploy again. done.data.publicRoutes
93
+ lists the routes the manifest opens to the internet without
94
+ sign-in; tell the person which.
95
+ Release: someone else's app, without a clone: pnpm everyapp install
96
+ https://github.com/<owner>/<repo> --json (its latest release's
97
+ everyapp-release.tar.gz; a tarball URL or file works too). NO_RELEASE:
98
+ the repository publishes none; clone it, pnpm install in it, then
99
+ this checkout's pnpm everyapp deploy from its directory. Never
100
+ build a repository from its URL yourself.
101
+ To publish one: pnpm everyapp release, then attach the
102
+ tarball to a GitHub release of the app's repository, or give
103
+ the repository the Release workflow that does it on every push
104
+ (pnpm pack-for-app <app> --workflow from this checkout;
105
+ docs/runbooks/app-releases.md).
106
+ Data: pnpm everyapp db migrate (local) pnpm everyapp db query "<sql>" [--local] --json
107
+ (a production write stops with CONFIRMATION_REQUIRED: add --yes only
108
+ if the user said so). pnpm everyapp db restore --json undoes the last
109
+ deploy's migrations (and every write since): only when the user asks.
110
+ Postgres: an app with resources.hyperdrive or migrations.dialect "postgresql"
111
+ reads POSTGRES_DATABASE_URL (and <BINDING>_ORIGIN_URL) from
112
+ .everyapp/secrets.env, which the user writes; never print, commit or
113
+ ask for its values in chat. deploy runs drizzle-kit migrate before
114
+ the upload.
115
+ Secrets: pnpm everyapp configure <app> --json sets up an app: every setting its
116
+ manifest declares, each where it is held (an AI key or a capability's
117
+ client on the Gateway, once for every app; the app's own secrets on
118
+ its Worker, after its first deploy); live at once, no deploy. At a
119
+ terminal it shows what is set (names only), asks for each required
120
+ setting that isn't with a hidden prompt (why, where to get it and its
121
+ guide above), which member of an any-of group, then offers the
122
+ optional ones; from you (no prompt): NAME=value lines on stdin (one
123
+ per line, from a command's output) or --from-env (each unset
124
+ setting from the variable of its own name; stdin isn't read then).
125
+ A value never goes on the command line (VALUE_ON_COMMAND_LINE). One
126
+ name at a time: pnpm everyapp secret set NAME [--app <id>] --json
127
+ (the value from a hidden prompt, stdin, or --from-env VAR). The name
128
+ decides where it goes. A Gateway setting (OPENAI_API_KEY, EMAIL_FROM,
129
+ GOOGLE_WORKSPACE_CLIENT_ID, …) is set on the Gateway from any
130
+ directory. An app's own secret must be listed under `secrets` in its
131
+ everyapp.config.ts and is set after its first deploy, from the app's
132
+ directory or from anywhere with --app <id> (SECRET_NOT_DECLARED: add
133
+ it there first). secret unset NAME [--app <id>]; secret list [<app>]
134
+ --json (one app's settings, where each is held and whether it is set,
135
+ with data.app.setup; elsewhere with no app named: the Gateway's names
136
+ and every app's settings).
137
+ Needs setup: read data.apps[].setup; for each unmet setting show its why and docs
138
+ and read its setup guide (.claude/skills/everyapp/<setup>); then run
139
+ setup.command, pnpm everyapp configure <app>, without a value passing
140
+ through the chat: tell the person to run it in their own terminal (it
141
+ asks for each setting with a hidden prompt and says where to get it)
142
+ and say when done; or drive it yourself, --json always: NAME=value
143
+ lines on stdin from a command's output, or --from-env for variables
144
+ the person exported before starting you. Never a value on the command
145
+ line; never ask for, print, repeat or invent a value. An app whose
146
+ setup.ready is false doesn't open (status shows "needs setup"; deploy
147
+ warns APP_NEEDS_SETUP and puts the command under next); an any-of
148
+ group (setup.groups) needs one member set, not all: configure asks
149
+ which.
150
+ Email: pnpm everyapp email set --sender "*@<domain>" --inbound <address> --json
151
+ In the app's directory, after its first deploy; nothing is deployed. --sender
152
+ replaces the From addresses the Gateway lets the app name (everyapp.config.ts
153
+ lists capabilities.email); --inbound replaces the addresses routed to its
154
+ email() handler (everyapp.config.ts declares email: { inbound: true }) and
155
+ makes the Email Routing rules now. EMAIL_ROUTING_DISABLED needs the user's
156
+ yes: rerun with --enable-email-routing only if they said so. email unset
157
+ senders|inbound clears; email show prints both.
158
+ Access: pnpm everyapp access on --json (Cloudflare Access in front of the Gateway
159
+ and its apps: finds or creates the Zero Trust team (--team NAME when the
160
+ account has none: permanent, ask the user), keeps both sign-in methods
161
+ (Sign in with Cloudflare for the account's members, the one-time PIN for
162
+ everyone else), keeps the allow policy with every member's and invitee's
163
+ email, puts two Access applications in front of the hosts with the
164
+ Gateway's MCP, OAuth and /health and each app's public routes let through
165
+ (an app host's /mcp only where its manifest sets mcp), and sets the three
166
+ CLOUDFLARE_ACCESS_* Gateway settings in one version. Rerunnable; run again
167
+ after inviting someone. access off asks (--yes); access show reports. Each
168
+ existing member then signs in once with their password through Access;
169
+ tell the user who (done.data.signIn).
170
+ Set up: what the Gateway holds for apps, end to end (the provider's side, the
171
+ key or OAuth client, configure or secret set, the check): read
172
+ setup/README.md in this skill first, then the file it names, each on
173
+ its own with the file-reading tool (together they pass a shell's
174
+ output cap). Cloudflare and the Gateway
175
+ (setup/gateway.md), Cloudflare Access (setup/access.md), email
176
+ (setup/email.md), API keys (setup/dataforseo.md, context.md,
177
+ openrouter.md, openai.md, workers-ai.md), OAuth clients
178
+ (setup/google.md, microsoft-365.md, slack.md), any other catalog
179
+ provider (setup/providers.md#<id>, generated from the catalog); a
180
+ provider registered on the Gateway has no file: pnpm everyapp
181
+ provider show <id> prints its spec. A key or client secret
182
+ never goes through the chat: README.md says the three ways it gets in.
183
+ Remove: pnpm everyapp delete <id> --json never add --purge, --force-dns,
184
+ --allow-delete-*, --allow-downgrade, or --yes to delete unless the
185
+ user asked.
186
+ MCP apps: pnpm everyapp mcp on dataforseo --json offers a data provider's
187
+ tools (dataforseo_<tool>) to members' AI clients through the Gateway's
188
+ /mcp with the Gateway's key (DATAFORSEO_API_KEY, setup/dataforseo.md);
189
+ no page, nothing to deploy (the launcher lists it under "MCP apps").
190
+ pnpm everyapp mcp add <url> --auth bearer --json does the same for
191
+ any remote MCP server (Streamable HTTP): <id>_<tool>, its credential
192
+ a Gateway setting <ID>_MCP_TOKEN set with secret set and pinned to
193
+ the URL's origin (setup/remote-mcp.md). add looks at the server once
194
+ and only warns. mcp list shows each app's kind, on/off/needs setup,
195
+ tools and key; mcp show <id> its URL, credential and every tool with
196
+ its risk; mcp tool <id> <tool> --risk read|write|destructive or --off
197
+ overrides one tool; mcp refresh <id> re-lists. mcp off <id> stops
198
+ serving and keeps access (mcp on is instant); mcp remove <id> deletes
199
+ it (the key stays; before 2026-10-25 off deleted it). The credential
200
+ never goes through the chat (setup/README.md's three ways). provider
201
+ on|off still run mcp on|off through launch week; provider list is the
202
+ API providers' list. You, owners and admins get it;
203
+ others are granted in Admin → Apps → Manage access. An owner or admin
204
+ caps an app's provider calls per month in Admin → Usage (off by
205
+ default): past it a provider tool answers a tool error naming the
206
+ cap, and an app's brokered call gets 429 app_monthly_cap_exceeded
207
+ until the next UTC month.
208
+ APIs: pnpm everyapp provider add stripe.json --json registers an API
209
+ provider the catalog doesn't have on the Gateway: a JSON spec (hosts,
210
+ auth, settings named after its id, STRIPE_…; never a value; fields
211
+ and rules on the providers page), from a file or an https URL. Then
212
+ pnpm everyapp secret set STRIPE_API_KEY (it names the provider and
213
+ the hosts the key goes to, and binds the key to them: a row replaced
214
+ with --replace needs the key set again). An app lists it under
215
+ capabilities like a catalog provider and calls integrationFetch().
216
+ provider list (the catalog and the rows), provider show <id> (the
217
+ whole spec; --save writes .everyapp/providers/<id>.json for dev),
218
+ provider remove <id> (PROVIDER_IN_USE while an app lists it).
219
+ Workspace: pnpm everyapp workspace rename "Acme Corp" --json renames the workspace
220
+ (the Gateway's organization: the name in the switcher and in invitations;
221
+ 1 to 64 characters, no line breaks). Nothing is deployed; members see it
222
+ on their next page. Owners and admins can do the same under Manage
223
+ workspace → Rename. WORKSPACE_NOT_CLAIMED: the Gateway has no owner yet.
224
+ Roles: pnpm everyapp workspace role <email> owner|admin|member --json sets a
225
+ member's role in the workspace under the Cloudflare login (the Users
226
+ page does the same). Owners are peers and the workspace always keeps
227
+ one: the last owner's role is refused (LAST_OWNER); someone still
228
+ invited isn't a member yet (MEMBER_NOT_FOUND).
229
+ Manage: pnpm everyapp workspace --help the rest of Manage workspace from the
230
+ terminal, under the Cloudflare login: show, users, invite (prints the
231
+ link; nothing is mailed), invite-link, uninvite, deactivate, reactivate,
232
+ remove (--yes), reset-link, groups, group create|set|delete|members|apps,
233
+ apps, app set|access|cap, usage, activity, tokens, token revoke,
234
+ connections, connection revoke; --json on each. --org <slug> acts on
235
+ another organization (a name two organizations share is refused:
236
+ ORG_AMBIGUOUS; scripts pass the slug or id). deactivate and remove keep
237
+ the last owner who can act (LAST_OWNER); a ban is account-wide, so
238
+ deactivate checks every organization the person owns.
239
+ Events: pnpm everyapp events add <url> --events 'cap.*,member.*' --json sends
240
+ those Gateway events to a webhook (Slack workflow, Zapier, n8n), signed;
241
+ the generated secret is data.secret, shown once: hand it to the person,
242
+ never invent or echo one. --email admins mails owners and admins instead.
243
+ events list|test <id>|deliveries --dead|redeliver <id>; events types lists
244
+ types. An app emits its own with emit() (app.<id>.<name>), declaring
245
+ nothing. Everything: /docs/agent/events.
246
+ Tools: an app's AI tools are defineTools() in src/tools.ts; the Gateway
247
+ serves them at https://<gateway>/mcp as <app>_<tool> (dev:
248
+ http://<app-id>.localhost:8787/mcp) and snapshots them at deploy.
249
+ How to write, connect and debug them: mcp.md beside this file.
250
+ Wipe: pnpm everyapp teardown --account-id <id> --dry-run --json deletes
251
+ EVERYTHING Every App made in one Cloudflare account (the Gateway too):
252
+ only on a dedicated test account the user names, never one they use.
253
+ Always --dry-run --json first and show the user data.plan. It needs
254
+ EVERYAPP_TEST_ACCOUNTS=<id> and CLOUDFLARE_API_TOKEN scoped to that
255
+ account in the environment, which the user sets. Without --confirm it
256
+ stops with TEARDOWN_CONFIRMATION_REQUIRED (authorize): never add
257
+ --confirm <id> unless the user explicitly asked for the deletion.
258
+
259
+ Security: the Gateway signs people in and passes identity to the app in a
260
+ header the SDK verifies (everyApp(), getEveryAppUser(),
261
+ requireEveryAppUser(), a tool call's identity); never read who the
262
+ user is from a client-supplied header, cookie or parameter. Routes
263
+ are private unless listed under `public` in everyapp.config.ts; a
264
+ public route is open to the internet and does its own checks.
265
+ Before opening a pull request that touches identity, public routes,
266
+ tools, secrets or queries, review the changes locally against the
267
+ app boundaries in security-review.md beside this file: in Claude
268
+ Code, /security-review (its built-in review of the branch's pending
269
+ changes) pointed at that file; any other agent reads git diff
270
+ against the list there. No CI job and no API key: the review runs
271
+ where you run. Fix what it finds, or say in the pull request why a
272
+ finding is a false positive. What the Gateway promises and what an
273
+ app must do itself: docs/threat-model.md in the Every App checkout
274
+ (https://everyapp.com/docs/agent/repo/threat-model).
275
+
276
+ Only a person can: approve the Cloudflare login, open the owner link, pick
277
+ a workers.dev subdomain, pay for Workers Paid, buy a domain. Interactive
278
+ setup optionally offers browser deployment: a Yes/No prompt (default No),
279
+ then a hidden Cloudflare API token prompt. Never ask for its value in chat.
280
+ It then offers Cloudflare Access (Yes by default; the team name asked for
281
+ when the account has none); under --json neither is asked, and --yes
282
+ answers No to both.
283
+ Handing off: stop, tell the person in one or two lines what to do (the
284
+ action's url and code, what to click, what to come back with), and resume
285
+ with data.rerun when they say it is done. Two more only a person can do:
286
+ pick the account (choose_account: data.choices, each with its rerun) and
287
+ confirm in a terminal a Worker changed outside everyapp (rerun_interactive,
288
+ data.needsTerminal: true: hand them the command without --json; adding it
289
+ back loops). https://everyapp.com/docs/agent/setup.md has the words for
290
+ each handoff and the order of a whole setup.
291
+ Full reference: https://everyapp.com/docs/agent/cli.md (every command and flag),
292
+ https://everyapp.com/llms.txt (every page).