primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.40

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 (216) hide show
  1. package/README.md +325 -31
  2. package/assets/skill/skills/primitive-platform/SKILL.md +281 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +266 -16
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/admins.js +123 -18
  8. package/dist/src/commands/admins.js.map +1 -1
  9. package/dist/src/commands/analytics.d.ts +2 -0
  10. package/dist/src/commands/analytics.js +464 -55
  11. package/dist/src/commands/analytics.js.map +1 -1
  12. package/dist/src/commands/apps.d.ts +2 -0
  13. package/dist/src/commands/apps.js +55 -13
  14. package/dist/src/commands/apps.js.map +1 -1
  15. package/dist/src/commands/auth.d.ts +2 -0
  16. package/dist/src/commands/auth.js +177 -7
  17. package/dist/src/commands/auth.js.map +1 -1
  18. package/dist/src/commands/blob-buckets.d.ts +2 -0
  19. package/dist/src/commands/blob-buckets.js +422 -0
  20. package/dist/src/commands/blob-buckets.js.map +1 -0
  21. package/dist/src/commands/catalog.d.ts +2 -0
  22. package/dist/src/commands/catalog.js +34 -35
  23. package/dist/src/commands/catalog.js.map +1 -1
  24. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  25. package/dist/src/commands/collection-type-configs.js +178 -0
  26. package/dist/src/commands/collection-type-configs.js.map +1 -0
  27. package/dist/src/commands/collections.d.ts +2 -0
  28. package/dist/src/commands/collections.js +515 -0
  29. package/dist/src/commands/collections.js.map +1 -0
  30. package/dist/src/commands/comparisons.d.ts +2 -0
  31. package/dist/src/commands/comparisons.js +6 -6
  32. package/dist/src/commands/comparisons.js.map +1 -1
  33. package/dist/src/commands/cron-triggers.d.ts +2 -0
  34. package/dist/src/commands/cron-triggers.js +357 -0
  35. package/dist/src/commands/cron-triggers.js.map +1 -0
  36. package/dist/src/commands/database-types.d.ts +2 -0
  37. package/dist/src/commands/database-types.js +471 -0
  38. package/dist/src/commands/database-types.js.map +1 -0
  39. package/dist/src/commands/databases.d.ts +2 -0
  40. package/dist/src/commands/databases.js +1462 -83
  41. package/dist/src/commands/databases.js.map +1 -1
  42. package/dist/src/commands/documents.d.ts +2 -0
  43. package/dist/src/commands/documents.js +518 -11
  44. package/dist/src/commands/documents.js.map +1 -1
  45. package/dist/src/commands/email-templates.d.ts +2 -0
  46. package/dist/src/commands/email-templates.js +281 -0
  47. package/dist/src/commands/email-templates.js.map +1 -0
  48. package/dist/src/commands/env.d.ts +12 -0
  49. package/dist/src/commands/env.js +260 -0
  50. package/dist/src/commands/env.js.map +1 -0
  51. package/dist/src/commands/group-type-configs.d.ts +2 -0
  52. package/dist/src/commands/group-type-configs.js +181 -0
  53. package/dist/src/commands/group-type-configs.js.map +1 -0
  54. package/dist/src/commands/groups.d.ts +2 -0
  55. package/dist/src/commands/groups.js +45 -43
  56. package/dist/src/commands/groups.js.map +1 -1
  57. package/dist/src/commands/guides.d.ts +159 -0
  58. package/dist/src/commands/guides.js +449 -35
  59. package/dist/src/commands/guides.js.map +1 -1
  60. package/dist/src/commands/init.d.ts +17 -0
  61. package/dist/src/commands/init.js +811 -201
  62. package/dist/src/commands/init.js.map +1 -1
  63. package/dist/src/commands/integrations.d.ts +2 -0
  64. package/dist/src/commands/integrations.js +533 -46
  65. package/dist/src/commands/integrations.js.map +1 -1
  66. package/dist/src/commands/llm.d.ts +2 -0
  67. package/dist/src/commands/llm.js +4 -2
  68. package/dist/src/commands/llm.js.map +1 -1
  69. package/dist/src/commands/prompts.d.ts +2 -0
  70. package/dist/src/commands/prompts.js +50 -52
  71. package/dist/src/commands/prompts.js.map +1 -1
  72. package/dist/src/commands/rule-sets.d.ts +3 -0
  73. package/dist/src/commands/rule-sets.js +388 -0
  74. package/dist/src/commands/rule-sets.js.map +1 -0
  75. package/dist/src/commands/scripts.d.ts +2 -0
  76. package/dist/src/commands/scripts.js +667 -0
  77. package/dist/src/commands/scripts.js.map +1 -0
  78. package/dist/src/commands/secrets.d.ts +2 -0
  79. package/dist/src/commands/secrets.js +108 -0
  80. package/dist/src/commands/secrets.js.map +1 -0
  81. package/dist/src/commands/skill.d.ts +2 -0
  82. package/dist/src/commands/skill.js +29 -0
  83. package/dist/src/commands/skill.js.map +1 -0
  84. package/dist/src/commands/sync.d.ts +174 -0
  85. package/dist/src/commands/sync.js +4392 -235
  86. package/dist/src/commands/sync.js.map +1 -1
  87. package/dist/src/commands/tokens.d.ts +2 -0
  88. package/dist/src/commands/tokens.js +122 -19
  89. package/dist/src/commands/tokens.js.map +1 -1
  90. package/dist/src/commands/users.d.ts +2 -0
  91. package/dist/src/commands/users.js +440 -22
  92. package/dist/src/commands/users.js.map +1 -1
  93. package/dist/src/commands/waitlist.d.ts +2 -0
  94. package/dist/src/commands/waitlist.js +10 -10
  95. package/dist/src/commands/waitlist.js.map +1 -1
  96. package/dist/src/commands/webhooks.d.ts +2 -0
  97. package/dist/src/commands/webhooks.js +391 -0
  98. package/dist/src/commands/webhooks.js.map +1 -0
  99. package/dist/src/commands/workflows.d.ts +49 -0
  100. package/dist/src/commands/workflows.js +1009 -123
  101. package/dist/src/commands/workflows.js.map +1 -1
  102. package/dist/src/lib/api-client.d.ts +1272 -0
  103. package/dist/src/lib/api-client.js +1115 -83
  104. package/dist/src/lib/api-client.js.map +1 -1
  105. package/dist/src/lib/auth-flow.d.ts +8 -0
  106. package/dist/src/lib/block-layout.d.ts +160 -0
  107. package/dist/src/lib/block-layout.js +451 -0
  108. package/dist/src/lib/block-layout.js.map +1 -0
  109. package/dist/src/lib/cli-manifest.d.ts +60 -0
  110. package/dist/src/lib/cli-manifest.js +70 -0
  111. package/dist/src/lib/cli-manifest.js.map +1 -0
  112. package/dist/src/lib/config.d.ts +53 -0
  113. package/dist/src/lib/config.js +92 -53
  114. package/dist/src/lib/config.js.map +1 -1
  115. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  116. package/dist/src/lib/confirm-prompt.js +110 -0
  117. package/dist/src/lib/confirm-prompt.js.map +1 -0
  118. package/dist/src/lib/constants.d.ts +2 -0
  119. package/dist/src/lib/constants.js +3 -0
  120. package/dist/src/lib/constants.js.map +1 -0
  121. package/dist/src/lib/crash-handlers.d.ts +20 -0
  122. package/dist/src/lib/crash-handlers.js +49 -0
  123. package/dist/src/lib/crash-handlers.js.map +1 -0
  124. package/dist/src/lib/credentials-store.d.ts +79 -0
  125. package/dist/src/lib/credentials-store.js +307 -0
  126. package/dist/src/lib/credentials-store.js.map +1 -0
  127. package/dist/src/lib/csv.d.ts +48 -0
  128. package/dist/src/lib/csv.js +177 -0
  129. package/dist/src/lib/csv.js.map +1 -0
  130. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  131. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  132. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  133. package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
  134. package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
  135. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  136. package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
  137. package/dist/src/lib/db-codegen/dbNaming.js +104 -0
  138. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  139. package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
  140. package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
  141. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  142. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
  143. package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
  144. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  145. package/dist/src/lib/env-resolver.d.ts +62 -0
  146. package/dist/src/lib/env-resolver.js +121 -0
  147. package/dist/src/lib/env-resolver.js.map +1 -0
  148. package/dist/src/lib/fetch.d.ts +5 -0
  149. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  150. package/dist/src/lib/generated-allowlist.js +213 -0
  151. package/dist/src/lib/generated-allowlist.js.map +1 -0
  152. package/dist/src/lib/init-config.d.ts +59 -0
  153. package/dist/src/lib/init-config.js +113 -0
  154. package/dist/src/lib/init-config.js.map +1 -0
  155. package/dist/src/lib/migration-nag.d.ts +49 -0
  156. package/dist/src/lib/migration-nag.js +163 -0
  157. package/dist/src/lib/migration-nag.js.map +1 -0
  158. package/dist/src/lib/output.d.ts +86 -0
  159. package/dist/src/lib/output.js +150 -8
  160. package/dist/src/lib/output.js.map +1 -1
  161. package/dist/src/lib/paginate.d.ts +33 -0
  162. package/dist/src/lib/paginate.js +42 -0
  163. package/dist/src/lib/paginate.js.map +1 -0
  164. package/dist/src/lib/project-config.d.ts +97 -0
  165. package/dist/src/lib/project-config.js +209 -0
  166. package/dist/src/lib/project-config.js.map +1 -0
  167. package/dist/src/lib/query-operators.d.ts +43 -0
  168. package/dist/src/lib/query-operators.js +80 -0
  169. package/dist/src/lib/query-operators.js.map +1 -0
  170. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  171. package/dist/src/lib/refresh-admin-credentials.js +103 -0
  172. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  173. package/dist/src/lib/resolve-platform.d.ts +45 -0
  174. package/dist/src/lib/resolve-platform.js +43 -0
  175. package/dist/src/lib/resolve-platform.js.map +1 -0
  176. package/dist/src/lib/skill-installer.d.ts +23 -0
  177. package/dist/src/lib/skill-installer.js +135 -0
  178. package/dist/src/lib/skill-installer.js.map +1 -0
  179. package/dist/src/lib/snapshots.d.ts +99 -0
  180. package/dist/src/lib/snapshots.js +357 -0
  181. package/dist/src/lib/snapshots.js.map +1 -0
  182. package/dist/src/lib/sync-paths.d.ts +72 -0
  183. package/dist/src/lib/sync-paths.js +130 -0
  184. package/dist/src/lib/sync-paths.js.map +1 -0
  185. package/dist/src/lib/template.d.ts +97 -0
  186. package/dist/src/lib/template.js +336 -62
  187. package/dist/src/lib/template.js.map +1 -1
  188. package/dist/src/lib/token-inject.d.ts +56 -0
  189. package/dist/src/lib/token-inject.js +204 -0
  190. package/dist/src/lib/token-inject.js.map +1 -0
  191. package/dist/src/lib/toml-database-config.d.ts +152 -0
  192. package/dist/src/lib/toml-database-config.js +600 -0
  193. package/dist/src/lib/toml-database-config.js.map +1 -0
  194. package/dist/src/lib/toml-metadata-config.d.ts +108 -0
  195. package/dist/src/lib/toml-metadata-config.js +371 -0
  196. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  197. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  198. package/dist/src/lib/toml-params-validator.js +298 -0
  199. package/dist/src/lib/toml-params-validator.js.map +1 -0
  200. package/dist/src/lib/version-check.d.ts +10 -0
  201. package/dist/src/lib/version-check.js +172 -0
  202. package/dist/src/lib/version-check.js.map +1 -0
  203. package/dist/src/lib/workflow-apply.d.ts +66 -0
  204. package/dist/src/lib/workflow-apply.js +117 -0
  205. package/dist/src/lib/workflow-apply.js.map +1 -0
  206. package/dist/src/lib/workflow-fragments.d.ts +41 -0
  207. package/dist/src/lib/workflow-fragments.js +121 -0
  208. package/dist/src/lib/workflow-fragments.js.map +1 -0
  209. package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
  210. package/dist/src/lib/workflow-toml-validator.js +323 -0
  211. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  212. package/dist/src/types/index.d.ts +520 -0
  213. package/dist/src/validators.d.ts +64 -0
  214. package/dist/src/validators.js +63 -0
  215. package/dist/src/validators.js.map +1 -0
  216. package/package.json +18 -4
package/README.md CHANGED
@@ -71,11 +71,15 @@ primitive apps --help
71
71
  The CLI uses browser-based OAuth for authentication. When you run `primitive login`, it opens your browser to authenticate via Google OAuth and stores credentials locally.
72
72
 
73
73
  ```bash
74
- # Login to the default server
74
+ # Login to the environment resolved from .primitive/config.json
75
+ # (project mode), or the default/PRIMITIVE_SERVER_URL server (legacy mode)
75
76
  primitive login
76
77
 
77
- # Login to a custom server
78
- primitive login --server https://api.example.com
78
+ # Login to a specific named environment
79
+ primitive -e prod login
80
+
81
+ # Login to a custom server without a project config (legacy mode)
82
+ PRIMITIVE_SERVER_URL=https://api.example.com primitive login
79
83
 
80
84
  # View current session
81
85
  primitive whoami
@@ -150,12 +154,13 @@ Manage users within an app.
150
154
 
151
155
  ```bash
152
156
  primitive users list [app-id] # List users
157
+ primitive users create <email> [--role admin|member] # Create/add user by email
153
158
  primitive users invite [app-id] <email> [--role admin] # Invite user
154
159
  primitive users remove [app-id] <user-id> # Remove user
155
160
  primitive users set-role [app-id] <user-id> <role> # Change role
156
161
  primitive users transfer-owner [app-id] <new-owner-id> # Transfer ownership
157
- primitive users invitations list [app-id] # List pending invitations
158
- primitive users invitations delete [app-id] <inv-id> # Delete invitation
162
+ primitive users mint-jwt <user-id> [--role <role>] # Mint test JWT (dev/test only)
163
+ primitive users invitations [app-id] # List pending invitations
159
164
  ```
160
165
 
161
166
  ### Waitlist
@@ -191,12 +196,38 @@ primitive integrations logs <id> # View invocation logs
191
196
  - `--timeout <ms>` - Request timeout in milliseconds
192
197
 
193
198
  **Secrets management:**
199
+
200
+ Integration-scoped secrets are **deprecated** and can no longer be created. Use
201
+ app-level secrets instead (`primitive secrets set <KEY> --value <value>`) and reference
202
+ them in integration config via `{{secrets.KEY}}`. See the **Secrets** section
203
+ below. The remaining integration-secret commands only list/archive legacy
204
+ secrets:
205
+
194
206
  ```bash
195
207
  primitive integrations secrets list <integration-id>
196
- primitive integrations secrets add <id> --data '{"apiKey":"..."}'
197
208
  primitive integrations secrets archive <id> <secret-id>
198
209
  ```
199
210
 
211
+ ### Secrets
212
+
213
+ Manage encrypted app secrets (API keys, tokens, credentials). Values are encrypted at rest and never displayed after creation.
214
+
215
+ ```bash
216
+ primitive secrets list [--app <app-id>] # List secrets (values never shown)
217
+ primitive secrets set <KEY> --value <value> [--summary <text>] # Create or update a secret
218
+ primitive secrets delete <KEY> # Delete a secret
219
+ ```
220
+
221
+ **Examples:**
222
+ ```bash
223
+ primitive secrets set OPENAI_API_KEY --value "sk-..." --summary "Production key"
224
+ primitive secrets set STRIPE_SECRET --value "sk_live_..."
225
+ primitive secrets list --json
226
+ primitive secrets delete STRIPE_SECRET
227
+ ```
228
+
229
+ Keys must be uppercase letters, digits, and underscores (e.g., `OPENAI_API_KEY`). Max 100 secrets per app, 2 KB per value. The `set` command is an upsert — it creates or updates automatically. Use `{{secrets.KEY}}` in workflows and `secrets.KEY` in CEL rules.
230
+
200
231
  ### Prompts
201
232
 
202
233
  Manage LLM prompt configurations.
@@ -248,6 +279,39 @@ primitive workflows runs list <workflow-id>
248
279
  primitive workflows runs status <workflow-id> <run-id>
249
280
  ```
250
281
 
282
+ **Workflow TOML push-time validation (issue #685):** Every command that
283
+ pushes a workflow TOML (`workflows create --from-file`,
284
+ `workflows draft update --from-file`, `workflows configs create --from-file`,
285
+ `workflows configs update --from-file`, and `primitive sync push`) runs the
286
+ file through `cli/src/lib/workflow-toml-validator.ts` before sending it to
287
+ the server. For a step whose `kind` the CLI knows about, the validator
288
+ rejects any top-level field outside the allowlist — most commonly catching
289
+ the footgun where `[steps.<id>.request]` is written under `[[steps]]` (TOML
290
+ places the sub-table under `steps[N][<id>]`, not the intended
291
+ `steps[N].request`, so the runtime silently runs the step with an empty
292
+ `request` block). The correct form for the most-recent step is
293
+ `[steps.request]`. For a step whose `kind` the CLI does NOT know (an older
294
+ CLI run against a newer server that registered a new step kind), the
295
+ per-field check is skipped — so a valid new-kind field is never falsely
296
+ rejected — while the kind-agnostic misnested-header footgun detector still
297
+ fires (issue #998).
298
+
299
+ **The allowlist is generated, not hand-maintained (issue #998):** The field
300
+ union and the known-kind set live in the committed artifact
301
+ `cli/src/lib/generated-allowlist.ts`, generated from the server step runners
302
+ (`src/workflows/steps/*.ts`) and the universal `BaseStepDefinition` shape by
303
+ `cli/scripts/gen-allowlist.mjs`. The generator runs automatically at CLI
304
+ `prebuild`; regenerate manually with `node cli/scripts/gen-allowlist.mjs`
305
+ (or `pnpm -C cli gen:allowlist`). The union is derived from what each runner
306
+ *reads* off the step config — not from what the `*StepDefinition` type
307
+ declares (types over-declare and would weaken the footgun detector). A
308
+ freshness guard (`node cli/scripts/gen-allowlist.mjs --check`, also asserted
309
+ by `cli/tests/unit/workflow-toml-validator-drift-guard.test.ts`) fails if the
310
+ committed artifact drifts from a fresh scan, so the historical hand-edit
311
+ drift (#685/#802/#971) cannot recur. When you add a new step kind or a runner
312
+ starts reading a new field, just regenerate and commit the artifact — there is
313
+ no hand-maintained list to update.
314
+
251
315
  ### Tokens
252
316
 
253
317
  Manage long-lived API access tokens for headless/server authentication.
@@ -263,16 +327,17 @@ primitive tokens revoke <token-id> [app-id] # Revoke t
263
327
  **Create options:**
264
328
  - `--name <name>` - Token name (required)
265
329
  - `--user <user-id>` - App user ID to associate the token with (required)
266
- - `--ttl <duration>` - Token lifetime (e.g., 7d, 30d, 4w, 3m, 1y). Omit for never-expiring
330
+ - `--ttl <duration>` - Token lifetime (e.g., 30m, 2h, 7d, 30d, 4w, 3mo, 1y). Omit for never-expiring. Units: `m`/`min`=minutes (≤1440), `h`=hours (≤24), `d`=days, `w`=weeks, `mo`=months, `y`=years. **Note:** as of #420 `m` means minutes (was months); use `mo` for months.
267
331
 
268
332
  ### Databases
269
333
 
270
- Manage online databases, permissions, and group permissions.
334
+ Manage online databases and permissions. `list` returns databases the user has direct access to; group-shared databases are listed via `primitive groups databases`.
271
335
 
272
336
  ```bash
273
- primitive databases list [app-id] # List databases
337
+ primitive databases list [app-id] # List databases (direct access)
274
338
  primitive databases create <title> [app-id] # Create database
275
339
  primitive databases get <database-id> [app-id] # Get details
340
+ primitive databases update <database-id> [options] # Update title or type
276
341
  primitive databases delete <database-id> [app-id] # Delete database
277
342
  ```
278
343
 
@@ -283,14 +348,68 @@ primitive databases permissions grant <database-id> --user-id <uid> --permission
283
348
  primitive databases permissions revoke <database-id> <user-id> [app-id]
284
349
  ```
285
350
 
286
- **Group permissions:**
351
+ Permission values: `owner` (set at creation), `manager`
352
+
353
+ **Metadata:**
354
+ ```bash
355
+ primitive databases metadata update <database-id> --data '{"key":"value"}' # Merge-update metadata
356
+ ```
357
+
358
+ **Operations:**
359
+ ```bash
360
+ primitive databases operations list <database-id> [app-id] # List registered operations
361
+ primitive databases operations execute <database-id> <op-name> --params '{}' # Execute operation
362
+ primitive databases operations execute <database-id> <op-name> --token <jwt> # Execute as specific user
363
+ ```
364
+
365
+ The `--token` flag lets you execute an operation as a specific user using a test JWT from `users mint-jwt`. Useful for testing access rules.
366
+
367
+ **Records (schema introspection):**
368
+ ```bash
369
+ primitive databases records models <database-id> [app-id] # List model names
370
+ primitive databases records describe <database-id> <model> [app-id] # Show inferred schema
371
+ ```
372
+
373
+ **Indexes:**
374
+ ```bash
375
+ primitive databases indexes list <database-id> [--model <name>] # List indexes
376
+ primitive databases indexes create <database-id> <model> <field> [options] # Create index
377
+ primitive databases indexes drop <database-id> <model> <field> # Drop index
378
+ ```
379
+
380
+ **Export / Import:**
381
+ ```bash
382
+ primitive databases export [app-id] <database-id> --output <dir> # Export records, indexes, constraints
383
+ primitive databases import [app-id] <path> --overwrite --dry-run # Import from export directory
384
+ ```
385
+
386
+ Export creates a directory with `metadata.json`, `records.jsonl`, `indexes.json`, and `constraints.json`. Import restores records and indexes into a new or existing database. Database type config (operations, triggers, access rules) is managed separately via `primitive sync` — run `sync push` on the target app before importing.
387
+
388
+ ### Documents
389
+
390
+ Manage document ownership, group permissions, and export/import.
391
+
392
+ ```bash
393
+ primitive documents transfer-owner [app-id] <document-id> <new-owner-id> # Transfer ownership
394
+ ```
395
+
396
+ **Group permissions on documents:**
397
+ ```bash
398
+ primitive documents group-permissions list <document-id>
399
+ primitive documents group-permissions grant <document-id> --group-type <type> --group-id <id> --permission <perm>
400
+ primitive documents group-permissions revoke <document-id> <group-type> <group-id>
401
+ ```
402
+
403
+ Permission values: `read-write`, `reader`
404
+
405
+ **Export / Import:**
287
406
  ```bash
288
- primitive databases group-permissions list <database-id> [app-id]
289
- primitive databases group-permissions grant <database-id> --group-type <type> --group-id <id> --permission <perm> [app-id]
290
- primitive databases group-permissions revoke <database-id> <group-type> <group-id> [app-id]
407
+ primitive documents export [app-id] <document-id> --output <dir> # Export Yjs state, blobs, permissions, aliases
408
+ primitive documents export-all [app-id] --user-id <id> --owned-only # Export all docs for a user
409
+ primitive documents import [app-id] <path> --overwrite --aliases overwrite|skip --dry-run # Import from export
291
410
  ```
292
411
 
293
- Permission values: `owner`, `read-write`, `reader`
412
+ Export creates a directory per document with `metadata.json`, `document.yjs` (Yjs state), `permissions.json` (for reference), and `blobs/` (attachments). Permissions are exported for reference but not restored during import — the importing admin is the new owner and manages sharing in the target app. Document IDs are preserved across import. User-scoped aliases can be restored with `--aliases overwrite` (update existing) or `--aliases skip` (keep existing, default).
294
413
 
295
414
  ### Groups
296
415
 
@@ -317,20 +436,46 @@ primitive groups members set-role <group-type> <group-id> <user-id> <role> [app-
317
436
  primitive groups memberships <user-id> [app-id] # List user's group memberships
318
437
  ```
319
438
 
439
+ **Group resource access:**
440
+ ```bash
441
+ primitive groups documents <group-type> <group-id> # List documents a group can access
442
+ ```
443
+
444
+ Group permissions on documents are managed via `primitive documents group-permissions` (see [Documents](#documents) above).
445
+
320
446
  ### Analytics
321
447
 
322
448
  View usage analytics for an app.
323
449
 
324
450
  ```bash
325
- primitive analytics overview [app-id] # Activity overview
326
- primitive analytics top-users [app-id] # Most active users
327
- primitive analytics user [app-id] <user-ulid> # User activity details
328
- primitive analytics integrations [app-id] # Integration metrics
451
+ # Overview & active users
452
+ primitive analytics overview [app-id] # DAU / WAU / MAU + growth
453
+ primitive analytics daily-active [app-id] # Daily active users time series
454
+ primitive analytics rolling-active [app-id] # Rolling active users (28 points)
455
+ primitive analytics cohort-retention [app-id] # Weekly cohort retention matrix
456
+
457
+ # Users
458
+ primitive analytics top-users [app-id] # Most active users
459
+ primitive analytics user-search [app-id] --query <q> # Search by email or ULID
460
+ primitive analytics user-detail <user-ulid> [app-id] # User activity breakdown
461
+ primitive analytics user-snapshot <user-ulid> [app-id] # Latest context snapshot
462
+
463
+ # Events
464
+ primitive analytics events [app-id] # Paginated event feed
465
+ primitive analytics events-grouped [app-id] # Events grouped by dimension
466
+
467
+ # Features
468
+ primitive analytics integrations [app-id] # Integration usage metrics
469
+ primitive analytics workflows [app-id] # Top workflows by runs
470
+ primitive analytics prompts [app-id] # Top prompts by executions
329
471
  ```
330
472
 
331
- **Options:**
332
- - `--window-days <n>` - Time window (default: 30)
333
- - `--limit <n>` - Result limit for top-users
473
+ **Common options:**
474
+ - `--window-days <n>` - Time window in days (default varies per command)
475
+ - `--limit <n>` - Result limit (top-users, workflows, prompts)
476
+ - `--group-by <dim>` - Dimension for events-grouped (action, feature, route, country, deviceType, plan, day)
477
+ - `--page <n>` - Page number for events feed (0-based)
478
+ - `--json` - Output raw JSON
334
479
 
335
480
  ### Admins (Super-Admin Only)
336
481
 
@@ -412,6 +557,9 @@ baseUrl = "https://myapp.com"
412
557
  googleOAuthEnabled = true
413
558
  passkeyEnabled = true
414
559
  magicLinkEnabled = false
560
+ otpEnabled = true # email one-time-passcode sign-in
561
+ appleSignInEnabled = true # Sign in with Apple
562
+ appleAudiences = ["com.example.MyApp"] # allowed Apple audiences
415
563
 
416
564
  [cors]
417
565
  mode = "custom"
@@ -419,6 +567,19 @@ allowedOrigins = ["https://myapp.com"]
419
567
  allowCredentials = true
420
568
  ```
421
569
 
570
+ Notes on `[auth]`:
571
+
572
+ - Only keys you actually list are pushed. Omitting a key leaves the server's
573
+ current value untouched — it is **not** reset to `false`. To turn a method
574
+ off, set it explicitly (e.g. `magicLinkEnabled = false`).
575
+ - An unrecognized `[auth]` key (for example a typo like `googleOauthEnabled`)
576
+ produces a warning on `push` and is ignored, rather than being silently
577
+ dropped. The push still applies the recognized keys.
578
+ - `appleAudiences = []` and an omitted `appleAudiences` both mean "no
579
+ audiences" (the server stores them the same way), so `pull` omits the key
580
+ when there are no audiences. A `[] ` round-trip reading back as an omitted
581
+ key is expected, not data loss.
582
+
422
583
  ### Example: integrations/weather-api.toml
423
584
 
424
585
  ```toml
@@ -475,6 +636,129 @@ primitive apps list
475
636
  primitive apps list --json | jq '.[0].appId'
476
637
  ```
477
638
 
639
+ ### Stdout vs. stderr
640
+
641
+ The CLI follows the same convention as `git`, `kubectl`, `aws`, and `gh`:
642
+
643
+ - **stdout** carries the *data* the command produced — JSON documents under
644
+ `--json`, tabular listings (`apps list`), raw values (`primitive token`),
645
+ and the primary `label: value` fields a `get` / `show` / `describe` command
646
+ emits (e.g. `primitive integrations get <id>`).
647
+ - **stderr** carries *diagnostics* — status text (`✓ Created…`,
648
+ `i Waiting for completion...`), warnings (`!` markers), progress lines, and
649
+ the `key: value` summaries shown *after* a side-effecting command runs
650
+ (e.g. the `Webhook ID: …` confirmation printed by `webhooks create`).
651
+
652
+ This means `primitive <cmd> --json | jq` always works, regardless of any
653
+ status / warning / progress text the command may print along the way.
654
+ It also means `primitive integrations get <id> > out.txt` writes the
655
+ integration's fields to `out.txt` while the status diagnostics still appear
656
+ on the terminal via stderr.
657
+
658
+ Conversely, redirecting stderr (`primitive <cmd> 2>/dev/null`) silences
659
+ diagnostics — including success checkmarks like `✓ Pushed 3 changes` — so
660
+ prefer `2>&1 >file.txt` if you want the full transcript.
661
+
662
+ For contributors writing new commands, the output helpers in
663
+ `cli/src/lib/output.ts` make the data-vs-diagnostic split explicit: use
664
+ `json()` and `result(label, value)` for data the caller asked for, and
665
+ `success()` / `info()` / `warn()` / `keyValue()` / `divider()` / `heading()`
666
+ for diagnostics about what the command did.
667
+
668
+ ## Programmatic surfaces
669
+
670
+ ### `primitive help --json` — command manifest
671
+
672
+ `primitive help --json` prints a machine-readable manifest of the full command
673
+ tree (commands, subcommands, positional args, options, aliases, global options,
674
+ and the CLI version) to **stdout** as a single JSON document, then exits `0`. It
675
+ is hidden/undocumented in human `--help` output, auth-free, and runs entirely
676
+ offline (no project config, no credentials, no network) — so it works in any
677
+ directory, including one with no `.primitive/`.
678
+
679
+ ```bash
680
+ primitive help --json | jq '.commands[] | select(.name=="sync") | .commands[].name'
681
+ ```
682
+
683
+ Shape (`schemaVersion: 1`):
684
+
685
+ ```jsonc
686
+ {
687
+ "schemaVersion": 1,
688
+ "name": "primitive",
689
+ "version": "1.0.49",
690
+ "globalOptions": [ { "flags": "-e, --env <name>", "description": "…", "required": true, "defaultValue": null } ],
691
+ "commands": [
692
+ {
693
+ "name": "sync",
694
+ "description": "…",
695
+ "aliases": [],
696
+ "args": [],
697
+ "options": [],
698
+ "commands": [
699
+ {
700
+ "name": "push",
701
+ "description": "…",
702
+ "aliases": [],
703
+ "args": [ { "name": "app-id", "required": false, "variadic": false, "description": "…" } ],
704
+ "options": [ { "flags": "--dir <path>", "description": "…", "required": true, "defaultValue": null } ],
705
+ "commands": []
706
+ }
707
+ ]
708
+ }
709
+ ]
710
+ }
711
+ ```
712
+
713
+ `schemaVersion` is the contract knob: consumers assert the shape they understand
714
+ and fail loud if a future CLI bumps it (a breaking change for downstream tooling
715
+ such as docs CI that drift-gates documented invocations against the published
716
+ CLI).
717
+
718
+ ### `primitive-admin/validators` — TOML validators
719
+
720
+ The TOML validators that `sync push` runs as its load-bearing gate are exported
721
+ from the `primitive-admin/validators` subpath (ESM-only, with types) so external
722
+ tooling can validate exactly the way the published CLI does — instead of
723
+ re-implementing the flow and silently drifting.
724
+
725
+ ```ts
726
+ import {
727
+ validateWorkflowTomlSource, // source-level wrapper (parse → expand → validate)
728
+ validateWorkflowToml, // raw: pure fn over already-parsed TOML
729
+ formatWorkflowTomlErrors,
730
+ validateOperations, // raw: pure fn over already-parsed operations[]
731
+ formatIssue,
732
+ } from "primitive-admin/validators";
733
+ ```
734
+
735
+ Two contracts:
736
+
737
+ - **`validateWorkflowTomlSource(raw, filePath)`** — pass the **raw TOML source
738
+ string** and the workflow's path. It runs the *identical* parse →
739
+ `expandWorkflowTomlData()` (`include` fragment expansion) → `validateWorkflowToml`
740
+ → `formatWorkflowTomlErrors` path that `sync push` runs, so it cannot diverge
741
+ from `sync push` on workflow `include` fragments. Returns
742
+ `{ errors, formatted }` (`formatted` is `null` when valid). Fragment files
743
+ resolve relative to `<filePath>/../../workflow-fragments/`, matching the CLI.
744
+
745
+ - **The raw validators** (`validateWorkflowToml`, `validateOperations`,
746
+ `formatWorkflowTomlErrors`, `formatIssue`) are **pure functions over
747
+ already-parsed TOML** — they do *not* read files or parse TOML. The consumer
748
+ parses (e.g. with `@iarna/toml`) and, for workflows, performs any `include`
749
+ expansion itself. There is **no coupling on a specific TOML parser version**:
750
+ pass whatever parsed object / `operations[]` you already have.
751
+
752
+ ```ts
753
+ import * as TOML from "@iarna/toml";
754
+ const data = TOML.parse(raw);
755
+ const errs = validateWorkflowToml(data);
756
+ if (errs.length) throw new Error(formatWorkflowTomlErrors(path, errs));
757
+ ```
758
+
759
+ Internal helpers (`collectParamRefs`, `declaredParamNames`,
760
+ `locateOperationLine`) are intentionally **not** exported.
761
+
478
762
  ## Exit Codes
479
763
 
480
764
  - `0` - Success
@@ -483,12 +767,20 @@ primitive apps list --json | jq '.[0].appId'
483
767
 
484
768
  ## Environment Variables
485
769
 
486
- - `PRIMITIVE_SERVER_URL` - Server URL for login (defaults to `https://primitiveapi.com`)
770
+ - `PRIMITIVE_SERVER_URL` - Server URL for login in legacy mode (defaults to `https://primitiveapi.com`)
771
+
772
+ **Login server resolution:**
773
+ 1. Project mode (`.primitive/config.json` present): the active environment's
774
+ `apiUrl`. Select the environment with `-e <name>`, `PRIMITIVE_ENV`, or
775
+ `defaultEnvironment`. An unresolvable environment fails loudly — `login`
776
+ never silently falls back to production.
777
+ 2. Legacy mode (no project config): `PRIMITIVE_SERVER_URL`, else the default
778
+ `https://primitiveapi.com`.
487
779
 
488
- **Server URL priority:**
489
- 1. `--server` flag (highest priority)
490
- 2. `PRIMITIVE_SERVER_URL` environment variable
491
- 3. Default: `https://primitiveapi.com`
780
+ > `login` no longer accepts `-s/--server` (removed in a breaking change). Use
781
+ > `-e <env>` in a project, or `PRIMITIVE_SERVER_URL` for local dev / scripts.
782
+ > `init` and `bootstrap` keep their own `-s/--server` (they run before a
783
+ > project config exists).
492
784
 
493
785
  ## Development
494
786
 
@@ -545,11 +837,13 @@ cli/tests/
545
837
  config.test.ts # Credentials and config management
546
838
  output.test.ts # Output formatting functions
547
839
  integration/
548
- api-client.test.ts # API client HTTP tests
549
- commands.test.ts # CLI command tests
550
- tokens.test.ts # Token lifecycle tests
551
- databases.test.ts # Database CRUD, permissions, group permissions tests
552
- groups.test.ts # Group CRUD, members, memberships tests
840
+ api-client.test.ts # API client HTTP tests
841
+ commands.test.ts # CLI command tests
842
+ tokens.test.ts # Token lifecycle tests
843
+ databases.test.ts # Database CRUD, permissions, group permissions tests
844
+ documents.test.ts # Document group permissions, group resource listing tests
845
+ groups.test.ts # Group CRUD, members, memberships tests
846
+ classroom-e2e.test.ts # End-to-end classroom app workflow (types, rules, operations, access)
553
847
  ```
554
848
 
555
849
  ## Troubleshooting