@contentrain/skills 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -60
- package/dist/index.cjs +65 -1
- package/dist/index.d.cts +50 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +50 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +65 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/skills/contentrain/SKILL.md +149 -0
- package/skills/contentrain/references/architecture.md +212 -0
- package/skills/contentrain/references/content-formats.md +279 -0
- package/skills/contentrain/references/i18n.md +298 -0
- package/skills/contentrain/references/mcp-pipelines.md +195 -0
- package/skills/contentrain/references/mcp-tools.md +308 -0
- package/skills/contentrain/references/model-kinds.md +330 -0
- package/skills/contentrain/references/schema-types.md +177 -0
- package/skills/contentrain/references/security.md +146 -0
- package/skills/contentrain/references/workflow.md +285 -0
- package/skills/contentrain-bulk/SKILL.md +99 -0
- package/skills/contentrain-content/SKILL.md +162 -0
- package/skills/contentrain-diff/SKILL.md +62 -0
- package/skills/contentrain-doctor/SKILL.md +62 -0
- package/skills/contentrain-generate/SKILL.md +183 -0
- package/skills/contentrain-generate/references/generated-client.md +198 -0
- package/skills/contentrain-init/SKILL.md +99 -0
- package/skills/contentrain-model/SKILL.md +141 -0
- package/skills/contentrain-normalize/SKILL.md +185 -0
- package/skills/contentrain-normalize/references/extraction.md +164 -0
- package/skills/contentrain-normalize/references/reuse.md +146 -0
- package/skills/contentrain-normalize/references/what-is-content.md +115 -0
- package/skills/contentrain-quality/SKILL.md +180 -0
- package/skills/contentrain-quality/references/accessibility.md +160 -0
- package/skills/contentrain-quality/references/content-quality.md +299 -0
- package/skills/contentrain-quality/references/media.md +170 -0
- package/skills/contentrain-quality/references/seo.md +229 -0
- package/skills/contentrain-review/SKILL.md +170 -0
- package/skills/contentrain-sdk/SKILL.md +145 -0
- package/skills/contentrain-sdk/references/bundler-config.md +135 -0
- package/skills/contentrain-serve/SKILL.md +96 -0
- package/skills/contentrain-translate/SKILL.md +180 -0
- package/skills/contentrain-validate-fix/SKILL.md +92 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: seo
|
|
3
|
+
description: "SEO optimization rules for content fields, meta tags, and structured data"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SEO Rules
|
|
7
|
+
|
|
8
|
+
These rules govern search engine optimization for all content managed through Contentrain.
|
|
9
|
+
Apply these rules to every content entry that produces a public-facing page.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Page Titles
|
|
14
|
+
|
|
15
|
+
### 1.1 Length and Format
|
|
16
|
+
|
|
17
|
+
- Length: 50-60 characters including spaces. Google truncates titles beyond ~60 characters.
|
|
18
|
+
- Measure character count before saving. Truncated titles harm click-through rates.
|
|
19
|
+
- No ALL CAPS titles. Use sentence case or title case per project convention.
|
|
20
|
+
- No excessive punctuation: no `!!!`, no `???`, no `...` in titles.
|
|
21
|
+
- No emoji in titles unless explicitly configured in `context.json`.
|
|
22
|
+
|
|
23
|
+
### 1.2 Keyword Placement
|
|
24
|
+
|
|
25
|
+
- Place the primary keyword within the first 30 characters.
|
|
26
|
+
- Structure: `Primary Keyword Phrase | Brand Name` or `Primary Keyword Phrase -- Brand Name`.
|
|
27
|
+
- Acceptable separators: ` | `, ` -- `, ` - `. Pick one and use it consistently across all entries.
|
|
28
|
+
- The keyword must read naturally -- no keyword stuffing.
|
|
29
|
+
- GOOD: "Deploy Node.js to Production | Acme Docs"
|
|
30
|
+
- BAD: "Node.js Deploy Node Production Deployment | Acme"
|
|
31
|
+
|
|
32
|
+
### 1.3 Uniqueness
|
|
33
|
+
|
|
34
|
+
- Every entry in a collection MUST have a unique title.
|
|
35
|
+
- Before saving, query existing entries and confirm no title collision.
|
|
36
|
+
- Titles across collections should also be unique when they produce public pages.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. Meta Descriptions
|
|
41
|
+
|
|
42
|
+
### 2.1 Length and Format
|
|
43
|
+
|
|
44
|
+
- Length: 120-155 characters including spaces.
|
|
45
|
+
- Must be a complete sentence or two short sentences.
|
|
46
|
+
- End with a period.
|
|
47
|
+
- No keyword stuffing -- write for humans, not crawlers.
|
|
48
|
+
|
|
49
|
+
### 2.2 Content Requirements
|
|
50
|
+
|
|
51
|
+
- Include a clear value proposition or call-to-action.
|
|
52
|
+
- GOOD: "Learn how to set up CI/CD for your Node.js project in 10 minutes. Step-by-step guide with examples."
|
|
53
|
+
- BAD: "CI/CD Node.js setup guide tutorial deployment continuous integration."
|
|
54
|
+
- Must differ from the page title -- do not repeat the title as the description.
|
|
55
|
+
- Must be unique per entry. No two entries may share the same meta description.
|
|
56
|
+
|
|
57
|
+
### 2.3 What to Avoid
|
|
58
|
+
|
|
59
|
+
- No quotes or special characters that break HTML meta tags: avoid `"`, `<`, `>`.
|
|
60
|
+
- No "Click here to learn more" -- search engines ignore generic CTAs.
|
|
61
|
+
- No date references that will become stale (prefer "latest" over "2024").
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. Slugs
|
|
66
|
+
|
|
67
|
+
### 3.1 Format
|
|
68
|
+
|
|
69
|
+
- Lowercase only. No uppercase characters.
|
|
70
|
+
- Words separated by hyphens (`-`). No underscores, no spaces, no dots.
|
|
71
|
+
- No special characters: only `a-z`, `0-9`, and `-`.
|
|
72
|
+
- No consecutive hyphens (`--`). No leading or trailing hyphens.
|
|
73
|
+
|
|
74
|
+
### 3.2 Length and Content
|
|
75
|
+
|
|
76
|
+
- Optimal: 3-5 words.
|
|
77
|
+
- Maximum: 60 characters.
|
|
78
|
+
- Remove stop words (`the`, `a`, `an`, `is`, `of`, `and`, `or`, `in`, `to`) unless removing them changes meaning.
|
|
79
|
+
- "how-to-deploy-node" -> "deploy-node" is acceptable.
|
|
80
|
+
- "state-of-the-art" -> keep as-is because meaning changes if shortened.
|
|
81
|
+
|
|
82
|
+
### 3.3 Stability
|
|
83
|
+
|
|
84
|
+
- NEVER change a slug on a published entry. Changing slugs breaks existing links, bookmarks, and search engine indexing.
|
|
85
|
+
- If a slug must change, the human must set up a redirect. The agent must not change published slugs without explicit human instruction.
|
|
86
|
+
- Draft entries may have their slugs changed freely before first publication.
|
|
87
|
+
|
|
88
|
+
### 3.4 Locale Consistency
|
|
89
|
+
|
|
90
|
+
- Use the same slug value across all locales for the same entry.
|
|
91
|
+
- Do NOT translate slugs. Slugs are identifiers, not content.
|
|
92
|
+
- Example: the slug `deploy-node` remains `deploy-node` in `en`, `tr`, `de`, and all other locales.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Heading Structure
|
|
97
|
+
|
|
98
|
+
### 4.1 H1 Rule
|
|
99
|
+
|
|
100
|
+
- Exactly one H1 per page. The H1 is the entry title or the primary heading.
|
|
101
|
+
- The H1 should contain the primary keyword naturally.
|
|
102
|
+
- Do not duplicate the H1 text elsewhere on the page.
|
|
103
|
+
|
|
104
|
+
### 4.2 Subheading Keywords
|
|
105
|
+
|
|
106
|
+
- Include relevant keywords in H2 and H3 headings where natural.
|
|
107
|
+
- Do not force keywords into every heading. Clarity takes priority over keyword density.
|
|
108
|
+
- Use question-format headings for FAQ-style content: "How do I reset my password?"
|
|
109
|
+
|
|
110
|
+
### 4.3 Hierarchy Reflects Content
|
|
111
|
+
|
|
112
|
+
- Heading levels must reflect the logical structure of the content.
|
|
113
|
+
- Do not use H3 because it renders smaller -- use CSS for styling, headings for structure.
|
|
114
|
+
- Every heading must have content beneath it. No empty sections.
|
|
115
|
+
- Do not place two headings consecutively without content between them.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 5. Images and Alt Text
|
|
120
|
+
|
|
121
|
+
### 5.1 Alt Text Requirements
|
|
122
|
+
|
|
123
|
+
- Every `image` field that displays on a public page MUST have corresponding alt text.
|
|
124
|
+
- Describe the image content and its context: "Dashboard showing monthly revenue chart with 15% growth indicator."
|
|
125
|
+
- Maximum length: 125 characters.
|
|
126
|
+
- Do not start with "Image of", "Photo of", "Picture of" -- describe directly.
|
|
127
|
+
|
|
128
|
+
### 5.2 Decorative Images
|
|
129
|
+
|
|
130
|
+
- If an image is purely decorative (visual separator, background pattern), set alt text to an empty string (`""`).
|
|
131
|
+
- Do not omit the alt field entirely -- explicitly set it to empty.
|
|
132
|
+
|
|
133
|
+
### 5.3 Keywords in Alt Text
|
|
134
|
+
|
|
135
|
+
- Include relevant keywords naturally when they describe the image.
|
|
136
|
+
- Do not stuff keywords: "Node.js Node deployment Node server Node production" is forbidden.
|
|
137
|
+
- The alt text must accurately describe what is in the image.
|
|
138
|
+
|
|
139
|
+
### 5.4 Image File Names
|
|
140
|
+
|
|
141
|
+
- When the agent controls the image file name, use descriptive, hyphenated names.
|
|
142
|
+
- GOOD: `revenue-dashboard-chart.png`
|
|
143
|
+
- BAD: `IMG_2847.png`, `screenshot.png`, `image1.png`
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 6. Open Graph and Social Media
|
|
148
|
+
|
|
149
|
+
### 6.1 Open Graph Tags
|
|
150
|
+
|
|
151
|
+
When OG fields exist in the model, populate them:
|
|
152
|
+
|
|
153
|
+
| Field | Max Length | Requirement |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `og:title` | 60 characters | Concise version of the page title |
|
|
156
|
+
| `og:description` | 200 characters | Slightly longer than meta description, conversational |
|
|
157
|
+
| `og:image` | -- | 1200x630 pixels recommended, must be absolute URL |
|
|
158
|
+
| `og:type` | -- | `article` for blog/docs, `website` for landing pages |
|
|
159
|
+
|
|
160
|
+
### 6.2 Twitter Card
|
|
161
|
+
|
|
162
|
+
- Use `summary_large_image` for content with strong visual elements.
|
|
163
|
+
- Use `summary` for text-focused content.
|
|
164
|
+
- Twitter title and description may differ from OG -- optimize for each platform.
|
|
165
|
+
|
|
166
|
+
### 6.3 Fallback
|
|
167
|
+
|
|
168
|
+
- If OG-specific fields are absent, the system uses the standard title and description.
|
|
169
|
+
- Ensure the standard title and description are suitable for social sharing.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 7. Internal Linking
|
|
174
|
+
|
|
175
|
+
### 7.1 Relation Fields
|
|
176
|
+
|
|
177
|
+
- Use Contentrain `relation` and `relations` model fields for cross-references between entries.
|
|
178
|
+
- Prefer structured relations over hardcoded links in body content.
|
|
179
|
+
- Structured relations are maintainable -- if a slug changes, relations still resolve.
|
|
180
|
+
|
|
181
|
+
### 7.2 Bidirectional Relations
|
|
182
|
+
|
|
183
|
+
- When Entry A references Entry B, check if Entry B should reference Entry A.
|
|
184
|
+
- Example: if a blog post links to a product, the product's "related articles" should include that blog post.
|
|
185
|
+
- The agent should suggest bidirectional relations but not create them without confirmation.
|
|
186
|
+
|
|
187
|
+
### 7.3 Manual Links in Body Content
|
|
188
|
+
|
|
189
|
+
- If the model does not have relation fields and links must be in body text, use relative paths.
|
|
190
|
+
- Verify that linked entries exist before saving.
|
|
191
|
+
- Do not link to draft or archived entries from published content.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 8. URL and Canonical Rules
|
|
196
|
+
|
|
197
|
+
### 8.1 Slug Patterns
|
|
198
|
+
|
|
199
|
+
- All entries in a collection must follow the same slug pattern.
|
|
200
|
+
- If existing entries use `verb-noun` format (e.g., `deploy-app`, `configure-server`), new entries must follow the same pattern.
|
|
201
|
+
- Check existing entries before generating a slug for a new entry.
|
|
202
|
+
|
|
203
|
+
### 8.2 Multi-Locale URLs
|
|
204
|
+
|
|
205
|
+
- Same slug across all locales (see Section 3.4).
|
|
206
|
+
- Locale prefix is handled by the frontend routing, not by the content slug.
|
|
207
|
+
- Example: `/en/deploy-node` and `/tr/deploy-node` share the slug `deploy-node`.
|
|
208
|
+
|
|
209
|
+
### 8.3 Trailing Slashes
|
|
210
|
+
|
|
211
|
+
- The agent does not control trailing slash behavior -- this is a frontend routing concern.
|
|
212
|
+
- Ensure slug values do not include leading or trailing slashes.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Validation Checklist
|
|
217
|
+
|
|
218
|
+
Before committing any content with SEO impact, verify:
|
|
219
|
+
|
|
220
|
+
- [ ] Title is 50-60 characters, primary keyword near start, unique
|
|
221
|
+
- [ ] Meta description is 120-155 characters, complete sentence, unique
|
|
222
|
+
- [ ] Slug is lowercase, hyphenated, 3-5 words, no special characters
|
|
223
|
+
- [ ] Published slugs have not been changed
|
|
224
|
+
- [ ] Slugs are identical across all locales for the same entry
|
|
225
|
+
- [ ] One H1 per page, sequential heading hierarchy
|
|
226
|
+
- [ ] All images have descriptive alt text (max 125 chars) or empty string for decorative
|
|
227
|
+
- [ ] OG fields populated if model supports them
|
|
228
|
+
- [ ] Relation fields used for internal cross-references
|
|
229
|
+
- [ ] No keyword stuffing in titles, descriptions, headings, or alt text
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: contentrain-review
|
|
3
|
+
description: "Review content changes for quality, accuracy, and compliance before publishing. Use when reviewing content, checking quality, or approving changes."
|
|
4
|
+
metadata:
|
|
5
|
+
author: Contentrain
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Skill: Review Pending Content Changes
|
|
10
|
+
|
|
11
|
+
> Review content changes on Contentrain branches, apply quality checks, and recommend approval or rejection.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
The user wants to review content quality, audit existing entries, check pending changes, or run a pre-publish checklist. Triggers: "review content", "check pending changes", "review the contentrain branch", "content QA", "audit content", "pre-publish check".
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
### 1. Check Project State
|
|
24
|
+
|
|
25
|
+
Call `contentrain_status` to understand the current project:
|
|
26
|
+
|
|
27
|
+
- All models, their kinds, domains, and entry counts.
|
|
28
|
+
- Configured locales and source locale.
|
|
29
|
+
- Pending branches or unsubmitted changes.
|
|
30
|
+
- Workflow mode (auto-merge or review).
|
|
31
|
+
|
|
32
|
+
### 2. List Content for Review
|
|
33
|
+
|
|
34
|
+
For each model (or the user-specified scope), call `contentrain_content_list(model: "<modelId>", locale: "<locale>")`:
|
|
35
|
+
|
|
36
|
+
- Retrieve entries for the source locale first.
|
|
37
|
+
- For i18n models, also retrieve entries for each target locale.
|
|
38
|
+
- Note entry counts per locale to identify i18n coverage gaps.
|
|
39
|
+
|
|
40
|
+
If reviewing a specific branch, list the `contentrain/*` branches and show:
|
|
41
|
+
|
|
42
|
+
- Branch name (indicates operation type: extract, reuse, content-save, model-save).
|
|
43
|
+
- Number of changed files.
|
|
44
|
+
- Last commit date.
|
|
45
|
+
|
|
46
|
+
### 3. Run Automated Validation
|
|
47
|
+
|
|
48
|
+
Call `contentrain_validate` to get the automated validation report:
|
|
49
|
+
|
|
50
|
+
- Schema compliance errors (missing required fields, type mismatches).
|
|
51
|
+
- Referential integrity issues (broken relation references).
|
|
52
|
+
- i18n completeness gaps (missing locale entries).
|
|
53
|
+
- Content policy violations.
|
|
54
|
+
|
|
55
|
+
### 4. Apply Quality Checklist
|
|
56
|
+
|
|
57
|
+
Go through each category and report findings:
|
|
58
|
+
|
|
59
|
+
#### 4.1 Content Quality (from content-quality.md)
|
|
60
|
+
|
|
61
|
+
- [ ] All required fields are populated with real, non-placeholder values.
|
|
62
|
+
- [ ] Text fields respect `min`/`max` constraints.
|
|
63
|
+
- [ ] No placeholder text (lorem ipsum, TODO, TBD, [insert here]).
|
|
64
|
+
- [ ] Tone is consistent with `context.json` → `conventions.tone`.
|
|
65
|
+
- [ ] Vocabulary terms from `vocabulary.json` are used consistently.
|
|
66
|
+
- [ ] Collection entries have no duplicate `unique` field values.
|
|
67
|
+
- [ ] Content follows the correct content type pattern (blog post, landing page, docs, etc.).
|
|
68
|
+
- [ ] No duplicate content across entries.
|
|
69
|
+
|
|
70
|
+
#### 4.2 SEO (from seo-rules.md)
|
|
71
|
+
|
|
72
|
+
- [ ] Page titles: 50-60 characters (max 70), primary keyword in first 30 characters.
|
|
73
|
+
- [ ] Meta descriptions: 120-160 characters, complete sentences.
|
|
74
|
+
- [ ] Slugs: kebab-case, descriptive, 3-5 words, no stop words.
|
|
75
|
+
- [ ] Heading hierarchy in markdown/richtext: h1 → h2 → h3 (no skips).
|
|
76
|
+
- [ ] Image alt text is present and descriptive (under 125 characters).
|
|
77
|
+
- [ ] No orphan pages (content exists but is not linked/referenced).
|
|
78
|
+
|
|
79
|
+
#### 4.3 Accessibility (from accessibility-rules.md)
|
|
80
|
+
|
|
81
|
+
- [ ] Image fields have corresponding alt text fields (or the alt is embedded).
|
|
82
|
+
- [ ] Color contrast: text color values meet WCAG AA ratio if paired colors are in the model.
|
|
83
|
+
- [ ] Link text is descriptive (no raw URLs, "click here", or "read more" without context).
|
|
84
|
+
- [ ] Media content has text alternatives where applicable.
|
|
85
|
+
- [ ] Heading hierarchy is sequential (no skipped levels).
|
|
86
|
+
- [ ] Color is not used as the sole means of conveying information.
|
|
87
|
+
|
|
88
|
+
#### 4.4 Security (from security-rules.md)
|
|
89
|
+
|
|
90
|
+
- [ ] No hardcoded secrets, API keys, or credentials in content fields.
|
|
91
|
+
- [ ] No internal URLs or staging domains in production content.
|
|
92
|
+
- [ ] User-generated or external URLs use HTTPS.
|
|
93
|
+
- [ ] No inline JavaScript or script injection vectors in richtext/markdown fields.
|
|
94
|
+
- [ ] No user-supplied HTML that could enable XSS.
|
|
95
|
+
- [ ] Email fields contain valid, non-internal addresses (unless intended).
|
|
96
|
+
|
|
97
|
+
#### 4.5 i18n Completeness (from i18n-quality.md)
|
|
98
|
+
|
|
99
|
+
- [ ] All models with `i18n: true` have entries for every supported locale.
|
|
100
|
+
- [ ] Entry counts match across locales (no missing translations).
|
|
101
|
+
- [ ] Required fields are populated in every locale.
|
|
102
|
+
- [ ] Dictionary keys are identical across all locale files.
|
|
103
|
+
- [ ] Vocabulary terms are translated in all locales.
|
|
104
|
+
- [ ] Translations are idiomatic (not word-for-word).
|
|
105
|
+
- [ ] Translated text fits within field `max` constraints.
|
|
106
|
+
- [ ] No source-locale text left untranslated in target locales.
|
|
107
|
+
|
|
108
|
+
#### 4.6 Referential Integrity
|
|
109
|
+
|
|
110
|
+
- [ ] All `relation` fields point to existing entries.
|
|
111
|
+
- [ ] All `relations` arrays contain valid references.
|
|
112
|
+
- [ ] No circular relations that could cause infinite loops.
|
|
113
|
+
- [ ] Referenced entries are in a publishable state.
|
|
114
|
+
|
|
115
|
+
### 5. Generate Review Report
|
|
116
|
+
|
|
117
|
+
Present a structured report:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
## Content Review Report
|
|
121
|
+
|
|
122
|
+
### Summary
|
|
123
|
+
- Models reviewed: X
|
|
124
|
+
- Total entries: X
|
|
125
|
+
- Locales checked: X
|
|
126
|
+
|
|
127
|
+
### Issues Found
|
|
128
|
+
|
|
129
|
+
#### Critical (must fix before publish)
|
|
130
|
+
- [list of blocking issues with field references]
|
|
131
|
+
|
|
132
|
+
#### Warning (should fix)
|
|
133
|
+
- [list of non-blocking issues]
|
|
134
|
+
|
|
135
|
+
#### Info (suggestions)
|
|
136
|
+
- [list of improvements]
|
|
137
|
+
|
|
138
|
+
### Per-Model Breakdown
|
|
139
|
+
| Model | Kind | Entries | Issues | Status |
|
|
140
|
+
|---|---|---|---|---|
|
|
141
|
+
|
|
142
|
+
### i18n Coverage
|
|
143
|
+
| Locale | Coverage | Missing Entries |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 6. Make a Recommendation
|
|
148
|
+
|
|
149
|
+
Based on the findings:
|
|
150
|
+
|
|
151
|
+
- **Approve:** No errors, few or no warnings. Changes are ready to merge.
|
|
152
|
+
- **Request changes:** Errors or significant warnings found. List the specific changes needed.
|
|
153
|
+
- **Reject:** Fundamental issues (wrong model structure, placeholder-only content, security violations). Explain why and suggest starting over.
|
|
154
|
+
|
|
155
|
+
### 7. Offer Fixes
|
|
156
|
+
|
|
157
|
+
For each fixable issue, offer to resolve it:
|
|
158
|
+
|
|
159
|
+
- Missing translations: offer to run `/contentrain-translate`.
|
|
160
|
+
- Schema violations: offer to update entries with `contentrain_content_save`.
|
|
161
|
+
- Missing required fields: draft content for empty fields.
|
|
162
|
+
- SEO issues: suggest improved titles, descriptions, slugs.
|
|
163
|
+
|
|
164
|
+
Only make changes after user approval.
|
|
165
|
+
|
|
166
|
+
### 8. Re-validate
|
|
167
|
+
|
|
168
|
+
If fixes were applied, call `contentrain_validate` again to confirm all issues are resolved.
|
|
169
|
+
|
|
170
|
+
Report the final status: "All checks passed" or list remaining issues that require manual attention.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: contentrain-sdk
|
|
3
|
+
description: "Use the @contentrain/query SDK to query Contentrain content with type safety. Use when importing from #contentrain, using QueryBuilder, SingletonAccessor, DictionaryAccessor, or DocumentQuery."
|
|
4
|
+
metadata:
|
|
5
|
+
author: Contentrain
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Contentrain Query SDK
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`@contentrain/query` is a Prisma-pattern generated client that provides type-safe access to Contentrain content. Generated output lives in `.contentrain/client/` — never edit it manually.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx contentrain generate # Generate client from models
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { query, singleton, dictionary, document } from '#contentrain'
|
|
23
|
+
|
|
24
|
+
// Collection
|
|
25
|
+
const posts = query('blog-post').where('category', 'eq', 'engineering').sort('published_at', 'desc').limit(10).all()
|
|
26
|
+
|
|
27
|
+
// Singleton
|
|
28
|
+
const hero = singleton('hero').get()
|
|
29
|
+
|
|
30
|
+
// Dictionary
|
|
31
|
+
const t = dictionary('ui-texts').get()
|
|
32
|
+
const loginLabel = t['auth.login.button']
|
|
33
|
+
|
|
34
|
+
// Document
|
|
35
|
+
const doc = document('blog-post').bySlug('getting-started')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Runtime API
|
|
39
|
+
|
|
40
|
+
### QueryBuilder (collections)
|
|
41
|
+
|
|
42
|
+
| Method | Description |
|
|
43
|
+
|--------|-------------|
|
|
44
|
+
| `.all()` | Get all entries |
|
|
45
|
+
| `.first()` | Get first entry |
|
|
46
|
+
| `.where(field, op, value)` | Filter (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `contains`) |
|
|
47
|
+
| `.sort(field, direction?)` | Sort (`asc`/`desc`, default `asc`) |
|
|
48
|
+
| `.limit(n)` | Limit results |
|
|
49
|
+
| `.offset(n)` | Skip results |
|
|
50
|
+
| `.include(relation)` | Resolve relation (1 level deep) |
|
|
51
|
+
|
|
52
|
+
### SingletonAccessor
|
|
53
|
+
|
|
54
|
+
| Method | Description |
|
|
55
|
+
|--------|-------------|
|
|
56
|
+
| `.get()` | Get the singleton data |
|
|
57
|
+
| `.include(relation)` | Resolve relation fields |
|
|
58
|
+
|
|
59
|
+
### DictionaryAccessor
|
|
60
|
+
|
|
61
|
+
| Method | Description |
|
|
62
|
+
|--------|-------------|
|
|
63
|
+
| `.get()` | Get all key-value pairs |
|
|
64
|
+
| `.get(key)` | Get single value by key |
|
|
65
|
+
| `.get(key, params)` | Get with interpolation: `{placeholder}` → value |
|
|
66
|
+
|
|
67
|
+
### DocumentQuery
|
|
68
|
+
|
|
69
|
+
| Method | Description |
|
|
70
|
+
|--------|-------------|
|
|
71
|
+
| `.all()` | Get all documents |
|
|
72
|
+
| `.bySlug(slug)` | Get document by slug |
|
|
73
|
+
| `.first()` | Get first document |
|
|
74
|
+
| `.where(field, op, value)` | Filter by frontmatter field |
|
|
75
|
+
| `.include(relation)` | Resolve relation fields |
|
|
76
|
+
|
|
77
|
+
## CDN Mode (Remote Data)
|
|
78
|
+
|
|
79
|
+
For server-side or client-side apps that fetch content from Contentrain Studio CDN:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createContentrain } from '@contentrain/query/cdn'
|
|
83
|
+
|
|
84
|
+
const client = createContentrain({
|
|
85
|
+
projectId: '350696e8-...',
|
|
86
|
+
apiKey: 'crn_live_xxx',
|
|
87
|
+
// baseUrl: 'https://studio.contentrain.io/api/cdn/v1' (default)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// All CDN queries are async (return Promise)
|
|
91
|
+
const posts = await client.collection('faq').locale('en').all()
|
|
92
|
+
const hero = await client.singleton('hero').locale('en').get()
|
|
93
|
+
const t = await client.dictionary('ui').locale('en').get()
|
|
94
|
+
const doc = await client.document('docs').locale('en').bySlug('intro')
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### CDN Collection Query Operators
|
|
98
|
+
|
|
99
|
+
| Operator | Example | Description |
|
|
100
|
+
|----------|---------|-------------|
|
|
101
|
+
| `eq` | `.where('status', 'eq', 'published')` | Equals |
|
|
102
|
+
| `ne` | `.where('status', 'ne', 'draft')` | Not equals |
|
|
103
|
+
| `gt` | `.where('order', 'gt', 5)` | Greater than |
|
|
104
|
+
| `gte` | `.where('order', 'gte', 5)` | Greater than or equal |
|
|
105
|
+
| `lt` | `.where('price', 'lt', 100)` | Less than |
|
|
106
|
+
| `lte` | `.where('price', 'lte', 100)` | Less than or equal |
|
|
107
|
+
| `in` | `.where('category', 'in', ['a','b'])` | In array |
|
|
108
|
+
| `contains` | `.where('tags', 'contains', 'vue')` | String/array contains |
|
|
109
|
+
|
|
110
|
+
### CDN vs Local
|
|
111
|
+
|
|
112
|
+
| Aspect | Local (`#contentrain`) | CDN (`createContentrain()`) |
|
|
113
|
+
|--------|----------------------|---------------------------|
|
|
114
|
+
| Data source | Bundled `.mjs` files | HTTP fetch from CDN |
|
|
115
|
+
| Return type | Sync (`T[]`) | Async (`Promise<T[]>`) |
|
|
116
|
+
| Auth | None | API key required |
|
|
117
|
+
| Caching | In-memory (embedded) | ETag-based HTTP cache |
|
|
118
|
+
| Use case | SSG, build-time | SSR, client-side, serverless |
|
|
119
|
+
|
|
120
|
+
## Key Rules
|
|
121
|
+
|
|
122
|
+
- **Local mode** queries are **synchronous** — no `await` needed
|
|
123
|
+
- **CDN mode** queries are **async** — always `await`
|
|
124
|
+
- Relation resolution is **1 level deep** — no recursive resolution
|
|
125
|
+
- Locale fallback chain: explicit → config default → first available
|
|
126
|
+
- Generated files in `.contentrain/client/` are **immutable** — always regenerate, never edit
|
|
127
|
+
- Run `contentrain generate` after any model change
|
|
128
|
+
|
|
129
|
+
## Framework Integration
|
|
130
|
+
|
|
131
|
+
| Framework | Import | Notes |
|
|
132
|
+
|-----------|--------|-------|
|
|
133
|
+
| Nuxt 3 | `import { query } from '#contentrain'` | Server-only (server routes, plugins) |
|
|
134
|
+
| Next.js | `import { query } from '#contentrain'` | Works in RSC and API routes |
|
|
135
|
+
| Astro | `import { query } from '#contentrain'` | Works in `.astro` frontmatter |
|
|
136
|
+
| SvelteKit | `import { query } from '#contentrain'` | Works in `+page.server.ts` |
|
|
137
|
+
| Vue + Vite | `import { query } from '#contentrain'` | Requires Vite alias config |
|
|
138
|
+
| React + Vite | `import { query } from '#contentrain'` | Requires Vite alias config |
|
|
139
|
+
| Node.js | `import { query } from '#contentrain'` | ESM with subpath imports |
|
|
140
|
+
|
|
141
|
+
## References
|
|
142
|
+
|
|
143
|
+
| Reference | Description |
|
|
144
|
+
|-----------|-------------|
|
|
145
|
+
| [Bundler Configuration](references/bundler-config.md) | Vite, Next.js, Nuxt, SvelteKit, Metro alias setup |
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bundler-config
|
|
3
|
+
description: "Framework-specific bundler configuration for #contentrain subpath imports"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bundler Configuration for #contentrain
|
|
7
|
+
|
|
8
|
+
The `#contentrain` import requires subpath imports configuration in `package.json` and bundler-specific aliases.
|
|
9
|
+
|
|
10
|
+
## package.json (all frameworks)
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"imports": {
|
|
15
|
+
"#contentrain": "./.contentrain/client/index.mjs"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Vite (Vue, React, Svelte)
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// vite.config.ts
|
|
24
|
+
import { resolve } from 'node:path'
|
|
25
|
+
import { defineConfig } from 'vite'
|
|
26
|
+
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
resolve: {
|
|
29
|
+
alias: {
|
|
30
|
+
'#contentrain': resolve(__dirname, '.contentrain/client/index.mjs')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Next.js
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// next.config.mjs
|
|
40
|
+
import { resolve } from 'node:path'
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
webpack(config) {
|
|
44
|
+
config.resolve.alias['#contentrain'] = resolve(process.cwd(), '.contentrain/client/index.mjs')
|
|
45
|
+
return config
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Nuxt 3
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// nuxt.config.ts
|
|
54
|
+
import { resolve } from 'node:path'
|
|
55
|
+
|
|
56
|
+
export default defineNuxtConfig({
|
|
57
|
+
alias: {
|
|
58
|
+
'#contentrain': resolve(__dirname, '.contentrain/client/index.mjs')
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Important:** Treat `#contentrain` as **server-only** in Nuxt. Use in server routes, server plugins, and `useAsyncData` callbacks only.
|
|
64
|
+
|
|
65
|
+
## SvelteKit
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
// svelte.config.js
|
|
69
|
+
import { resolve } from 'node:path'
|
|
70
|
+
|
|
71
|
+
export default {
|
|
72
|
+
kit: {
|
|
73
|
+
alias: {
|
|
74
|
+
'#contentrain': resolve('.contentrain/client/index.mjs')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Expo / React Native (Metro)
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// metro.config.js
|
|
84
|
+
const { getDefaultConfig } = require('expo/metro-config')
|
|
85
|
+
const path = require('path')
|
|
86
|
+
|
|
87
|
+
const config = getDefaultConfig(__dirname)
|
|
88
|
+
config.resolver.extraNodeModules = {
|
|
89
|
+
'#contentrain': path.resolve(__dirname, '.contentrain/client/index.cjs')
|
|
90
|
+
}
|
|
91
|
+
module.exports = config
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Bootstrap required:**
|
|
95
|
+
```javascript
|
|
96
|
+
// App.js (before any #contentrain import)
|
|
97
|
+
require('#contentrain').init()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Node.js (pure ESM)
|
|
101
|
+
|
|
102
|
+
No alias needed — Node.js natively supports `#imports` in `package.json`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"type": "module",
|
|
107
|
+
"imports": {
|
|
108
|
+
"#contentrain": "./.contentrain/client/index.mjs"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Node.js (CJS)
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"imports": {
|
|
118
|
+
"#contentrain": "./.contentrain/client/index.cjs"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Bootstrap required:
|
|
124
|
+
```javascript
|
|
125
|
+
const contentrain = require('#contentrain')
|
|
126
|
+
await contentrain.init()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Watch Mode
|
|
130
|
+
|
|
131
|
+
For development, run the generator in watch mode to auto-regenerate on model/content changes:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx contentrain generate --watch
|
|
135
|
+
```
|