@dyrected/core 1.0.9 → 2.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.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
+ createDyrectedApp,
2
3
  normalizeConfig
3
- } from "./chunk-GM4WW6IE.js";
4
+ } from "./chunk-22JTWD74.js";
4
5
 
5
6
  // src/utils/setup-prompt.ts
6
7
  function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
@@ -10,41 +11,75 @@ function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
10
11
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
11
12
  1. ENVIRONMENT
12
13
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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
- - Framework : ${frameworkLabel}
14
+ - Framework : ${frameworkLabel || "Detect it"}
14
15
  - Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
15
16
  - API Base : ${config.baseUrl || "http://localhost:3000"}
16
17
  ${credentialLines}`;
17
18
  }
18
- function buildDiagnosticSection(existingSite) {
19
- if (existingSite) {
20
- return `
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. PHASE 0 \u2014 DIAGNOSTIC (EXISTING SITE)
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
- 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
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
- }
19
+ function buildDiagnosticSection() {
37
20
  return `
38
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
39
- 2. PHASE 0 \u2014 DISCOVERY (NEW SITE)
22
+ 2. PHASE 0 \u2014 DISCOVERY
40
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
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?"
46
-
47
- Do NOT write any code until the user has answered these questions.`;
24
+ Before writing any code, you MUST ask the user these questions.
25
+ Write them exactly as shown below \u2014 in plain language with examples.
26
+ Wait for the user to answer ALL of them before proceeding.
27
+
28
+ \u2500\u2500\u2500 QUESTION 1 \u2500\u2500\u2500
29
+ Ask:
30
+ "Do you already have a website built, or are we starting fresh?
31
+
32
+ \u2192 Already built: share your project or describe what pages you have
33
+ (e.g. Home, About, Services, Contact)
34
+ \u2192 Starting fresh: just say 'new site' and describe what the site is for"
35
+
36
+ \u2500\u2500\u2500 QUESTION 2 \u2500\u2500\u2500
37
+ Ask:
38
+ "What kind of content will your client need to update regularly?
39
+
40
+ Here are some examples to help you think:
41
+ \u2192 Blog posts or news articles
42
+ \u2192 Team member profiles (name, photo, bio)
43
+ \u2192 Services or product descriptions
44
+ \u2192 Testimonials or reviews
45
+ \u2192 Event listings
46
+ \u2192 FAQs
47
+ \u2192 Homepage text (headline, hero image, call-to-action button)
48
+
49
+ Just list the ones that apply. You can say things like:
50
+ 'They need to update blog posts and team members'"
51
+
52
+ \u2500\u2500\u2500 QUESTION 3 \u2500\u2500\u2500
53
+ Ask:
54
+ "Are there any pages on the site that should NEVER change \u2014 like a
55
+ custom-coded page you want to leave exactly as it is?
56
+
57
+ Example answer: 'The homepage has a custom animation, leave that alone.
58
+ Everything else can be managed from the CMS.'
59
+
60
+ If everything should be manageable, just say 'all pages'"
61
+
62
+ \u2500\u2500\u2500 QUESTION 4 \u2500\u2500\u2500
63
+ Ask:
64
+ "What is this website for? Pick the closest description:
65
+
66
+ A) A business or agency marketing site (show services, get leads)
67
+ B) A blog or content site (publish articles regularly)
68
+ C) A SaaS or product site (landing page, pricing, features)
69
+ D) A portfolio (show work, case studies)
70
+ E) An e-commerce or product catalogue
71
+ F) Something else \u2014 describe it in one sentence"
72
+
73
+ \u2500\u2500\u2500 IF EXISTING SITE \u2500\u2500\u2500
74
+ If the user confirms they have an existing site, also:
75
+ - Scan the codebase for all hardcoded text that should be CMS-managed
76
+ - Identify repeated data structures that should become collections
77
+ - Propose saving current content to a migration/ folder as .json files
78
+ BEFORE touching any code
79
+ - Report your findings to the user and get confirmation before proceeding
80
+
81
+ Do NOT write any implementation code until all questions are answered
82
+ and the user has confirmed the content plan.`;
48
83
  }
49
84
  function buildConstraintsSection() {
50
85
  return `
@@ -74,7 +109,7 @@ function buildSchemaRulesSection() {
74
109
  - Never drop existing fields from the schema. Mark unused fields as deprecated only.
75
110
  - All new fields must have a defaultValue.
76
111
  - 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.`;
112
+ - For Cloud deployments, run npx @dyrected/cli sync:schema after every config change. Self-hosted deployments sync automatically on startup.`;
78
113
  }
79
114
  function buildDoNotSection() {
80
115
  return `
@@ -85,7 +120,7 @@ function buildDoNotSection() {
85
120
  - Do NOT add custom auth middleware to the admin route.
86
121
  Dyrected handles admin authentication internally. Do not wrap,
87
122
  protect, or redirect the admin route yourself.
88
- - Do NOT use renderAdminUI in a Nuxt project. Use the DyrectedAdmin
123
+ - Do NOT use renderAdminUI in a Nuxt.js project. Use the DyrectedAdmin
89
124
  component which is auto-imported by @dyrected/nuxt.
90
125
  - Do NOT modify or overwrite existing pages without first preserving their data.
91
126
  - Do NOT drop, rename, or remove fields from an existing schema.
@@ -100,23 +135,26 @@ function buildTechnicalReferenceSection() {
100
135
  Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
101
136
 
102
137
  FIELD TYPES:
103
- - Primitive : text | textarea | richText | number | boolean | date | email | url | json
138
+ - Primitive : text | textarea | richText | number | boolean | date | email | url | json | image
104
139
  - Choice : select | multiSelect (requires options: [{ label, value }])
105
140
  - Structural : array | object (requires nested fields: [...])
106
- - Relation : relationship (requires collection: '<slug>')
141
+ - Relation : relationship (requires relationTo: '<slug>')
107
142
  - Media : relationship to an upload collection (e.g. 'media')
108
143
  - Blocks : blocks (requires blocks: [{ slug, labels, fields }])
109
144
 
110
145
  COLLECTION OPTIONS:
111
146
  - upload: true \u2014 media library with file upload support
112
147
  - auth: true \u2014 adds login/me endpoints; password field is auto-managed
148
+ - audit: true \u2014 enables activity logging
113
149
  - admin.useAsTitle \u2014 field used as display title in admin list view
114
150
  - admin.group \u2014 groups collection under a sidebar heading
115
151
  - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
116
152
 
117
153
  FIELD OPTIONS:
154
+ - label \u2014 user-friendly display name (REQUIRED for all fields)
118
155
  - required \u2014 validation
119
156
  - unique \u2014 database-level uniqueness constraint
157
+ - hasMany \u2014 allow multiple values (for relationship, select, image)
120
158
  - defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
121
159
  - admin.condition \u2014 Jexl expression string to conditionally show/hide field
122
160
  e.g. "status == \\"published\\""
@@ -138,14 +176,13 @@ Always include a default case in your switch for unknown block types.
138
176
  COMPLETE SCHEMA EXAMPLE:
139
177
  \`\`\`ts
140
178
  import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
141
- import { MongoAdapter } from '@dyrected/db-mongodb'
142
- import { S3StorageAdapter } from '@dyrected/storage-s3'
179
+ import { SqliteAdapter } from '@dyrected/db-sqlite'
143
180
 
144
181
  const media = defineCollection({
145
182
  slug: 'media',
146
183
  upload: true,
147
184
  fields: [
148
- { name: 'alt', type: 'text', label: 'Alt Text' },
185
+ { name: 'alt', label: 'Alt Text', type: 'text' },
149
186
  ],
150
187
  })
151
188
 
@@ -153,44 +190,45 @@ const pages = defineCollection({
153
190
  slug: 'pages',
154
191
  admin: { useAsTitle: 'title', group: 'Content' },
155
192
  fields: [
156
- { name: 'title', type: 'text', required: true },
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' },
193
+ { name: 'title', label: 'Title', type: 'text', required: true },
194
+ { name: 'slug', label: 'URL Slug', type: 'text', required: true, unique: true },
195
+ { name: 'seo', label: 'SEO Metadata', type: 'object', fields: [
196
+ { name: 'metaTitle', label: 'Meta Title', type: 'text' },
197
+ { name: 'metaDescription', label: 'Meta Description', type: 'textarea' },
198
+ { name: 'ogImage', label: 'OG Image', type: 'relationship', relationTo: 'media' },
162
199
  ]},
163
200
  {
164
201
  name: 'layout',
202
+ label: 'Page Layout',
165
203
  type: 'blocks',
166
204
  blocks: [
167
205
  {
168
206
  slug: 'hero',
169
207
  labels: { singular: 'Hero', plural: 'Heroes' },
170
208
  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' },
209
+ { name: 'heading', label: 'Heading', type: 'text', required: true },
210
+ { name: 'subheading', label: 'Subheading', type: 'textarea' },
211
+ { name: 'image', label: 'Hero Image', type: 'relationship', relationTo: 'media' },
212
+ { name: 'ctaLabel', label: 'Button Label', type: 'text' },
213
+ { name: 'ctaLink', label: 'Button Link', type: 'url' },
176
214
  ],
177
215
  },
178
216
  {
179
217
  slug: 'richContent',
180
218
  labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
181
219
  fields: [
182
- { name: 'content', type: 'richText', required: true },
220
+ { name: 'content', label: 'Content', type: 'richText', required: true },
183
221
  ],
184
222
  },
185
223
  {
186
224
  slug: 'callToAction',
187
225
  labels: { singular: 'Call to Action', plural: 'Calls to Action' },
188
226
  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: [
227
+ { name: 'heading', label: 'Heading', type: 'text', required: true },
228
+ { name: 'description', label: 'Description', type: 'textarea' },
229
+ { name: 'buttonLabel', label: 'Button Text', type: 'text' },
230
+ { name: 'buttonLink', label: 'Button Link', type: 'url' },
231
+ { name: 'theme', label: 'Theme', type: 'select', options: [
194
232
  { label: 'Primary', value: 'primary' },
195
233
  { label: 'Secondary', value: 'secondary' },
196
234
  { label: 'Dark', value: 'dark' },
@@ -206,28 +244,17 @@ const settings = defineGlobal({
206
244
  slug: 'settings',
207
245
  label: 'Site Settings',
208
246
  fields: [
209
- { name: 'siteName', type: 'text' },
210
- { name: 'tagline', type: 'text' },
211
- { name: 'logo', type: 'relationship', collection: 'media' },
212
- { name: 'footerText', type: 'textarea' },
247
+ { name: 'siteName', label: 'Site Name', type: 'text' },
248
+ { name: 'tagline', label: 'Site Tagline', type: 'text' },
249
+ { name: 'logo', label: 'Site Logo', type: 'relationship', relationTo: 'media' },
250
+ { name: 'footerText', label: 'Footer Text', type: 'textarea' },
213
251
  ],
214
252
  })
215
253
 
216
254
  export default defineConfig({
217
255
  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
- }),
256
+ globals: [settings],
257
+ db: new SqliteAdapter({ filename: './dyrected.db' }),
231
258
  })
232
259
  \`\`\``;
233
260
  }
@@ -246,7 +273,8 @@ Return your response in exactly this order. Do not combine steps. Do not skip st
246
273
  6. Migration/fallback strategy \u2014 numbered steps
247
274
  7. Schema sync command
248
275
 
249
- API Reference: https://docs.dyrected.com`;
276
+ API Reference: https://docs.dyrected.com
277
+ If you are unsure about any syntax or property, refer to the documentation above.`;
250
278
  }
251
279
  function buildFrameworkSection(activeTab, isSelfHosted, config) {
252
280
  const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
@@ -268,14 +296,14 @@ export const dyrected = createClient({
268
296
  })
269
297
  \`\`\`
270
298
 
271
- 2. Admin Route (app/admin/[[...slug]]/page.tsx):
299
+ 2. Admin Route (app/cms/page.tsx):
272
300
  \`\`\`tsx
273
301
  import { DyrectedAdmin } from '@dyrected/next/admin'
274
302
 
275
303
  export default function AdminPage() {
276
304
  // DyrectedAdmin handles routing, auth, and CSS automatically.
277
305
  // Do NOT wrap this in custom auth middleware.
278
- return <DyrectedAdmin basename="/admin" />
306
+ return <DyrectedAdmin basename="/cms" />
279
307
  }
280
308
  \`\`\`
281
309
 
@@ -300,9 +328,9 @@ export default async function CmsPage({ params }: { params: { slug: string[] } }
300
328
  \`\`\``,
301
329
  nuxt: `
302
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
303
- 8. IMPLEMENTATION \u2014 Nuxt
331
+ 8. IMPLEMENTATION \u2014 Nuxt.js
304
332
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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):
333
+ 1. Nuxt.js Config (nuxt.config.ts):
306
334
  \`\`\`ts
307
335
  export default defineNuxtConfig({
308
336
  modules: ['@dyrected/nuxt'],
@@ -314,7 +342,7 @@ export default defineNuxtConfig({
314
342
  })
315
343
  \`\`\`
316
344
 
317
- 2. Admin Route (pages/admin.vue):
345
+ 2. Admin Route (pages/cms/index.vue):
318
346
  \`\`\`vue
319
347
  <script setup lang="ts">
320
348
  // DyrectedAdmin is auto-imported by @dyrected/nuxt.
@@ -326,7 +354,7 @@ definePageMeta({ layout: false })
326
354
 
327
355
  <template>
328
356
  <ClientOnly>
329
- <DyrectedAdmin basename="/admin" />
357
+ <DyrectedAdmin basename="/cms" />
330
358
  </ClientOnly>
331
359
  </template>
332
360
  \`\`\`
@@ -373,7 +401,7 @@ export const dyrected = createClient({
373
401
  })
374
402
  \`\`\`
375
403
 
376
- 2. Admin Route (pages/admin.tsx):
404
+ 2. Admin Route (pages/cms.tsx):
377
405
  \`\`\`tsx
378
406
  import { AdminUI } from '@dyrected/admin'
379
407
  import '@dyrected/admin/styles'
@@ -405,7 +433,7 @@ export const dyrected = createClient({
405
433
  })
406
434
  \`\`\`
407
435
 
408
- 2. Admin Route (pages/admin.vue):
436
+ 2. Admin Route (pages/cms.vue):
409
437
  \`\`\`vue
410
438
  <template>
411
439
  <div ref="container" style="height: 100vh" />
@@ -433,14 +461,350 @@ onUnmounted(() => cleanup?.())
433
461
  };
434
462
  return sections[activeTab] || sections.next;
435
463
  }
464
+ function generateFreshSetupPrompt(activeTab, config) {
465
+ const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
466
+ const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
467
+ const frameworkSetup = {
468
+ nuxt: `
469
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
470
+ 3. INSTALLATION STEPS \u2014 Run these in order
471
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
472
+ Tell the user to follow these steps exactly. Explain each one in plain
473
+ language before showing the command. Do not skip any step.
474
+
475
+ STEP 1 \u2014 Initialize Dyrected
476
+ Tell the user:
477
+ "Run this command in your terminal inside your Nuxt.js project folder.
478
+ It will set everything up for you automatically."
479
+
480
+ \`\`\`bash
481
+ npx @dyrected/cli init
482
+ \`\`\`
483
+
484
+ When it asks questions, tell the user to:
485
+ - Choose: Nuxt.js 3
486
+ - Choose: SQLite (easiest option, no extra setup needed)
487
+
488
+ The CLI will automatically:
489
+ - Install all required packages
490
+ - Create a dyrected.config.ts file
491
+ - Mount the Admin UI at pages/cms/index.vue
492
+ - Generate a .env.example file
493
+
494
+ STEP 2 \u2014 Register the module
495
+ Tell the user to open nuxt.config.ts and add '@dyrected/nuxt' to modules:
496
+
497
+ \`\`\`ts
498
+ export default defineNuxtConfig({
499
+ modules: ['@dyrected/nuxt'],
500
+ })
501
+ \`\`\`
502
+
503
+ STEP 3 \u2014 Set up environment variables
504
+ Tell the user:
505
+ "Find the file called .env.example in your project.
506
+ Make a copy of it and rename the copy to .env
507
+ Then open .env and fill in the values."
508
+
509
+ STEP 4 \u2014 Start the project
510
+ \`\`\`bash
511
+ pnpm dev
512
+ \`\`\`
513
+
514
+ STEP 5 \u2014 Open the dashboard
515
+ Tell the user to open their browser and go to:
516
+ http://localhost:3000/cms
517
+
518
+ If they see the Dyrected admin dashboard, the installation worked.
519
+ Tell them: "You are ready. Now let's set up your content."`,
520
+ next: `
521
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
522
+ 3. INSTALLATION STEPS \u2014 Run these in order
523
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
524
+ Tell the user to follow these steps exactly. Explain each one in plain
525
+ language before showing the command. Do not skip any step.
526
+
527
+ STEP 1 \u2014 Initialize Dyrected
528
+ Tell the user:
529
+ "Run this command in your terminal inside your Next.js project folder."
530
+
531
+ \`\`\`bash
532
+ npx @dyrected/cli init
533
+ \`\`\`
534
+
535
+ When it asks questions, tell the user to:
536
+ - Choose: Next.js
537
+ - Choose: SQLite (easiest option, no extra setup needed)
538
+
539
+ The CLI will automatically:
540
+ - Install all required packages
541
+ - Create a dyrected.config.ts file
542
+ - Mount the Admin UI at app/cms/page.tsx
543
+ - Generate a .env.example file
544
+
545
+ STEP 2 \u2014 Set up environment variables
546
+ Tell the user:
547
+ "Find the file called .env.example in your project.
548
+ Make a copy of it and rename the copy to .env.local
549
+ Then open .env.local and fill in the values."
550
+
551
+ STEP 3 \u2014 Start the project
552
+ \`\`\`bash
553
+ pnpm dev
554
+ \`\`\`
555
+
556
+ STEP 4 \u2014 Open the dashboard
557
+ Tell the user to open their browser and go to:
558
+ http://localhost:3000/cms
559
+
560
+ If they see the Dyrected admin dashboard, the installation worked.
561
+ Tell them: "You are ready. Now let's set up your content."`,
562
+ react: `
563
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
564
+ 3. INSTALLATION STEPS \u2014 Run these in order
565
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
566
+ STEP 1 \u2014 Install the SDK and admin packages
567
+ \`\`\`bash
568
+ npm install @dyrected/sdk @dyrected/admin
569
+ \`\`\`
570
+
571
+ STEP 2 \u2014 Set up the client (lib/dyrected.ts):
572
+ \`\`\`ts
573
+ import { createClient } from '@dyrected/sdk'
574
+
575
+ export const dyrected = createClient({
576
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
577
+ apiKey: '${config.apiKey}',
578
+ siteId: '${config.siteId}',`}
579
+ })
580
+ \`\`\`
581
+
582
+ STEP 3 \u2014 Mount the Admin UI (pages/cms.tsx):
583
+ \`\`\`tsx
584
+ import { AdminUI } from '@dyrected/admin'
585
+ import '@dyrected/admin/styles'
586
+
587
+ export default function AdminPage() {
588
+ return (
589
+ <div style={{ height: '100vh' }}>
590
+ <AdminUI
591
+ baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
592
+ apiKey="${config.apiKey}"
593
+ siteId="${config.siteId}"`}
594
+ />
595
+ </div>
596
+ )
597
+ }
598
+ \`\`\``,
599
+ vue: `
600
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
601
+ 3. INSTALLATION STEPS \u2014 Run these in order
602
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
603
+ STEP 1 \u2014 Install the SDK and admin packages
604
+ \`\`\`bash
605
+ npm install @dyrected/sdk @dyrected/admin
606
+ \`\`\`
607
+
608
+ STEP 2 \u2014 Set up the client (lib/dyrected.ts):
609
+ \`\`\`ts
610
+ import { createClient } from '@dyrected/sdk'
611
+
612
+ export const dyrected = createClient({
613
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
614
+ apiKey: '${config.apiKey}',
615
+ siteId: '${config.siteId}',`}
616
+ })
617
+ \`\`\`
618
+
619
+ STEP 3 \u2014 Mount the Admin UI (pages/cms.vue):
620
+ \`\`\`vue
621
+ <template>
622
+ <div ref="container" style="height: 100vh" />
623
+ </template>
624
+
625
+ <script setup>
626
+ import { ref, onMounted, onUnmounted } from 'vue'
627
+ import { renderAdminUI } from '@dyrected/admin'
628
+ import '@dyrected/admin/styles'
629
+
630
+ const container = ref(null)
631
+ let cleanup
632
+
633
+ onMounted(() => {
634
+ cleanup = renderAdminUI(container.value, {
635
+ baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
636
+ apiKey: '${config.apiKey}',
637
+ siteId: '${config.siteId}',`}
638
+ })
639
+ })
640
+
641
+ onUnmounted(() => cleanup?.())
642
+ </script>
643
+ \`\`\``
644
+ };
645
+ return [
646
+ `You are a friendly technical assistant helping someone set up Dyrected CMS for the very first time in a ${frameworkLabel} project. They have NOT installed anything yet.
647
+
648
+ Your job is to:
649
+ 1. Understand who you are talking to before giving any instructions
650
+ 2. Ask simple questions about their project and content needs
651
+ 3. Walk them through setup in a way that matches their technical level
652
+ 4. Confirm each step worked before moving to the next
653
+ 5. Help them design their content so their client can manage it
654
+
655
+ Speak in plain language at all times. Never assume technical knowledge.
656
+ Never show more than one step at a time.
657
+ Never mention the terminal, command line, or any commands until you have
658
+ confirmed the user is comfortable running them.`,
659
+ `
660
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
661
+ 1. ENVIRONMENT
662
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
663
+ - Framework : ${frameworkLabel}
664
+ - Host Type : ${isSelfHosted ? "Self-Hosted" : "Dyrected Cloud"}
665
+ - API Base : ${config.baseUrl || "http://localhost:3000"}
666
+ ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
667
+ - API Key : ${config.apiKey}`}`.trim(),
668
+ `
669
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
670
+ 2. PHASE 0 \u2014 UNDERSTAND THE USER FIRST
671
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
672
+ Ask these questions one at a time. Wait for each answer before asking the next.
673
+ Never ask more than one question at a time.
674
+
675
+ \u2500\u2500\u2500 QUESTION 1 \u2014 TECH LEVEL \u2500\u2500\u2500
676
+ This is the most important question. Ask it first.
677
+
678
+ Ask exactly this:
679
+ "Before we dive in \u2014 how would you describe yourself?
680
+
681
+ A) I write code myself (I'm comfortable with the terminal and editing files)
682
+ B) I use tools like Lovable, Bolt, or v0 to build with AI \u2014 I don't write much code myself
683
+ C) I'm a designer or project manager \u2014 someone else usually handles the technical stuff
684
+ D) Something else \u2014 just describe how you work"
685
+
686
+ Use their answer to decide how to guide them for the rest of the setup:
687
+ - If A \u2192 they are TECHNICAL. You may show terminal commands and code directly.
688
+ - If B \u2192 they are SEMI-TECHNICAL. Explain what each step does before showing
689
+ any code or commands. Ask before running anything in the terminal.
690
+ For Lovable users specifically: guide them to use the built-in
691
+ terminal or ask them to paste code into the right file in their editor.
692
+ - If C \u2192 they are NON-TECHNICAL. Do not show terminal commands at all.
693
+ Generate all the code and config for them. Walk them through
694
+ copy-pasting into specific files by name. If a terminal step is
695
+ unavoidable, warn them first and offer to write the exact command
696
+ with a clear explanation of what it does and where to run it.
697
+ - If D \u2192 ask one follow-up question to understand their workflow before
698
+ deciding which path above fits best.
699
+
700
+ \u2500\u2500\u2500 QUESTION 2 \u2014 PROJECT STATUS \u2500\u2500\u2500
701
+ Ask after Q1 is answered:
702
+
703
+ "Do you already have a ${frameworkLabel} project open,
704
+ or are we starting from scratch?
705
+
706
+ \u2192 Already have a project: tell me what the site is about or
707
+ share the folder name
708
+ \u2192 Starting fresh: just say 'new project' and I will help you
709
+ create one first"
710
+
711
+ \u2500\u2500\u2500 QUESTION 3 \u2014 SITE PURPOSE \u2500\u2500\u2500
712
+ Ask after Q2 is answered:
713
+
714
+ "What kind of website is this?
715
+
716
+ A) A business or agency site (show services, get enquiries)
717
+ B) A blog or news site (publish articles regularly)
718
+ C) A SaaS or product landing page (features, pricing, sign up)
719
+ D) A portfolio (show work and past projects)
720
+ E) Something else \u2014 describe it in one sentence"
721
+
722
+ \u2500\u2500\u2500 QUESTION 4 \u2014 CONTENT NEEDS \u2500\u2500\u2500
723
+ Ask after Q3 is answered:
724
+
725
+ "What will your client need to update themselves \u2014 without calling you?
726
+
727
+ Some examples to help you think:
728
+ \u2192 Blog posts or news articles
729
+ \u2192 Team member profiles (name, photo, bio)
730
+ \u2192 Services or product descriptions
731
+ \u2192 Homepage text (headline, buttons, images)
732
+ \u2192 Testimonials or reviews
733
+ \u2192 FAQs
734
+ \u2192 Event listings or announcements
735
+
736
+ Just list the ones that apply. Or say 'not sure yet'
737
+ and we will figure it out together."
738
+
739
+ \u2500\u2500\u2500 AFTER ALL QUESTIONS \u2500\u2500\u2500
740
+ Once all four questions are answered:
741
+ 1. Summarise what you understood in plain English
742
+ 2. Ask the user to confirm before touching any code
743
+ 3. Then move to the installation steps using the correct path
744
+ for their tech level from Question 1`,
745
+ frameworkSetup[activeTab] || frameworkSetup.nuxt,
746
+ `
747
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
748
+ 4. AFTER INSTALLATION \u2014 CONTENT SETUP
749
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
750
+ Once the dashboard is confirmed working, help the user design their
751
+ content model in dyrected.config.ts based on what they told you in
752
+ Phase 0.
753
+
754
+ RULES for this phase:
755
+ - Use defineCollection and defineConfig from '@dyrected/core'
756
+ - Use client.collection(slug) only \u2014 never client.collections
757
+ - Always use initialData in all data fetches
758
+ - Use a catch-all pages collection for marketing-managed pages
759
+ - Use blocks for flexible page layouts
760
+ - Use type: 'relationship', relationTo: 'collectionSlug' for relations
761
+ - Never throw during render \u2014 fall back to initialData on errors
762
+ - All relationship fields must handle null gracefully
763
+
764
+ CONTENT SETUP DELIVERABLES \u2014 in this order:
765
+ 1. dyrected.config.ts \u2014 complete file based on their answers
766
+ 2. Catch-all page route for CMS-managed pages
767
+ 3. Block components list (names and fields only)
768
+ 4. One example fetch showing how to load content on a page
769
+
770
+ After delivering the config, tell the user to sync their schema.
771
+ For TECHNICAL users show the command directly:
772
+ \`\`\`bash
773
+ npx @dyrected/cli sync:schema
774
+ \`\`\`
775
+ For SEMI-TECHNICAL or NON-TECHNICAL users explain it first:
776
+ "This next step tells Dyrected to read your content setup file
777
+ and prepare the database. Here is the command to run:" then show it.
778
+
779
+ Then ask: "Do you want me to help you connect this content to your
780
+ frontend pages now?"
781
+
782
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
783
+ DO NOT
784
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
785
+ - Do NOT show terminal commands before confirming the user's tech level
786
+ - Do NOT assume the user knows what a terminal, CLI, or package manager is
787
+ - Do NOT show all steps at once \u2014 one step at a time always
788
+ - Do NOT use client.collections \u2014 use client.collection(slug)
789
+ - Do NOT add custom auth middleware to the admin route
790
+ - Do NOT use renderAdminUI in a Nuxt.js project
791
+ - Do NOT skip the confirmation after each installation step
792
+ - Do NOT use jargon without explaining it in plain English first
793
+ - Do NOT assume the installation worked \u2014 ask the user to confirm
794
+
795
+ API Reference: https://docs.dyrected.com
796
+ If you are unsure about any syntax, refer to the official documentation above.`
797
+ ].join("\n");
798
+ }
436
799
  function generateAIPrompt(activeTab, config) {
437
- const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
800
+ const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
438
801
  const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
439
802
  const existingSite = config.existingSite ?? false;
803
+ const missionText = existingSite ? `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into an EXISTING ${frameworkLabel} project. Your absolute priority is DATA PRESERVATION and MIGRATION of existing hardcoded content into a flexible, blocks-based schema that empowers marketing teams to move independently.` : `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a NEW ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`;
440
804
  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.`,
805
+ missionText,
442
806
  buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
443
- buildDiagnosticSection(existingSite),
807
+ buildDiagnosticSection(),
444
808
  buildConstraintsSection(),
445
809
  buildSchemaRulesSection(),
446
810
  buildDoNotSection(),
@@ -462,9 +826,11 @@ function defineConfig(config) {
462
826
  return config;
463
827
  }
464
828
  export {
829
+ createDyrectedApp,
465
830
  defineCollection,
466
831
  defineConfig,
467
832
  defineGlobal,
468
833
  generateAIPrompt,
834
+ generateFreshSetupPrompt,
469
835
  normalizeConfig
470
836
  };