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
@@ -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,10 @@ 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";
12
14
  import { registerPromptsCommands } from "../src/commands/prompts.js";
15
+ import { registerScriptsCommands } from "../src/commands/scripts.js";
13
16
  import { registerWorkflowsCommands } from "../src/commands/workflows.js";
14
17
  import { registerAdminsCommands } from "../src/commands/admins.js";
15
18
  import { registerCatalogCommands } from "../src/commands/catalog.js";
@@ -20,47 +23,230 @@ import { registerComparisonsCommands } from "../src/commands/comparisons.js";
20
23
  import { registerTokensCommands } from "../src/commands/tokens.js";
21
24
  import { registerDatabasesCommands } from "../src/commands/databases.js";
22
25
  import { registerGroupsCommands } from "../src/commands/groups.js";
26
+ import { registerRuleSetsCommands } from "../src/commands/rule-sets.js";
27
+ import { registerGroupTypeConfigsCommands } from "../src/commands/group-type-configs.js";
28
+ import { registerDatabaseTypesCommands } from "../src/commands/database-types.js";
23
29
  import { registerGuidesCommands } from "../src/commands/guides.js";
24
30
  import { registerDocumentsCommands } from "../src/commands/documents.js";
31
+ import chalk from "chalk";
32
+ import { registerEmailTemplatesCommands } from "../src/commands/email-templates.js";
33
+ import { registerCollectionsCommands } from "../src/commands/collections.js";
34
+ import { registerCollectionTypeConfigsCommands } from "../src/commands/collection-type-configs.js";
25
35
  import { error } from "../src/lib/output.js";
36
+ import { installGlobalCrashHandlers } from "../src/lib/crash-handlers.js";
37
+ import { buildCliManifest } from "../src/lib/cli-manifest.js";
26
38
  import { ApiError } from "../src/lib/api-client.js";
39
+ import { checkForUpdate } from "../src/lib/version-check.js";
40
+ import { loadCredentials } from "../src/lib/config.js";
41
+ import { checkSkillStatus } from "../src/lib/skill-installer.js";
42
+ import { registerSkillCommands } from "../src/commands/skill.js";
43
+ import { registerSecretsCommands } from "../src/commands/secrets.js";
44
+ import { registerBlobBucketsCommands } from "../src/commands/blob-buckets.js";
45
+ import { registerEnvCommands } from "../src/commands/env.js";
46
+ import { setCurrentEnvName, getCurrentEnvNameSafe, getProjectConfig, } from "../src/lib/env-resolver.js";
47
+ import { ProjectConfigError, PROJECT_CONFIG_DISPLAY_NAME, findProjectConfigPath } from "../src/lib/project-config.js";
48
+ import { maybeWarnLegacyMigration } from "../src/lib/migration-nag.js";
49
+ // Install global crash handlers first thing. Without these, an error that
50
+ // escapes the top-level `.catch` below — e.g. ERR_USE_AFTER_CLOSE thrown from
51
+ // inside an inquirer event callback when stdin isn't a usable TTY — makes Node
52
+ // print only its bare `Node.js vX` banner and die with no message (issue #972).
53
+ installGlobalCrashHandlers();
27
54
  const __filename = fileURLToPath(import.meta.url);
28
55
  const __dirname = dirname(__filename);
29
56
  const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf-8"));
57
+ /**
58
+ * Write a string to a file descriptor synchronously, in full, before
59
+ * returning. Handles two pitfalls of a single `fs.writeSync` on a pipe:
60
+ * - partial writes: `writeSync` may write fewer bytes than requested (e.g.
61
+ * only what fits in the OS pipe buffer), so we loop on the returned count;
62
+ * - `EAGAIN` on a non-blocking fd with a slow/full consumer: retry instead of
63
+ * throwing.
64
+ * This guarantees the whole payload reaches the kernel before the caller
65
+ * exits, with no dependency on the event loop draining stdout.
66
+ */
67
+ function writeAllSync(fd, text) {
68
+ const buf = Buffer.from(text, "utf-8");
69
+ let offset = 0;
70
+ while (offset < buf.length) {
71
+ try {
72
+ offset += writeSync(fd, buf, offset, buf.length - offset);
73
+ }
74
+ catch (err) {
75
+ // Retry on EAGAIN (non-blocking fd, consumer not ready yet). Rethrow
76
+ // anything else.
77
+ if (err.code === "EAGAIN")
78
+ continue;
79
+ throw err;
80
+ }
81
+ }
82
+ }
83
+ const isVersionFlag = process.argv.includes("--version") || process.argv.includes("-V");
84
+ const subcommand = process.argv[2];
85
+ const isJsonOutput = process.argv.includes("--json");
86
+ const skipHeader = isVersionFlag || isJsonOutput || subcommand === "login" || subcommand === "logout" || subcommand === "token";
87
+ // --env <name> is a global option that must be applied BEFORE any command
88
+ // code runs, because loadCredentials() and getServerUrl() both consult the
89
+ // active environment. Commander only parses options during .parse(), so we
90
+ // pre-extract it here via a tiny manual scan. The actual option is still
91
+ // registered on the program below so it appears in --help.
92
+ function preParseEnvFlag() {
93
+ const argv = process.argv;
94
+ for (let i = 2; i < argv.length; i++) {
95
+ const a = argv[i];
96
+ if (a === "--env" || a === "-e") {
97
+ return argv[i + 1] || null;
98
+ }
99
+ if (a.startsWith("--env="))
100
+ return a.slice("--env=".length);
101
+ }
102
+ return null;
103
+ }
104
+ const preParsedEnv = preParseEnvFlag();
105
+ if (preParsedEnv) {
106
+ setCurrentEnvName(preParsedEnv);
107
+ }
108
+ // Surface project-config errors loudly and early. A broken config file
109
+ // should fail fast with a clear message rather than producing cryptic
110
+ // "Not logged in" errors deeper in.
111
+ try {
112
+ getProjectConfig();
113
+ }
114
+ catch (err) {
115
+ if (err instanceof ProjectConfigError) {
116
+ error(err.message);
117
+ if (err.path)
118
+ error(` at ${err.path}`);
119
+ process.exit(1);
120
+ }
121
+ throw err;
122
+ }
123
+ // If the user explicitly passed --env <name>, verify it resolves to a
124
+ // real environment NOW so we don't silently fall back to legacy creds.
125
+ if (preParsedEnv) {
126
+ try {
127
+ const project = getProjectConfig();
128
+ if (!project) {
129
+ error(`--env was given but no ${PROJECT_CONFIG_DISPLAY_NAME} was found in this directory or any parent.`);
130
+ process.exit(1);
131
+ }
132
+ if (!project.environments[preParsedEnv]) {
133
+ const available = Object.keys(project.environments).join(", ") || "(none)";
134
+ error(`Environment "${preParsedEnv}" is not defined in ${PROJECT_CONFIG_DISPLAY_NAME}. Available: ${available}`);
135
+ process.exit(1);
136
+ }
137
+ }
138
+ catch (err) {
139
+ if (err instanceof ProjectConfigError) {
140
+ error(err.message);
141
+ process.exit(1);
142
+ }
143
+ throw err;
144
+ }
145
+ }
146
+ if (!skipHeader) {
147
+ const creds = loadCredentials();
148
+ // In project mode the env may declare an appId even before login.
149
+ // Show that so the header is useful during `env add` / `login` flows.
150
+ const envNameForHeader = getCurrentEnvNameSafe();
151
+ let headerAppId = creds?.currentAppId;
152
+ let headerAppName = creds?.currentAppName;
153
+ if (envNameForHeader && (!headerAppId || !headerAppName)) {
154
+ try {
155
+ const envCfg = getProjectConfig()?.environments[envNameForHeader];
156
+ headerAppId = headerAppId || envCfg?.appId;
157
+ headerAppName = headerAppName || envCfg?.appName;
158
+ }
159
+ catch {
160
+ // ignore
161
+ }
162
+ }
163
+ const appInfo = headerAppName
164
+ ? `${headerAppName} (${headerAppId})`
165
+ : headerAppId || "None set";
166
+ // Prefer the active environment's apiUrl so the header stays correct
167
+ // even before you've logged in. Only fall back to credentials' serverUrl
168
+ // in legacy mode.
169
+ let server = creds?.serverUrl;
170
+ if (envNameForHeader) {
171
+ try {
172
+ const env = getProjectConfig()?.environments[envNameForHeader];
173
+ if (env)
174
+ server = env.apiUrl;
175
+ }
176
+ catch {
177
+ // fall through
178
+ }
179
+ }
180
+ server = server || "Not configured";
181
+ const projectConfigPath = findProjectConfigPath();
182
+ const envLabel = envNameForHeader
183
+ ? ` | Env: ${envNameForHeader}`
184
+ : projectConfigPath
185
+ ? " | Env: (none selected)"
186
+ : "";
187
+ console.log(chalk.dim(`CLI Version: ${pkg.version}${envLabel} | App: ${appInfo} | Server: ${server}`));
188
+ console.log();
189
+ }
190
+ // Nudge legacy-mode users (no .primitive/config.json, but a working
191
+ // `~/.primitive/credentials.json` + a sync marker in CWD) to migrate.
192
+ // Wholly self-contained: rate-limited to once per 24h, opt-out via
193
+ // PRIMITIVE_NO_MIGRATE_NAG=1, silent in CI / non-TTY. See #552.
194
+ maybeWarnLegacyMigration({ argv: process.argv });
30
195
  const program = new Command();
31
196
  program
32
197
  .name("primitive")
198
+ .exitOverride()
33
199
  .description(`CLI for administering Primitive applications.
34
200
 
35
201
  Manage apps, users, integrations, prompts, workflows, and more from the command line.
36
202
  Supports TOML-based configuration sync for version-controlled app settings.`)
37
203
  .version(pkg.version)
204
+ // Global --env flag. Parsed above in preParseEnvFlag too; declaring it
205
+ // here so commander shows it in help output and forwards it to
206
+ // subcommands.
207
+ .option("-e, --env <name>", "Named environment from .primitive/config.json (e.g. dev, prod)")
38
208
  .addHelpText("after", `
39
209
  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
210
+ $ primitive init my-app # Scaffold a new project + .primitive/config.json
211
+ $ primitive env list # Show environments
212
+ $ primitive env add prod --api-url ... # Add a new environment
213
+ $ primitive --env prod users list # Run a command against a specific env
214
+ $ primitive login # Authenticate via browser OAuth
215
+ $ primitive sync pull # Pull config (auto per-env path)
216
+
217
+ Project configuration (.primitive/config.json):
218
+ In a directory with a .primitive/config.json (or any subdirectory of one),
219
+ the CLI operates in project mode: credentials and sync state live under
220
+ <projectRoot>/.primitive/ and each named environment is isolated.
221
+ Without a .primitive/config.json the CLI runs in legacy mode, reading
222
+ credentials from ~/.primitive/credentials.json.
45
223
 
46
- Authentication:
47
- Credentials are stored in ~/.primitive/credentials.json
224
+ Environment selection (project mode):
225
+ 1. --env <name> flag
226
+ 2. PRIMITIVE_ENV env var
227
+ 3. "defaultEnvironment" in .primitive/config.json
228
+ 4. The only environment, if there's exactly one
48
229
 
49
230
  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.
231
+ Bind an app to an environment via "appId" in .primitive/config.json, or
232
+ run 'primitive use <app>' to set one in credentials. Most commands accept
233
+ --app to override.
52
234
 
53
235
  Documentation:
54
236
  See https://primitive-labs.github.io/primitive-docs/ for full documentation.
55
237
  `);
56
238
  // Register all command groups (init first, then logically grouped)
57
239
  registerInitCommand(program);
240
+ registerEnvCommands(program);
58
241
  registerAuthCommands(program);
59
242
  registerAppsCommands(program);
60
243
  registerUsersCommands(program);
61
244
  registerWaitlistCommands(program);
62
245
  registerIntegrationsCommands(program);
246
+ registerWebhooksCommands(program);
247
+ registerCronTriggersCommands(program);
63
248
  registerPromptsCommands(program);
249
+ registerScriptsCommands(program);
64
250
  registerWorkflowsCommands(program);
65
251
  registerAdminsCommands(program);
66
252
  registerCatalogCommands(program);
@@ -71,24 +257,88 @@ registerComparisonsCommands(program);
71
257
  registerTokensCommands(program);
72
258
  registerDatabasesCommands(program);
73
259
  registerGroupsCommands(program);
260
+ registerRuleSetsCommands(program);
261
+ registerGroupTypeConfigsCommands(program);
262
+ registerDatabaseTypesCommands(program);
74
263
  registerGuidesCommands(program);
75
264
  registerDocumentsCommands(program);
265
+ registerEmailTemplatesCommands(program);
266
+ registerCollectionsCommands(program);
267
+ registerCollectionTypeConfigsCommands(program);
268
+ registerSkillCommands(program);
269
+ registerSecretsCommands(program);
270
+ registerBlobBucketsCommands(program);
271
+ // `primitive help --json` — machine-readable command manifest (issue #983).
272
+ //
273
+ // Mechanism: a pre-parse argv guard, NOT a registered `help` subcommand.
274
+ // Commander already injects its own built-in `help [command]`; registering our
275
+ // own would risk clobbering `primitive help <command>`. By special-casing the
276
+ // invocation here — after every register*Commands() call so the full tree is
277
+ // built — we serialize commander's public accessors and exit before parseAsync.
278
+ // It is auth-free, runs offline (no project config / credentials / network),
279
+ // and `--json` already suppresses the header, skill-status nag, and update
280
+ // check (see `isJsonOutput` above), so stdout carries pure JSON.
281
+ //
282
+ // Hidden/undocumented: there is no visible `help --json` entry in human help
283
+ // output; it's discoverable for tooling/agents that know to ask for it.
284
+ if (process.argv[2] === "help" && process.argv.includes("--json")) {
285
+ // Emit the full manifest synchronously, then exit. The naive form — the
286
+ // async `json()` helper (`console.log`) followed by an immediate
287
+ // `process.exit(0)` — races the flush on the documented `primitive help
288
+ // --json | jq` pipe path: the ~365KB manifest far exceeds the 64KB OS pipe
289
+ // buffer, so exiting before the kernel drains it truncates the JSON for
290
+ // piped consumers.
291
+ //
292
+ // Robust fix: write to fd 1 with `fs.writeSync` in a loop. A single
293
+ // `writeSync` is NOT enough — on a non-blocking pipe fd it returns after a
294
+ // *partial* write (only what currently fits in the pipe buffer) without
295
+ // looping, silently dropping the tail; and on a slow consumer it can throw
296
+ // `EAGAIN`. Looping over the returned byte count and retrying on `EAGAIN`
297
+ // guarantees every byte is handed to the kernel before `process.exit(0)`,
298
+ // independent of the event loop. Output is byte-identical to `json()`: the
299
+ // same `JSON.stringify(..., null, 2)` plus a single trailing newline.
300
+ writeAllSync(1, JSON.stringify(buildCliManifest(program, pkg.version), null, 2) + "\n");
301
+ process.exit(0);
302
+ }
76
303
  // Global error handler
77
304
  program.hook("preAction", () => {
78
305
  // Reset API client state before each command
79
306
  });
307
+ // Commands that should never show post-command messages (update check, skill status)
308
+ const suppressPostMessages = isJsonOutput || subcommand === "token";
309
+ async function runPostCommandMessages() {
310
+ if (suppressPostMessages)
311
+ return;
312
+ // Claude Code skill: auto-update if installed, or show hint if not.
313
+ // Skip for `init` (handles its own prompt), `skill` (explicit management),
314
+ // and non-interactive contexts (--json, --version, token, etc.).
315
+ if (!skipHeader && subcommand !== "skill" && subcommand !== "init") {
316
+ await checkSkillStatus();
317
+ }
318
+ await checkForUpdate(pkg.version);
319
+ }
80
320
  // Parse and execute
81
- program.parseAsync(process.argv).catch((err) => {
321
+ program.parseAsync(process.argv)
322
+ .then(async () => {
323
+ await runPostCommandMessages();
324
+ })
325
+ .catch(async (err) => {
326
+ // Commander throws CommanderError for help display, version flag, etc.
327
+ // Run post-command messages then exit with the intended code.
328
+ if (err instanceof CommanderError) {
329
+ await runPostCommandMessages();
330
+ process.exit(err.exitCode);
331
+ }
82
332
  if (err instanceof ApiError) {
83
333
  error(err.message);
84
334
  if (err.statusCode === 401) {
85
335
  error("Try running 'primitive login' to authenticate.");
86
336
  }
337
+ await runPostCommandMessages();
87
338
  process.exit(err.statusCode === 401 ? 2 : 1);
88
339
  }
89
- else {
90
- error(err.message || "An unexpected error occurred");
91
- process.exit(1);
92
- }
340
+ error(err.message || "An unexpected error occurred");
341
+ await runPostCommandMessages();
342
+ process.exit(1);
93
343
  });
94
344
  //# sourceMappingURL=primitive.js.map