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

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 (219) 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 +268 -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 +48 -45
  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/metadata.d.ts +2 -0
  70. package/dist/src/commands/metadata.js +194 -0
  71. package/dist/src/commands/metadata.js.map +1 -0
  72. package/dist/src/commands/prompts.d.ts +2 -0
  73. package/dist/src/commands/prompts.js +50 -52
  74. package/dist/src/commands/prompts.js.map +1 -1
  75. package/dist/src/commands/rule-sets.d.ts +3 -0
  76. package/dist/src/commands/rule-sets.js +388 -0
  77. package/dist/src/commands/rule-sets.js.map +1 -0
  78. package/dist/src/commands/scripts.d.ts +2 -0
  79. package/dist/src/commands/scripts.js +667 -0
  80. package/dist/src/commands/scripts.js.map +1 -0
  81. package/dist/src/commands/secrets.d.ts +2 -0
  82. package/dist/src/commands/secrets.js +108 -0
  83. package/dist/src/commands/secrets.js.map +1 -0
  84. package/dist/src/commands/skill.d.ts +2 -0
  85. package/dist/src/commands/skill.js +29 -0
  86. package/dist/src/commands/skill.js.map +1 -0
  87. package/dist/src/commands/sync.d.ts +175 -0
  88. package/dist/src/commands/sync.js +4412 -233
  89. package/dist/src/commands/sync.js.map +1 -1
  90. package/dist/src/commands/tokens.d.ts +2 -0
  91. package/dist/src/commands/tokens.js +122 -19
  92. package/dist/src/commands/tokens.js.map +1 -1
  93. package/dist/src/commands/users.d.ts +2 -0
  94. package/dist/src/commands/users.js +440 -22
  95. package/dist/src/commands/users.js.map +1 -1
  96. package/dist/src/commands/waitlist.d.ts +2 -0
  97. package/dist/src/commands/waitlist.js +10 -10
  98. package/dist/src/commands/waitlist.js.map +1 -1
  99. package/dist/src/commands/webhooks.d.ts +2 -0
  100. package/dist/src/commands/webhooks.js +391 -0
  101. package/dist/src/commands/webhooks.js.map +1 -0
  102. package/dist/src/commands/workflows.d.ts +49 -0
  103. package/dist/src/commands/workflows.js +1009 -123
  104. package/dist/src/commands/workflows.js.map +1 -1
  105. package/dist/src/lib/api-client.d.ts +1283 -0
  106. package/dist/src/lib/api-client.js +1129 -83
  107. package/dist/src/lib/api-client.js.map +1 -1
  108. package/dist/src/lib/auth-flow.d.ts +8 -0
  109. package/dist/src/lib/block-layout.d.ts +160 -0
  110. package/dist/src/lib/block-layout.js +451 -0
  111. package/dist/src/lib/block-layout.js.map +1 -0
  112. package/dist/src/lib/cli-manifest.d.ts +60 -0
  113. package/dist/src/lib/cli-manifest.js +70 -0
  114. package/dist/src/lib/cli-manifest.js.map +1 -0
  115. package/dist/src/lib/config.d.ts +53 -0
  116. package/dist/src/lib/config.js +92 -53
  117. package/dist/src/lib/config.js.map +1 -1
  118. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  119. package/dist/src/lib/confirm-prompt.js +110 -0
  120. package/dist/src/lib/confirm-prompt.js.map +1 -0
  121. package/dist/src/lib/constants.d.ts +2 -0
  122. package/dist/src/lib/constants.js +3 -0
  123. package/dist/src/lib/constants.js.map +1 -0
  124. package/dist/src/lib/crash-handlers.d.ts +20 -0
  125. package/dist/src/lib/crash-handlers.js +49 -0
  126. package/dist/src/lib/crash-handlers.js.map +1 -0
  127. package/dist/src/lib/credentials-store.d.ts +79 -0
  128. package/dist/src/lib/credentials-store.js +307 -0
  129. package/dist/src/lib/credentials-store.js.map +1 -0
  130. package/dist/src/lib/csv.d.ts +48 -0
  131. package/dist/src/lib/csv.js +177 -0
  132. package/dist/src/lib/csv.js.map +1 -0
  133. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  134. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  135. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  136. package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
  137. package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
  138. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  139. package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
  140. package/dist/src/lib/db-codegen/dbNaming.js +104 -0
  141. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  142. package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
  143. package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
  144. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  145. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
  146. package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
  147. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  148. package/dist/src/lib/env-resolver.d.ts +62 -0
  149. package/dist/src/lib/env-resolver.js +121 -0
  150. package/dist/src/lib/env-resolver.js.map +1 -0
  151. package/dist/src/lib/fetch.d.ts +5 -0
  152. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  153. package/dist/src/lib/generated-allowlist.js +220 -0
  154. package/dist/src/lib/generated-allowlist.js.map +1 -0
  155. package/dist/src/lib/init-config.d.ts +59 -0
  156. package/dist/src/lib/init-config.js +113 -0
  157. package/dist/src/lib/init-config.js.map +1 -0
  158. package/dist/src/lib/migration-nag.d.ts +49 -0
  159. package/dist/src/lib/migration-nag.js +163 -0
  160. package/dist/src/lib/migration-nag.js.map +1 -0
  161. package/dist/src/lib/output.d.ts +86 -0
  162. package/dist/src/lib/output.js +150 -8
  163. package/dist/src/lib/output.js.map +1 -1
  164. package/dist/src/lib/paginate.d.ts +33 -0
  165. package/dist/src/lib/paginate.js +42 -0
  166. package/dist/src/lib/paginate.js.map +1 -0
  167. package/dist/src/lib/project-config.d.ts +97 -0
  168. package/dist/src/lib/project-config.js +209 -0
  169. package/dist/src/lib/project-config.js.map +1 -0
  170. package/dist/src/lib/query-operators.d.ts +43 -0
  171. package/dist/src/lib/query-operators.js +80 -0
  172. package/dist/src/lib/query-operators.js.map +1 -0
  173. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  174. package/dist/src/lib/refresh-admin-credentials.js +103 -0
  175. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  176. package/dist/src/lib/resolve-platform.d.ts +45 -0
  177. package/dist/src/lib/resolve-platform.js +43 -0
  178. package/dist/src/lib/resolve-platform.js.map +1 -0
  179. package/dist/src/lib/skill-installer.d.ts +23 -0
  180. package/dist/src/lib/skill-installer.js +135 -0
  181. package/dist/src/lib/skill-installer.js.map +1 -0
  182. package/dist/src/lib/snapshots.d.ts +99 -0
  183. package/dist/src/lib/snapshots.js +357 -0
  184. package/dist/src/lib/snapshots.js.map +1 -0
  185. package/dist/src/lib/sync-paths.d.ts +72 -0
  186. package/dist/src/lib/sync-paths.js +130 -0
  187. package/dist/src/lib/sync-paths.js.map +1 -0
  188. package/dist/src/lib/template.d.ts +97 -0
  189. package/dist/src/lib/template.js +336 -62
  190. package/dist/src/lib/template.js.map +1 -1
  191. package/dist/src/lib/token-inject.d.ts +56 -0
  192. package/dist/src/lib/token-inject.js +204 -0
  193. package/dist/src/lib/token-inject.js.map +1 -0
  194. package/dist/src/lib/toml-database-config.d.ts +152 -0
  195. package/dist/src/lib/toml-database-config.js +600 -0
  196. package/dist/src/lib/toml-database-config.js.map +1 -0
  197. package/dist/src/lib/toml-metadata-config.d.ts +139 -0
  198. package/dist/src/lib/toml-metadata-config.js +427 -0
  199. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  200. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  201. package/dist/src/lib/toml-params-validator.js +298 -0
  202. package/dist/src/lib/toml-params-validator.js.map +1 -0
  203. package/dist/src/lib/version-check.d.ts +10 -0
  204. package/dist/src/lib/version-check.js +172 -0
  205. package/dist/src/lib/version-check.js.map +1 -0
  206. package/dist/src/lib/workflow-apply.d.ts +66 -0
  207. package/dist/src/lib/workflow-apply.js +117 -0
  208. package/dist/src/lib/workflow-apply.js.map +1 -0
  209. package/dist/src/lib/workflow-fragments.d.ts +41 -0
  210. package/dist/src/lib/workflow-fragments.js +121 -0
  211. package/dist/src/lib/workflow-fragments.js.map +1 -0
  212. package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
  213. package/dist/src/lib/workflow-toml-validator.js +323 -0
  214. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  215. package/dist/src/types/index.d.ts +520 -0
  216. package/dist/src/validators.d.ts +64 -0
  217. package/dist/src/validators.js +63 -0
  218. package/dist/src/validators.js.map +1 -0
  219. package/package.json +18 -4
@@ -0,0 +1,281 @@
1
+ ---
2
+ name: primitive-platform
3
+ description: >
4
+ Expert guide for building applications on the Primitive platform. MUST be used whenever the user
5
+ is writing code that uses js-bao, js-bao-wss-client, primitive-app components, or any Primitive
6
+ platform feature (documents, databases, workflows, prompts, integrations, blobs, authentication,
7
+ users/groups). Also trigger whenever about to run any `primitive` CLI command (e.g., primitive sync, primitive integrations, primitive apps, primitive env) to ensure Step 0 CLI verification is performed first. After writing or modifying code that touches Primitive
8
+ APIs, this skill cross-references the implementation against official guides and automatically
9
+ corrects common mistakes. Use this skill even if the user doesn't explicitly ask for it —
10
+ any Primitive-related code should be validated against current best practices.
11
+ allowed-tools: Bash, Read, Edit, Write, Glob, Grep, Agent
12
+ ---
13
+
14
+ # Primitive Platform Development Guide
15
+
16
+ You are an expert on the Primitive platform. Your job is to help developers write correct,
17
+ idiomatic Primitive code by leveraging the CLI's built-in guide system and enforcing best practices.
18
+
19
+ **The CLI guides are the single source of truth.** Never hardcode or memorize guide content —
20
+ always fetch the latest from the CLI.
21
+
22
+ ## Step 0: Verify CLI Configuration
23
+
24
+ The Primitive CLI is **project-scoped**, and project mode is **strongly preferred** for any work
25
+ inside a repo. Each project has a `.primitive/config.json` (committed to the repo) that defines
26
+ named environments (`dev`, `prod`, `staging`, …), where each environment binds an `apiUrl` and
27
+ (optionally) an `appId`. Per-environment auth tokens live in `.primitive/credentials.json`
28
+ (gitignored). There is no global "currently active app" — the active environment determines the
29
+ server *and* the app.
30
+
31
+ The legacy global fallback (`~/.primitive/credentials.json`) exists only for one-off use outside a
32
+ project. **Inside a project, treat its absence as a setup gap to fix, not a mode to operate in.**
33
+
34
+ **Before running any CLI commands**, your *first* check is whether the project is in project mode:
35
+
36
+ ```bash
37
+ ls .primitive/config.json # exists at project root or any ancestor?
38
+ ```
39
+
40
+ The two branches below are not equivalent — pick the one that matches reality and follow it.
41
+
42
+ ### Branch A — `.primitive/config.json` exists (project mode)
43
+
44
+ The active environment is resolved in this order:
45
+ 1. `--env <name>` flag on the command
46
+ 2. `PRIMITIVE_ENV` environment variable
47
+ 3. `defaultEnvironment` in `.primitive/config.json`
48
+ 4. The sole environment, if exactly one is defined
49
+
50
+ Confirm you're targeting the correct environment:
51
+
52
+ 1. **Read the CLI header.** Every command prints `Env | App | Server` at the top of its output —
53
+ verify these match the project's intended target.
54
+ 2. **Inspect the project config:**
55
+
56
+ ```bash
57
+ primitive env list # All environments (default marked with *)
58
+ primitive env show # Details for the currently-resolved env
59
+ primitive whoami # Authenticated user + resolved server/app
60
+ ```
61
+
62
+ **To switch environments** for a one-off command, pass `--env <name>`. To change the project
63
+ default, run `primitive env use <name>`. To switch the *app* an env points at, edit the env's
64
+ `appId` in `.primitive/config.json` (or re-run `primitive env add`). `primitive use <app>` is a
65
+ no-op when the active env already pins an `appId`.
66
+
67
+ ### Branch B — no `.primitive/config.json` (project mode NOT set up)
68
+
69
+ Without project config the CLI silently falls back to global state in `~/.primitive/credentials.json`
70
+ (legacy mode). Commands run against whatever app/server happens to be globally active — which the
71
+ agent didn't set and the user may have forgotten about. **This is a footgun, not a supported way to
72
+ work inside a project.** Do not proceed silently, and do not treat the global fallback as the
73
+ default path.
74
+
75
+ **Your default action is to set up project mode.** Stop and prompt the user to create the project
76
+ config before doing anything else. Don't bury the recommendation behind an equal-weight "or proceed
77
+ against global state" option — make setting up project config the clear, recommended next step.
78
+
79
+ First gather the context you'll propose (so the prompt is concrete, not abstract):
80
+
81
+ ```bash
82
+ primitive whoami # current global server + app, if any — shows what the fallback WOULD target
83
+ ```
84
+
85
+ Then prompt the user, e.g.:
86
+
87
+ > "This project has no `.primitive/config.json`, so the CLI isn't in project mode. I recommend
88
+ > setting up project-scoped config so this repo pins its own environment instead of relying on your
89
+ > global state (currently `<server>` / `<app from whoami>`, which I didn't set). I'll add an env
90
+ > with:
91
+ >
92
+ > ```bash
93
+ > primitive env add dev --api-url <url> --app-id <id>
94
+ > ```
95
+ >
96
+ > Does this look right, or should I adjust the env name / URL / app?"
97
+
98
+ If you need the user to pick the env name, server, or app, ask them. Confirm the values before
99
+ running `env add` — but the question to resolve is *which* project config to create, not *whether*
100
+ to create one.
101
+
102
+ `primitive env add` is additive and safe — it only writes an entry to `.primitive/config.json`
103
+ (creating the file if needed). It does not touch source code, create apps on the server, or install
104
+ dependencies.
105
+
106
+ **Only fall back to global state if the user explicitly declines project setup** after you've
107
+ recommended it. Even then, name the exact server/app the command will hit and get clear
108
+ confirmation before running anything mutating (`primitive sync push`, `primitive apps create`, etc.).
109
+ A read-only command (`whoami`, `guides list`) against global state is fine while you're still
110
+ working out the config.
111
+
112
+ Do not rely on `.env` files like `PRIMITIVE_API_URL` to control CLI targeting — those are not
113
+ read by the CLI in project mode, and the project config is the source of truth.
114
+
115
+ **Why this matters:** If the CLI is pointed at the wrong environment (e.g., prod instead of dev),
116
+ commands like `primitive sync push` will modify the wrong server. Silent fallback to global state
117
+ makes this exact mistake easy to commit. Setting up project config is the durable fix — verify and
118
+ surface before running mutating operations.
119
+
120
+ ## Step 1: Discover Available Guides
121
+
122
+ Before writing or reviewing any Primitive code, run:
123
+
124
+ ```bash
125
+ primitive guides list
126
+ ```
127
+
128
+ This returns the full list of available guide topics with descriptions, keywords, and use cases.
129
+ The `COMBINATIONS` column shows which `(language, platform)` variants each guide is available in
130
+ (e.g. `ts; swift`). Use this output to determine which guides are relevant to the current task —
131
+ and which language/platform variant to request in Step 2.
132
+
133
+ ### Determine the project's language and platform
134
+
135
+ Figure out what the project you're working in targets, then request the matching variant when
136
+ fetching guides:
137
+
138
+ - A `Package.swift`, `*.xcodeproj`, or `project.yml` → `--language swift` (plus `--platform ios`
139
+ or `--platform macos` as appropriate).
140
+ - A Vite/React/Node web app (`package.json`, `js-bao-wss-client`) → `--language ts --platform web`.
141
+
142
+ If you can't tell, omit the flags — every guide has a default variant, so a bare
143
+ `primitive guides get <topic>` always returns something useful.
144
+
145
+ ## Step 2: Fetch the Relevant Guides
146
+
147
+ For each relevant topic identified in Step 1, fetch the full guide, passing the project's
148
+ language/platform so you get the right variant:
149
+
150
+ ```bash
151
+ primitive guides get <topic> --language <ts|swift> --platform <web|ios|macos>
152
+ # or, when the project's language/platform is unknown or doesn't matter:
153
+ primitive guides get <topic>
154
+ ```
155
+
156
+ `--language` accepts aliases (`typescript`/`javascript`/`js` → `ts`). These flags **never fail**:
157
+ an unknown value or an unavailable combination falls back to the guide's default variant rather
158
+ than erroring, so it's always safe to pass your best guess.
159
+
160
+ **Always fetch guide(s) BEFORE writing code.** If multiple features are involved, fetch multiple
161
+ guides. The guides contain:
162
+ - Complete API documentation with method signatures
163
+ - Working code examples in the requested language (e.g. TypeScript or Swift)
164
+ - Common patterns and anti-patterns
165
+ - Configuration examples (TOML files for `primitive sync`)
166
+ - Decision frameworks for architecture choices
167
+
168
+ **Do not guess or assume API patterns.** If you're unsure about a method signature, parameter,
169
+ or pattern, fetch the guide. The guides are comprehensive and authoritative.
170
+
171
+ ## Step 3: Write Code Following Guide Patterns
172
+
173
+ When writing Primitive code:
174
+
175
+ 1. **Follow the patterns from the fetched guides exactly** — method names, argument order, lifecycle patterns
176
+ 2. **Use `primitive sync`** for all backend configuration (workflows, prompts, integrations, databases)
177
+ 3. **Configuration lives in TOML files** in version control, pushed via `primitive sync push`
178
+ 4. **Run `pnpm codegen`** after creating or modifying js-bao models
179
+
180
+ ## Step 4: Post-Code Review (Automatic)
181
+
182
+ After writing or modifying Primitive-related code, **automatically perform this review**:
183
+
184
+ ### 4a. Identify What Was Written
185
+ Determine which Primitive features the new/modified code touches by scanning for:
186
+ - Import statements from `js-bao`, `js-bao-wss-client`, or `primitive-app`
187
+ - Primitive API calls (documents.open, databases.connect, workflows, etc.)
188
+ - Model definitions, schemas, queries
189
+ - Configuration files (TOML for sync)
190
+
191
+ ### 4b. Fetch and Cross-Reference
192
+ Run `primitive guides list` to identify which guides cover the features used, then fetch each one
193
+ in the project's language/platform:
194
+ ```bash
195
+ primitive guides get <topic> --language <ts|swift> --platform <web|ios|macos>
196
+ ```
197
+
198
+ Compare the written code against the guide content:
199
+ - **API usage patterns** — Are methods called correctly with proper arguments?
200
+ - **Lifecycle management** — Are documents opened before queries? Is auth checked first?
201
+ - **Access control** — Are CEL expressions or permissions configured properly?
202
+ - **Anti-patterns** — Does the code do anything the guide explicitly warns against?
203
+ - **Missing steps** — Does the code need `pnpm codegen`, `primitive sync push`, or other follow-up?
204
+
205
+ ### 4c. Report and Fix
206
+ If issues are found:
207
+ 1. **Explain the issue** — cite the specific guide section that applies
208
+ 2. **Show the fix** — provide corrected code
209
+ 3. **Apply the fix** — edit the file directly (don't just suggest, actually fix it)
210
+ 4. **Note any CLI commands needed** — e.g., `pnpm codegen` or `primitive sync push`
211
+
212
+ If no issues are found, briefly confirm the code follows best practices.
213
+
214
+ ## CLI Quick Reference
215
+
216
+ Remind users of these essential commands when relevant:
217
+
218
+ ```bash
219
+ # Verify current configuration (DO THIS FIRST)
220
+ primitive env list # List environments in .primitive/config.json (default marked *)
221
+ primitive env show # Details for the currently-resolved env (api URL, app ID)
222
+ primitive whoami # Authenticated user + resolved server/app
223
+
224
+ # Switching environments
225
+ primitive env use <name> # Change the project's default environment
226
+ primitive --env <name> <command> # One-off override for a single command
227
+ PRIMITIVE_ENV=<name> <command> # Override via env var (useful in scripts/CI)
228
+
229
+ # Setup — existing project (most common: adopting Primitive in an existing repo)
230
+ npm install -g primitive-admin # Install CLI
231
+ primitive env add dev --api-url <url> --app-id <id> # Add env to .primitive/config.json
232
+ primitive env add prod --api-url <url> --app-id <id> # (creates the file if missing)
233
+ primitive login # Authenticate (tokens stored per-env)
234
+
235
+ # Setup — brand-new project (greenfield only)
236
+ primitive init my-new-app # Scaffolds template, creates a new app
237
+ # on the server, runs pnpm install.
238
+ # Do NOT run inside an existing repo.
239
+
240
+ # Guides (the most important commands for development)
241
+ primitive guides list # See all guides: topics, descriptions, available (lang,platform) combinations
242
+ primitive guides get <topic> # Read a guide's default variant
243
+ primitive guides get <topic> --language swift --platform ios # Read a specific language/platform variant
244
+
245
+ # Configuration as Code
246
+ primitive sync init --dir ./config # Initialize config directory
247
+ primitive sync pull --dir ./config # Pull config from server
248
+ primitive sync push --dir ./config # Push config to server
249
+ primitive sync diff --dir ./config # Preview changes before push
250
+
251
+ # Common operations
252
+ primitive apps list # List apps on the active env's server
253
+ primitive apps create "Name" # Create an app (does NOT auto-bind to an env;
254
+ # edit .primitive/config.json or use `env add` to bind)
255
+ ```
256
+
257
+ ## When the User is Starting a New Feature
258
+
259
+ If the user describes a new feature they want to build:
260
+
261
+ 1. **Verify CLI configuration** per Step 0 — confirm the active environment in
262
+ `.primitive/config.json` (and its bound `apiUrl` / `appId`) match the project's intended target
263
+ before running any commands
264
+ 2. **Run `primitive guides list`** to discover available topics and their `(language, platform)` combinations
265
+ 3. **Identify which guides are relevant** to their feature from the list output
266
+ 4. **Fetch those guides** with `primitive guides get <topic> --language <lang> --platform <platform>`
267
+ (using the project's language/platform; omit the flags if unknown)
268
+ 5. **Recommend a data modeling approach** based on the guide content. If requirements are unclear or ambiguous, **ask the user clarifying questions before proceeding** — it's much easier to get the data model right upfront than to migrate later
269
+ 6. **Outline the implementation steps** referencing specific patterns from the guides
270
+ 7. **Write the code** following the patterns exactly
271
+ 8. **Review automatically** per Step 4 above
272
+
273
+ ## When the User Asks "How Do I...?"
274
+
275
+ For any question about Primitive platform capabilities:
276
+
277
+ 1. **Run `primitive guides list`** to find the relevant topic (and its available language/platform combinations)
278
+ 2. **Fetch the guide**: `primitive guides get <topic> --language <lang> --platform <platform>` (omit the flags if the language/platform is unknown)
279
+ 3. **Answer from the guide content** — don't guess or make up APIs
280
+ 4. **Include working code examples** from the guide
281
+ 5. **Point the user to the guide** for further reading: "You can see more examples by running `primitive guides get <topic>`"
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import { readFileSync } from "fs";
2
+ import { Command, CommanderError } from "commander";
3
+ import { readFileSync, writeSync } from "fs";
4
4
  import { fileURLToPath } from "url";
5
5
  import { dirname, resolve } from "path";
6
6
  import { registerInitCommand } from "../src/commands/init.js";
@@ -9,7 +9,11 @@ import { registerAppsCommands } from "../src/commands/apps.js";
9
9
  import { registerUsersCommands } from "../src/commands/users.js";
10
10
  import { registerWaitlistCommands } from "../src/commands/waitlist.js";
11
11
  import { registerIntegrationsCommands } from "../src/commands/integrations.js";
12
+ import { registerWebhooksCommands } from "../src/commands/webhooks.js";
13
+ import { registerCronTriggersCommands } from "../src/commands/cron-triggers.js";
14
+ import { registerMetadataCommands } from "../src/commands/metadata.js";
12
15
  import { registerPromptsCommands } from "../src/commands/prompts.js";
16
+ import { registerScriptsCommands } from "../src/commands/scripts.js";
13
17
  import { registerWorkflowsCommands } from "../src/commands/workflows.js";
14
18
  import { registerAdminsCommands } from "../src/commands/admins.js";
15
19
  import { registerCatalogCommands } from "../src/commands/catalog.js";
@@ -20,47 +24,231 @@ import { registerComparisonsCommands } from "../src/commands/comparisons.js";
20
24
  import { registerTokensCommands } from "../src/commands/tokens.js";
21
25
  import { registerDatabasesCommands } from "../src/commands/databases.js";
22
26
  import { registerGroupsCommands } from "../src/commands/groups.js";
27
+ import { registerRuleSetsCommands } from "../src/commands/rule-sets.js";
28
+ import { registerGroupTypeConfigsCommands } from "../src/commands/group-type-configs.js";
29
+ import { registerDatabaseTypesCommands } from "../src/commands/database-types.js";
23
30
  import { registerGuidesCommands } from "../src/commands/guides.js";
24
31
  import { registerDocumentsCommands } from "../src/commands/documents.js";
32
+ import chalk from "chalk";
33
+ import { registerEmailTemplatesCommands } from "../src/commands/email-templates.js";
34
+ import { registerCollectionsCommands } from "../src/commands/collections.js";
35
+ import { registerCollectionTypeConfigsCommands } from "../src/commands/collection-type-configs.js";
25
36
  import { error } from "../src/lib/output.js";
37
+ import { installGlobalCrashHandlers } from "../src/lib/crash-handlers.js";
38
+ import { buildCliManifest } from "../src/lib/cli-manifest.js";
26
39
  import { ApiError } from "../src/lib/api-client.js";
40
+ import { checkForUpdate } from "../src/lib/version-check.js";
41
+ import { loadCredentials } from "../src/lib/config.js";
42
+ import { checkSkillStatus } from "../src/lib/skill-installer.js";
43
+ import { registerSkillCommands } from "../src/commands/skill.js";
44
+ import { registerSecretsCommands } from "../src/commands/secrets.js";
45
+ import { registerBlobBucketsCommands } from "../src/commands/blob-buckets.js";
46
+ import { registerEnvCommands } from "../src/commands/env.js";
47
+ import { setCurrentEnvName, getCurrentEnvNameSafe, getProjectConfig, } from "../src/lib/env-resolver.js";
48
+ import { ProjectConfigError, PROJECT_CONFIG_DISPLAY_NAME, findProjectConfigPath } from "../src/lib/project-config.js";
49
+ import { maybeWarnLegacyMigration } from "../src/lib/migration-nag.js";
50
+ // Install global crash handlers first thing. Without these, an error that
51
+ // escapes the top-level `.catch` below — e.g. ERR_USE_AFTER_CLOSE thrown from
52
+ // inside an inquirer event callback when stdin isn't a usable TTY — makes Node
53
+ // print only its bare `Node.js vX` banner and die with no message (issue #972).
54
+ installGlobalCrashHandlers();
27
55
  const __filename = fileURLToPath(import.meta.url);
28
56
  const __dirname = dirname(__filename);
29
57
  const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf-8"));
58
+ /**
59
+ * Write a string to a file descriptor synchronously, in full, before
60
+ * returning. Handles two pitfalls of a single `fs.writeSync` on a pipe:
61
+ * - partial writes: `writeSync` may write fewer bytes than requested (e.g.
62
+ * only what fits in the OS pipe buffer), so we loop on the returned count;
63
+ * - `EAGAIN` on a non-blocking fd with a slow/full consumer: retry instead of
64
+ * throwing.
65
+ * This guarantees the whole payload reaches the kernel before the caller
66
+ * exits, with no dependency on the event loop draining stdout.
67
+ */
68
+ function writeAllSync(fd, text) {
69
+ const buf = Buffer.from(text, "utf-8");
70
+ let offset = 0;
71
+ while (offset < buf.length) {
72
+ try {
73
+ offset += writeSync(fd, buf, offset, buf.length - offset);
74
+ }
75
+ catch (err) {
76
+ // Retry on EAGAIN (non-blocking fd, consumer not ready yet). Rethrow
77
+ // anything else.
78
+ if (err.code === "EAGAIN")
79
+ continue;
80
+ throw err;
81
+ }
82
+ }
83
+ }
84
+ const isVersionFlag = process.argv.includes("--version") || process.argv.includes("-V");
85
+ const subcommand = process.argv[2];
86
+ const isJsonOutput = process.argv.includes("--json");
87
+ const skipHeader = isVersionFlag || isJsonOutput || subcommand === "login" || subcommand === "logout" || subcommand === "token";
88
+ // --env <name> is a global option that must be applied BEFORE any command
89
+ // code runs, because loadCredentials() and getServerUrl() both consult the
90
+ // active environment. Commander only parses options during .parse(), so we
91
+ // pre-extract it here via a tiny manual scan. The actual option is still
92
+ // registered on the program below so it appears in --help.
93
+ function preParseEnvFlag() {
94
+ const argv = process.argv;
95
+ for (let i = 2; i < argv.length; i++) {
96
+ const a = argv[i];
97
+ if (a === "--env" || a === "-e") {
98
+ return argv[i + 1] || null;
99
+ }
100
+ if (a.startsWith("--env="))
101
+ return a.slice("--env=".length);
102
+ }
103
+ return null;
104
+ }
105
+ const preParsedEnv = preParseEnvFlag();
106
+ if (preParsedEnv) {
107
+ setCurrentEnvName(preParsedEnv);
108
+ }
109
+ // Surface project-config errors loudly and early. A broken config file
110
+ // should fail fast with a clear message rather than producing cryptic
111
+ // "Not logged in" errors deeper in.
112
+ try {
113
+ getProjectConfig();
114
+ }
115
+ catch (err) {
116
+ if (err instanceof ProjectConfigError) {
117
+ error(err.message);
118
+ if (err.path)
119
+ error(` at ${err.path}`);
120
+ process.exit(1);
121
+ }
122
+ throw err;
123
+ }
124
+ // If the user explicitly passed --env <name>, verify it resolves to a
125
+ // real environment NOW so we don't silently fall back to legacy creds.
126
+ if (preParsedEnv) {
127
+ try {
128
+ const project = getProjectConfig();
129
+ if (!project) {
130
+ error(`--env was given but no ${PROJECT_CONFIG_DISPLAY_NAME} was found in this directory or any parent.`);
131
+ process.exit(1);
132
+ }
133
+ if (!project.environments[preParsedEnv]) {
134
+ const available = Object.keys(project.environments).join(", ") || "(none)";
135
+ error(`Environment "${preParsedEnv}" is not defined in ${PROJECT_CONFIG_DISPLAY_NAME}. Available: ${available}`);
136
+ process.exit(1);
137
+ }
138
+ }
139
+ catch (err) {
140
+ if (err instanceof ProjectConfigError) {
141
+ error(err.message);
142
+ process.exit(1);
143
+ }
144
+ throw err;
145
+ }
146
+ }
147
+ if (!skipHeader) {
148
+ const creds = loadCredentials();
149
+ // In project mode the env may declare an appId even before login.
150
+ // Show that so the header is useful during `env add` / `login` flows.
151
+ const envNameForHeader = getCurrentEnvNameSafe();
152
+ let headerAppId = creds?.currentAppId;
153
+ let headerAppName = creds?.currentAppName;
154
+ if (envNameForHeader && (!headerAppId || !headerAppName)) {
155
+ try {
156
+ const envCfg = getProjectConfig()?.environments[envNameForHeader];
157
+ headerAppId = headerAppId || envCfg?.appId;
158
+ headerAppName = headerAppName || envCfg?.appName;
159
+ }
160
+ catch {
161
+ // ignore
162
+ }
163
+ }
164
+ const appInfo = headerAppName
165
+ ? `${headerAppName} (${headerAppId})`
166
+ : headerAppId || "None set";
167
+ // Prefer the active environment's apiUrl so the header stays correct
168
+ // even before you've logged in. Only fall back to credentials' serverUrl
169
+ // in legacy mode.
170
+ let server = creds?.serverUrl;
171
+ if (envNameForHeader) {
172
+ try {
173
+ const env = getProjectConfig()?.environments[envNameForHeader];
174
+ if (env)
175
+ server = env.apiUrl;
176
+ }
177
+ catch {
178
+ // fall through
179
+ }
180
+ }
181
+ server = server || "Not configured";
182
+ const projectConfigPath = findProjectConfigPath();
183
+ const envLabel = envNameForHeader
184
+ ? ` | Env: ${envNameForHeader}`
185
+ : projectConfigPath
186
+ ? " | Env: (none selected)"
187
+ : "";
188
+ console.log(chalk.dim(`CLI Version: ${pkg.version}${envLabel} | App: ${appInfo} | Server: ${server}`));
189
+ console.log();
190
+ }
191
+ // Nudge legacy-mode users (no .primitive/config.json, but a working
192
+ // `~/.primitive/credentials.json` + a sync marker in CWD) to migrate.
193
+ // Wholly self-contained: rate-limited to once per 24h, opt-out via
194
+ // PRIMITIVE_NO_MIGRATE_NAG=1, silent in CI / non-TTY. See #552.
195
+ maybeWarnLegacyMigration({ argv: process.argv });
30
196
  const program = new Command();
31
197
  program
32
198
  .name("primitive")
199
+ .exitOverride()
33
200
  .description(`CLI for administering Primitive applications.
34
201
 
35
202
  Manage apps, users, integrations, prompts, workflows, and more from the command line.
36
203
  Supports TOML-based configuration sync for version-controlled app settings.`)
37
204
  .version(pkg.version)
205
+ // Global --env flag. Parsed above in preParseEnvFlag too; declaring it
206
+ // here so commander shows it in help output and forwards it to
207
+ // subcommands.
208
+ .option("-e, --env <name>", "Named environment from .primitive/config.json (e.g. dev, prod)")
38
209
  .addHelpText("after", `
39
210
  Examples:
40
- $ primitive login # Authenticate via browser OAuth
41
- $ primitive use "My App" # Set working app context
42
- $ primitive users list # List users (uses current app)
43
- $ primitive integrations list --json # JSON output for scripting
44
- $ primitive sync pull --dir ./config # Export config to TOML files
211
+ $ primitive init my-app # Scaffold a new project + .primitive/config.json
212
+ $ primitive env list # Show environments
213
+ $ primitive env add prod --api-url ... # Add a new environment
214
+ $ primitive --env prod users list # Run a command against a specific env
215
+ $ primitive login # Authenticate via browser OAuth
216
+ $ primitive sync pull # Pull config (auto per-env path)
217
+
218
+ Project configuration (.primitive/config.json):
219
+ In a directory with a .primitive/config.json (or any subdirectory of one),
220
+ the CLI operates in project mode: credentials and sync state live under
221
+ <projectRoot>/.primitive/ and each named environment is isolated.
222
+ Without a .primitive/config.json the CLI runs in legacy mode, reading
223
+ credentials from ~/.primitive/credentials.json.
45
224
 
46
- Authentication:
47
- Credentials are stored in ~/.primitive/credentials.json
225
+ Environment selection (project mode):
226
+ 1. --env <name> flag
227
+ 2. PRIMITIVE_ENV env var
228
+ 3. "defaultEnvironment" in .primitive/config.json
229
+ 4. The only environment, if there's exactly one
48
230
 
49
231
  App Context:
50
- Set with 'primitive use <app>' to avoid passing --app to every command.
51
- Most commands accept --app flag to override the current context.
232
+ Bind an app to an environment via "appId" in .primitive/config.json, or
233
+ run 'primitive use <app>' to set one in credentials. Most commands accept
234
+ --app to override.
52
235
 
53
236
  Documentation:
54
237
  See https://primitive-labs.github.io/primitive-docs/ for full documentation.
55
238
  `);
56
239
  // Register all command groups (init first, then logically grouped)
57
240
  registerInitCommand(program);
241
+ registerEnvCommands(program);
58
242
  registerAuthCommands(program);
59
243
  registerAppsCommands(program);
60
244
  registerUsersCommands(program);
61
245
  registerWaitlistCommands(program);
62
246
  registerIntegrationsCommands(program);
247
+ registerWebhooksCommands(program);
248
+ registerCronTriggersCommands(program);
249
+ registerMetadataCommands(program);
63
250
  registerPromptsCommands(program);
251
+ registerScriptsCommands(program);
64
252
  registerWorkflowsCommands(program);
65
253
  registerAdminsCommands(program);
66
254
  registerCatalogCommands(program);
@@ -71,24 +259,88 @@ registerComparisonsCommands(program);
71
259
  registerTokensCommands(program);
72
260
  registerDatabasesCommands(program);
73
261
  registerGroupsCommands(program);
262
+ registerRuleSetsCommands(program);
263
+ registerGroupTypeConfigsCommands(program);
264
+ registerDatabaseTypesCommands(program);
74
265
  registerGuidesCommands(program);
75
266
  registerDocumentsCommands(program);
267
+ registerEmailTemplatesCommands(program);
268
+ registerCollectionsCommands(program);
269
+ registerCollectionTypeConfigsCommands(program);
270
+ registerSkillCommands(program);
271
+ registerSecretsCommands(program);
272
+ registerBlobBucketsCommands(program);
273
+ // `primitive help --json` — machine-readable command manifest (issue #983).
274
+ //
275
+ // Mechanism: a pre-parse argv guard, NOT a registered `help` subcommand.
276
+ // Commander already injects its own built-in `help [command]`; registering our
277
+ // own would risk clobbering `primitive help <command>`. By special-casing the
278
+ // invocation here — after every register*Commands() call so the full tree is
279
+ // built — we serialize commander's public accessors and exit before parseAsync.
280
+ // It is auth-free, runs offline (no project config / credentials / network),
281
+ // and `--json` already suppresses the header, skill-status nag, and update
282
+ // check (see `isJsonOutput` above), so stdout carries pure JSON.
283
+ //
284
+ // Hidden/undocumented: there is no visible `help --json` entry in human help
285
+ // output; it's discoverable for tooling/agents that know to ask for it.
286
+ if (process.argv[2] === "help" && process.argv.includes("--json")) {
287
+ // Emit the full manifest synchronously, then exit. The naive form — the
288
+ // async `json()` helper (`console.log`) followed by an immediate
289
+ // `process.exit(0)` — races the flush on the documented `primitive help
290
+ // --json | jq` pipe path: the ~365KB manifest far exceeds the 64KB OS pipe
291
+ // buffer, so exiting before the kernel drains it truncates the JSON for
292
+ // piped consumers.
293
+ //
294
+ // Robust fix: write to fd 1 with `fs.writeSync` in a loop. A single
295
+ // `writeSync` is NOT enough — on a non-blocking pipe fd it returns after a
296
+ // *partial* write (only what currently fits in the pipe buffer) without
297
+ // looping, silently dropping the tail; and on a slow consumer it can throw
298
+ // `EAGAIN`. Looping over the returned byte count and retrying on `EAGAIN`
299
+ // guarantees every byte is handed to the kernel before `process.exit(0)`,
300
+ // independent of the event loop. Output is byte-identical to `json()`: the
301
+ // same `JSON.stringify(..., null, 2)` plus a single trailing newline.
302
+ writeAllSync(1, JSON.stringify(buildCliManifest(program, pkg.version), null, 2) + "\n");
303
+ process.exit(0);
304
+ }
76
305
  // Global error handler
77
306
  program.hook("preAction", () => {
78
307
  // Reset API client state before each command
79
308
  });
309
+ // Commands that should never show post-command messages (update check, skill status)
310
+ const suppressPostMessages = isJsonOutput || subcommand === "token";
311
+ async function runPostCommandMessages() {
312
+ if (suppressPostMessages)
313
+ return;
314
+ // Claude Code skill: auto-update if installed, or show hint if not.
315
+ // Skip for `init` (handles its own prompt), `skill` (explicit management),
316
+ // and non-interactive contexts (--json, --version, token, etc.).
317
+ if (!skipHeader && subcommand !== "skill" && subcommand !== "init") {
318
+ await checkSkillStatus();
319
+ }
320
+ await checkForUpdate(pkg.version);
321
+ }
80
322
  // Parse and execute
81
- program.parseAsync(process.argv).catch((err) => {
323
+ program.parseAsync(process.argv)
324
+ .then(async () => {
325
+ await runPostCommandMessages();
326
+ })
327
+ .catch(async (err) => {
328
+ // Commander throws CommanderError for help display, version flag, etc.
329
+ // Run post-command messages then exit with the intended code.
330
+ if (err instanceof CommanderError) {
331
+ await runPostCommandMessages();
332
+ process.exit(err.exitCode);
333
+ }
82
334
  if (err instanceof ApiError) {
83
335
  error(err.message);
84
336
  if (err.statusCode === 401) {
85
337
  error("Try running 'primitive login' to authenticate.");
86
338
  }
339
+ await runPostCommandMessages();
87
340
  process.exit(err.statusCode === 401 ? 2 : 1);
88
341
  }
89
- else {
90
- error(err.message || "An unexpected error occurred");
91
- process.exit(1);
92
- }
342
+ error(err.message || "An unexpected error occurred");
343
+ await runPostCommandMessages();
344
+ process.exit(1);
93
345
  });
94
346
  //# sourceMappingURL=primitive.js.map