foundrycms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/__tests__/foundry.test.d.ts +2 -0
- package/dist/__tests__/foundry.test.d.ts.map +1 -0
- package/dist/__tests__/foundry.test.js +1013 -0
- package/dist/__tests__/foundry.test.js.map +1 -0
- package/dist/config-manager.d.ts +33 -0
- package/dist/config-manager.d.ts.map +1 -0
- package/dist/config-manager.js +169 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/hook-system.d.ts +61 -0
- package/dist/hook-system.d.ts.map +1 -0
- package/dist/hook-system.js +114 -0
- package/dist/hook-system.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/page-builder/element-registry.d.ts +47 -0
- package/dist/page-builder/element-registry.d.ts.map +1 -0
- package/dist/page-builder/element-registry.js +98 -0
- package/dist/page-builder/element-registry.js.map +1 -0
- package/dist/page-builder/elements/index.d.ts +22 -0
- package/dist/page-builder/elements/index.d.ts.map +1 -0
- package/dist/page-builder/elements/index.js +770 -0
- package/dist/page-builder/elements/index.js.map +1 -0
- package/dist/page-builder/renderer.d.ts +14 -0
- package/dist/page-builder/renderer.d.ts.map +1 -0
- package/dist/page-builder/renderer.js +240 -0
- package/dist/page-builder/renderer.js.map +1 -0
- package/dist/page-builder/serializer.d.ts +1220 -0
- package/dist/page-builder/serializer.d.ts.map +1 -0
- package/dist/page-builder/serializer.js +111 -0
- package/dist/page-builder/serializer.js.map +1 -0
- package/dist/page-builder/template-studio.d.ts +37 -0
- package/dist/page-builder/template-studio.d.ts.map +1 -0
- package/dist/page-builder/template-studio.js +923 -0
- package/dist/page-builder/template-studio.js.map +1 -0
- package/dist/page-builder/types.d.ts +99 -0
- package/dist/page-builder/types.d.ts.map +1 -0
- package/dist/page-builder/types.js +5 -0
- package/dist/page-builder/types.js.map +1 -0
- package/dist/plugin-system.d.ts +128 -0
- package/dist/plugin-system.d.ts.map +1 -0
- package/dist/plugin-system.js +252 -0
- package/dist/plugin-system.js.map +1 -0
- package/dist/plugins/communication.d.ts +6 -0
- package/dist/plugins/communication.d.ts.map +1 -0
- package/dist/plugins/communication.js +922 -0
- package/dist/plugins/communication.js.map +1 -0
- package/dist/plugins/core.d.ts +6 -0
- package/dist/plugins/core.d.ts.map +1 -0
- package/dist/plugins/core.js +675 -0
- package/dist/plugins/core.js.map +1 -0
- package/dist/plugins/growth.d.ts +6 -0
- package/dist/plugins/growth.d.ts.map +1 -0
- package/dist/plugins/growth.js +668 -0
- package/dist/plugins/growth.js.map +1 -0
- package/dist/plugins/index.d.ts +8 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +43 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/operations.d.ts +7 -0
- package/dist/plugins/operations.d.ts.map +1 -0
- package/dist/plugins/operations.js +930 -0
- package/dist/plugins/operations.js.map +1 -0
- package/dist/theme/presets.d.ts +8 -0
- package/dist/theme/presets.d.ts.map +1 -0
- package/dist/theme/presets.js +257 -0
- package/dist/theme/presets.js.map +1 -0
- package/dist/theme/types.d.ts +83 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +5 -0
- package/dist/theme/types.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Forge — Visual Site Builder
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export const forgePlugin = {
|
|
6
|
+
id: '@foundry/forge',
|
|
7
|
+
name: 'Forge',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
description: 'Visual drag-and-drop site builder with live preview and responsive editing',
|
|
10
|
+
adminPages: [
|
|
11
|
+
{
|
|
12
|
+
path: 'forge',
|
|
13
|
+
label: 'Site Builder',
|
|
14
|
+
icon: 'hammer',
|
|
15
|
+
component: 'ForgeEditorPage',
|
|
16
|
+
order: 1,
|
|
17
|
+
permission: 'forge:edit',
|
|
18
|
+
children: [
|
|
19
|
+
{
|
|
20
|
+
path: 'pages',
|
|
21
|
+
label: 'Pages',
|
|
22
|
+
component: 'ForgePagesListPage',
|
|
23
|
+
permission: 'forge:edit',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: 'templates',
|
|
27
|
+
label: 'Templates',
|
|
28
|
+
component: 'ForgeTemplatesPage',
|
|
29
|
+
permission: 'forge:templates',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: 'global-styles',
|
|
33
|
+
label: 'Global Styles',
|
|
34
|
+
component: 'ForgeGlobalStylesPage',
|
|
35
|
+
permission: 'forge:styles',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
apiRoutes: [
|
|
41
|
+
{
|
|
42
|
+
method: 'GET',
|
|
43
|
+
path: '/api/forge/pages',
|
|
44
|
+
handler: 'forge.listPages',
|
|
45
|
+
middleware: ['auth'],
|
|
46
|
+
description: 'List all site pages',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
method: 'POST',
|
|
50
|
+
path: '/api/forge/pages',
|
|
51
|
+
handler: 'forge.createPage',
|
|
52
|
+
middleware: ['auth', 'permission:forge:edit'],
|
|
53
|
+
description: 'Create a new page',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
method: 'PUT',
|
|
57
|
+
path: '/api/forge/pages/:id',
|
|
58
|
+
handler: 'forge.updatePage',
|
|
59
|
+
middleware: ['auth', 'permission:forge:edit'],
|
|
60
|
+
description: 'Update a page by ID',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
method: 'POST',
|
|
64
|
+
path: '/api/forge/pages/:id/publish',
|
|
65
|
+
handler: 'forge.publishPage',
|
|
66
|
+
middleware: ['auth', 'permission:forge:publish'],
|
|
67
|
+
description: 'Publish a page',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
method: 'DELETE',
|
|
71
|
+
path: '/api/forge/pages/:id',
|
|
72
|
+
handler: 'forge.deletePage',
|
|
73
|
+
middleware: ['auth', 'permission:forge:delete'],
|
|
74
|
+
description: 'Delete a page',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
pageElements: [
|
|
78
|
+
{
|
|
79
|
+
type: 'forge-section',
|
|
80
|
+
name: 'Section',
|
|
81
|
+
category: 'layout',
|
|
82
|
+
component: 'ForgeSectionElement',
|
|
83
|
+
settingsSchema: z.object({
|
|
84
|
+
backgroundColor: z.string().optional(),
|
|
85
|
+
padding: z.enum(['none', 'sm', 'md', 'lg', 'xl']).default('md'),
|
|
86
|
+
fullWidth: z.boolean().default(false),
|
|
87
|
+
backgroundImage: z.string().url().optional(),
|
|
88
|
+
parallax: z.boolean().default(false),
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'forge-container',
|
|
93
|
+
name: 'Container',
|
|
94
|
+
category: 'layout',
|
|
95
|
+
component: 'ForgeContainerElement',
|
|
96
|
+
settingsSchema: z.object({
|
|
97
|
+
maxWidth: z.enum(['sm', 'md', 'lg', 'xl', 'full']).default('lg'),
|
|
98
|
+
alignment: z.enum(['left', 'center', 'right']).default('center'),
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
widgets: [
|
|
103
|
+
{
|
|
104
|
+
id: 'forge-recent-pages',
|
|
105
|
+
name: 'Recent Pages',
|
|
106
|
+
component: 'ForgeRecentPagesWidget',
|
|
107
|
+
areas: ['dashboard'],
|
|
108
|
+
defaultSize: { width: 4, height: 3 },
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
hooks: [
|
|
112
|
+
{
|
|
113
|
+
hook: 'page:before_render',
|
|
114
|
+
handler: (payload) => {
|
|
115
|
+
// Inject Forge builder data attributes for live editing
|
|
116
|
+
if (payload.page && payload.page._forgeEditing) {
|
|
117
|
+
payload.page._renderMode = 'builder';
|
|
118
|
+
payload.page._builderScripts = ['/forge/builder.js'];
|
|
119
|
+
payload.page._builderStyles = ['/forge/builder.css'];
|
|
120
|
+
}
|
|
121
|
+
return payload;
|
|
122
|
+
},
|
|
123
|
+
priority: 1,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
hook: 'page:before_save',
|
|
127
|
+
handler: (payload) => {
|
|
128
|
+
// Validate page structure before saving
|
|
129
|
+
if (payload.page?.rows) {
|
|
130
|
+
for (const row of payload.page.rows) {
|
|
131
|
+
if (!row.columns || row.columns.length === 0) {
|
|
132
|
+
throw new Error(`Forge: Row "${row.id}" must have at least one column`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return payload;
|
|
137
|
+
},
|
|
138
|
+
priority: 5,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
settingsSchema: z.object({
|
|
142
|
+
defaultTemplate: z.string().default('blank'),
|
|
143
|
+
autosaveInterval: z.number().min(5000).max(120000).default(30000),
|
|
144
|
+
enableVersionHistory: z.boolean().default(true),
|
|
145
|
+
maxVersions: z.number().min(1).max(100).default(25),
|
|
146
|
+
livePreview: z.boolean().default(true),
|
|
147
|
+
}),
|
|
148
|
+
async onActivate(core) {
|
|
149
|
+
core.hooks.on('page:after_save', (payload) => {
|
|
150
|
+
// Trigger rebuild of static pages if needed
|
|
151
|
+
if (payload.page?.status === 'published') {
|
|
152
|
+
core.hooks.emit('content:after_save', {
|
|
153
|
+
content: { type: 'page', id: payload.page.id, action: 'publish' },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
async onDeactivate(_core) {
|
|
159
|
+
// Clean up any live-preview websocket connections
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Ledger — Invoicing (ChronoCraft)
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
export const ledgerPlugin = {
|
|
166
|
+
id: '@foundry/ledger',
|
|
167
|
+
name: 'Ledger',
|
|
168
|
+
version: '1.0.0',
|
|
169
|
+
description: 'Professional invoicing system with Stripe integration, recurring billing, and payment tracking',
|
|
170
|
+
adminPages: [
|
|
171
|
+
{
|
|
172
|
+
path: 'ledger',
|
|
173
|
+
label: 'Invoicing',
|
|
174
|
+
icon: 'file-text',
|
|
175
|
+
component: 'LedgerDashboardPage',
|
|
176
|
+
order: 10,
|
|
177
|
+
permission: 'ledger:view',
|
|
178
|
+
children: [
|
|
179
|
+
{
|
|
180
|
+
path: 'invoices',
|
|
181
|
+
label: 'Invoices',
|
|
182
|
+
component: 'LedgerInvoicesPage',
|
|
183
|
+
permission: 'ledger:view',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
path: 'invoices/new',
|
|
187
|
+
label: 'New Invoice',
|
|
188
|
+
component: 'LedgerInvoiceEditorPage',
|
|
189
|
+
permission: 'ledger:create',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
path: 'clients',
|
|
193
|
+
label: 'Clients',
|
|
194
|
+
component: 'LedgerClientsPage',
|
|
195
|
+
permission: 'ledger:view',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
path: 'recurring',
|
|
199
|
+
label: 'Recurring',
|
|
200
|
+
component: 'LedgerRecurringPage',
|
|
201
|
+
permission: 'ledger:manage',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
path: 'settings',
|
|
205
|
+
label: 'Settings',
|
|
206
|
+
component: 'LedgerSettingsPage',
|
|
207
|
+
permission: 'ledger:settings',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
apiRoutes: [
|
|
213
|
+
{
|
|
214
|
+
method: 'GET',
|
|
215
|
+
path: '/api/ledger/invoices',
|
|
216
|
+
handler: 'ledger.listInvoices',
|
|
217
|
+
middleware: ['auth'],
|
|
218
|
+
description: 'List all invoices with optional filters',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
method: 'POST',
|
|
222
|
+
path: '/api/ledger/invoices',
|
|
223
|
+
handler: 'ledger.createInvoice',
|
|
224
|
+
middleware: ['auth', 'permission:ledger:create'],
|
|
225
|
+
description: 'Create a new invoice',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
method: 'GET',
|
|
229
|
+
path: '/api/ledger/invoices/:id',
|
|
230
|
+
handler: 'ledger.getInvoice',
|
|
231
|
+
middleware: ['auth'],
|
|
232
|
+
description: 'Get invoice by ID',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
method: 'PUT',
|
|
236
|
+
path: '/api/ledger/invoices/:id',
|
|
237
|
+
handler: 'ledger.updateInvoice',
|
|
238
|
+
middleware: ['auth', 'permission:ledger:edit'],
|
|
239
|
+
description: 'Update an invoice',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
method: 'POST',
|
|
243
|
+
path: '/api/ledger/invoices/:id/send',
|
|
244
|
+
handler: 'ledger.sendInvoice',
|
|
245
|
+
middleware: ['auth', 'permission:ledger:send'],
|
|
246
|
+
description: 'Email an invoice to the client',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
method: 'POST',
|
|
250
|
+
path: '/api/ledger/webhooks/stripe',
|
|
251
|
+
handler: 'ledger.stripeWebhook',
|
|
252
|
+
description: 'Stripe webhook endpoint for payment events',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
method: 'GET',
|
|
256
|
+
path: '/api/ledger/invoices/:id/pdf',
|
|
257
|
+
handler: 'ledger.generatePdf',
|
|
258
|
+
middleware: ['auth'],
|
|
259
|
+
description: 'Generate PDF for an invoice',
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
widgets: [
|
|
263
|
+
{
|
|
264
|
+
id: 'ledger-revenue-summary',
|
|
265
|
+
name: 'Revenue Summary',
|
|
266
|
+
component: 'LedgerRevenueSummaryWidget',
|
|
267
|
+
areas: ['dashboard'],
|
|
268
|
+
defaultSize: { width: 4, height: 2 },
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'ledger-outstanding',
|
|
272
|
+
name: 'Outstanding Invoices',
|
|
273
|
+
component: 'LedgerOutstandingWidget',
|
|
274
|
+
areas: ['dashboard', 'sidebar'],
|
|
275
|
+
defaultSize: { width: 4, height: 3 },
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
hooks: [
|
|
279
|
+
{
|
|
280
|
+
hook: 'content:before_save',
|
|
281
|
+
handler: (payload) => {
|
|
282
|
+
// Validate invoice data before saving
|
|
283
|
+
if (payload.content?.type === 'invoice') {
|
|
284
|
+
const invoice = payload.content;
|
|
285
|
+
if (!invoice.lineItems || invoice.lineItems.length === 0) {
|
|
286
|
+
throw new Error('Ledger: Invoice must have at least one line item');
|
|
287
|
+
}
|
|
288
|
+
// Recalculate totals
|
|
289
|
+
let subtotal = 0;
|
|
290
|
+
for (const item of invoice.lineItems) {
|
|
291
|
+
item.amount = (item.quantity ?? 1) * (item.rate ?? 0);
|
|
292
|
+
subtotal += item.amount;
|
|
293
|
+
}
|
|
294
|
+
invoice.subtotal = subtotal;
|
|
295
|
+
invoice.tax = subtotal * (invoice.taxRate ?? 0);
|
|
296
|
+
invoice.total = invoice.subtotal + invoice.tax;
|
|
297
|
+
}
|
|
298
|
+
return payload;
|
|
299
|
+
},
|
|
300
|
+
priority: 5,
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
settingsSchema: z.object({
|
|
304
|
+
companyName: z.string().default(''),
|
|
305
|
+
companyAddress: z.string().default(''),
|
|
306
|
+
companyEmail: z.string().email().optional(),
|
|
307
|
+
companyLogo: z.string().url().optional(),
|
|
308
|
+
currency: z.string().default('GBP'),
|
|
309
|
+
defaultPaymentTerms: z.number().min(0).max(90).default(30),
|
|
310
|
+
defaultTaxRate: z.number().min(0).max(1).default(0.2),
|
|
311
|
+
stripeSecretKey: z.string().optional(),
|
|
312
|
+
stripeWebhookSecret: z.string().optional(),
|
|
313
|
+
invoicePrefix: z.string().default('INV'),
|
|
314
|
+
nextInvoiceNumber: z.number().default(1001),
|
|
315
|
+
}),
|
|
316
|
+
async onActivate(core) {
|
|
317
|
+
// Register recurring invoice cron-like check
|
|
318
|
+
core.hooks.on('content:after_save', (payload) => {
|
|
319
|
+
if (payload.content?.type === 'invoice' && payload.content?.status === 'paid') {
|
|
320
|
+
// Check if this invoice has a recurring schedule
|
|
321
|
+
if (payload.content.recurringSchedule) {
|
|
322
|
+
core.hooks.emit('content:before_save', {
|
|
323
|
+
content: {
|
|
324
|
+
type: 'invoice',
|
|
325
|
+
action: 'schedule_next',
|
|
326
|
+
sourceInvoiceId: payload.content.id,
|
|
327
|
+
schedule: payload.content.recurringSchedule,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
},
|
|
334
|
+
async onDeactivate(_core) {
|
|
335
|
+
// Clean up Stripe webhook listeners
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Compass — CRM
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
export const compassPlugin = {
|
|
342
|
+
id: '@foundry/compass',
|
|
343
|
+
name: 'Compass',
|
|
344
|
+
version: '1.0.0',
|
|
345
|
+
description: 'Customer relationship management with contact lifecycle tracking, deal pipeline, and activity logs',
|
|
346
|
+
adminPages: [
|
|
347
|
+
{
|
|
348
|
+
path: 'compass',
|
|
349
|
+
label: 'CRM',
|
|
350
|
+
icon: 'users',
|
|
351
|
+
component: 'CompassDashboardPage',
|
|
352
|
+
order: 15,
|
|
353
|
+
permission: 'compass:view',
|
|
354
|
+
children: [
|
|
355
|
+
{
|
|
356
|
+
path: 'contacts',
|
|
357
|
+
label: 'Contacts',
|
|
358
|
+
component: 'CompassContactsPage',
|
|
359
|
+
permission: 'compass:view',
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
path: 'contacts/:id',
|
|
363
|
+
label: 'Contact Detail',
|
|
364
|
+
component: 'CompassContactDetailPage',
|
|
365
|
+
permission: 'compass:view',
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
path: 'deals',
|
|
369
|
+
label: 'Deals',
|
|
370
|
+
component: 'CompassDealsPage',
|
|
371
|
+
permission: 'compass:deals',
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
path: 'pipeline',
|
|
375
|
+
label: 'Pipeline',
|
|
376
|
+
component: 'CompassPipelinePage',
|
|
377
|
+
permission: 'compass:deals',
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
path: 'activities',
|
|
381
|
+
label: 'Activities',
|
|
382
|
+
component: 'CompassActivitiesPage',
|
|
383
|
+
permission: 'compass:view',
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
apiRoutes: [
|
|
389
|
+
{
|
|
390
|
+
method: 'GET',
|
|
391
|
+
path: '/api/compass/contacts',
|
|
392
|
+
handler: 'compass.listContacts',
|
|
393
|
+
middleware: ['auth'],
|
|
394
|
+
description: 'List contacts with search and filters',
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
method: 'POST',
|
|
398
|
+
path: '/api/compass/contacts',
|
|
399
|
+
handler: 'compass.createContact',
|
|
400
|
+
middleware: ['auth', 'permission:compass:create'],
|
|
401
|
+
description: 'Create a new contact',
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
method: 'GET',
|
|
405
|
+
path: '/api/compass/contacts/:id',
|
|
406
|
+
handler: 'compass.getContact',
|
|
407
|
+
middleware: ['auth'],
|
|
408
|
+
description: 'Get contact details',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
method: 'PUT',
|
|
412
|
+
path: '/api/compass/contacts/:id',
|
|
413
|
+
handler: 'compass.updateContact',
|
|
414
|
+
middleware: ['auth', 'permission:compass:edit'],
|
|
415
|
+
description: 'Update a contact',
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
method: 'GET',
|
|
419
|
+
path: '/api/compass/deals',
|
|
420
|
+
handler: 'compass.listDeals',
|
|
421
|
+
middleware: ['auth'],
|
|
422
|
+
description: 'List all deals',
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
method: 'POST',
|
|
426
|
+
path: '/api/compass/deals',
|
|
427
|
+
handler: 'compass.createDeal',
|
|
428
|
+
middleware: ['auth', 'permission:compass:deals'],
|
|
429
|
+
description: 'Create a new deal',
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
method: 'PATCH',
|
|
433
|
+
path: '/api/compass/deals/:id/stage',
|
|
434
|
+
handler: 'compass.updateDealStage',
|
|
435
|
+
middleware: ['auth', 'permission:compass:deals'],
|
|
436
|
+
description: 'Move a deal to a new pipeline stage',
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
widgets: [
|
|
440
|
+
{
|
|
441
|
+
id: 'compass-pipeline-summary',
|
|
442
|
+
name: 'Pipeline Summary',
|
|
443
|
+
component: 'CompassPipelineSummaryWidget',
|
|
444
|
+
areas: ['dashboard'],
|
|
445
|
+
defaultSize: { width: 6, height: 3 },
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: 'compass-recent-contacts',
|
|
449
|
+
name: 'Recent Contacts',
|
|
450
|
+
component: 'CompassRecentContactsWidget',
|
|
451
|
+
areas: ['dashboard', 'sidebar'],
|
|
452
|
+
defaultSize: { width: 3, height: 4 },
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
hooks: [
|
|
456
|
+
{
|
|
457
|
+
hook: 'content:before_save',
|
|
458
|
+
handler: (payload) => {
|
|
459
|
+
if (payload.content?.type === 'contact') {
|
|
460
|
+
// Normalize email
|
|
461
|
+
if (payload.content.email) {
|
|
462
|
+
payload.content.email = payload.content.email.toLowerCase().trim();
|
|
463
|
+
}
|
|
464
|
+
// Auto-assign lifecycle stage if missing
|
|
465
|
+
if (!payload.content.lifecycleStage) {
|
|
466
|
+
payload.content.lifecycleStage = 'lead';
|
|
467
|
+
}
|
|
468
|
+
// Set timestamps
|
|
469
|
+
if (!payload.content.createdAt) {
|
|
470
|
+
payload.content.createdAt = new Date().toISOString();
|
|
471
|
+
}
|
|
472
|
+
payload.content.updatedAt = new Date().toISOString();
|
|
473
|
+
}
|
|
474
|
+
return payload;
|
|
475
|
+
},
|
|
476
|
+
priority: 5,
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
hook: 'content:after_save',
|
|
480
|
+
handler: (payload) => {
|
|
481
|
+
if (payload.content?.type === 'deal' && payload.content?.stage === 'won') {
|
|
482
|
+
// When a deal is won, update the contact's lifecycle stage
|
|
483
|
+
// This would trigger another save with the updated contact
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
priority: 10,
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
settingsSchema: z.object({
|
|
490
|
+
pipelineStages: z.array(z.object({
|
|
491
|
+
id: z.string(),
|
|
492
|
+
label: z.string(),
|
|
493
|
+
color: z.string(),
|
|
494
|
+
order: z.number(),
|
|
495
|
+
})).default([
|
|
496
|
+
{ id: 'lead', label: 'Lead', color: '#6366f1', order: 0 },
|
|
497
|
+
{ id: 'qualified', label: 'Qualified', color: '#8b5cf6', order: 1 },
|
|
498
|
+
{ id: 'proposal', label: 'Proposal', color: '#a855f7', order: 2 },
|
|
499
|
+
{ id: 'negotiation', label: 'Negotiation', color: '#d946ef', order: 3 },
|
|
500
|
+
{ id: 'won', label: 'Won', color: '#22c55e', order: 4 },
|
|
501
|
+
{ id: 'lost', label: 'Lost', color: '#ef4444', order: 5 },
|
|
502
|
+
]),
|
|
503
|
+
lifecycleStages: z.array(z.string()).default([
|
|
504
|
+
'subscriber', 'lead', 'qualified', 'opportunity', 'customer', 'evangelist',
|
|
505
|
+
]),
|
|
506
|
+
defaultCurrency: z.string().default('GBP'),
|
|
507
|
+
}),
|
|
508
|
+
async onActivate(core) {
|
|
509
|
+
// Wire up CRM activity logging
|
|
510
|
+
core.hooks.on('content:after_save', (payload) => {
|
|
511
|
+
if (['contact', 'deal'].includes(payload.content?.type)) {
|
|
512
|
+
// Log activity entry
|
|
513
|
+
core.hooks.emit('content:after_save', {
|
|
514
|
+
content: {
|
|
515
|
+
type: 'activity',
|
|
516
|
+
action: 'updated',
|
|
517
|
+
entityType: payload.content.type,
|
|
518
|
+
entityId: payload.content.id,
|
|
519
|
+
timestamp: new Date().toISOString(),
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
},
|
|
525
|
+
async onDeactivate(_core) {
|
|
526
|
+
// Cleanup CRM hooks
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
// Oracle — AI Integration
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
export const oraclePlugin = {
|
|
533
|
+
id: '@foundry/oracle',
|
|
534
|
+
name: 'Oracle',
|
|
535
|
+
version: '1.0.0',
|
|
536
|
+
description: 'AI-powered content generation, data analysis, SEO suggestions, and intelligent automation',
|
|
537
|
+
adminPages: [
|
|
538
|
+
{
|
|
539
|
+
path: 'oracle',
|
|
540
|
+
label: 'AI Assistant',
|
|
541
|
+
icon: 'sparkles',
|
|
542
|
+
component: 'OracleDashboardPage',
|
|
543
|
+
order: 5,
|
|
544
|
+
permission: 'oracle:view',
|
|
545
|
+
children: [
|
|
546
|
+
{
|
|
547
|
+
path: 'generate',
|
|
548
|
+
label: 'Content Generator',
|
|
549
|
+
component: 'OracleGeneratorPage',
|
|
550
|
+
permission: 'oracle:generate',
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
path: 'analyze',
|
|
554
|
+
label: 'Data Analysis',
|
|
555
|
+
component: 'OracleAnalysisPage',
|
|
556
|
+
permission: 'oracle:analyze',
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
path: 'suggestions',
|
|
560
|
+
label: 'Suggestions',
|
|
561
|
+
component: 'OracleSuggestionsPage',
|
|
562
|
+
permission: 'oracle:view',
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
path: 'usage',
|
|
566
|
+
label: 'AI Usage',
|
|
567
|
+
component: 'OracleUsagePage',
|
|
568
|
+
permission: 'oracle:settings',
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
},
|
|
572
|
+
],
|
|
573
|
+
apiRoutes: [
|
|
574
|
+
{
|
|
575
|
+
method: 'POST',
|
|
576
|
+
path: '/api/oracle/generate',
|
|
577
|
+
handler: 'oracle.generateContent',
|
|
578
|
+
middleware: ['auth', 'permission:oracle:generate', 'rateLimit:oracle'],
|
|
579
|
+
description: 'Generate content using AI',
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
method: 'POST',
|
|
583
|
+
path: '/api/oracle/analyze',
|
|
584
|
+
handler: 'oracle.analyzeData',
|
|
585
|
+
middleware: ['auth', 'permission:oracle:analyze'],
|
|
586
|
+
description: 'Run AI analysis on provided data',
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
method: 'POST',
|
|
590
|
+
path: '/api/oracle/suggest/seo',
|
|
591
|
+
handler: 'oracle.suggestSeo',
|
|
592
|
+
middleware: ['auth'],
|
|
593
|
+
description: 'Get AI-powered SEO suggestions for content',
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
method: 'POST',
|
|
597
|
+
path: '/api/oracle/rewrite',
|
|
598
|
+
handler: 'oracle.rewriteContent',
|
|
599
|
+
middleware: ['auth', 'permission:oracle:generate'],
|
|
600
|
+
description: 'Rewrite or improve existing content',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
method: 'GET',
|
|
604
|
+
path: '/api/oracle/usage',
|
|
605
|
+
handler: 'oracle.getUsage',
|
|
606
|
+
middleware: ['auth'],
|
|
607
|
+
description: 'Get AI token usage stats',
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
widgets: [
|
|
611
|
+
{
|
|
612
|
+
id: 'oracle-suggestions',
|
|
613
|
+
name: 'AI Suggestions',
|
|
614
|
+
component: 'OracleSuggestionsWidget',
|
|
615
|
+
areas: ['dashboard', 'sidebar'],
|
|
616
|
+
defaultSize: { width: 3, height: 4 },
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
id: 'oracle-usage',
|
|
620
|
+
name: 'AI Usage',
|
|
621
|
+
component: 'OracleUsageWidget',
|
|
622
|
+
areas: ['dashboard'],
|
|
623
|
+
defaultSize: { width: 2, height: 2 },
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
hooks: [
|
|
627
|
+
{
|
|
628
|
+
hook: 'content:before_save',
|
|
629
|
+
handler: (payload) => {
|
|
630
|
+
// Auto-generate meta description if missing and AI is enabled
|
|
631
|
+
if (payload.content?.type === 'page' && !payload.content.metaDescription) {
|
|
632
|
+
payload.content._oracleTask = 'generate_meta_description';
|
|
633
|
+
}
|
|
634
|
+
return payload;
|
|
635
|
+
},
|
|
636
|
+
priority: 20,
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
hook: 'page:before_render',
|
|
640
|
+
handler: (payload) => {
|
|
641
|
+
// Inject AI-powered content personalization markers
|
|
642
|
+
if (payload.page?._personalize) {
|
|
643
|
+
payload.page._oraclePersonalization = {
|
|
644
|
+
enabled: true,
|
|
645
|
+
segments: payload.page._personalizeSegments ?? ['default'],
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
return payload;
|
|
649
|
+
},
|
|
650
|
+
priority: 15,
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
settingsSchema: z.object({
|
|
654
|
+
provider: z.enum(['anthropic', 'openai', 'local']).default('anthropic'),
|
|
655
|
+
apiKey: z.string().optional(),
|
|
656
|
+
model: z.string().default('claude-sonnet-4-20250514'),
|
|
657
|
+
maxTokensPerRequest: z.number().min(100).max(8000).default(2000),
|
|
658
|
+
monthlyTokenBudget: z.number().default(1000000),
|
|
659
|
+
autoSeoSuggestions: z.boolean().default(true),
|
|
660
|
+
autoMetaGeneration: z.boolean().default(false),
|
|
661
|
+
contentTone: z.enum(['professional', 'casual', 'formal', 'friendly']).default('professional'),
|
|
662
|
+
}),
|
|
663
|
+
async onActivate(core) {
|
|
664
|
+
// Register AI content generation pipeline
|
|
665
|
+
core.hooks.on('content:after_save', (payload) => {
|
|
666
|
+
if (payload.content?._oracleTask === 'generate_meta_description') {
|
|
667
|
+
// Queue meta description generation
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
async onDeactivate(_core) {
|
|
672
|
+
// Clean up AI processing queue
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
//# sourceMappingURL=core.js.map
|