@dyrected/core 1.0.6 → 1.0.8

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 (3) hide show
  1. package/dist/index.cjs +325 -92
  2. package/dist/index.js +325 -92
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -29,125 +29,306 @@ __export(index_exports, {
29
29
  module.exports = __toCommonJS(index_exports);
30
30
 
31
31
  // src/utils/setup-prompt.ts
32
- function generateAIPrompt(activeTab, config) {
33
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
34
- const isSelfHosted = config.isSelfHosted ?? (config.baseUrl?.includes("localhost") || !config.apiKey);
35
- const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
36
- const mission = `You are a Senior Content Architect. Your mission is to create a robust CMS integration plan for a ${frameworkLabel} website using Dyrected CMS.
37
-
32
+ function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
33
+ const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
34
+ - API Key : ${config.apiKey}`;
35
+ return `
38
36
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
39
37
  1. ENVIRONMENT
40
38
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
41
- - Framework: ${frameworkLabel}
42
- - Host Type: ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
43
- - API Base : ${config.baseUrl || "http://localhost:3000"}
44
- ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
45
- - API Key : ${config.apiKey}`}
46
-
39
+ - Framework : ${frameworkLabel}
40
+ - Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
41
+ - API Base : ${config.baseUrl || "http://localhost:3000"}
42
+ ${credentialLines}`;
43
+ }
44
+ function buildDiagnosticSection(existingSite) {
45
+ if (existingSite) {
46
+ return `
47
47
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
48
- 2. ARCHITECTURE & CONSTRAINTS
48
+ 2. PHASE 0 \u2014 DIAGNOSTIC (EXISTING SITE)
49
49
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
50
- - API ACCESS: Use \`client.collection(slug)\` as the primary entry point. Do NOT use \`client.collections\`.
51
- - ZERO-STATE ROBUSTNESS: Always use \`initialData\` in all data fetches to ensure the site renders correctly on first load.
52
- - MARKETING INDEPENDENCE: Use a dynamic \`pages\` collection with a catch-all route for marketing-managed pages.
53
- - BLOCKS-BASED DESIGN: Use \`blocks\` for flexible page builders. Iterate the array and switch on \`blockType\` in your frontend.
54
- - DATA PRESERVATION: Do NOT modify or overwrite existing pages without first extracting and preserving their data.
55
- - NO DEPRECATIONS: Use the framework-specific \`DyrectedAdmin\` components (Next/Nuxt) which handle routing and CSS automatically.
50
+ Before writing any code, scan the existing codebase and report:
51
+ - All hardcoded text strings that should be CMS-managed
52
+ - All repeated data structures that should become collections
53
+ - All static pages that marketing will want to edit independently
54
+ - Any existing fetch or API calls that overlap with Dyrected
55
+
56
+ Then propose a backup plan: extract all current content into a
57
+ migration/ folder as structured .md or .json files BEFORE
58
+ modifying any code.
56
59
 
60
+ Do NOT write any implementation code until you have reported
61
+ your findings and the user has confirmed the content plan.`;
62
+ }
63
+ return `
57
64
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
58
- 3. REQUIRED DELIVERABLES
65
+ 2. PHASE 0 \u2014 DISCOVERY (NEW SITE)
59
66
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
60
- Your final response MUST include:
61
- 1. A complete content model plan (\`dyrected.config.ts\`).
62
- 2. The Admin UI mounting route (e.g. \`/admin\`) using the specialized \`DyrectedAdmin\` component.
63
- 3. A catch-all frontend page route for CMS-managed dynamic pages.
64
- 4. A block type list covering existing site sections (Hero, Content, CTA, etc.).
65
- 5. A migration/fallback strategy for current static pages.
66
- 6. A safe schema sync step using \`npx @dyrected/cli sync:schema\`.
67
+ This is a new project. Before designing the content model, ask:
68
+ - "What are your core content types? (e.g. Services, Team, Blog, Projects)"
69
+ - "Which pages should marketing manage independently with blocks?"
70
+ - "Are there any pages that must remain static and never become CMS-managed?"
71
+ - "What is the primary goal of the site \u2014 marketing, e-commerce, portfolio, SaaS?"
67
72
 
73
+ Do NOT write any code until the user has answered these questions.`;
74
+ }
75
+ function buildConstraintsSection() {
76
+ return `
68
77
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
69
- 4. PHASE 0 \u2014 DISCOVERY & PRESERVATION
78
+ 3. ARCHITECTURE & CONSTRAINTS
70
79
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
71
- Before writing any code, you MUST:
72
- 1. BACKUP: Propose a plan to save current site content into a \`migration/\` folder.
73
- 2. ASK QUESTIONS: If the site content types are unknown, ask the user:
74
- - "What are your core content types (Services, Team, Projects)?"
75
- - "Which existing pages must remain static vs. becoming dynamic?"
76
- - "What layouts should marketing be able to manage with blocks?"
77
- - "What existing hardcoded sections must be preserved?"
78
-
80
+ - API ACCESS : Use client.collection(slug) as the only entry point.
81
+ - ZERO-STATE : Always use initialData in all data fetches so the site
82
+ renders correctly on first load and never throws during render.
83
+ - MARKETING FREEDOM : Use a dynamic pages collection with a catch-all route
84
+ so marketing can create and manage pages without a developer.
85
+ - BLOCKS DESIGN : Use blocks for flexible page builders. Store as
86
+ [{ blockType: 'slug', ...fields }] and switch on blockType
87
+ in the frontend renderer.
88
+ - DATA SAFETY : Never overwrite or drop existing content or pages.
89
+ Preserve everything before making changes.
90
+ - RESILIENCE : If Dyrected backend is unreachable, fall back to
91
+ initialData and show stale content \u2014 never an error page.
92
+ All relationship fields must handle null gracefully.
93
+ Every block renderer must have a default fallback case.`;
94
+ }
95
+ function buildSchemaRulesSection() {
96
+ return `
97
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
98
+ 4. SCHEMA EVOLUTION RULES
99
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
100
+ - Never drop existing fields from the schema. Mark unused fields as deprecated only.
101
+ - All new fields must have a defaultValue.
102
+ - Never rename a field slug \u2014 add a new field and migrate data separately.
103
+ - Run npx @dyrected/cli sync:schema after every config change.`;
104
+ }
105
+ function buildDoNotSection() {
106
+ return `
107
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
108
+ 5. DO NOT
109
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
110
+ - Do NOT use client.collections \u2014 always use client.collection(slug).
111
+ - Do NOT add custom auth middleware to the admin route.
112
+ Dyrected handles admin authentication internally. Do not wrap,
113
+ protect, or redirect the admin route yourself.
114
+ - Do NOT use renderAdminUI in a Nuxt project. Use the DyrectedAdmin
115
+ component which is auto-imported by @dyrected/nuxt.
116
+ - Do NOT modify or overwrite existing pages without first preserving their data.
117
+ - Do NOT drop, rename, or remove fields from an existing schema.
118
+ - Do NOT integrate blog posts or testimonials unless explicitly requested.
119
+ - Do NOT skip the diagnostic or discovery phase.`;
120
+ }
121
+ function buildTechnicalReferenceSection() {
122
+ return `
79
123
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
80
- 5. TECHNICAL REFERENCE (Field Types & Syntax)
124
+ 6. TECHNICAL REFERENCE
81
125
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
82
- Use \`defineCollection\`, \`defineGlobal\`, and \`defineConfig\` from '@dyrected/core'.
126
+ Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
83
127
 
84
128
  FIELD TYPES:
85
129
  - Primitive : text | textarea | richText | number | boolean | date | email | url | json
86
- - Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
87
- - Structural : array | object (requires \`fields: [...]\`)
88
- - Relation : relationship (requires \`collection: '<slug>'\`)
130
+ - Choice : select | multiSelect (requires options: [{ label, value }])
131
+ - Structural : array | object (requires nested fields: [...])
132
+ - Relation : relationship (requires collection: '<slug>')
89
133
  - Media : relationship to an upload collection (e.g. 'media')
90
- - Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
134
+ - Blocks : blocks (requires blocks: [{ slug, labels, fields }])
91
135
 
92
136
  COLLECTION OPTIONS:
93
- - \`upload: true\`: Use for media libraries.
94
- - \`auth: true\`: Adds auth endpoints (login/me) and an auto-managed password field.
95
- - \`admin.useAsTitle\`: Field to display in admin lists.
137
+ - upload: true \u2014 media library with file upload support
138
+ - auth: true \u2014 adds login/me endpoints; password field is auto-managed
139
+ - admin.useAsTitle \u2014 field used as display title in admin list view
140
+ - admin.group \u2014 groups collection under a sidebar heading
141
+ - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
96
142
 
97
- BLOCKS SYNTAX:
98
- Blocks are stored as \`[{ blockType: 'slug', ...fields }]\`. The Admin UI renders a drag-and-drop editor for these automatically.
143
+ FIELD OPTIONS:
144
+ - required \u2014 validation
145
+ - unique \u2014 database-level uniqueness constraint
146
+ - defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
147
+ - admin.condition \u2014 Jexl expression string to conditionally show/hide field
148
+ e.g. "status == \\"published\\""
149
+ - admin.readOnly \u2014 display only, not editable
150
+ - admin.hidden \u2014 hidden from editor UI entirely
151
+ - hooks.beforeChange \u2014 [async (value) => newValue] transform before save
152
+ - hooks.afterRead \u2014 [async (value) => newValue] transform after read
99
153
 
100
- SCHEMA EXAMPLE:
154
+ GLOBALS:
155
+ Use defineGlobal for single-instance documents like site settings or navigation.
156
+ Access via client.global(slug).get() and client.global(slug).update(data).
157
+
158
+ BLOCKS:
159
+ Blocks are stored as [{ blockType: '<slug>', ...fields }].
160
+ The admin renders a drag-and-drop block editor automatically.
161
+ On the frontend, iterate the array and switch on block.blockType.
162
+ Always include a default case in your switch for unknown block types.
163
+
164
+ COMPLETE SCHEMA EXAMPLE:
101
165
  \`\`\`ts
102
- import { defineCollection, defineConfig } from '@dyrected/core'
166
+ import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
167
+ import { MongoAdapter } from '@dyrected/db-mongodb'
168
+ import { S3StorageAdapter } from '@dyrected/storage-s3'
103
169
 
104
170
  const media = defineCollection({
105
171
  slug: 'media',
106
172
  upload: true,
107
- fields: [{ name: 'alt', type: 'text' }]
173
+ fields: [
174
+ { name: 'alt', type: 'text', label: 'Alt Text' },
175
+ ],
108
176
  })
109
177
 
110
178
  const pages = defineCollection({
111
179
  slug: 'pages',
180
+ admin: { useAsTitle: 'title', group: 'Content' },
112
181
  fields: [
113
182
  { name: 'title', type: 'text', required: true },
114
- { name: 'slug', type: 'text', required: true, unique: true },
115
- { name: 'layout', type: 'blocks', blocks: [
116
- { slug: 'hero', fields: [{ name: 'title', type: 'text' }] }
117
- ]}
118
- ]
183
+ { name: 'slug', type: 'text', required: true, unique: true },
184
+ { name: 'seo', type: 'object', fields: [
185
+ { name: 'metaTitle', type: 'text' },
186
+ { name: 'metaDescription', type: 'textarea' },
187
+ { name: 'ogImage', type: 'relationship', collection: 'media' },
188
+ ]},
189
+ {
190
+ name: 'layout',
191
+ type: 'blocks',
192
+ blocks: [
193
+ {
194
+ slug: 'hero',
195
+ labels: { singular: 'Hero', plural: 'Heroes' },
196
+ fields: [
197
+ { name: 'heading', type: 'text', required: true },
198
+ { name: 'subheading', type: 'textarea' },
199
+ { name: 'image', type: 'relationship', collection: 'media' },
200
+ { name: 'ctaLabel', type: 'text' },
201
+ { name: 'ctaLink', type: 'url' },
202
+ ],
203
+ },
204
+ {
205
+ slug: 'richContent',
206
+ labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
207
+ fields: [
208
+ { name: 'content', type: 'richText', required: true },
209
+ ],
210
+ },
211
+ {
212
+ slug: 'callToAction',
213
+ labels: { singular: 'Call to Action', plural: 'Calls to Action' },
214
+ fields: [
215
+ { name: 'heading', type: 'text', required: true },
216
+ { name: 'description', type: 'textarea' },
217
+ { name: 'buttonLabel', type: 'text' },
218
+ { name: 'buttonLink', type: 'url' },
219
+ { name: 'theme', type: 'select', options: [
220
+ { label: 'Primary', value: 'primary' },
221
+ { label: 'Secondary', value: 'secondary' },
222
+ { label: 'Dark', value: 'dark' },
223
+ ]},
224
+ ],
225
+ },
226
+ ],
227
+ },
228
+ ],
119
229
  })
120
230
 
121
- export default defineConfig({ collections: [media, pages] })
122
- \`\`\`
231
+ const settings = defineGlobal({
232
+ slug: 'settings',
233
+ label: 'Site Settings',
234
+ fields: [
235
+ { name: 'siteName', type: 'text' },
236
+ { name: 'tagline', type: 'text' },
237
+ { name: 'logo', type: 'relationship', collection: 'media' },
238
+ { name: 'footerText', type: 'textarea' },
239
+ ],
240
+ })
241
+
242
+ export default defineConfig({
243
+ collections: [media, pages],
244
+ globals: [settings],
245
+ db: new MongoAdapter({
246
+ url: process.env.DATABASE_URL!,
247
+ dbName: 'dyrected_cms',
248
+ }),
249
+ storage: new S3StorageAdapter({
250
+ bucket: process.env.S3_BUCKET!,
251
+ region: process.env.S3_REGION!,
252
+ credentials: {
253
+ accessKeyId: process.env.S3_ACCESS_KEY_ID!,
254
+ secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
255
+ },
256
+ }),
257
+ })
258
+ \`\`\``;
259
+ }
260
+ function buildDeliverablesSection() {
261
+ return `
262
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
263
+ 7. REQUIRED DELIVERABLES \u2014 IN THIS ORDER
264
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
265
+ Return your response in exactly this order. Do not combine steps. Do not skip steps.
123
266
 
267
+ 1. Diagnostic findings (what exists, what needs to become CMS-managed)
268
+ 2. dyrected.config.ts \u2014 complete file
269
+ 3. Admin route file \u2014 complete file
270
+ 4. Catch-all frontend page route \u2014 complete file
271
+ 5. Block components \u2014 names and fields only
272
+ 6. Migration/fallback strategy \u2014 numbered steps
273
+ 7. Schema sync command
274
+
275
+ API Reference: https://docs.dyrected.com`;
276
+ }
277
+ function buildFrameworkSection(activeTab, isSelfHosted, config) {
278
+ const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
279
+ const credentialEnvLines = isSelfHosted ? "" : `
280
+ apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
281
+ siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`;
282
+ const baseUrlLine = `process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}'`;
283
+ const sections = {
284
+ next: `
124
285
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
125
- 6. IMPLEMENTATION DETAILS (${frameworkLabel})
286
+ 8. IMPLEMENTATION \u2014 Next.js
126
287
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
127
- `;
128
- const frameworks = {
129
- next: `1. **SDK Setup** (\`lib/dyrected.ts\`):
288
+ 1. SDK Setup (lib/dyrected.ts):
130
289
  \`\`\`ts
131
290
  import { createClient } from '@dyrected/sdk'
132
291
 
133
292
  export const dyrected = createClient({
134
- baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
135
- apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
136
- siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
293
+ baseUrl: ${baseUrlLine},${credentialEnvLines}
137
294
  })
138
295
  \`\`\`
139
296
 
140
- 2. **Admin Dashboard** (\`app/admin/[[...slug]]/page.tsx\`):
297
+ 2. Admin Route (app/admin/[[...slug]]/page.tsx):
141
298
  \`\`\`tsx
142
299
  import { DyrectedAdmin } from '@dyrected/next/admin'
143
300
 
144
301
  export default function AdminPage() {
145
- // DyrectedAdmin handles router, CSS, and "use client" for you
302
+ // DyrectedAdmin handles routing, auth, and CSS automatically.
303
+ // Do NOT wrap this in custom auth middleware.
146
304
  return <DyrectedAdmin basename="/admin" />
147
305
  }
148
306
  \`\`\`
149
- `,
150
- nuxt: `1. **Nuxt Config** (\`nuxt.config.ts\`):
307
+
308
+ 3. Catch-all Page Route (app/[...slug]/page.tsx):
309
+ \`\`\`tsx
310
+ import { dyrected } from '@/lib/dyrected'
311
+
312
+ export default async function CmsPage({ params }: { params: { slug: string[] } }) {
313
+ const slug = params.slug?.join('/') || 'home'
314
+ const page = await dyrected.collection('pages').findOne({ where: { slug: { equals: slug } } })
315
+
316
+ if (!page) return <div>Page not found</div>
317
+
318
+ return (
319
+ <main>
320
+ {page.layout?.map((block: any, i: number) => (
321
+ <BlockRenderer key={i} block={block} />
322
+ ))}
323
+ </main>
324
+ )
325
+ }
326
+ \`\`\``,
327
+ nuxt: `
328
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
329
+ 8. IMPLEMENTATION \u2014 Nuxt
330
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
331
+ 1. Nuxt Config (nuxt.config.ts):
151
332
  \`\`\`ts
152
333
  export default defineNuxtConfig({
153
334
  modules: ['@dyrected/nuxt'],
@@ -159,10 +340,13 @@ export default defineNuxtConfig({
159
340
  })
160
341
  \`\`\`
161
342
 
162
- 2. **Admin Dashboard** (\`pages/admin.vue\`):
343
+ 2. Admin Route (pages/admin.vue):
163
344
  \`\`\`vue
164
345
  <script setup lang="ts">
165
- // DyrectedAdmin is auto-imported by the module
346
+ // DyrectedAdmin is auto-imported by @dyrected/nuxt.
347
+ // Do NOT manually import it.
348
+ // Do NOT use renderAdminUI here.
349
+ // Do NOT add custom auth middleware \u2014 Dyrected handles authentication.
166
350
  definePageMeta({ layout: false })
167
351
  </script>
168
352
 
@@ -172,10 +356,39 @@ definePageMeta({ layout: false })
172
356
  </ClientOnly>
173
357
  </template>
174
358
  \`\`\`
175
- `,
176
- react: `Install \`@dyrected/sdk\`:
177
359
 
178
- CLIENT SETUP (\`lib/dyrected.ts\`):
360
+ 3. Catch-all Page Route (pages/[...slug].vue):
361
+ \`\`\`vue
362
+ <script setup lang="ts">
363
+ const route = useRoute()
364
+ const slug = computed(() =>
365
+ Array.isArray(route.params.slug)
366
+ ? route.params.slug.join('/')
367
+ : route.params.slug || 'home'
368
+ )
369
+
370
+ const { data: page } = await useDyrected('pages').findOne({
371
+ where: { slug: { equals: slug.value } },
372
+ initialData: null,
373
+ })
374
+ </script>
375
+
376
+ <template>
377
+ <main v-if="page">
378
+ <BlockRenderer
379
+ v-for="(block, i) in page.layout"
380
+ :key="i"
381
+ :block="block"
382
+ />
383
+ </main>
384
+ <div v-else>Page not found</div>
385
+ </template>
386
+ \`\`\``,
387
+ react: `
388
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
389
+ 8. IMPLEMENTATION \u2014 React
390
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
391
+ 1. SDK Setup (lib/dyrected.ts):
179
392
  \`\`\`ts
180
393
  import { createClient } from '@dyrected/sdk'
181
394
 
@@ -186,7 +399,7 @@ export const dyrected = createClient({
186
399
  })
187
400
  \`\`\`
188
401
 
189
- MOUNTING THE ADMIN DASHBOARD (\`pages/admin.tsx\`):
402
+ 2. Admin Route (pages/admin.tsx):
190
403
  \`\`\`tsx
191
404
  import { AdminUI } from '@dyrected/admin'
192
405
  import '@dyrected/admin/styles'
@@ -194,19 +407,20 @@ import '@dyrected/admin/styles'
194
407
  export default function AdminPage() {
195
408
  return (
196
409
  <div style={{ height: '100vh' }}>
197
- <AdminUI
198
- apiKey='${config.apiKey}'
199
- siteId='${config.siteId}'
200
- baseUrl='${config.baseUrl || "https://api.dyrected.cloud"}'
410
+ <AdminUI
411
+ baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
412
+ apiKey="${config.apiKey}"
413
+ siteId="${config.siteId}"`}
201
414
  />
202
415
  </div>
203
416
  )
204
417
  }
205
- \`\`\`
206
- `,
207
- vue: `Install \`@dyrected/sdk\`:
208
-
209
- CLIENT SETUP (\`lib/dyrected.ts\`):
418
+ \`\`\``,
419
+ vue: `
420
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
421
+ 8. IMPLEMENTATION \u2014 Vue
422
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
423
+ 1. SDK Setup (lib/dyrected.ts):
210
424
  \`\`\`ts
211
425
  import { createClient } from '@dyrected/sdk'
212
426
 
@@ -217,31 +431,50 @@ export const dyrected = createClient({
217
431
  })
218
432
  \`\`\`
219
433
 
220
- MOUNTING THE ADMIN DASHBOARD (\`pages/admin.vue\`):
434
+ 2. Admin Route (pages/admin.vue):
221
435
  \`\`\`vue
222
436
  <template>
223
437
  <div ref="container" style="height: 100vh" />
224
438
  </template>
225
439
 
226
440
  <script setup>
227
- import { ref, onMounted } from 'vue'
441
+ import { ref, onMounted, onUnmounted } from 'vue'
228
442
  import { renderAdminUI } from '@dyrected/admin'
229
443
  import '@dyrected/admin/styles'
230
444
 
231
445
  const container = ref(null)
446
+ let cleanup: (() => void) | undefined
447
+
232
448
  onMounted(() => {
233
- renderAdminUI(container.value, {
449
+ cleanup = renderAdminUI(container.value, {
450
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
234
451
  apiKey: '${config.apiKey}',
235
- siteId: '${config.siteId}',
236
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}'
452
+ siteId: '${config.siteId}',`}
237
453
  })
238
454
  })
455
+
456
+ onUnmounted(() => cleanup?.())
239
457
  </script>
240
- \`\`\`
241
- `
458
+ \`\`\``
242
459
  };
243
- return mission + (frameworks[activeTab] || frameworks.next) + `
244
- API Reference: https://docs.dyrected.com`;
460
+ return sections[activeTab] || sections.next;
461
+ }
462
+ function generateAIPrompt(activeTab, config) {
463
+ const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
464
+ const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
465
+ const existingSite = config.existingSite ?? false;
466
+ const sections = [
467
+ `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`,
468
+ buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
469
+ buildDiagnosticSection(existingSite),
470
+ buildConstraintsSection(),
471
+ buildSchemaRulesSection(),
472
+ buildDoNotSection(),
473
+ buildTechnicalReferenceSection(),
474
+ buildDeliverablesSection(),
475
+ buildFrameworkSection(activeTab, isSelfHosted, config)
476
+ ];
477
+ return sections.join("\n");
245
478
  }
246
479
 
247
480
  // src/utils/config.ts
package/dist/index.js CHANGED
@@ -3,125 +3,306 @@ import {
3
3
  } from "./chunk-GM4WW6IE.js";
4
4
 
5
5
  // src/utils/setup-prompt.ts
6
- function generateAIPrompt(activeTab, config) {
7
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
8
- const isSelfHosted = config.isSelfHosted ?? (config.baseUrl?.includes("localhost") || !config.apiKey);
9
- const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
10
- const mission = `You are a Senior Content Architect. Your mission is to create a robust CMS integration plan for a ${frameworkLabel} website using Dyrected CMS.
11
-
6
+ function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
7
+ const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
8
+ - API Key : ${config.apiKey}`;
9
+ return `
12
10
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13
11
  1. ENVIRONMENT
14
12
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
15
- - Framework: ${frameworkLabel}
16
- - Host Type: ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
17
- - API Base : ${config.baseUrl || "http://localhost:3000"}
18
- ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
19
- - API Key : ${config.apiKey}`}
20
-
13
+ - Framework : ${frameworkLabel}
14
+ - Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
15
+ - API Base : ${config.baseUrl || "http://localhost:3000"}
16
+ ${credentialLines}`;
17
+ }
18
+ function buildDiagnosticSection(existingSite) {
19
+ if (existingSite) {
20
+ return `
21
21
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
22
- 2. ARCHITECTURE & CONSTRAINTS
22
+ 2. PHASE 0 \u2014 DIAGNOSTIC (EXISTING SITE)
23
23
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
24
- - API ACCESS: Use \`client.collection(slug)\` as the primary entry point. Do NOT use \`client.collections\`.
25
- - ZERO-STATE ROBUSTNESS: Always use \`initialData\` in all data fetches to ensure the site renders correctly on first load.
26
- - MARKETING INDEPENDENCE: Use a dynamic \`pages\` collection with a catch-all route for marketing-managed pages.
27
- - BLOCKS-BASED DESIGN: Use \`blocks\` for flexible page builders. Iterate the array and switch on \`blockType\` in your frontend.
28
- - DATA PRESERVATION: Do NOT modify or overwrite existing pages without first extracting and preserving their data.
29
- - NO DEPRECATIONS: Use the framework-specific \`DyrectedAdmin\` components (Next/Nuxt) which handle routing and CSS automatically.
24
+ Before writing any code, scan the existing codebase and report:
25
+ - All hardcoded text strings that should be CMS-managed
26
+ - All repeated data structures that should become collections
27
+ - All static pages that marketing will want to edit independently
28
+ - Any existing fetch or API calls that overlap with Dyrected
30
29
 
30
+ Then propose a backup plan: extract all current content into a
31
+ migration/ folder as structured .md or .json files BEFORE
32
+ modifying any code.
33
+
34
+ Do NOT write any implementation code until you have reported
35
+ your findings and the user has confirmed the content plan.`;
36
+ }
37
+ return `
31
38
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
32
- 3. REQUIRED DELIVERABLES
39
+ 2. PHASE 0 \u2014 DISCOVERY (NEW SITE)
33
40
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
34
- Your final response MUST include:
35
- 1. A complete content model plan (\`dyrected.config.ts\`).
36
- 2. The Admin UI mounting route (e.g. \`/admin\`) using the specialized \`DyrectedAdmin\` component.
37
- 3. A catch-all frontend page route for CMS-managed dynamic pages.
38
- 4. A block type list covering existing site sections (Hero, Content, CTA, etc.).
39
- 5. A migration/fallback strategy for current static pages.
40
- 6. A safe schema sync step using \`npx @dyrected/cli sync:schema\`.
41
+ This is a new project. Before designing the content model, ask:
42
+ - "What are your core content types? (e.g. Services, Team, Blog, Projects)"
43
+ - "Which pages should marketing manage independently with blocks?"
44
+ - "Are there any pages that must remain static and never become CMS-managed?"
45
+ - "What is the primary goal of the site \u2014 marketing, e-commerce, portfolio, SaaS?"
41
46
 
47
+ Do NOT write any code until the user has answered these questions.`;
48
+ }
49
+ function buildConstraintsSection() {
50
+ return `
42
51
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
43
- 4. PHASE 0 \u2014 DISCOVERY & PRESERVATION
52
+ 3. ARCHITECTURE & CONSTRAINTS
44
53
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
45
- Before writing any code, you MUST:
46
- 1. BACKUP: Propose a plan to save current site content into a \`migration/\` folder.
47
- 2. ASK QUESTIONS: If the site content types are unknown, ask the user:
48
- - "What are your core content types (Services, Team, Projects)?"
49
- - "Which existing pages must remain static vs. becoming dynamic?"
50
- - "What layouts should marketing be able to manage with blocks?"
51
- - "What existing hardcoded sections must be preserved?"
52
-
54
+ - API ACCESS : Use client.collection(slug) as the only entry point.
55
+ - ZERO-STATE : Always use initialData in all data fetches so the site
56
+ renders correctly on first load and never throws during render.
57
+ - MARKETING FREEDOM : Use a dynamic pages collection with a catch-all route
58
+ so marketing can create and manage pages without a developer.
59
+ - BLOCKS DESIGN : Use blocks for flexible page builders. Store as
60
+ [{ blockType: 'slug', ...fields }] and switch on blockType
61
+ in the frontend renderer.
62
+ - DATA SAFETY : Never overwrite or drop existing content or pages.
63
+ Preserve everything before making changes.
64
+ - RESILIENCE : If Dyrected backend is unreachable, fall back to
65
+ initialData and show stale content \u2014 never an error page.
66
+ All relationship fields must handle null gracefully.
67
+ Every block renderer must have a default fallback case.`;
68
+ }
69
+ function buildSchemaRulesSection() {
70
+ return `
71
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
72
+ 4. SCHEMA EVOLUTION RULES
73
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
74
+ - Never drop existing fields from the schema. Mark unused fields as deprecated only.
75
+ - All new fields must have a defaultValue.
76
+ - Never rename a field slug \u2014 add a new field and migrate data separately.
77
+ - Run npx @dyrected/cli sync:schema after every config change.`;
78
+ }
79
+ function buildDoNotSection() {
80
+ return `
81
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
82
+ 5. DO NOT
83
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
84
+ - Do NOT use client.collections \u2014 always use client.collection(slug).
85
+ - Do NOT add custom auth middleware to the admin route.
86
+ Dyrected handles admin authentication internally. Do not wrap,
87
+ protect, or redirect the admin route yourself.
88
+ - Do NOT use renderAdminUI in a Nuxt project. Use the DyrectedAdmin
89
+ component which is auto-imported by @dyrected/nuxt.
90
+ - Do NOT modify or overwrite existing pages without first preserving their data.
91
+ - Do NOT drop, rename, or remove fields from an existing schema.
92
+ - Do NOT integrate blog posts or testimonials unless explicitly requested.
93
+ - Do NOT skip the diagnostic or discovery phase.`;
94
+ }
95
+ function buildTechnicalReferenceSection() {
96
+ return `
53
97
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
54
- 5. TECHNICAL REFERENCE (Field Types & Syntax)
98
+ 6. TECHNICAL REFERENCE
55
99
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
56
- Use \`defineCollection\`, \`defineGlobal\`, and \`defineConfig\` from '@dyrected/core'.
100
+ Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
57
101
 
58
102
  FIELD TYPES:
59
103
  - Primitive : text | textarea | richText | number | boolean | date | email | url | json
60
- - Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
61
- - Structural : array | object (requires \`fields: [...]\`)
62
- - Relation : relationship (requires \`collection: '<slug>'\`)
104
+ - Choice : select | multiSelect (requires options: [{ label, value }])
105
+ - Structural : array | object (requires nested fields: [...])
106
+ - Relation : relationship (requires collection: '<slug>')
63
107
  - Media : relationship to an upload collection (e.g. 'media')
64
- - Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
108
+ - Blocks : blocks (requires blocks: [{ slug, labels, fields }])
65
109
 
66
110
  COLLECTION OPTIONS:
67
- - \`upload: true\`: Use for media libraries.
68
- - \`auth: true\`: Adds auth endpoints (login/me) and an auto-managed password field.
69
- - \`admin.useAsTitle\`: Field to display in admin lists.
111
+ - upload: true \u2014 media library with file upload support
112
+ - auth: true \u2014 adds login/me endpoints; password field is auto-managed
113
+ - admin.useAsTitle \u2014 field used as display title in admin list view
114
+ - admin.group \u2014 groups collection under a sidebar heading
115
+ - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
70
116
 
71
- BLOCKS SYNTAX:
72
- Blocks are stored as \`[{ blockType: 'slug', ...fields }]\`. The Admin UI renders a drag-and-drop editor for these automatically.
117
+ FIELD OPTIONS:
118
+ - required \u2014 validation
119
+ - unique \u2014 database-level uniqueness constraint
120
+ - defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
121
+ - admin.condition \u2014 Jexl expression string to conditionally show/hide field
122
+ e.g. "status == \\"published\\""
123
+ - admin.readOnly \u2014 display only, not editable
124
+ - admin.hidden \u2014 hidden from editor UI entirely
125
+ - hooks.beforeChange \u2014 [async (value) => newValue] transform before save
126
+ - hooks.afterRead \u2014 [async (value) => newValue] transform after read
73
127
 
74
- SCHEMA EXAMPLE:
128
+ GLOBALS:
129
+ Use defineGlobal for single-instance documents like site settings or navigation.
130
+ Access via client.global(slug).get() and client.global(slug).update(data).
131
+
132
+ BLOCKS:
133
+ Blocks are stored as [{ blockType: '<slug>', ...fields }].
134
+ The admin renders a drag-and-drop block editor automatically.
135
+ On the frontend, iterate the array and switch on block.blockType.
136
+ Always include a default case in your switch for unknown block types.
137
+
138
+ COMPLETE SCHEMA EXAMPLE:
75
139
  \`\`\`ts
76
- import { defineCollection, defineConfig } from '@dyrected/core'
140
+ import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
141
+ import { MongoAdapter } from '@dyrected/db-mongodb'
142
+ import { S3StorageAdapter } from '@dyrected/storage-s3'
77
143
 
78
144
  const media = defineCollection({
79
145
  slug: 'media',
80
146
  upload: true,
81
- fields: [{ name: 'alt', type: 'text' }]
147
+ fields: [
148
+ { name: 'alt', type: 'text', label: 'Alt Text' },
149
+ ],
82
150
  })
83
151
 
84
152
  const pages = defineCollection({
85
153
  slug: 'pages',
154
+ admin: { useAsTitle: 'title', group: 'Content' },
86
155
  fields: [
87
156
  { name: 'title', type: 'text', required: true },
88
- { name: 'slug', type: 'text', required: true, unique: true },
89
- { name: 'layout', type: 'blocks', blocks: [
90
- { slug: 'hero', fields: [{ name: 'title', type: 'text' }] }
91
- ]}
92
- ]
157
+ { name: 'slug', type: 'text', required: true, unique: true },
158
+ { name: 'seo', type: 'object', fields: [
159
+ { name: 'metaTitle', type: 'text' },
160
+ { name: 'metaDescription', type: 'textarea' },
161
+ { name: 'ogImage', type: 'relationship', collection: 'media' },
162
+ ]},
163
+ {
164
+ name: 'layout',
165
+ type: 'blocks',
166
+ blocks: [
167
+ {
168
+ slug: 'hero',
169
+ labels: { singular: 'Hero', plural: 'Heroes' },
170
+ fields: [
171
+ { name: 'heading', type: 'text', required: true },
172
+ { name: 'subheading', type: 'textarea' },
173
+ { name: 'image', type: 'relationship', collection: 'media' },
174
+ { name: 'ctaLabel', type: 'text' },
175
+ { name: 'ctaLink', type: 'url' },
176
+ ],
177
+ },
178
+ {
179
+ slug: 'richContent',
180
+ labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
181
+ fields: [
182
+ { name: 'content', type: 'richText', required: true },
183
+ ],
184
+ },
185
+ {
186
+ slug: 'callToAction',
187
+ labels: { singular: 'Call to Action', plural: 'Calls to Action' },
188
+ fields: [
189
+ { name: 'heading', type: 'text', required: true },
190
+ { name: 'description', type: 'textarea' },
191
+ { name: 'buttonLabel', type: 'text' },
192
+ { name: 'buttonLink', type: 'url' },
193
+ { name: 'theme', type: 'select', options: [
194
+ { label: 'Primary', value: 'primary' },
195
+ { label: 'Secondary', value: 'secondary' },
196
+ { label: 'Dark', value: 'dark' },
197
+ ]},
198
+ ],
199
+ },
200
+ ],
201
+ },
202
+ ],
93
203
  })
94
204
 
95
- export default defineConfig({ collections: [media, pages] })
96
- \`\`\`
205
+ const settings = defineGlobal({
206
+ slug: 'settings',
207
+ label: 'Site Settings',
208
+ fields: [
209
+ { name: 'siteName', type: 'text' },
210
+ { name: 'tagline', type: 'text' },
211
+ { name: 'logo', type: 'relationship', collection: 'media' },
212
+ { name: 'footerText', type: 'textarea' },
213
+ ],
214
+ })
97
215
 
216
+ export default defineConfig({
217
+ collections: [media, pages],
218
+ globals: [settings],
219
+ db: new MongoAdapter({
220
+ url: process.env.DATABASE_URL!,
221
+ dbName: 'dyrected_cms',
222
+ }),
223
+ storage: new S3StorageAdapter({
224
+ bucket: process.env.S3_BUCKET!,
225
+ region: process.env.S3_REGION!,
226
+ credentials: {
227
+ accessKeyId: process.env.S3_ACCESS_KEY_ID!,
228
+ secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
229
+ },
230
+ }),
231
+ })
232
+ \`\`\``;
233
+ }
234
+ function buildDeliverablesSection() {
235
+ return `
236
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
237
+ 7. REQUIRED DELIVERABLES \u2014 IN THIS ORDER
238
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
239
+ Return your response in exactly this order. Do not combine steps. Do not skip steps.
240
+
241
+ 1. Diagnostic findings (what exists, what needs to become CMS-managed)
242
+ 2. dyrected.config.ts \u2014 complete file
243
+ 3. Admin route file \u2014 complete file
244
+ 4. Catch-all frontend page route \u2014 complete file
245
+ 5. Block components \u2014 names and fields only
246
+ 6. Migration/fallback strategy \u2014 numbered steps
247
+ 7. Schema sync command
248
+
249
+ API Reference: https://docs.dyrected.com`;
250
+ }
251
+ function buildFrameworkSection(activeTab, isSelfHosted, config) {
252
+ const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
253
+ const credentialEnvLines = isSelfHosted ? "" : `
254
+ apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
255
+ siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`;
256
+ const baseUrlLine = `process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}'`;
257
+ const sections = {
258
+ next: `
98
259
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
99
- 6. IMPLEMENTATION DETAILS (${frameworkLabel})
260
+ 8. IMPLEMENTATION \u2014 Next.js
100
261
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
101
- `;
102
- const frameworks = {
103
- next: `1. **SDK Setup** (\`lib/dyrected.ts\`):
262
+ 1. SDK Setup (lib/dyrected.ts):
104
263
  \`\`\`ts
105
264
  import { createClient } from '@dyrected/sdk'
106
265
 
107
266
  export const dyrected = createClient({
108
- baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
109
- apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
110
- siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
267
+ baseUrl: ${baseUrlLine},${credentialEnvLines}
111
268
  })
112
269
  \`\`\`
113
270
 
114
- 2. **Admin Dashboard** (\`app/admin/[[...slug]]/page.tsx\`):
271
+ 2. Admin Route (app/admin/[[...slug]]/page.tsx):
115
272
  \`\`\`tsx
116
273
  import { DyrectedAdmin } from '@dyrected/next/admin'
117
274
 
118
275
  export default function AdminPage() {
119
- // DyrectedAdmin handles router, CSS, and "use client" for you
276
+ // DyrectedAdmin handles routing, auth, and CSS automatically.
277
+ // Do NOT wrap this in custom auth middleware.
120
278
  return <DyrectedAdmin basename="/admin" />
121
279
  }
122
280
  \`\`\`
123
- `,
124
- nuxt: `1. **Nuxt Config** (\`nuxt.config.ts\`):
281
+
282
+ 3. Catch-all Page Route (app/[...slug]/page.tsx):
283
+ \`\`\`tsx
284
+ import { dyrected } from '@/lib/dyrected'
285
+
286
+ export default async function CmsPage({ params }: { params: { slug: string[] } }) {
287
+ const slug = params.slug?.join('/') || 'home'
288
+ const page = await dyrected.collection('pages').findOne({ where: { slug: { equals: slug } } })
289
+
290
+ if (!page) return <div>Page not found</div>
291
+
292
+ return (
293
+ <main>
294
+ {page.layout?.map((block: any, i: number) => (
295
+ <BlockRenderer key={i} block={block} />
296
+ ))}
297
+ </main>
298
+ )
299
+ }
300
+ \`\`\``,
301
+ nuxt: `
302
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
303
+ 8. IMPLEMENTATION \u2014 Nuxt
304
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
305
+ 1. Nuxt Config (nuxt.config.ts):
125
306
  \`\`\`ts
126
307
  export default defineNuxtConfig({
127
308
  modules: ['@dyrected/nuxt'],
@@ -133,10 +314,13 @@ export default defineNuxtConfig({
133
314
  })
134
315
  \`\`\`
135
316
 
136
- 2. **Admin Dashboard** (\`pages/admin.vue\`):
317
+ 2. Admin Route (pages/admin.vue):
137
318
  \`\`\`vue
138
319
  <script setup lang="ts">
139
- // DyrectedAdmin is auto-imported by the module
320
+ // DyrectedAdmin is auto-imported by @dyrected/nuxt.
321
+ // Do NOT manually import it.
322
+ // Do NOT use renderAdminUI here.
323
+ // Do NOT add custom auth middleware \u2014 Dyrected handles authentication.
140
324
  definePageMeta({ layout: false })
141
325
  </script>
142
326
 
@@ -146,10 +330,39 @@ definePageMeta({ layout: false })
146
330
  </ClientOnly>
147
331
  </template>
148
332
  \`\`\`
149
- `,
150
- react: `Install \`@dyrected/sdk\`:
151
333
 
152
- CLIENT SETUP (\`lib/dyrected.ts\`):
334
+ 3. Catch-all Page Route (pages/[...slug].vue):
335
+ \`\`\`vue
336
+ <script setup lang="ts">
337
+ const route = useRoute()
338
+ const slug = computed(() =>
339
+ Array.isArray(route.params.slug)
340
+ ? route.params.slug.join('/')
341
+ : route.params.slug || 'home'
342
+ )
343
+
344
+ const { data: page } = await useDyrected('pages').findOne({
345
+ where: { slug: { equals: slug.value } },
346
+ initialData: null,
347
+ })
348
+ </script>
349
+
350
+ <template>
351
+ <main v-if="page">
352
+ <BlockRenderer
353
+ v-for="(block, i) in page.layout"
354
+ :key="i"
355
+ :block="block"
356
+ />
357
+ </main>
358
+ <div v-else>Page not found</div>
359
+ </template>
360
+ \`\`\``,
361
+ react: `
362
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
363
+ 8. IMPLEMENTATION \u2014 React
364
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
365
+ 1. SDK Setup (lib/dyrected.ts):
153
366
  \`\`\`ts
154
367
  import { createClient } from '@dyrected/sdk'
155
368
 
@@ -160,7 +373,7 @@ export const dyrected = createClient({
160
373
  })
161
374
  \`\`\`
162
375
 
163
- MOUNTING THE ADMIN DASHBOARD (\`pages/admin.tsx\`):
376
+ 2. Admin Route (pages/admin.tsx):
164
377
  \`\`\`tsx
165
378
  import { AdminUI } from '@dyrected/admin'
166
379
  import '@dyrected/admin/styles'
@@ -168,19 +381,20 @@ import '@dyrected/admin/styles'
168
381
  export default function AdminPage() {
169
382
  return (
170
383
  <div style={{ height: '100vh' }}>
171
- <AdminUI
172
- apiKey='${config.apiKey}'
173
- siteId='${config.siteId}'
174
- baseUrl='${config.baseUrl || "https://api.dyrected.cloud"}'
384
+ <AdminUI
385
+ baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
386
+ apiKey="${config.apiKey}"
387
+ siteId="${config.siteId}"`}
175
388
  />
176
389
  </div>
177
390
  )
178
391
  }
179
- \`\`\`
180
- `,
181
- vue: `Install \`@dyrected/sdk\`:
182
-
183
- CLIENT SETUP (\`lib/dyrected.ts\`):
392
+ \`\`\``,
393
+ vue: `
394
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
395
+ 8. IMPLEMENTATION \u2014 Vue
396
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
397
+ 1. SDK Setup (lib/dyrected.ts):
184
398
  \`\`\`ts
185
399
  import { createClient } from '@dyrected/sdk'
186
400
 
@@ -191,31 +405,50 @@ export const dyrected = createClient({
191
405
  })
192
406
  \`\`\`
193
407
 
194
- MOUNTING THE ADMIN DASHBOARD (\`pages/admin.vue\`):
408
+ 2. Admin Route (pages/admin.vue):
195
409
  \`\`\`vue
196
410
  <template>
197
411
  <div ref="container" style="height: 100vh" />
198
412
  </template>
199
413
 
200
414
  <script setup>
201
- import { ref, onMounted } from 'vue'
415
+ import { ref, onMounted, onUnmounted } from 'vue'
202
416
  import { renderAdminUI } from '@dyrected/admin'
203
417
  import '@dyrected/admin/styles'
204
418
 
205
419
  const container = ref(null)
420
+ let cleanup: (() => void) | undefined
421
+
206
422
  onMounted(() => {
207
- renderAdminUI(container.value, {
423
+ cleanup = renderAdminUI(container.value, {
424
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
208
425
  apiKey: '${config.apiKey}',
209
- siteId: '${config.siteId}',
210
- baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}'
426
+ siteId: '${config.siteId}',`}
211
427
  })
212
428
  })
429
+
430
+ onUnmounted(() => cleanup?.())
213
431
  </script>
214
- \`\`\`
215
- `
432
+ \`\`\``
216
433
  };
217
- return mission + (frameworks[activeTab] || frameworks.next) + `
218
- API Reference: https://docs.dyrected.com`;
434
+ return sections[activeTab] || sections.next;
435
+ }
436
+ function generateAIPrompt(activeTab, config) {
437
+ const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
438
+ const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
439
+ const existingSite = config.existingSite ?? false;
440
+ const sections = [
441
+ `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`,
442
+ buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
443
+ buildDiagnosticSection(existingSite),
444
+ buildConstraintsSection(),
445
+ buildSchemaRulesSection(),
446
+ buildDoNotSection(),
447
+ buildTechnicalReferenceSection(),
448
+ buildDeliverablesSection(),
449
+ buildFrameworkSection(activeTab, isSelfHosted, config)
450
+ ];
451
+ return sections.join("\n");
219
452
  }
220
453
 
221
454
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/core",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",