cinqcinqdev-seo 0.1.14 → 0.1.16

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 (54) hide show
  1. package/README.md +554 -113
  2. package/dist/module.d.mts +6 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +14 -6
  5. package/dist/runtime/assets/admin-tw.css +1 -1
  6. package/dist/runtime/components/admin/Navbar.vue +6 -5
  7. package/dist/runtime/components/admin/Sidebar.vue +21 -8
  8. package/dist/runtime/components/sections/ContactForm.d.vue.ts +10 -0
  9. package/dist/runtime/components/sections/ContactForm.vue +138 -0
  10. package/dist/runtime/components/sections/ContactForm.vue.d.ts +10 -0
  11. package/dist/runtime/components/sections/FAQ.d.vue.ts +3 -0
  12. package/dist/runtime/components/sections/FAQ.vue +46 -0
  13. package/dist/runtime/components/sections/FAQ.vue.d.ts +3 -0
  14. package/dist/runtime/components/sections/Features.d.vue.ts +3 -0
  15. package/dist/runtime/components/sections/Features.vue +41 -0
  16. package/dist/runtime/components/sections/Features.vue.d.ts +3 -0
  17. package/dist/runtime/components/sections/HeroSection.d.vue.ts +3 -0
  18. package/dist/runtime/components/sections/HeroSection.vue +133 -0
  19. package/dist/runtime/components/sections/HeroSection.vue.d.ts +3 -0
  20. package/dist/runtime/components/sections/Pricing.d.vue.ts +3 -0
  21. package/dist/runtime/components/sections/Pricing.vue +108 -0
  22. package/dist/runtime/components/sections/Pricing.vue.d.ts +3 -0
  23. package/dist/runtime/components/sections/Process.d.vue.ts +3 -0
  24. package/dist/runtime/components/sections/Process.vue +95 -0
  25. package/dist/runtime/components/sections/Process.vue.d.ts +3 -0
  26. package/dist/runtime/components/sections/ServiceGrid.d.vue.ts +3 -0
  27. package/dist/runtime/components/sections/ServiceGrid.vue +97 -0
  28. package/dist/runtime/components/sections/ServiceGrid.vue.d.ts +3 -0
  29. package/dist/runtime/components/sections/Testimonials.d.vue.ts +3 -0
  30. package/dist/runtime/components/sections/Testimonials.vue +95 -0
  31. package/dist/runtime/components/sections/Testimonials.vue.d.ts +3 -0
  32. package/dist/runtime/components/sections/TextVisual.d.vue.ts +3 -0
  33. package/dist/runtime/components/sections/TextVisual.vue +94 -0
  34. package/dist/runtime/components/sections/TextVisual.vue.d.ts +3 -0
  35. package/dist/runtime/composables/useAdminBasePath.d.ts +1 -0
  36. package/dist/runtime/composables/useAdminBasePath.js +5 -0
  37. package/dist/runtime/composables/useAdminSections.d.ts +0 -7
  38. package/dist/runtime/composables/useAdminSections.js +68 -17
  39. package/dist/runtime/composables/useAdminSettings.d.ts +31 -0
  40. package/dist/runtime/composables/useAdminSettings.js +31 -0
  41. package/dist/runtime/middleware/admin-auth.js +3 -2
  42. package/dist/runtime/pages/admin/editor/[id].vue +10 -5
  43. package/dist/runtime/pages/admin/index.vue +3 -1
  44. package/dist/runtime/pages/admin/pages/[type].vue +5 -3
  45. package/dist/runtime/pages/admin/personas/[id].vue +3 -2
  46. package/dist/runtime/pages/admin/personas/index.vue +3 -2
  47. package/dist/runtime/pages/admin/setup.d.vue.ts +3 -0
  48. package/dist/runtime/pages/admin/setup.vue +220 -0
  49. package/dist/runtime/pages/admin/setup.vue.d.ts +3 -0
  50. package/dist/runtime/pages/admin/strategies/[id].vue +3 -2
  51. package/dist/runtime/pages/admin/strategies/index.vue +3 -2
  52. package/dist/runtime/server/api/ai/generate.post.js +8 -3
  53. package/dist/runtime/server/api/ai/seo-audit.post.js +8 -3
  54. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  <script setup>
2
2
  import { ref, onMounted, useHead, useRoute, navigateTo } from "#imports";
3
- import { useAdminPersonas, useAdminContent } from "#imports";
3
+ import { useAdminPersonas, useAdminContent, useAdminBasePath } from "#imports";
4
4
  const route = useRoute();
5
5
  const id = route.params.id;
6
+ const basePath = useAdminBasePath();
6
7
  useHead({ title: "Persona - Admin" });
7
8
  const { fetchPersonaById, updatePersona } = useAdminPersonas();
8
9
  const { getContentByPersona } = useAdminContent();
@@ -47,7 +48,7 @@ const statusClass = {
47
48
  <template>
48
49
  <AdminDashboardLayout>
49
50
  <div class="max-w-5xl mx-auto px-6 md:px-10 py-10">
50
- <button @click="navigateTo('/admin/personas')" class="flex items-center gap-2 text-slate-500 hover:text-blue-600 font-semibold transition-all mb-8 group">
51
+ <button @click="navigateTo(`${basePath}/personas`)" class="flex items-center gap-2 text-slate-500 hover:text-blue-600 font-semibold transition-all mb-8 group">
51
52
  <Icon name="mdi:arrow-left" class="text-xl group-hover:-translate-x-1 transition-transform" />
52
53
  Retour aux Personas
53
54
  </button>
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
2
  import { ref, computed, onMounted, useHead, navigateTo } from "#imports";
3
- import { useAdminPersonas } from "#imports";
3
+ import { useAdminPersonas, useAdminBasePath } from "#imports";
4
4
  useHead({ title: "Personas - Admin" });
5
+ const basePath = useAdminBasePath();
5
6
  const { fetchPersonas, createPersona, updatePersona, deletePersona } = useAdminPersonas();
6
7
  const personas = ref([]);
7
8
  const loading = ref(true);
@@ -88,7 +89,7 @@ const handleDelete = async (id) => {
88
89
  }
89
90
  }
90
91
  };
91
- const viewPersona = (persona) => navigateTo(`/admin/personas/${persona.id}`);
92
+ const viewPersona = (persona) => navigateTo(`${basePath}/personas/${persona.id}`);
92
93
  </script>
93
94
 
94
95
  <template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,220 @@
1
+ <script setup>
2
+ import { ref, onMounted, computed, useHead } from "#imports";
3
+ import { useAdminSettings } from "#imports";
4
+ import { useAdminSections } from "#imports";
5
+ useHead({ title: "Configuration \u2014 Admin" });
6
+ const { settings, loading, saving, load, save } = useAdminSettings();
7
+ const sections = useAdminSections();
8
+ const aiContext = ref("");
9
+ const presets = ref({});
10
+ const saved = ref(false);
11
+ onMounted(async () => {
12
+ await load();
13
+ aiContext.value = settings.value?.ai_context || "";
14
+ presets.value = JSON.parse(JSON.stringify(settings.value?.section_presets || {}));
15
+ });
16
+ const setPreset = (sectionType, field, value) => {
17
+ if (!presets.value[sectionType]) presets.value[sectionType] = {};
18
+ presets.value[sectionType][field] = value;
19
+ };
20
+ const getPreset = (sectionType, field, fallback) => {
21
+ return presets.value[sectionType]?.[field] ?? fallback ?? "";
22
+ };
23
+ const saveAll = async () => {
24
+ await save({ ai_context: aiContext.value, section_presets: presets.value });
25
+ saved.value = true;
26
+ setTimeout(() => {
27
+ saved.value = false;
28
+ }, 2e3);
29
+ };
30
+ const styleFields = ["layout", "spacing", "radius", "animation"];
31
+ const hasStyleOptions = (conf) => styleFields.some((f) => conf.fields?.[f]?.options?.length);
32
+ </script>
33
+
34
+ <template>
35
+ <AdminDashboardLayout>
36
+ <div data-admin-cms class="max-w-3xl mx-auto px-6 py-10">
37
+
38
+ <!-- Header -->
39
+ <div class="flex items-start justify-between mb-10">
40
+ <div>
41
+ <h1 class="text-2xl font-bold text-slate-900">Configuration du Projet</h1>
42
+ <p class="text-sm text-slate-500 mt-1">Contexte IA et préférences des composants</p>
43
+ </div>
44
+ <button
45
+ @click="saveAll"
46
+ :disabled="saving"
47
+ class="px-5 py-2.5 bg-slate-900 text-white text-sm font-semibold rounded-xl hover:bg-slate-700 disabled:opacity-50 transition-all flex items-center gap-2"
48
+ >
49
+ <svg v-if="!saving && !saved" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
51
+ </svg>
52
+ <svg v-if="saving" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
53
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
54
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/>
55
+ </svg>
56
+ {{ saved ? "Enregistr\xE9 \u2713" : saving ? "Enregistrement..." : "Enregistrer" }}
57
+ </button>
58
+ </div>
59
+
60
+ <div v-if="loading" class="text-center py-20 text-slate-400 text-sm">Chargement...</div>
61
+
62
+ <div v-else class="space-y-6">
63
+
64
+ <!-- ── AI Context ───────────────────────────────────────────────── -->
65
+ <div class="bg-white border border-slate-200 rounded-2xl p-8">
66
+ <div class="flex items-center gap-3 mb-2">
67
+ <span class="text-xl">✨</span>
68
+ <h2 class="text-base font-bold text-slate-900">Contexte IA</h2>
69
+ </div>
70
+ <p class="text-xs text-slate-500 mb-5 leading-relaxed">
71
+ Décrivez votre activité, cible et ton. L'IA s'en souviendra pour chaque génération de contenu et audit SEO.
72
+ </p>
73
+ <textarea
74
+ v-model="aiContext"
75
+ rows="7"
76
+ placeholder="Ex: Agence web basée à Alger spécialisée dans la création de sites vitrines et e-commerce pour les PME algériennes. Ton professionnel mais accessible, orienté résultats. Nos clients sont des entrepreneurs et dirigeants algériens entre 30 et 50 ans..."
77
+ class="w-full px-4 py-3 text-sm border border-slate-200 rounded-xl focus:outline-none focus:border-slate-400 resize-none leading-relaxed text-slate-700 placeholder:text-slate-300"
78
+ />
79
+ <p class="text-xs text-slate-400 mt-2 text-right">{{ aiContext.length }} caractères</p>
80
+ </div>
81
+
82
+ <!-- ── Component Defaults ──────────────────────────────────────── -->
83
+ <div class="bg-white border border-slate-200 rounded-2xl p-8">
84
+ <div class="flex items-center gap-3 mb-2">
85
+ <span class="text-xl">🎨</span>
86
+ <h2 class="text-base font-bold text-slate-900">Préférences des Composants</h2>
87
+ </div>
88
+ <p class="text-xs text-slate-500 mb-8 leading-relaxed">
89
+ Définissez le style par défaut de chaque section. Ces préférences s'appliquent automatiquement quand vous ajoutez une section dans l'éditeur.
90
+ </p>
91
+
92
+ <div class="space-y-8">
93
+ <div
94
+ v-for="(conf, type) in sections"
95
+ :key="type"
96
+ v-show="hasStyleOptions(conf)"
97
+ class="border border-slate-100 rounded-xl p-5"
98
+ >
99
+ <!-- Component label -->
100
+ <div class="flex items-center gap-2 mb-5">
101
+ <span class="text-lg leading-none">{{ conf.icon }}</span>
102
+ <span class="text-sm font-bold text-slate-800">{{ conf.label }}</span>
103
+ </div>
104
+
105
+ <div class="space-y-4">
106
+
107
+ <!-- Layout -->
108
+ <div v-if="conf.fields?.layout?.options">
109
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-slate-400 mb-2">Layout</p>
110
+ <div class="flex flex-wrap gap-1.5">
111
+ <button
112
+ v-for="opt in conf.fields.layout.options"
113
+ :key="opt.value"
114
+ @click="setPreset(String(type), 'layout', opt.value)"
115
+ :class="[
116
+ 'px-3 py-1.5 rounded-lg text-xs font-semibold border transition-all',
117
+ getPreset(String(type), 'layout', conf.defaultProps?.layout) === opt.value ? 'bg-slate-900 text-white border-slate-900' : 'bg-white text-slate-600 border-slate-200 hover:border-slate-400'
118
+ ]"
119
+ >
120
+ <span v-if="opt.icon" class="mr-1">{{ opt.icon }}</span>{{ opt.label }}
121
+ </button>
122
+ </div>
123
+ </div>
124
+
125
+ <!-- Animation -->
126
+ <div v-if="conf.fields?.animation?.options">
127
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-slate-400 mb-2">Animation</p>
128
+ <div class="flex flex-wrap gap-1.5">
129
+ <button
130
+ v-for="opt in conf.fields.animation.options"
131
+ :key="opt.value"
132
+ @click="setPreset(String(type), 'animation', opt.value)"
133
+ :class="[
134
+ 'px-3 py-1.5 rounded-lg text-xs font-semibold border transition-all',
135
+ getPreset(String(type), 'animation', conf.defaultProps?.animation) === opt.value ? 'bg-slate-900 text-white border-slate-900' : 'bg-white text-slate-600 border-slate-200 hover:border-slate-400'
136
+ ]"
137
+ >
138
+ <span v-if="opt.icon" class="mr-1">{{ opt.icon }}</span>{{ opt.label }}
139
+ </button>
140
+ </div>
141
+ </div>
142
+
143
+ <!-- Radius + Spacing inline -->
144
+ <div class="flex gap-8">
145
+ <div v-if="conf.fields?.radius?.options">
146
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-slate-400 mb-2">Arrondis</p>
147
+ <div class="flex gap-1.5">
148
+ <button
149
+ v-for="opt in conf.fields.radius.options"
150
+ :key="opt.value"
151
+ @click="setPreset(String(type), 'radius', opt.value)"
152
+ :class="[
153
+ 'px-3 py-1.5 rounded-lg text-xs font-semibold border transition-all',
154
+ getPreset(String(type), 'radius', conf.defaultProps?.radius) === opt.value ? 'bg-slate-900 text-white border-slate-900' : 'bg-white text-slate-600 border-slate-200 hover:border-slate-400'
155
+ ]"
156
+ >
157
+ <span v-if="opt.icon" class="mr-1">{{ opt.icon }}</span>{{ opt.label }}
158
+ </button>
159
+ </div>
160
+ </div>
161
+
162
+ <div v-if="conf.fields?.spacing?.options">
163
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-slate-400 mb-2">Espacement</p>
164
+ <div class="flex gap-1.5">
165
+ <button
166
+ v-for="opt in conf.fields.spacing.options"
167
+ :key="opt.value"
168
+ @click="setPreset(String(type), 'spacing', opt.value)"
169
+ :class="[
170
+ 'px-3 py-1.5 rounded-lg text-xs font-semibold border transition-all',
171
+ getPreset(String(type), 'spacing', conf.defaultProps?.spacing) === opt.value ? 'bg-slate-900 text-white border-slate-900' : 'bg-white text-slate-600 border-slate-200 hover:border-slate-400'
172
+ ]"
173
+ >
174
+ <span v-if="opt.icon" class="mr-1">{{ opt.icon }}</span>{{ opt.label }}
175
+ </button>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Colors -->
181
+ <div v-if="conf.fields?.bgColor || conf.fields?.textColor" class="flex gap-6">
182
+ <div v-if="conf.fields?.bgColor">
183
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-slate-400 mb-2">Fond</p>
184
+ <div class="flex items-center gap-2">
185
+ <input
186
+ type="color"
187
+ :value="getPreset(String(type), 'bgColor', conf.defaultProps?.bgColor || '#ffffff')"
188
+ @input="setPreset(String(type), 'bgColor', $event.target.value)"
189
+ class="w-8 h-8 rounded-lg border border-slate-200 cursor-pointer p-0.5"
190
+ />
191
+ <span class="text-xs font-mono text-slate-500">
192
+ {{ getPreset(String(type), "bgColor", conf.defaultProps?.bgColor || "#ffffff") }}
193
+ </span>
194
+ </div>
195
+ </div>
196
+ <div v-if="conf.fields?.textColor">
197
+ <p class="text-[10px] font-semibold uppercase tracking-widest text-slate-400 mb-2">Texte</p>
198
+ <div class="flex items-center gap-2">
199
+ <input
200
+ type="color"
201
+ :value="getPreset(String(type), 'textColor', conf.defaultProps?.textColor || '#000000')"
202
+ @input="setPreset(String(type), 'textColor', $event.target.value)"
203
+ class="w-8 h-8 rounded-lg border border-slate-200 cursor-pointer p-0.5"
204
+ />
205
+ <span class="text-xs font-mono text-slate-500">
206
+ {{ getPreset(String(type), "textColor", conf.defaultProps?.textColor || "#000000") }}
207
+ </span>
208
+ </div>
209
+ </div>
210
+ </div>
211
+
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ </div>
218
+ </div>
219
+ </AdminDashboardLayout>
220
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -1,8 +1,9 @@
1
1
  <script setup>
2
2
  import { ref, onMounted, useHead, useRoute, navigateTo } from "#imports";
3
- import { useAdminStrategies, useAdminContent, useAdminPersonas } from "#imports";
3
+ import { useAdminStrategies, useAdminContent, useAdminPersonas, useAdminBasePath } from "#imports";
4
4
  const route = useRoute();
5
5
  const id = route.params.id;
6
+ const basePath = useAdminBasePath();
6
7
  useHead({ title: "Strategie - Admin" });
7
8
  const { fetchStrategyById, updateStrategy, fetchStrategyContents, fetchStrategyPersonas } = useAdminStrategies();
8
9
  const { fetchContent, createContent } = useAdminContent();
@@ -74,7 +75,7 @@ const contentStatusClass = {
74
75
  <template>
75
76
  <AdminDashboardLayout>
76
77
  <div class="max-w-6xl mx-auto px-6 md:px-10 py-10">
77
- <NuxtLink to="/admin/strategies" class="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 mb-8 font-medium">
78
+ <NuxtLink :to="`${basePath}/strategies`" class="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 mb-8 font-medium">
78
79
  <Icon name="mdi:arrow-left" />
79
80
  Retour aux strategies
80
81
  </NuxtLink>
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
2
  import { ref, computed, onMounted, useHead, navigateTo } from "#imports";
3
- import { useAdminStrategies } from "#imports";
3
+ import { useAdminStrategies, useAdminBasePath } from "#imports";
4
4
  useHead({ title: "Strategies - Admin" });
5
+ const basePath = useAdminBasePath();
5
6
  const { fetchStrategies, createStrategy, updateStrategy, deleteStrategy, fetchStrategyContents, fetchStrategyPersonas } = useAdminStrategies();
6
7
  const strategies = ref([]);
7
8
  const strategyContents = ref({});
@@ -125,7 +126,7 @@ const handleDelete = async () => {
125
126
  <p class="text-sm text-slate-600">{{ strategy.description }}</p>
126
127
  </div>
127
128
  <div class="flex gap-2 ml-4">
128
- <button @click="navigateTo(`/admin/strategies/${strategy.id}`)" class="px-5 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-semibold hover:bg-blue-700 transition-all">Gerer</button>
129
+ <button @click="navigateTo(`${basePath}/strategies/${strategy.id}`)" class="px-5 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-semibold hover:bg-blue-700 transition-all">Gerer</button>
129
130
  <button @click="openEdit(strategy)" class="p-2.5 bg-slate-100 text-slate-600 rounded-xl hover:bg-slate-200 transition-all"><Icon name="mdi:pencil" /></button>
130
131
  </div>
131
132
  </div>
@@ -1,7 +1,7 @@
1
1
  const isI18nField = (val) => val !== null && typeof val === "object" && !Array.isArray(val) && ("fr" in val || "ar" in val || "en" in val);
2
2
  export default defineEventHandler(async (event) => {
3
3
  const body = await readBody(event);
4
- const { sectionType, sectionLabel, fields, currentProps, prompt, itemFieldsConfig } = body;
4
+ const { sectionType, sectionLabel, fields, currentProps, prompt, itemFieldsConfig, aiContext } = body;
5
5
  const config = useRuntimeConfig();
6
6
  if (!config.openrouterApiKey) {
7
7
  throw createError({ statusCode: 500, message: "OPENROUTER_API_KEY not configured" });
@@ -52,8 +52,13 @@ export default defineEventHandler(async (event) => {
52
52
  return `items[${i}]: "${typeof val === "object" ? val?.fr : val || ""}"`;
53
53
  }) : []
54
54
  ].join("\n");
55
- const systemPrompt = `You are a professional multilingual copywriter for a web agency CMS.
56
- Always respond with a valid JSON object only \u2014 no markdown, no code blocks, no explanation.`;
55
+ const systemPrompt = [
56
+ "You are a professional multilingual copywriter for a web agency CMS.",
57
+ aiContext ? `
58
+ Website context (always keep this in mind):
59
+ ${aiContext}` : "",
60
+ "\nAlways respond with a valid JSON object only \u2014 no markdown, no code blocks, no explanation."
61
+ ].filter(Boolean).join("");
57
62
  const userPrompt = `Section: ${sectionLabel} (${sectionType})
58
63
  User instruction: ${prompt}
59
64
 
@@ -1,6 +1,6 @@
1
1
  export default defineEventHandler(async (event) => {
2
2
  const body = await readBody(event);
3
- const { pageTitle, pageType, content, seoConfig } = body;
3
+ const { pageTitle, pageType, content, seoConfig, aiContext } = body;
4
4
  const config = useRuntimeConfig();
5
5
  if (!config.openrouterApiKey) {
6
6
  throw createError({ statusCode: 500, message: "OPENROUTER_API_KEY not configured" });
@@ -25,8 +25,13 @@ export default defineEventHandler(async (event) => {
25
25
  title: typeof seoConfig?.meta_title === "object" ? seoConfig.meta_title?.fr : seoConfig?.meta_title || "",
26
26
  description: typeof seoConfig?.meta_description === "object" ? seoConfig.meta_description?.fr : seoConfig?.meta_description || ""
27
27
  };
28
- const systemPrompt = `You are an expert SEO consultant.
29
- Respond with a valid JSON object only \u2014 no markdown, no code blocks, no extra text.`;
28
+ const systemPrompt = [
29
+ "You are an expert SEO consultant.",
30
+ aiContext ? `
31
+ Website context:
32
+ ${aiContext}` : "",
33
+ "\nRespond with a valid JSON object only \u2014 no markdown, no code blocks, no extra text."
34
+ ].filter(Boolean).join("");
30
35
  const userPrompt = `Audit this web page for SEO and provide actionable recommendations.
31
36
 
32
37
  Page title: "${pageTitle}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cinqcinqdev-seo",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "A reusable Nuxt 3 admin CMS module with visual page editor powered by Supabase",
5
5
  "license": "MIT",
6
6
  "module": "./dist/module.mjs",