everyapp 0.2.2 → 0.3.0-rc.1

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 (120) hide show
  1. package/README.md +6 -77
  2. package/bin/everyapp.js +235 -0
  3. package/dist/access-F3GKBKAW.js +1 -0
  4. package/dist/activity-4MFMH5JP.js +12 -0
  5. package/dist/apps-63K2DGVT.js +9 -0
  6. package/dist/chunk-2UFJRKEE.js +4 -0
  7. package/dist/chunk-3AAHHUJC.js +11 -0
  8. package/dist/chunk-3Z53CH4F.js +6 -0
  9. package/dist/chunk-5ZTFC6DK.js +1 -0
  10. package/dist/chunk-6O3GJS5H.js +15 -0
  11. package/dist/chunk-74EST4VW.js +2 -0
  12. package/dist/chunk-7LG3F4FF.js +10 -0
  13. package/dist/chunk-AUDGOIHE.js +1 -0
  14. package/dist/chunk-BHMRY53V.js +2 -0
  15. package/dist/chunk-DQWOXSL3.js +32 -0
  16. package/dist/chunk-E3EPK27P.js +10 -0
  17. package/dist/chunk-E7EUXUVD.js +1 -0
  18. package/dist/chunk-ECLJ5IAL.js +126 -0
  19. package/dist/chunk-EQX4GKJ2.js +4 -0
  20. package/dist/chunk-EUNGJMJ6.js +20 -0
  21. package/dist/chunk-F6IH47GT.js +4 -0
  22. package/dist/chunk-G64S77GC.js +33 -0
  23. package/dist/chunk-GD2JZMD3.js +1 -0
  24. package/dist/chunk-GRFPP4UW.js +7 -0
  25. package/dist/chunk-IA534DGW.js +10 -0
  26. package/dist/chunk-IG26HYRR.js +45 -0
  27. package/dist/chunk-IZEOAOKL.js +6 -0
  28. package/dist/chunk-JYKRUS5C.js +1 -0
  29. package/dist/chunk-KMAOZRV4.js +18 -0
  30. package/dist/chunk-MC56CUQI.js +7 -0
  31. package/dist/chunk-MDORVBPD.js +1 -0
  32. package/dist/chunk-N4E5YE4X.js +5 -0
  33. package/dist/chunk-NJFWEYX7.js +1 -0
  34. package/dist/chunk-OEOVNJAN.js +1 -0
  35. package/dist/chunk-OVKVV6MK.js +2 -0
  36. package/dist/chunk-PL6EORDJ.js +15 -0
  37. package/dist/chunk-PMPIMHKG.js +6 -0
  38. package/dist/chunk-QZWSSWMP.js +22 -0
  39. package/dist/chunk-R4CGF622.js +2 -0
  40. package/dist/chunk-RFVMR32Y.js +1 -0
  41. package/dist/chunk-RQFHVU3Y.js +25 -0
  42. package/dist/chunk-SAT3PGZU.js +57 -0
  43. package/dist/chunk-SPPR6WCI.js +1 -0
  44. package/dist/chunk-TPWSGRNT.js +30 -0
  45. package/dist/chunk-VVQRET35.js +2 -0
  46. package/dist/chunk-VXJQGJ7Y.js +2 -0
  47. package/dist/chunk-WEM3WLOC.js +1 -0
  48. package/dist/chunk-Y3VIFPN5.js +1 -0
  49. package/dist/chunk-YJBYZWSP.js +22 -0
  50. package/dist/chunk-ZYL2ZHCF.js +98 -0
  51. package/dist/configure-2KMHM22T.js +8 -0
  52. package/dist/create-ELEJMZBS.js +22 -0
  53. package/dist/db-MZIAKGBN.js +9 -0
  54. package/dist/delete-H47KPJSB.js +3 -0
  55. package/dist/deploy-HGFJLLUO.js +1 -0
  56. package/dist/dev-62VA7UEM.js +86 -0
  57. package/dist/email-NNFD3VVB.js +10 -0
  58. package/dist/events-JDY76EG6.js +15 -0
  59. package/dist/groups-NQFXDZZP.js +8 -0
  60. package/dist/index.js +92 -1
  61. package/dist/install-JTZWRZIL.js +1 -0
  62. package/dist/login-P6LYQQWP.js +1 -0
  63. package/dist/mcp-NCFWEBMD.js +1 -0
  64. package/dist/provider-WVBI2VED.js +13 -0
  65. package/dist/release-6FYSPXXA.js +1 -0
  66. package/dist/restore-XBU7DOJV.js +3 -0
  67. package/dist/secret-ML5EXS3O.js +1 -0
  68. package/dist/setup-IEXCISLP.js +1 -0
  69. package/dist/status-OPLPKJ4U.js +4 -0
  70. package/dist/teardown-3JMMLK6O.js +22 -0
  71. package/dist/update-37ZDATLR.js +1 -0
  72. package/dist/workspace-DAFZVYQ2.js +21 -0
  73. package/package.json +30 -27
  74. package/skills/build-every-app/SKILL.md +199 -0
  75. package/skills/build-every-app/patterns.md +134 -0
  76. package/skills/everyapp/SKILL.md +292 -0
  77. package/skills/everyapp/mcp.md +336 -0
  78. package/skills/everyapp/security-review.md +61 -0
  79. package/skills/everyapp/setup/README.md +263 -0
  80. package/skills/everyapp/setup/access.md +178 -0
  81. package/skills/everyapp/setup/context.md +117 -0
  82. package/skills/everyapp/setup/dataforseo.md +287 -0
  83. package/skills/everyapp/setup/email.md +366 -0
  84. package/skills/everyapp/setup/gateway.md +243 -0
  85. package/skills/everyapp/setup/google.md +360 -0
  86. package/skills/everyapp/setup/microsoft-365.md +281 -0
  87. package/skills/everyapp/setup/openai.md +318 -0
  88. package/skills/everyapp/setup/openrouter.md +309 -0
  89. package/skills/everyapp/setup/providers.md +353 -0
  90. package/skills/everyapp/setup/remote-mcp.md +199 -0
  91. package/skills/everyapp/setup/slack.md +344 -0
  92. package/skills/everyapp/setup/workers-ai.md +138 -0
  93. package/bin/__everyapp_bash_complete.js +0 -12
  94. package/dist/bash-complete.js +0 -3
  95. package/dist/chunk-7BOQJFI5.js +0 -8
  96. package/dist/chunk-7YVJFS4A.js +0 -1
  97. package/dist/chunk-BIXKPCDH.js +0 -2
  98. package/dist/chunk-DLGPLHBH.js +0 -47
  99. package/dist/chunk-FD6K2YA7.js +0 -9
  100. package/dist/chunk-GAJEQYJH.js +0 -39
  101. package/dist/chunk-O2KDOP5R.js +0 -13
  102. package/dist/chunk-OI2MBGBG.js +0 -4
  103. package/dist/chunk-POSDLD3G.js +0 -8
  104. package/dist/chunk-RB5D2R45.js +0 -1
  105. package/dist/chunk-U4LIQ5VR.js +0 -1
  106. package/dist/chunk-VEYSNPDH.js +0 -3
  107. package/dist/chunk-VP6SKW44.js +0 -7
  108. package/dist/chunk-WO3NE7ZU.js +0 -7
  109. package/dist/chunk-XTO2VTPY.js +0 -6
  110. package/dist/chunk-YJ2DKIZS.js +0 -1
  111. package/dist/create-DYQ3NBWJ.js +0 -22
  112. package/dist/deploy-PYTCMDGV.js +0 -7
  113. package/dist/deploy-RMAHOA4Z.js +0 -55
  114. package/dist/dev-SAE237FS.js +0 -0
  115. package/dist/generateConfig-46Z3ERF3.js +0 -1
  116. package/dist/login-B6EF3SMX.js +0 -1
  117. package/dist/logout-XMKZBLNV.js +0 -1
  118. package/dist/remoteD1Shell-A67JMO3H.js +0 -6
  119. package/dist/setupLocal-TZJUW5ES.js +0 -6
  120. package/gateway/every-app-gateway-build.tar.gz +0 -0
@@ -0,0 +1,360 @@
1
+ # Google (Gmail, Calendar, Search Console and Analytics OAuth client) for the Gateway
2
+
3
+ One OAuth client in a Google Cloud project the person owns, held by the
4
+ Gateway, lets each member of an app connect their own Google account, read
5
+ only: Gmail, Google Calendar, Search Console and Google Analytics, whichever
6
+ of the four an app declares. The Gateway completes Google's token endpoint
7
+ with the client; the app builds the authorize URL from the public client
8
+ id, keeps the member's tokens and reads Google's APIs itself. Open CRM (app
9
+ id `crm`) uses it today, for Gmail and Calendar: Settings → Connections →
10
+ Google Workspace (`/settings/connections/google`). OpenSEO still reads
11
+ Search Console and Analytics with its own Google client and moves onto this
12
+ one after launch (TODO.md "OAuth scopes (2026-10-24)", follow-up 2): until
13
+ an app declares those two, nothing here enables them. At the end the person
14
+ has a Cloud project with the APIs behind the declared sets enabled (for
15
+ Open CRM: the Gmail API and the Google Calendar API), a consent screen
16
+ listing their scopes, a Web application client whose redirect URI is the
17
+ app's callback, and both settings on the Gateway. A Gateway-held token broker for
18
+ organization Google accounts is not built (the direction is in `docs/hosted-vision.md`); the scope sets
19
+ below come from `docs/design/2026-10-oauth-scopes.md`.
20
+
21
+ ## Settings
22
+
23
+ | Name | Required | What it holds |
24
+ | -------------------------------- | -------- | --------------------------------------------------------------------------------- |
25
+ | `GOOGLE_WORKSPACE_CLIENT_ID` | required | The client's id, ending in `.apps.googleusercontent.com`; public, not a secret |
26
+ | `GOOGLE_WORKSPACE_CLIENT_SECRET` | required | The client's secret, shown once at creation; starts with `GOCSPX-` in practice (secret scanners key on the prefix; Google does not document the format) |
27
+
28
+ Both belong to the `google-workspace` capability (`packages/sdk/src/internal/capabilities.ts`,
29
+ label "Google (Gmail, Calendar, Search Console and Analytics)"). Since #398
30
+ (2026-10-24) the client carries five read-only scope sets, each one
31
+ provider scope: `identity` (`openid`, `email`, `profile`), `gmail.read`
32
+ (`https://www.googleapis.com/auth/gmail.readonly`), `calendar.read`
33
+ (`…/auth/calendar.readonly`), `search-console.read`
34
+ (`…/auth/webmasters.readonly`) and `analytics.read`
35
+ (`…/auth/analytics.readonly`); no write set exists (the table in
36
+ `docs/capabilities.md`, "OAuth clients"). The console rule: enable the
37
+ APIs and allow on the consent screen the union of the provider scopes
38
+ behind the sets of every app you approve, not the registry's full list. A
39
+ scope no app asks for needn't be on the consent screen, and Google's
40
+ verification follows what is listed: `gmail.readonly` is restricted;
41
+ `webmasters.readonly` and `analytics.readonly` are sensitive (unverified:
42
+ Google publishes no list, the Data Access page labels each scope once
43
+ added). For the launch that is Open CRM: `gmail.readonly` and
44
+ `calendar.readonly`, with the Gmail API and the Google Calendar API
45
+ enabled. Add `webmasters.readonly` and `analytics.readonly`, and enable the
46
+ Search Console API and the Analytics Admin and Data APIs, only when an app
47
+ declares those sets (OpenSEO will, after launch).
48
+
49
+ An app names its sets in its manifest,
50
+ `"google-workspace": { access: "write", scopes: ["gmail.read", "calendar.read"], why }`
51
+ (#414; `everyapp deploy` refuses the entry without `scopes`), and the
52
+ Gateway holds it to them at every token exchange and refresh (#411, the
53
+ design's §5): a token whose granted scopes exceed the sets is refused
54
+ (`integration_scope_exceeded`, 403), so apps must not send
55
+ `include_granted_scopes=true` (Google then makes the token cover every
56
+ scope the person has granted the Cloud project, and a wider grant to
57
+ another app on the same project gets the narrower app refused until they
58
+ reconnect without the flag); a deployed manifest without `scopes` is
59
+ refused too (`integration_scopes_undeclared`, 403). Open CRM declares
60
+ both sets and sends no flag; a CRM deployed before that must redeploy
61
+ before the Gateway is updated from main (TODO.md "OAuth scopes
62
+ (2026-10-24)", follow-up 0). Redirect URI:
63
+ `https://<app>.<app base domain>/integrations/google/callback` for every
64
+ app that lists the capability; for Open CRM,
65
+ `https://crm.<app base domain>/integrations/google/callback`.
66
+
67
+ ## Costs and plans
68
+
69
+ As of October 2026: a Google Cloud project is free and needs no billing
70
+ account for this. Standard use of the Gmail API and the Google Calendar API
71
+ is free; Google plans to charge above the quota limits later in 2026. Limits:
72
+ Gmail 80,000,000 quota units per day per project, 6,000 per minute per user;
73
+ Calendar 1,000,000 requests per day per project, 600 per minute per user;
74
+ 100 live refresh tokens per Google Account per client (the oldest is dropped
75
+ silently). The Search Console API and the Analytics Data API list quotas
76
+ and no price (Search Console 1,200 search-analytics queries per minute per
77
+ site and per user; Analytics Data 200,000 tokens per property per day on a
78
+ standard property): they matter only once an app declares those sets.
79
+ *Internal* needs the project in a Google Workspace or Cloud
80
+ Identity organization: Workspace Starter lists at about $7 per user per
81
+ month, or Cloud Identity Free costs nothing and creates the organization
82
+ once the person verifies a domain they own (a DNS TXT record, recognized
83
+ within 72 hours; then continue at step 2). *External* with `gmail.readonly`
84
+ needs Google's restricted-scope verification (several weeks of review, by
85
+ email) plus a yearly CASA security assessment by a third-party assessor:
86
+ Google's Health page quotes $500 to $4,500 depending on the app's
87
+ complexity, and 2 to 3 weeks for the lower tier, 4 to 6 for the higher
88
+ (the CASA page now calls them AL1 and AL2). Sensitive scopes alone (Search
89
+ Console and Analytics without `gmail.read`) need the verification and no
90
+ assessment.
91
+
92
+ ## Only a person can
93
+
94
+ - Sign in to Google, create the Cloud project and pick its organization.
95
+ - Accept the Google API Services User Data Policy on the consent screen.
96
+ - Create the OAuth client and download its JSON: no API or gcloud command
97
+ creates a Web application client (step 6).
98
+ - Add test users, publish the app, submit verification, pay an assessor.
99
+ - Click Connect in Open CRM and approve the consent screen with their account.
100
+ - Allow the app in the Workspace admin console when `admin_policy_enforced`.
101
+
102
+ ## Before you start
103
+
104
+ 1. `pnpm everyapp secret list crm --json`, from any directory (the id reads
105
+ the manifest Open CRM's last deploy stored; in its directory the id can
106
+ be left off), one call, before any Google step. The rule: read
107
+ `data.app.setup.settings`, `unmet` and `command`. The client is one
108
+ setting there, `id: "google-workspace"`, `kind: "capability"`, `holder:
109
+ "gateway"`, `names: ["GOOGLE_WORKSPACE_CLIENT_ID",
110
+ "GOOGLE_WORKSPACE_CLIENT_SECRET"]` (an OAuth client's id and secret are
111
+ the two names of one setting, set only when both are), `need.kind` as
112
+ the CRM's manifest declares it (Open CRM: `optional`: it sits in
113
+ `settings` with `set: false`, never in `unmet`, the CRM opens without
114
+ Gmail and Calendar and `command`, `pnpm everyapp configure crm`, offers
115
+ it after the required ones; `required` would put it in `unmet`, close
116
+ the CRM and have configure ask for both names first), `setup:
117
+ "setup/google.md"` (this file). While its `set` is false this file
118
+ applies. The same entry in
119
+ `data.app.setup.settings` with `set: true`: ask before replacing (a new
120
+ secret for the same client is harmless, see Rotate; a different client
121
+ id makes every member reconnect Gmail and Calendar).
122
+ `TOKEN_ENCRYPTION_KEY`, the CRM's own secret that connecting needs, is
123
+ in `settings` too (`holder: "app"`; optional, so never in `unmet`): the
124
+ CRM's admin sets it after the CRM's first deploy, at the hidden prompt
125
+ of `pnpm everyapp configure crm` (which offers it with the optional
126
+ ones) or `pnpm everyapp secret set TOKEN_ENCRYPTION_KEY --app crm`, from
127
+ anywhere, 32 or more random characters (the CRM refuses shorter); a
128
+ missing key shows at step 9, the table's last row. `APP_NOT_FOUND`:
129
+ deploy Open CRM first (its callback must answer before anyone
130
+ connects).
131
+ 2. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true,
132
+ `data.appBaseDomain` is `<app base domain>` (step 1 builds the redirect
133
+ URI from it), and `data.apps[]` has `id: "crm"` with `status: "active"`
134
+ and `bound: true`. Its `setup` is 1's `data.app.setup` again and says
135
+ what else the CRM needs; not installed, or no owner:
136
+ [gateway.md](gateway.md) first.
137
+ 3. Ask whether a Cloud project or a Web client exists for this already:
138
+ then step 6 adds the redirect URI to it. Never reuse OpenSEO's verified
139
+ client (the design doc's rule). The sets the apps declare decide steps
140
+ 3 and 5: today Open CRM, `gmail.read` and `calendar.read`; Search
141
+ Console and Analytics only for an app that declares them.
142
+ 4. Ask whether every member who will connect is in one Google Workspace
143
+ organization. Yes: *Internal*, no verification. No: *External* (step 7).
144
+ A member on a gmail.com account is outside any organization: *External*.
145
+ Wait for both answers before giving the steps: they decide step 2's
146
+ Location, step 4's Audience and whether step 7 applies.
147
+
148
+ ## Steps
149
+
150
+ Once the audience is known, steps 2 to 6 (and 7 for *External*) can go to
151
+ the person in one message, with step 1's URI; then wait for the file
152
+ before step 8.
153
+
154
+ 1. **You:** work out the redirect URI from `data.appBaseDomain`:
155
+ `https://crm.<app base domain>/integrations/google/callback`. Open CRM's
156
+ Settings → Connections → Google Workspace page, signed in as an owner or
157
+ admin (members see no setup rows), shows the same value under "Redirect
158
+ URI" while the Gateway holds no client. Give it to the person for step 6.
159
+ 2. **Person:** signed in to Google with the Workspace account (a gmail.com
160
+ account shows no organization), create the project at
161
+ https://console.cloud.google.com/projectcreate: Project name (for example
162
+ `Every App`), Location → Browse → the Workspace organization (needed for
163
+ *Internal*; "No organization" otherwise) → Create. Done when the
164
+ project's dashboard opens with its name in the top bar. With gcloud
165
+ (checked against the reference today, not run here): the person runs
166
+ `gcloud auth login` (opens a browser), then
167
+ `gcloud projects create every-app-gateway-<4 digits> --name="Every App" --set-as-default`
168
+ (any unused id: ids are global and never reusable, so a taken one is
169
+ refused; add `--organization=ORGANIZATION_ID` for *Internal*, the number
170
+ from `gcloud organizations list`).
171
+ 3. **Person:** enable the APIs behind the declared sets; for Open CRM, two:
172
+ open
173
+ https://console.cloud.google.com/apis/enableflow;apiid=gmail.googleapis.com
174
+ → Enable, then
175
+ https://console.cloud.google.com/apis/enableflow;apiid=calendar-json.googleapis.com
176
+ → Enable (or APIs & Services → Library → Google Workspace → each API →
177
+ Enable). Done when each shows "API Enabled". With gcloud (checked against
178
+ the reference): `gcloud services enable gmail.googleapis.com calendar-json.googleapis.com`.
179
+ Only for an app that declares `search-console.read` or `analytics.read`,
180
+ the same with `searchconsole.googleapis.com` (Google Search Console
181
+ API), `analyticsadmin.googleapis.com` (Google Analytics Admin API) and
182
+ `analyticsdata.googleapis.com` (Google Analytics Data API): the service
183
+ ids are the APIs' own hosts (their discovery documents, Sources).
184
+ 4. **Person:** configure the consent screen at
185
+ https://console.cloud.google.com/auth/overview → Get Started. App
186
+ Information: App name (what members see, for example `Every App`), User
187
+ support email → Next. Audience: *Internal* (only that Workspace's
188
+ accounts, no verification) or *External* → Next. Contact Information: an
189
+ email for Google's notices → Next. Finish: agree to the User Data Policy
190
+ → Continue → Create. Done when the Google Auth platform overview shows
191
+ the app.
192
+ 5. **Person:** add the scopes on the Data Access page,
193
+ https://console.cloud.google.com/auth/scopes → Add or remove scopes →
194
+ filter `gmail.readonly` and `calendar.readonly`, tick both → Update →
195
+ Save; `webmasters.readonly` and `analytics.readonly` too, only for an
196
+ app that declares those sets, never a scope no app asks for. Only the
197
+ scopes of enabled APIs are listed: a missing one means step 3, or paste
198
+ its URL under "Manually add scopes". `gmail.readonly` is a restricted
199
+ scope, `calendar.readonly` a sensitive one (the page labels the other
200
+ two once added). Done when each is listed under "Your restricted
201
+ scopes" or "Your sensitive scopes".
202
+ 6. **Person:** create the client at https://console.cloud.google.com/auth/clients
203
+ → Create client → Application type: Web application → Name (for example
204
+ `Every App Gateway`) → Authorized redirect URIs → Add URI → paste the exact
205
+ URI from step 1 → Create. No Authorized JavaScript origins. A dialog shows
206
+ the Client ID and the Client secret once (afterwards only its last four
207
+ characters). Click Download JSON; browsers save it as
208
+ `client_secret_….json`, in `~/Downloads` unless set otherwise, outside
209
+ the repository. The person tells you it is there, and where when it
210
+ isn't in `~/Downloads`. No command does this: the IAP OAuth Admin API that
211
+ created clients was shut down on 2026-03-19, and
212
+ `gcloud iam oauth-clients create` makes Workforce Identity Federation
213
+ clients, not Google Account clients. URI changes take 5 minutes to a few
214
+ hours to apply.
215
+ 7. **Person,** *External* only: https://console.cloud.google.com/auth/audience
216
+ → Test users → Add users → each member's Google email (up to 100) → Save.
217
+ In Testing every consent expires after 7 days: members reconnect weekly.
218
+ Publish app moves to In production: the unverified-app screen and a
219
+ lifetime cap of 100 new users until verification (plus the CASA
220
+ assessment for `gmail.readonly`) passes. Verification (after Publish
221
+ app: Prepare for Verification → Submit for Verification) needs a home
222
+ page and a privacy policy on a domain the person owns, listed under
223
+ Authorized domains and verified in Search Console by a project owner or
224
+ editor, a demo video of the consent flow and a written scope
225
+ justification; Google answers by email over several weeks. Until then
226
+ stay in Testing (weekly reconnects, 100 test users); the run resumes at
227
+ Verify when Google's mail comes. Skip for *Internal*.
228
+ 8. **You,** only after the person says the file is there: the README's "a
229
+ command produced it" way, pipe each value from the file, never read the
230
+ file yourself. `ls ~/Downloads/client_secret_*.json` (or the folder they
231
+ named): more than one file, ask which (the newest:
232
+ `ls -t ~/Downloads/client_secret_*.json | head -1`), and use that one
233
+ path below, never the glob (`jq` prints one value per matching file).
234
+ `jq -e 'has("web")' <path>` (`true`; `false` is a Desktop client's file:
235
+ step 6 again). Then
236
+ `jq -r .web.client_id <path> | pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_ID --json`
237
+ and
238
+ `jq -r .web.client_secret <path> | pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_SECRET --json`.
239
+ Each ends with a `done` event whose `data.gateway.names` holds the name
240
+ (`client_id`, `client_secret`, `redirect_uris`, `auth_uri` and
241
+ `token_uri` are all mandatory under `web`). Then tell the person the
242
+ file still holds the secret and ask them to delete it (or ask before
243
+ running `rm` on it).
244
+ Alternative, the person types it: in their own terminal in the every-app
245
+ folder, `pnpm everyapp configure crm` (it asks for the client's two
246
+ names, id then secret, with hidden prompts, after the CRM's required
247
+ settings; the client is optional for Open CRM, so Yes to `Set the N
248
+ optional settings too?`, Enter skipping the others; with one of
249
+ the two names set it asks only for the other; or pick
250
+ `GOOGLE_WORKSPACE_CLIENT_ID + GOOGLE_WORKSPACE_CLIENT_SECRET` from the
251
+ menu once the CRM is ready) or
252
+ `pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_ID GOOGLE_WORKSPACE_CLIENT_SECRET`
253
+ (two hidden prompts, just these), then they tell you.
254
+ 9. **Person,** once `data.app.setup.ready` is true: while another required
255
+ setting of the CRM is unmet, the Gateway shows the CRM as "Needs setup"
256
+ and opens its setup dialog instead of the app, so finish those first
257
+ (each `unmet` entry's `setup` names its file); the Google client itself
258
+ is verified by Verify 1 meanwhile. Then open
259
+ https://crm.<app base domain>/settings/connections/google
260
+ (Settings → Connections → Google Workspace) → Connect → choose the
261
+ account → (Testing: "Google hasn't verified this app" → Advanced → Go to
262
+ Every App (unsafe)) → Allow. Done when the page shows the "Connected"
263
+ badge with their address. Wait for that.
264
+
265
+ ## Verify
266
+
267
+ 1. `pnpm everyapp secret list crm --json`, from anywhere: the
268
+ `google-workspace` setting has `set: true` (optional for Open CRM, so
269
+ it was never in `data.app.setup.unmet`; a manifest that requires it has
270
+ it leave `unmet`, with `ready: true` and `command` null once nothing
271
+ else is missing); both names in `data.gateway.names`,
272
+ their `data.gateway.needed` entries `"set": true`, and
273
+ `data.gateway.missing` doesn't list them. Then
274
+ `pnpm everyapp status --json`: the `crm` entry's `setup.ready` is true
275
+ and `data.fixes` has no `APP_NEEDS_SETUP` for it (the Gateway opens the
276
+ CRM again).
277
+ 2. Smoke check, in the person's browser. 2 and 3 need the CRM open: 1's
278
+ `data.app.setup.ready` true. While another required setting is unmet,
279
+ the Gateway sends a browser to its setup dialog instead of the CRM
280
+ (anything else gets 503 `app_needs_setup`): finish those first (each
281
+ `unmet` entry's `setup` names its file); until then 1 is the check of
282
+ the Google client. Then, signed in as an owner or admin (no secret
283
+ involved): Open CRM's Settings → Connections → Google Workspace shows a
284
+ Connect button and the "Not connected" badge instead of "Not
285
+ configured" with the "Redirect URI" and "Command" rows (a plain member
286
+ never sees those rows). The button means the Gateway answered
287
+ `ready: true` with the client id for `google-workspace`.
288
+ 3. App check: step 9. Google's account chooser means the client id and
289
+ redirect URI match; the "Connected" badge means the Gateway completed
290
+ `POST oauth2.googleapis.com/token` with the secret. Mail and meetings
291
+ appear on companies after the first sync.
292
+ 4. Logs: the dashboard, README's path:
293
+ **Workers & Pages** → `every-app-gateway` → **Observability** (**Logs** on older accounts) → **Live**
294
+ (the stored Workers Logs are on the same page); filter
295
+ `capability: "google-workspace"`. Each token call is one
296
+ `integration.proxy.call` line with `grantType` (`authorization_code` on
297
+ connect, `refresh_token` on refresh) and `status`, never the body.
298
+
299
+ ## If it fails
300
+
301
+ | Error or symptom | Meaning | What to do |
302
+ | --- | --- | --- |
303
+ | `SECRET_VALUE_REQUIRED` | Nothing was piped and there is no terminal | Use the pipe in step 8 or the person's terminal |
304
+ | `SECRET_VALUE_REQUIRED` from `pnpm everyapp configure crm --json` | Nothing was piped (with `--from-env`, stdin isn't read), no variable of a missing name was found, and there is no terminal; the message names what the CRM still needs (this client is optional for Open CRM, so never among them) | The pipes in step 8, or the person's terminal (step 8's alternative) |
305
+ | `SECRET_SEVERAL_NAMES` | A pipe or `--from-env` with two names | One `secret set` per name |
306
+ | `SECRET_NOT_DECLARED` with `data.didYouMean` | Name typed wrong | rerun with the name in `data.didYouMean` and the same value source (`--from-env VAR --json`, or the same pipe); `data.rerun` carries only the names, so run as is it stops with `SECRET_VALUE_REQUIRED` |
307
+ | `has("web")` prints `false`; `jq -r .web.client_id` prints `null` | A Desktop client's file (`installed` key); `null` must never be piped | Create a Web application client; unset anything set from it |
308
+ | App: "Not configured", "Redirect URI" and "Command" rows, no Connect | The Gateway's status for `google-workspace` answers `ready: false`: it holds no client (or one name only), or the deployed manifest lists the client without `scopes` | Step 8 (or add `scopes` and deploy); then reload |
309
+ | `integration_not_configured` (503) in the app's logs | A token call while the Gateway holds no client, or the name is wrong | `secret list --json`; set both names |
310
+ | `capability_not_requested` (403) | The deployed manifest doesn't list `google-workspace` | Add `"google-workspace": { access: "write", scopes: ["gmail.read", "calendar.read"], why }` to everyapp.config.ts, deploy |
311
+ | `integration_scopes_undeclared` (403) | The deployed manifest lists the client without `scopes` (a deploy from before #414) | Add `scopes: ["gmail.read", "calendar.read"]` to the entry, deploy |
312
+ | `integration_scope_exceeded` (403) | Google granted more than the app's sets: the app asked for a scope outside them, or sent `include_granted_scopes=true` and the member had granted a wider scope to another app on this project | The app asks for only the status's scopes without the flag and the member connects again, or the set is added to everyapp.config.ts and deployed; the token was dropped, nothing to revoke |
313
+ | `integration_path_not_allowed` (403) | The app called something other than `POST /token` through the Gateway | App bug: API calls go to Google directly |
314
+ | Google "Error 400: redirect_uri_mismatch" | The URI on the client differs from step 1 | Fix it on the client; wait up to a few hours |
315
+ | Google "Error 401: invalid_client" | Client id or secret wrong, or secret disabled | Rotate (below) and set both again |
316
+ | Google "Error 403: access_denied" | The member is not a test user (Testing) | Add them in step 7, retry |
317
+ | App: "You didn't give Google access." (`error=access_denied` in the address bar) | The member clicked Cancel on the consent screen | Connect again |
318
+ | App: "Google returned an error. Try again." (`error=exchange_failed`) | Google refused with any code but a decline (`admin_policy_enforced`, `org_internal`, a failed token exchange); the CRM's logs say `[mailbox] code exchange failed` with a reason | Read the code in the CRM's logs or on Google's page, then the rows below |
319
+ | Google "Error 400: admin_policy_enforced" | The Workspace admin restricts the scopes or the app | A Workspace admin (Service Settings privilege): Admin console → Security → Access and data control → API controls (admin.google.com/ac/owl) → Manage App Access → Configure new app → search the client id → Continue → Trusted → Continue → Finish |
320
+ | Google "Error 403: org_internal" | *Internal* audience, account outside the organization | Use a Workspace account, or switch to *External* |
321
+ | "Google hasn't verified this app" | *External*, unverified, sensitive or restricted scopes | Advanced → Go to Every App (unsafe), or verify |
322
+ | Sync stops after 7 days, `invalid_grant` | *External* in Testing: consent expired | Reconnect; publish and verify to stop it |
323
+ | `403 accessNotConfigured` ("has not been used in project") | Gmail or Calendar API not enabled in that project | Step 3 in the project the error names; wait a minute |
324
+ | Google "Error 401: deleted_client" | The client was deleted (30-day restore window) | Restore it under Deleted credentials, or make a new one |
325
+ | Connect fails before Google opens | Open CRM's `TOKEN_ENCRYPTION_KEY` unset | Set it after the CRM's first deploy: `pnpm everyapp configure crm` (which asks for it) or `pnpm everyapp secret set TOKEN_ENCRYPTION_KEY --app crm` from anywhere (in the CRM's directory the flag can be left off), the value in one of README's three ways, 32 or more random characters; connect again |
326
+
327
+ ## Rerun, rotate, remove
328
+
329
+ - Rerun: every step is safe to repeat. `configure` and `secret set`
330
+ replace; the console shows what exists. After an interruption start at
331
+ Verify and continue at the first step that isn't done.
332
+ - Rotate the secret, never the id: a new client id invalidates every
333
+ member's refresh token. Person: Clients → the client → Add secret (two at
334
+ most; both work until one is disabled); the new secret is shown once, and
335
+ they type it at the hidden prompt in their terminal
336
+ (`pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_SECRET`). You verify
337
+ with `secret list --json`; they test Connect; then they Disable the old
338
+ secret, test again, and Delete it.
339
+ - Remove: `pnpm everyapp secret unset GOOGLE_WORKSPACE_CLIENT_ID GOOGLE_WORKSPACE_CLIENT_SECRET --json`
340
+ (add `--yes` only when the person said so); every connected mailbox stops
341
+ syncing. Then at Google: Clients → tick the client → Delete (restorable
342
+ for 30 days; Google also deletes clients unused for six months).
343
+
344
+ ## Sources
345
+
346
+ - https://developers.google.com/workspace/guides/create-project (step 2)
347
+ - https://developers.google.com/workspace/guides/enable-apis (step 3)
348
+ - https://developers.google.com/workspace/guides/configure-oauth-consent (step 4)
349
+ - https://support.google.com/cloud/answer/15549135 (Data Access: only enabled APIs' scopes listed, Manually add scopes; step 5)
350
+ - https://support.google.com/cloud/answer/15549257 (clients: secret shown once, two at most, Add secret, 30-day restore, six-month inactivity; steps 6 and 8, Rotate, Remove)
351
+ - https://support.google.com/cloud/answer/15549945 (Audience: Internal needs an organization, External, Testing's 100 users and 7 days, Publish app; step 7)
352
+ - https://support.google.com/cloud/answer/13464321 (verification: home page, privacy policy, Search Console, demo video, scope justification; restricted scopes alone add the yearly assessment; step 7, Costs and plans)
353
+ - https://support.google.com/cloud/answer/9110914 (a scope's category is labeled in the console once added; step 5)
354
+ - https://developers.google.com/identity/protocols/oauth2/web-server (`include_granted_scopes`: the token covers every scope the person granted the project; the response's `scope`; Settings)
355
+ - https://searchconsole.googleapis.com/$discovery/rest?version=v1, https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta, https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta (service names and the scopes each API takes; step 3)
356
+ - https://developers.google.com/webmaster-tools/limits and https://developers.google.com/analytics/devguides/reporting/data/v1/quotas (quotas; Costs and plans)
357
+ - `docs/design/2026-10-oauth-scopes.md` (the sets, §4; the token-endpoint check and its codes, §5; §13 what was built), TODO.md "OAuth scopes (2026-10-24)" (Search Console and Analytics on this client; Open CRM's and OpenSEO's follow-ups) and `docs/capabilities.md` "OAuth clients" (the generated sets table; Settings)
358
+ - https://support.google.com/accounts/answer/16668185 (Google's sign-in error codes; If it fails)
359
+ - https://docs.cloud.google.com/iap/docs/programmatic-oauth-clients (the API that created clients, shut down 2026-03-19; step 6)
360
+ - https://developers.google.com/health/app-verification (assessor fees; Costs and plans)