@contentrain/skills 0.1.0

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.
@@ -0,0 +1,154 @@
1
+ # Skill: Create Content for Existing Models
2
+
3
+ > Generate and save content entries for models already defined in the project.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ The user wants to create new content, add entries to a collection, populate a singleton, or says something like "add a blog post", "create content", "fill in the hero section".
10
+
11
+ ---
12
+
13
+ ## Steps
14
+
15
+ ### 1. Check Project State
16
+
17
+ Call `contentrain_status` to get the full project context:
18
+
19
+ - Confirm the project is initialized.
20
+ - List available models, their kinds, domains, and field counts.
21
+ - Note supported locales and the source locale.
22
+ - Check for any pending changes or validation errors.
23
+
24
+ If `.contentrain/` does not exist, stop and suggest running `/contentrain-init` first.
25
+
26
+ ### 2. Identify the Target Model
27
+
28
+ If the user specified a model, use it. Otherwise:
29
+
30
+ - Present the list of available models from status.
31
+ - Ask the user which model to create content for.
32
+ - If the user describes content without naming a model (e.g., "write a blog post about X"), infer the model from context.
33
+
34
+ ### 3. Read the Model Definition
35
+
36
+ Call `contentrain_describe(model: "<model-id>", include_sample: true)` to get:
37
+
38
+ - All field definitions (name, type, required, constraints).
39
+ - A sample entry if one exists (use it as a structural reference).
40
+ - Relation fields and their target models.
41
+
42
+ If the user's request does not match an existing model, offer to create one first with `contentrain_model_save`.
43
+
44
+ ### 4. Read Vocabulary and Context
45
+
46
+ - Check `.contentrain/vocabulary.json` for canonical terms. All content must use these terms consistently across locales. Do not use alternative spellings or synonyms for vocabulary-defined terms.
47
+ - Check `.contentrain/context.json` for tone conventions (professional, casual, technical).
48
+
49
+ ### 5. Generate Content
50
+
51
+ Create content following the rules in `content-quality.md`:
52
+
53
+ - **Required fields:** Every required field must have a real, non-placeholder value.
54
+ - **Field constraints:** Respect `min`, `max`, `pattern`, and `options` constraints.
55
+ - **Unique fields:** Verify `unique` field values do not collide with existing entries.
56
+ - **Tone:** Match the tone defined in `.contentrain/context.json` → `conventions.tone`.
57
+ - **Vocabulary:** Use terms from `.contentrain/vocabulary.json` if it exists.
58
+ - **Content patterns:** Follow the appropriate content type pattern (blog post, landing page, documentation, etc.) from `content-quality.md` Section 3.
59
+ - **Consistency:** If existing entries exist, read 2-3 via `contentrain_content_list` to match the established style and structure.
60
+
61
+ **Relation integrity:** For `relation` and `relations` fields, verify referenced entries exist by calling `contentrain_content_list` on the target model.
62
+
63
+ ### 6. Apply SEO Rules
64
+
65
+ For fields that affect SEO (from `seo-rules.md`):
66
+
67
+ - **Title:** 50-60 characters optimal, max 70. Primary keyword in the first 30 characters.
68
+ - **Slug:** Lowercase, hyphen-separated, 3-5 words, no stop words.
69
+ - **Description/excerpt:** 120-160 characters, complete sentence, summarizes value.
70
+ - **Alt text:** Descriptive, includes context, under 125 characters.
71
+ - **Heading hierarchy:** In markdown/richtext content: h1 → h2 → h3 (no skips).
72
+
73
+ ### 7. Present Draft for Approval
74
+
75
+ Show the user the content draft before writing. Include:
76
+
77
+ - The model name and kind.
78
+ - The content for each locale (if i18n).
79
+ - Any relations being set.
80
+ - A summary of which quality rules were applied.
81
+
82
+ Wait for user confirmation before proceeding.
83
+
84
+ ### 8. Save Content
85
+
86
+ Call `contentrain_content_save` with the approved content. The tool takes `model` and an `entries` array:
87
+
88
+ ```json
89
+ contentrain_content_save({
90
+ "model": "<model-id>",
91
+ "entries": [
92
+ { "locale": "en", "data": { "field": "value" } }
93
+ ]
94
+ })
95
+ ```
96
+
97
+ **Kind-specific entry format:**
98
+
99
+ - **singleton:** `{ "locale": "en", "data": { "title": "My Site", "cta": "Get Started" } }` -- no `id` or `slug`.
100
+ - **collection:** `{ "id": "optional-id", "locale": "en", "data": { "name": "John", "role": "CEO" } }` -- omit `id` to auto-generate, provide `id` to update.
101
+ - **document:** `{ "slug": "getting-started", "locale": "en", "data": { "title": "Getting Started", "body": "# Welcome\n..." } }` -- `slug` is required, use `"body"` key for markdown content.
102
+ - **dictionary:** `{ "locale": "en", "data": { "auth.login": "Log In", "auth.logout": "Log Out" } }` -- flat key-value pairs, no `id` or `slug`.
103
+
104
+ **Critical rules:**
105
+ - NEVER include system fields (`status`, `source`, `updated_by`, `updated_at`, `createdAt`, `updatedAt`) in `data`.
106
+ - Prefer batch mode -- send multiple entries in a single call when possible.
107
+ - Locale defaults to the project's default locale if omitted, but explicit locale is recommended.
108
+
109
+ ### 9. Handle i18n Completeness
110
+
111
+ If the project has multiple supported locales and the model has `i18n: true`:
112
+
113
+ - Ask the user if they want translations created now.
114
+ - If yes, generate translations following `i18n-quality.md` rules:
115
+ - Translate meaning, not words.
116
+ - Respect string expansion ratios.
117
+ - Preserve tone across locales.
118
+ - Check `vocabulary.json` for approved term translations.
119
+ - Call `contentrain_content_save` for each target locale.
120
+
121
+ If the user defers translations, note incomplete locales in the final summary.
122
+
123
+ ### 10. Validate
124
+
125
+ Call `contentrain_validate` to check all changes:
126
+
127
+ - Schema compliance (all required fields, correct types).
128
+ - Referential integrity (relations point to existing entries).
129
+ - i18n completeness (all locales have matching keys).
130
+ - Content policy compliance.
131
+
132
+ Fix any errors reported. Acknowledge any warnings.
133
+
134
+ ### 11. Submit
135
+
136
+ Call `contentrain_submit` to push branches to remote:
137
+
138
+ - Write operations already create branches and commit changes automatically.
139
+ - `contentrain_submit` pushes unmerged `contentrain/*` branches to the remote.
140
+ - In `auto-merge` mode, branches are already merged to the base branch during the write operation.
141
+ - In `review` mode, branches are pushed for team review.
142
+
143
+ Report the Studio URL if provided in the tool response -- the user can review and approve content there.
144
+
145
+ ### 12. Final Summary
146
+
147
+ Report to the user:
148
+
149
+ - How many entries were created/updated.
150
+ - Which locales were covered.
151
+ - The validation result.
152
+ - The submission status (auto-merged or pending review).
153
+ - Studio link for visual review (if available).
154
+ - Next steps: run `/contentrain-generate` to update SDK types if models changed.
@@ -0,0 +1,55 @@
1
+ # Skill: Review Pending Branches with `contentrain diff`
2
+
3
+ > Inspect pending Contentrain branches and decide whether to merge, reject, or defer them.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Use this when:
10
+
11
+ - the project has pending `contentrain/*` branches
12
+ - the user asks to review or merge changes
13
+ - branch pressure is growing and cleanup is needed
14
+
15
+ ---
16
+
17
+ ## Steps
18
+
19
+ ### 1. Check Branch Pressure
20
+
21
+ Start with `contentrain status`.
22
+
23
+ If many `contentrain/*` branches are active, move into review immediately.
24
+
25
+ ### 2. Run `contentrain diff`
26
+
27
+ Use the CLI to:
28
+
29
+ - list pending branches
30
+ - inspect the diff summary
31
+ - review full patch output
32
+
33
+ ### 3. Evaluate the Change
34
+
35
+ Check:
36
+
37
+ - which model/content files changed
38
+ - whether the change matches the intended operation
39
+ - whether validation/normalize work was completed properly
40
+
41
+ ### 4. Choose an Outcome
42
+
43
+ - merge when the diff is correct and complete
44
+ - delete when the change is wrong or should be rejected
45
+ - defer only when more human input is required
46
+
47
+ ### 5. Report the Decision
48
+
49
+ Summarize:
50
+
51
+ - branch reviewed
52
+ - action taken
53
+ - base branch affected
54
+ - any follow-up branch still pending
55
+
@@ -0,0 +1,55 @@
1
+ # Skill: Diagnose Project Health with `contentrain doctor`
2
+
3
+ > Run health checks and turn failures into actionable fixes.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Use this when:
10
+
11
+ - setup seems broken
12
+ - generated client may be stale
13
+ - Git/content structure may be inconsistent
14
+ - the user asks "what is wrong with this project?"
15
+
16
+ ---
17
+
18
+ ## Steps
19
+
20
+ ### 1. Run `contentrain doctor`
21
+
22
+ Use the CLI health check as the first diagnostic pass.
23
+
24
+ ### 2. Group Findings
25
+
26
+ Interpret failures by category:
27
+
28
+ - Git problems
29
+ - missing `.contentrain/` structure
30
+ - bad config/model parsing
31
+ - orphan content
32
+ - too many pending branches
33
+ - stale SDK client
34
+
35
+ ### 3. Fix in Priority Order
36
+
37
+ Recommended order:
38
+
39
+ 1. Git / initialization blockers
40
+ 2. config/model parse failures
41
+ 3. orphan content or missing paths
42
+ 4. branch pressure
43
+ 5. stale generated client
44
+
45
+ ### 4. Apply the Correct Follow-up
46
+
47
+ - not initialized → `contentrain init`
48
+ - stale client → `contentrain generate`
49
+ - branch pressure → `contentrain diff`
50
+ - invalid content/model state → `contentrain validate`
51
+
52
+ ### 5. Re-run Doctor
53
+
54
+ After changes, run `contentrain doctor` again to confirm the project is healthy.
55
+
@@ -0,0 +1,352 @@
1
+ # Skill: Generate SDK Client
2
+
3
+ > Generate the typed Contentrain query client from model definitions using the Prisma-pattern codegen.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ The user wants to generate or regenerate the SDK client, or says something like "generate client", "regenerate types", "set up contentrain imports", "run contentrain generate", "update SDK types".
10
+
11
+ ---
12
+
13
+ ## Steps
14
+
15
+ ### 1. Check Project State
16
+
17
+ Call `contentrain_status` to verify:
18
+
19
+ - `.contentrain/` directory exists with `config.json`.
20
+ - At least one model exists in `.contentrain/models/`.
21
+ - Content files exist for the configured locales.
22
+
23
+ If the project is not initialized, stop and suggest running `/contentrain-init` first. If no models exist, suggest `/contentrain-content` to create content first.
24
+
25
+ ### 2. Verify Prerequisites
26
+
27
+ Check that the required packages are available:
28
+
29
+ - `@contentrain/query` must be installed in the project's `package.json` (as a dependency).
30
+ - The `contentrain` CLI must be available (via `npx contentrain` or globally installed).
31
+
32
+ If `@contentrain/query` is not installed, install it using the project's package manager:
33
+
34
+ ```bash
35
+ # Use whichever package manager the project uses
36
+ pnpm add @contentrain/query
37
+ # or: npm install @contentrain/query
38
+ # or: yarn add @contentrain/query
39
+ ```
40
+
41
+ ### 3. Run the Generator
42
+
43
+ Execute the generation command:
44
+
45
+ ```bash
46
+ npx contentrain generate
47
+ ```
48
+
49
+ If the project uses a non-standard root directory, specify it:
50
+
51
+ ```bash
52
+ npx contentrain generate --root .
53
+ ```
54
+
55
+ This reads `.contentrain/models/` and `.contentrain/content/` to produce:
56
+
57
+ ```
58
+ .contentrain/client/
59
+ index.mjs — ESM entry (query runtime + re-exports)
60
+ index.cjs — CJS entry (NestJS, Express, legacy tooling)
61
+ index.d.ts — Generated types (from model schemas)
62
+ data/
63
+ {model}.{locale}.mjs — Static data modules per model/locale
64
+ ```
65
+
66
+ ### 4. Verify package.json Imports
67
+
68
+ The generator should have added `#contentrain` subpath imports to `package.json`. Verify the full import map:
69
+
70
+ ```json
71
+ {
72
+ "imports": {
73
+ "#contentrain": {
74
+ "types": "./.contentrain/client/index.d.ts",
75
+ "import": "./.contentrain/client/index.mjs",
76
+ "require": "./.contentrain/client/index.cjs",
77
+ "default": "./.contentrain/client/index.mjs"
78
+ },
79
+ "#contentrain/*": {
80
+ "types": "./.contentrain/client/*.d.ts",
81
+ "import": "./.contentrain/client/*.mjs",
82
+ "require": "./.contentrain/client/*.cjs",
83
+ "default": "./.contentrain/client/*.mjs"
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ If the imports entry is missing or incomplete, update `package.json` manually to match the full structure above.
90
+
91
+ ### 5. Verify TypeScript Configuration
92
+
93
+ Ensure `tsconfig.json` does not block the generated client:
94
+
95
+ - `paths` should not conflict with `#contentrain`.
96
+ - `rootDir` or `include` should not exclude `.contentrain/client/`.
97
+ - If using `moduleResolution: "bundler"` or `"node16"`, subpath imports resolve natively.
98
+
99
+ ### 5.5. Configure Bundler Alias
100
+
101
+ The `#contentrain` subpath import works natively in Node.js 22+ but **does NOT resolve in browser bundlers**. If the project uses a bundler, configure an alias so `#contentrain` resolves to the generated client.
102
+
103
+ #### Vite (Vue, React, Svelte, Astro)
104
+
105
+ ```ts
106
+ // vite.config.ts
107
+ import { resolve } from 'node:path'
108
+ export default defineConfig({
109
+ resolve: {
110
+ alias: {
111
+ '#contentrain': resolve(__dirname, '.contentrain/client/index.mjs'),
112
+ },
113
+ },
114
+ })
115
+ ```
116
+
117
+ Also add a `paths` entry to `tsconfig.json` so the TypeScript language server resolves the alias:
118
+
119
+ ```json
120
+ {
121
+ "compilerOptions": {
122
+ "paths": {
123
+ "#contentrain": ["./.contentrain/client/index.d.ts"]
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ #### Next.js (webpack)
130
+
131
+ ```js
132
+ // next.config.js
133
+ const path = require('path')
134
+ module.exports = {
135
+ webpack: (config) => {
136
+ config.resolve.alias['#contentrain'] = path.resolve(__dirname, '.contentrain/client/index.mjs')
137
+ return config
138
+ },
139
+ }
140
+ ```
141
+
142
+ Add the same `tsconfig.json` paths entry:
143
+
144
+ ```json
145
+ {
146
+ "compilerOptions": {
147
+ "paths": {
148
+ "#contentrain": ["./.contentrain/client/index.d.ts"]
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ #### Nuxt 3
155
+
156
+ Nuxt provides a top-level `alias` option — no Vite config needed:
157
+
158
+ ```ts
159
+ // nuxt.config.ts
160
+ export default defineNuxtConfig({
161
+ alias: {
162
+ '#contentrain': './.contentrain/client/index.mjs',
163
+ },
164
+ })
165
+ ```
166
+
167
+ Add the `tsconfig.json` paths entry as above. Nuxt auto-extends `tsconfig.json` via `.nuxt/tsconfig.json`, so ensure the paths entry is in the project root `tsconfig.json`.
168
+
169
+ #### SvelteKit
170
+
171
+ SvelteKit uses Vite internally. Add the alias in `vite.config.ts` as shown in the Vite section above.
172
+
173
+ #### Expo / React Native (Metro)
174
+
175
+ ```js
176
+ // metro.config.js
177
+ const path = require('path')
178
+ module.exports = {
179
+ resolver: {
180
+ extraNodeModules: {
181
+ '#contentrain': path.resolve(__dirname, '.contentrain/client/index.mjs'),
182
+ },
183
+ },
184
+ }
185
+ ```
186
+
187
+ #### Pure Node.js / SSR-only
188
+
189
+ No alias needed. Node.js 22+ resolves `#contentrain` from `package.json` imports natively.
190
+
191
+ ### 6. Verify Imports Work
192
+
193
+ Run a quick verification that the imports resolve correctly.
194
+
195
+ **Node.js / SSR-only projects:**
196
+
197
+ ```bash
198
+ node -e "import('#contentrain').then(m => console.log('OK:', Object.keys(m)))"
199
+ ```
200
+
201
+ **Browser / bundler projects:** run the framework's build command instead, since the Node.js check does not exercise the bundler alias:
202
+
203
+ ```bash
204
+ # Vite-based (Vue, React, Svelte, Astro)
205
+ npx vite build
206
+
207
+ # Next.js
208
+ npx next build
209
+
210
+ # Nuxt 3
211
+ npx nuxi build
212
+
213
+ # Expo
214
+ npx expo export
215
+ ```
216
+
217
+ A successful build confirms the `#contentrain` alias resolves correctly through the bundler pipeline.
218
+
219
+ If verification fails, check:
220
+ - Node.js version >= 22 (required for native subpath imports).
221
+ - `package.json` has `"type": "module"` (for ESM projects).
222
+ - The `.contentrain/client/` directory was generated successfully.
223
+ - The bundler alias is configured (see Step 5.5) for browser projects.
224
+
225
+ ### 7. Show Usage Examples
226
+
227
+ Based on the detected stack and available models, show relevant examples using actual model IDs and field names from the project — not generic placeholders.
228
+
229
+ #### Complete SDK API Reference
230
+
231
+ ```ts
232
+ import { query, singleton, dictionary, document } from '#contentrain'
233
+ ```
234
+
235
+ **QueryBuilder (for collection models) -- SYNC, no await needed:**
236
+
237
+ ```ts
238
+ const posts = query('blog-post')
239
+ .locale('en') // set locale
240
+ .where('status', 'published') // exact match filter
241
+ .sort('date', 'desc') // sort by field
242
+ .limit(10) // limit results
243
+ .offset(5) // skip results
244
+ .include('author', 'tags') // resolve relation fields (1 level deep)
245
+ .all() // --> T[] (returns array)
246
+ .first() // --> T | undefined (first match)
247
+ ```
248
+
249
+ **SingletonAccessor (for singleton models):**
250
+
251
+ ```ts
252
+ const hero = singleton('hero')
253
+ .locale('en')
254
+ .include('featured_post') // resolve relations on singletons too
255
+ .get() // --> T (single object)
256
+ ```
257
+
258
+ **DictionaryAccessor (for dictionary models):**
259
+
260
+ ```ts
261
+ const allLabels = dictionary('ui-labels').locale('en').get() // --> Record<string, string>
262
+ const oneLabel = dictionary('ui-labels').locale('en').get('key') // --> string | undefined
263
+ const withParams = dictionary('ui-labels').locale('en').get('add-entry', { model: 'blog' }) // --> "Add a new entry to blog"
264
+ ```
265
+
266
+ Parameterized templates use `{placeholder}` syntax in dictionary values. The `get(key, params)` overload replaces `{name}` with the provided value. Unmatched placeholders are left as-is.
267
+
268
+ **DocumentQuery (for document models -- markdown + frontmatter):**
269
+
270
+ ```ts
271
+ const article = document('blog-article')
272
+ .locale('en')
273
+ .where('category', 'tech')
274
+ .include('author') // resolve relations in frontmatter
275
+ .bySlug('getting-started') // --> T | undefined (find by slug)
276
+
277
+ const docs = document('doc-page').locale('en').all() // --> T[]
278
+ const first = document('doc-page').locale('en').first() // --> T | undefined
279
+ ```
280
+
281
+ **DOES NOT EXIST -- never use these:**
282
+ - `.filter()` -- use `.where(field, value)` instead
283
+ - `.byId()` -- use `.where('id', value).first()` instead
284
+ - `.count()` -- use `.all().length` instead
285
+ - `dictionary().all()` -- use `.get()` instead
286
+ - Queries are SYNC -- do not use `await` with `query()`, `singleton()`, `dictionary()`, or `document()`
287
+ - `.where('field', 'eq', value)` -- just `.where('field', value)`
288
+ - `.get()` on QueryBuilder -- use `.all()` or `.first()`
289
+
290
+ #### With Relations
291
+
292
+ ```ts
293
+ // Relations are resolved 1 level deep via .include()
294
+ const posts = query('blog-post')
295
+ .locale('en')
296
+ .include('author', 'tags')
297
+ .all()
298
+ // posts[0].author --> { id: '...', name: 'John', ... } (resolved object)
299
+
300
+ // Without include:
301
+ const raw = query('blog-post').locale('en').all()
302
+ // raw[0].author --> 'author-id-123' (raw string ID, NOT resolved)
303
+
304
+ // Singletons support include too:
305
+ const hero = singleton('hero').locale('en').include('featured_post').get()
306
+
307
+ // Documents support include too:
308
+ const article = document('blog-article').locale('en').include('author').bySlug('my-post')
309
+ ```
310
+
311
+ #### Framework-Specific Patterns
312
+
313
+ | Stack | Usage Pattern | Alias Setup |
314
+ |---|---|---|
315
+ | Nuxt 3 | `useAsyncData(() => singleton('hero').locale(locale).get())` | `nuxt.config.ts` alias (Step 5.5) |
316
+ | Next.js | In RSC: `const data = singleton('hero').locale('en').get()` | `next.config.js` webpack alias (Step 5.5) |
317
+ | Astro | In frontmatter: `const posts = query('blog-post').locale('en').all()` | `vite.config.ts` alias (Step 5.5) |
318
+ | SvelteKit | In `+page.server.ts`: `export const load = () => ({ hero: singleton('hero').locale('en').get() })` | `vite.config.ts` alias (Step 5.5) |
319
+ | Expo / RN | `const hero = singleton('hero').locale('en').get()` | `metro.config.js` resolver (Step 5.5) |
320
+ | Node.js / SSR | Direct import — no alias needed | Native subpath imports |
321
+
322
+ ### 8. Offer Watch Mode
323
+
324
+ Suggest setting up watch mode for development:
325
+
326
+ ```bash
327
+ npx contentrain generate --watch
328
+ ```
329
+
330
+ This re-generates the client automatically whenever models or content change under `.contentrain/`. Recommend running it alongside the framework's dev server.
331
+
332
+ For convenience, suggest adding a script to `package.json`:
333
+
334
+ ```json
335
+ {
336
+ "scripts": {
337
+ "contentrain:watch": "contentrain generate --watch"
338
+ }
339
+ }
340
+ ```
341
+
342
+ ### 9. Final Summary
343
+
344
+ Report to the user:
345
+
346
+ - Models processed: list each model and its kind.
347
+ - Generated files: list the `.contentrain/client/` contents.
348
+ - Locales included: list data files per locale.
349
+ - Import path: `#contentrain` is ready to use.
350
+ - TypeScript types: available via `index.d.ts`.
351
+ - Next steps: import `query` from `#contentrain` and start querying content.
352
+ - Reminder: re-run `contentrain generate` after model or content changes.