@l4yercak3/cli 1.1.12 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +6 -0
- package/docs/mcp_server/MCP_SERVER_ARCHITECTURE.md +1481 -0
- package/docs/mcp_server/applicationOntology.ts +817 -0
- package/docs/mcp_server/cliApplications.ts +639 -0
- package/docs/mcp_server/crmOntology.ts +1063 -0
- package/docs/mcp_server/eventOntology.ts +1183 -0
- package/docs/mcp_server/formsOntology.ts +1401 -0
- package/docs/mcp_server/ontologySchemas.ts +185 -0
- package/docs/mcp_server/schema.ts +250 -0
- package/package.json +5 -2
- package/src/commands/login.js +0 -6
- package/src/commands/mcp-server.js +32 -0
- package/src/commands/spread.js +54 -1
- package/src/detectors/expo-detector.js +163 -0
- package/src/detectors/registry.js +2 -0
- package/src/mcp/auth.js +127 -0
- package/src/mcp/registry/domains/applications.js +516 -0
- package/src/mcp/registry/domains/codegen.js +894 -0
- package/src/mcp/registry/domains/core.js +326 -0
- package/src/mcp/registry/domains/crm.js +591 -0
- package/src/mcp/registry/domains/events.js +649 -0
- package/src/mcp/registry/domains/forms.js +696 -0
- package/src/mcp/registry/index.js +162 -0
- package/src/mcp/server.js +116 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Generation Domain Tools
|
|
3
|
+
*
|
|
4
|
+
* Tools for generating API clients, sync adapters, and other
|
|
5
|
+
* integration code for Next.js projects.
|
|
6
|
+
*
|
|
7
|
+
* @module mcp/registry/domains/codegen
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Note: backendClient not currently used but reserved for future API-based code generation
|
|
11
|
+
// const backendClient = require('../../../api/backend-client');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* CodeGen domain definition
|
|
15
|
+
*/
|
|
16
|
+
module.exports = {
|
|
17
|
+
name: 'codegen',
|
|
18
|
+
description: 'Code generation - API clients, sync adapters, webhooks',
|
|
19
|
+
tools: [
|
|
20
|
+
// ========================================
|
|
21
|
+
// Schema Analysis Tools
|
|
22
|
+
// ========================================
|
|
23
|
+
{
|
|
24
|
+
name: 'l4yercak3_analyze_schema',
|
|
25
|
+
description: `Analyze a database schema and suggest mappings to L4YERCAK3 types.
|
|
26
|
+
Supports Prisma schema, SQL, Convex schema, and JSON model definitions.
|
|
27
|
+
|
|
28
|
+
Use this to help users understand how their existing models map to L4YERCAK3's
|
|
29
|
+
contact, event, form, and transaction types.`,
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
schema: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'The database schema content (Prisma, SQL, Convex, or JSON)',
|
|
36
|
+
},
|
|
37
|
+
schemaType: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
enum: ['prisma', 'sql', 'convex', 'json'],
|
|
40
|
+
description: 'Schema format (auto-detected if not specified)',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ['schema'],
|
|
44
|
+
},
|
|
45
|
+
requiresAuth: true,
|
|
46
|
+
handler: async (params, authContext) => {
|
|
47
|
+
// Parse schema and extract models
|
|
48
|
+
const models = parseSchema(params.schema, params.schemaType);
|
|
49
|
+
|
|
50
|
+
// Suggest mappings
|
|
51
|
+
const mappings = models.map(model => ({
|
|
52
|
+
localModel: model.name,
|
|
53
|
+
fields: model.fields,
|
|
54
|
+
suggestedMappings: suggestMappings(model),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
models: models.length,
|
|
59
|
+
mappings,
|
|
60
|
+
layerCakeTypes: [
|
|
61
|
+
{
|
|
62
|
+
type: 'crm_contact',
|
|
63
|
+
description: 'Customer/lead/prospect contacts',
|
|
64
|
+
fields: ['email', 'firstName', 'lastName', 'phone', 'company', 'jobTitle', 'tags'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'crm_organization',
|
|
68
|
+
description: 'Companies/businesses',
|
|
69
|
+
fields: ['name', 'website', 'industry', 'size', 'phone', 'address'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'event',
|
|
73
|
+
description: 'Events (conferences, workshops, meetups)',
|
|
74
|
+
fields: ['name', 'description', 'startDate', 'endDate', 'location'],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'form',
|
|
78
|
+
description: 'Forms (registration, surveys)',
|
|
79
|
+
fields: ['name', 'fields', 'status'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: 'product',
|
|
83
|
+
description: 'Products/tickets for sale',
|
|
84
|
+
fields: ['name', 'description', 'priceInCents', 'currency'],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: 'transaction',
|
|
88
|
+
description: 'Financial transactions',
|
|
89
|
+
fields: ['amount', 'currency', 'status', 'contactId'],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// ========================================
|
|
97
|
+
// API Client Generation
|
|
98
|
+
// ========================================
|
|
99
|
+
{
|
|
100
|
+
name: 'l4yercak3_generate_api_client',
|
|
101
|
+
description: `Generate a TypeScript API client for L4YERCAK3.
|
|
102
|
+
Creates a type-safe client with methods for the selected features.
|
|
103
|
+
|
|
104
|
+
Returns the code to write to your project.`,
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
features: {
|
|
109
|
+
type: 'array',
|
|
110
|
+
items: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
enum: ['crm', 'events', 'forms', 'invoicing', 'checkout'],
|
|
113
|
+
},
|
|
114
|
+
description: 'Features to include in the client',
|
|
115
|
+
},
|
|
116
|
+
outputPath: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'Suggested output path (default: src/lib/l4yercak3/client.ts)',
|
|
119
|
+
},
|
|
120
|
+
includeTypes: {
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
description: 'Include TypeScript type definitions (default: true)',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['features'],
|
|
126
|
+
},
|
|
127
|
+
requiresAuth: true,
|
|
128
|
+
handler: async (params, authContext) => {
|
|
129
|
+
const features = params.features || ['crm'];
|
|
130
|
+
const outputPath = params.outputPath || 'src/lib/l4yercak3/client.ts';
|
|
131
|
+
const includeTypes = params.includeTypes !== false;
|
|
132
|
+
|
|
133
|
+
// Generate client code
|
|
134
|
+
const code = generateApiClient(features, includeTypes);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
outputPath,
|
|
138
|
+
code,
|
|
139
|
+
features,
|
|
140
|
+
instructions: [
|
|
141
|
+
`1. Create the file: ${outputPath}`,
|
|
142
|
+
'2. Add L4YERCAK3_API_KEY to your .env.local',
|
|
143
|
+
'3. Import and use: import { l4yercak3 } from "@/lib/l4yercak3/client"',
|
|
144
|
+
],
|
|
145
|
+
envVariables: [
|
|
146
|
+
{ name: 'L4YERCAK3_API_KEY', description: 'Your L4YERCAK3 API key' },
|
|
147
|
+
{ name: 'L4YERCAK3_BACKEND_URL', description: 'Backend URL (optional, defaults to production)' },
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// ========================================
|
|
154
|
+
// Sync Adapter Generation
|
|
155
|
+
// ========================================
|
|
156
|
+
{
|
|
157
|
+
name: 'l4yercak3_generate_sync_adapter',
|
|
158
|
+
description: `Generate a sync adapter to keep local models in sync with L4YERCAK3.
|
|
159
|
+
Creates code for bidirectional or one-way syncing.`,
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
localModel: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
description: 'Name of the local model (e.g., "User", "Customer")',
|
|
166
|
+
},
|
|
167
|
+
layerCakeType: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
enum: ['crm_contact', 'crm_organization', 'event', 'form', 'product'],
|
|
170
|
+
description: 'L4YERCAK3 type to sync with',
|
|
171
|
+
},
|
|
172
|
+
fieldMappings: {
|
|
173
|
+
type: 'array',
|
|
174
|
+
items: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
local: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'Local field name',
|
|
180
|
+
},
|
|
181
|
+
layerCake: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
description: 'L4YERCAK3 field name',
|
|
184
|
+
},
|
|
185
|
+
transform: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'Optional transformation (e.g., "uppercase", "lowercase")',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['local', 'layerCake'],
|
|
191
|
+
},
|
|
192
|
+
description: 'Field mappings between local and L4YERCAK3',
|
|
193
|
+
},
|
|
194
|
+
syncDirection: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
enum: ['push', 'pull', 'bidirectional'],
|
|
197
|
+
description: 'Sync direction (default: bidirectional)',
|
|
198
|
+
},
|
|
199
|
+
outputPath: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'Output path for the adapter',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
required: ['localModel', 'layerCakeType', 'fieldMappings'],
|
|
205
|
+
},
|
|
206
|
+
requiresAuth: true,
|
|
207
|
+
handler: async (params, authContext) => {
|
|
208
|
+
const {
|
|
209
|
+
localModel,
|
|
210
|
+
layerCakeType,
|
|
211
|
+
fieldMappings,
|
|
212
|
+
syncDirection = 'bidirectional',
|
|
213
|
+
outputPath = `src/lib/l4yercak3/sync/${localModel.toLowerCase()}-adapter.ts`,
|
|
214
|
+
} = params;
|
|
215
|
+
|
|
216
|
+
const code = generateSyncAdapter(
|
|
217
|
+
localModel,
|
|
218
|
+
layerCakeType,
|
|
219
|
+
fieldMappings,
|
|
220
|
+
syncDirection
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
outputPath,
|
|
225
|
+
code,
|
|
226
|
+
localModel,
|
|
227
|
+
layerCakeType,
|
|
228
|
+
syncDirection,
|
|
229
|
+
instructions: [
|
|
230
|
+
`1. Create the file: ${outputPath}`,
|
|
231
|
+
`2. Import the adapter in your ${localModel} service`,
|
|
232
|
+
'3. Call sync methods when creating/updating/deleting local records',
|
|
233
|
+
'4. Set up a webhook endpoint to receive updates from L4YERCAK3',
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// ========================================
|
|
240
|
+
// Webhook Handler Generation
|
|
241
|
+
// ========================================
|
|
242
|
+
{
|
|
243
|
+
name: 'l4yercak3_generate_webhook_handler',
|
|
244
|
+
description: `Generate a webhook handler for receiving L4YERCAK3 events.
|
|
245
|
+
Creates a Next.js API route that handles L4YERCAK3 webhooks.`,
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: 'object',
|
|
248
|
+
properties: {
|
|
249
|
+
events: {
|
|
250
|
+
type: 'array',
|
|
251
|
+
items: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
enum: [
|
|
254
|
+
'contact.created',
|
|
255
|
+
'contact.updated',
|
|
256
|
+
'contact.deleted',
|
|
257
|
+
'event.created',
|
|
258
|
+
'event.updated',
|
|
259
|
+
'event.published',
|
|
260
|
+
'form.submitted',
|
|
261
|
+
'ticket.purchased',
|
|
262
|
+
'payment.completed',
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
description: 'Events to handle',
|
|
266
|
+
},
|
|
267
|
+
routerType: {
|
|
268
|
+
type: 'string',
|
|
269
|
+
enum: ['app', 'pages'],
|
|
270
|
+
description: 'Next.js router type (default: app)',
|
|
271
|
+
},
|
|
272
|
+
outputPath: {
|
|
273
|
+
type: 'string',
|
|
274
|
+
description: 'Output path for the webhook handler',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
required: ['events'],
|
|
278
|
+
},
|
|
279
|
+
requiresAuth: true,
|
|
280
|
+
handler: async (params, authContext) => {
|
|
281
|
+
const events = params.events || [];
|
|
282
|
+
const routerType = params.routerType || 'app';
|
|
283
|
+
const outputPath =
|
|
284
|
+
params.outputPath ||
|
|
285
|
+
(routerType === 'app'
|
|
286
|
+
? 'app/api/webhooks/l4yercak3/route.ts'
|
|
287
|
+
: 'pages/api/webhooks/l4yercak3.ts');
|
|
288
|
+
|
|
289
|
+
const code = generateWebhookHandler(events, routerType);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
outputPath,
|
|
293
|
+
code,
|
|
294
|
+
events,
|
|
295
|
+
routerType,
|
|
296
|
+
instructions: [
|
|
297
|
+
`1. Create the file: ${outputPath}`,
|
|
298
|
+
'2. Add L4YERCAK3_WEBHOOK_SECRET to your .env.local',
|
|
299
|
+
'3. Register the webhook URL in your L4YERCAK3 dashboard',
|
|
300
|
+
'4. Implement the event handlers for your specific use case',
|
|
301
|
+
],
|
|
302
|
+
webhookUrl: 'https://your-domain.com/api/webhooks/l4yercak3',
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// ========================================
|
|
308
|
+
// Environment File Generation
|
|
309
|
+
// ========================================
|
|
310
|
+
{
|
|
311
|
+
name: 'l4yercak3_generate_env_template',
|
|
312
|
+
description: `Generate environment variable template for L4YERCAK3 integration.`,
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: 'object',
|
|
315
|
+
properties: {
|
|
316
|
+
features: {
|
|
317
|
+
type: 'array',
|
|
318
|
+
items: {
|
|
319
|
+
type: 'string',
|
|
320
|
+
enum: ['crm', 'events', 'forms', 'invoicing', 'checkout', 'webhooks'],
|
|
321
|
+
},
|
|
322
|
+
description: 'Features to include env vars for',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
required: ['features'],
|
|
326
|
+
},
|
|
327
|
+
requiresAuth: true,
|
|
328
|
+
handler: async (params, authContext) => {
|
|
329
|
+
const features = params.features || [];
|
|
330
|
+
|
|
331
|
+
let template = `# L4YERCAK3 Integration
|
|
332
|
+
# Add these to your .env.local file
|
|
333
|
+
|
|
334
|
+
# Core (Required)
|
|
335
|
+
L4YERCAK3_API_KEY=your-api-key-here
|
|
336
|
+
L4YERCAK3_BACKEND_URL=https://agreeable-lion-828.convex.site
|
|
337
|
+
`;
|
|
338
|
+
|
|
339
|
+
if (features.includes('webhooks')) {
|
|
340
|
+
template += `
|
|
341
|
+
# Webhooks
|
|
342
|
+
L4YERCAK3_WEBHOOK_SECRET=your-webhook-secret
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (features.includes('checkout')) {
|
|
347
|
+
template += `
|
|
348
|
+
# Checkout / Stripe
|
|
349
|
+
STRIPE_SECRET_KEY=sk_live_xxx
|
|
350
|
+
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
template += `
|
|
355
|
+
# Organization ID (auto-set by CLI)
|
|
356
|
+
L4YERCAK3_ORGANIZATION_ID=${authContext.organizationId}
|
|
357
|
+
`;
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
template,
|
|
361
|
+
outputPath: '.env.local.example',
|
|
362
|
+
instructions: [
|
|
363
|
+
'1. Copy this to .env.local.example',
|
|
364
|
+
'2. Copy .env.local.example to .env.local',
|
|
365
|
+
'3. Fill in your actual values',
|
|
366
|
+
'4. Never commit .env.local to git',
|
|
367
|
+
],
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// ============================================
|
|
375
|
+
// Helper Functions
|
|
376
|
+
// ============================================
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Parse schema and extract models
|
|
380
|
+
*/
|
|
381
|
+
function parseSchema(schema, schemaType) {
|
|
382
|
+
// Auto-detect schema type if not specified
|
|
383
|
+
if (!schemaType) {
|
|
384
|
+
if (schema.includes('model ') && schema.includes('@')) {
|
|
385
|
+
schemaType = 'prisma';
|
|
386
|
+
} else if (schema.includes('CREATE TABLE') || schema.includes('create table')) {
|
|
387
|
+
schemaType = 'sql';
|
|
388
|
+
} else if (schema.includes('defineTable')) {
|
|
389
|
+
schemaType = 'convex';
|
|
390
|
+
} else {
|
|
391
|
+
schemaType = 'json';
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const models = [];
|
|
396
|
+
|
|
397
|
+
if (schemaType === 'prisma') {
|
|
398
|
+
// Parse Prisma schema
|
|
399
|
+
const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
400
|
+
let match;
|
|
401
|
+
while ((match = modelRegex.exec(schema)) !== null) {
|
|
402
|
+
const name = match[1];
|
|
403
|
+
const body = match[2];
|
|
404
|
+
const fields = [];
|
|
405
|
+
|
|
406
|
+
const fieldRegex = /(\w+)\s+(\w+)(\[\])?\s*(@.*)?/g;
|
|
407
|
+
let fieldMatch;
|
|
408
|
+
while ((fieldMatch = fieldRegex.exec(body)) !== null) {
|
|
409
|
+
fields.push({
|
|
410
|
+
name: fieldMatch[1],
|
|
411
|
+
type: fieldMatch[2] + (fieldMatch[3] || ''),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
models.push({ name, fields });
|
|
416
|
+
}
|
|
417
|
+
} else if (schemaType === 'json') {
|
|
418
|
+
// Parse JSON model definition
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(schema);
|
|
421
|
+
if (Array.isArray(parsed)) {
|
|
422
|
+
return parsed;
|
|
423
|
+
} else if (parsed.models) {
|
|
424
|
+
return parsed.models;
|
|
425
|
+
}
|
|
426
|
+
} catch {
|
|
427
|
+
// Not valid JSON, return empty
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return models;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Suggest L4YERCAK3 type mappings based on model fields
|
|
436
|
+
*/
|
|
437
|
+
function suggestMappings(model) {
|
|
438
|
+
const mappings = [];
|
|
439
|
+
const fieldNames = model.fields.map(f => f.name.toLowerCase());
|
|
440
|
+
|
|
441
|
+
// Check for contact-like model
|
|
442
|
+
if (
|
|
443
|
+
fieldNames.includes('email') ||
|
|
444
|
+
fieldNames.includes('firstname') ||
|
|
445
|
+
fieldNames.includes('first_name') ||
|
|
446
|
+
fieldNames.includes('phone')
|
|
447
|
+
) {
|
|
448
|
+
mappings.push({
|
|
449
|
+
type: 'crm_contact',
|
|
450
|
+
confidence: 0.8,
|
|
451
|
+
reason: 'Contains contact fields (email, name, phone)',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Check for company-like model
|
|
456
|
+
if (
|
|
457
|
+
fieldNames.includes('company') ||
|
|
458
|
+
fieldNames.includes('website') ||
|
|
459
|
+
fieldNames.includes('industry')
|
|
460
|
+
) {
|
|
461
|
+
mappings.push({
|
|
462
|
+
type: 'crm_organization',
|
|
463
|
+
confidence: 0.7,
|
|
464
|
+
reason: 'Contains organization fields (company, website)',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check for event-like model
|
|
469
|
+
if (
|
|
470
|
+
(fieldNames.includes('startdate') || fieldNames.includes('start_date')) &&
|
|
471
|
+
(fieldNames.includes('enddate') || fieldNames.includes('end_date'))
|
|
472
|
+
) {
|
|
473
|
+
mappings.push({
|
|
474
|
+
type: 'event',
|
|
475
|
+
confidence: 0.8,
|
|
476
|
+
reason: 'Contains event date fields',
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Check for product-like model
|
|
481
|
+
if (fieldNames.includes('price') || fieldNames.includes('priceinCents')) {
|
|
482
|
+
mappings.push({
|
|
483
|
+
type: 'product',
|
|
484
|
+
confidence: 0.7,
|
|
485
|
+
reason: 'Contains pricing fields',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return mappings;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Generate API client code
|
|
494
|
+
*/
|
|
495
|
+
function generateApiClient(features, includeTypes) {
|
|
496
|
+
let code = `/**
|
|
497
|
+
* L4YERCAK3 API Client
|
|
498
|
+
* Generated by L4YERCAK3 CLI
|
|
499
|
+
*/
|
|
500
|
+
|
|
501
|
+
const L4YERCAK3_API_KEY = process.env.L4YERCAK3_API_KEY;
|
|
502
|
+
const BACKEND_URL = process.env.L4YERCAK3_BACKEND_URL || 'https://agreeable-lion-828.convex.site';
|
|
503
|
+
|
|
504
|
+
class L4yercak3Client {
|
|
505
|
+
constructor() {
|
|
506
|
+
if (!L4YERCAK3_API_KEY) {
|
|
507
|
+
console.warn('L4YERCAK3_API_KEY not set');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async request(method, endpoint, data = null) {
|
|
512
|
+
const url = \`\${BACKEND_URL}\${endpoint}\`;
|
|
513
|
+
const options = {
|
|
514
|
+
method,
|
|
515
|
+
headers: {
|
|
516
|
+
'Content-Type': 'application/json',
|
|
517
|
+
'Authorization': \`Bearer \${L4YERCAK3_API_KEY}\`,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
522
|
+
options.body = JSON.stringify(data);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const response = await fetch(url, options);
|
|
526
|
+
const responseData = await response.json();
|
|
527
|
+
|
|
528
|
+
if (!response.ok) {
|
|
529
|
+
throw new Error(responseData.error || \`API request failed: \${response.status}\`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return responseData;
|
|
533
|
+
}
|
|
534
|
+
`;
|
|
535
|
+
|
|
536
|
+
if (features.includes('crm')) {
|
|
537
|
+
code += `
|
|
538
|
+
// ==================
|
|
539
|
+
// CRM Methods
|
|
540
|
+
// ==================
|
|
541
|
+
|
|
542
|
+
async listContacts(params = {}) {
|
|
543
|
+
const query = new URLSearchParams(params).toString();
|
|
544
|
+
return this.request('GET', \`/api/v1/crm/contacts?\${query}\`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async createContact(data) {
|
|
548
|
+
return this.request('POST', '/api/v1/crm/contacts', data);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async getContact(contactId) {
|
|
552
|
+
return this.request('GET', \`/api/v1/crm/contacts/\${contactId}\`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async updateContact(contactId, data) {
|
|
556
|
+
return this.request('PATCH', \`/api/v1/crm/contacts/\${contactId}\`, data);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async deleteContact(contactId) {
|
|
560
|
+
return this.request('DELETE', \`/api/v1/crm/contacts/\${contactId}\`);
|
|
561
|
+
}
|
|
562
|
+
`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (features.includes('events')) {
|
|
566
|
+
code += `
|
|
567
|
+
// ==================
|
|
568
|
+
// Events Methods
|
|
569
|
+
// ==================
|
|
570
|
+
|
|
571
|
+
async listEvents(params = {}) {
|
|
572
|
+
const query = new URLSearchParams(params).toString();
|
|
573
|
+
return this.request('GET', \`/api/v1/events?\${query}\`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async createEvent(data) {
|
|
577
|
+
return this.request('POST', '/api/v1/events', data);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async getEvent(eventId) {
|
|
581
|
+
return this.request('GET', \`/api/v1/events/\${eventId}\`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async updateEvent(eventId, data) {
|
|
585
|
+
return this.request('PATCH', \`/api/v1/events/\${eventId}\`, data);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async getEventAttendees(eventId) {
|
|
589
|
+
return this.request('GET', \`/api/v1/events/\${eventId}/attendees\`);
|
|
590
|
+
}
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (features.includes('forms')) {
|
|
595
|
+
code += `
|
|
596
|
+
// ==================
|
|
597
|
+
// Forms Methods
|
|
598
|
+
// ==================
|
|
599
|
+
|
|
600
|
+
async listForms(params = {}) {
|
|
601
|
+
const query = new URLSearchParams(params).toString();
|
|
602
|
+
return this.request('GET', \`/api/v1/forms?\${query}\`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async createForm(data) {
|
|
606
|
+
return this.request('POST', '/api/v1/forms', data);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async getForm(formId) {
|
|
610
|
+
return this.request('GET', \`/api/v1/forms/\${formId}\`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async getFormResponses(formId) {
|
|
614
|
+
return this.request('GET', \`/api/v1/forms/\${formId}/responses\`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async submitFormResponse(formId, data) {
|
|
618
|
+
return this.request('POST', \`/api/v1/forms/\${formId}/responses\`, data);
|
|
619
|
+
}
|
|
620
|
+
`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
code += `}
|
|
624
|
+
|
|
625
|
+
export const l4yercak3 = new L4yercak3Client();
|
|
626
|
+
export default l4yercak3;
|
|
627
|
+
`;
|
|
628
|
+
|
|
629
|
+
return code;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Generate sync adapter code
|
|
634
|
+
*/
|
|
635
|
+
function generateSyncAdapter(localModel, layerCakeType, fieldMappings, syncDirection) {
|
|
636
|
+
const modelLower = localModel.toLowerCase();
|
|
637
|
+
|
|
638
|
+
return `/**
|
|
639
|
+
* ${localModel} <-> L4YERCAK3 ${layerCakeType} Sync Adapter
|
|
640
|
+
* Generated by L4YERCAK3 CLI
|
|
641
|
+
*/
|
|
642
|
+
|
|
643
|
+
import { l4yercak3 } from './client';
|
|
644
|
+
|
|
645
|
+
// Field mappings
|
|
646
|
+
const FIELD_MAPPINGS = ${JSON.stringify(fieldMappings, null, 2)};
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Transform local model to L4YERCAK3 format
|
|
650
|
+
*/
|
|
651
|
+
export function toLayerCake(local${localModel}) {
|
|
652
|
+
const data = {};
|
|
653
|
+
|
|
654
|
+
for (const mapping of FIELD_MAPPINGS) {
|
|
655
|
+
let value = local${localModel}[mapping.local];
|
|
656
|
+
|
|
657
|
+
// Apply transformations
|
|
658
|
+
if (value && mapping.transform) {
|
|
659
|
+
switch (mapping.transform) {
|
|
660
|
+
case 'uppercase':
|
|
661
|
+
value = value.toUpperCase();
|
|
662
|
+
break;
|
|
663
|
+
case 'lowercase':
|
|
664
|
+
value = value.toLowerCase();
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
data[mapping.layerCake] = value;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return data;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Transform L4YERCAK3 format to local model
|
|
677
|
+
*/
|
|
678
|
+
export function toLocal(layerCakeData) {
|
|
679
|
+
const data = {};
|
|
680
|
+
|
|
681
|
+
for (const mapping of FIELD_MAPPINGS) {
|
|
682
|
+
data[mapping.local] = layerCakeData[mapping.layerCake];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return data;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
${
|
|
689
|
+
syncDirection === 'push' || syncDirection === 'bidirectional'
|
|
690
|
+
? `
|
|
691
|
+
/**
|
|
692
|
+
* Push local ${localModel} to L4YERCAK3
|
|
693
|
+
*/
|
|
694
|
+
export async function push${localModel}(local${localModel}, layerCakeId = null) {
|
|
695
|
+
const data = toLayerCake(local${localModel});
|
|
696
|
+
|
|
697
|
+
if (layerCakeId) {
|
|
698
|
+
// Update existing
|
|
699
|
+
return l4yercak3.request('PATCH', \`/api/v1/${layerCakeType}/\${layerCakeId}\`, data);
|
|
700
|
+
} else {
|
|
701
|
+
// Create new
|
|
702
|
+
return l4yercak3.request('POST', '/api/v1/${layerCakeType}', data);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
`
|
|
706
|
+
: ''
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
${
|
|
710
|
+
syncDirection === 'pull' || syncDirection === 'bidirectional'
|
|
711
|
+
? `
|
|
712
|
+
/**
|
|
713
|
+
* Pull ${localModel} from L4YERCAK3
|
|
714
|
+
*/
|
|
715
|
+
export async function pull${localModel}(layerCakeId) {
|
|
716
|
+
const response = await l4yercak3.request('GET', \`/api/v1/${layerCakeType}/\${layerCakeId}\`);
|
|
717
|
+
return toLocal(response);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Pull all ${localModel}s from L4YERCAK3
|
|
722
|
+
*/
|
|
723
|
+
export async function pullAll${localModel}s(params = {}) {
|
|
724
|
+
const response = await l4yercak3.request('GET', '/api/v1/${layerCakeType}', params);
|
|
725
|
+
return (response.items || []).map(toLocal);
|
|
726
|
+
}
|
|
727
|
+
`
|
|
728
|
+
: ''
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Handle webhook event
|
|
733
|
+
*/
|
|
734
|
+
export function handleWebhookEvent(event) {
|
|
735
|
+
switch (event.type) {
|
|
736
|
+
case '${layerCakeType}.created':
|
|
737
|
+
return { action: 'create', data: toLocal(event.data) };
|
|
738
|
+
case '${layerCakeType}.updated':
|
|
739
|
+
return { action: 'update', data: toLocal(event.data) };
|
|
740
|
+
case '${layerCakeType}.deleted':
|
|
741
|
+
return { action: 'delete', id: event.data.id };
|
|
742
|
+
default:
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
`;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Generate webhook handler code
|
|
751
|
+
*/
|
|
752
|
+
function generateWebhookHandler(events, routerType) {
|
|
753
|
+
if (routerType === 'app') {
|
|
754
|
+
return `/**
|
|
755
|
+
* L4YERCAK3 Webhook Handler
|
|
756
|
+
* Generated by L4YERCAK3 CLI
|
|
757
|
+
*
|
|
758
|
+
* Route: app/api/webhooks/l4yercak3/route.ts
|
|
759
|
+
*/
|
|
760
|
+
|
|
761
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
762
|
+
import crypto from 'crypto';
|
|
763
|
+
|
|
764
|
+
const WEBHOOK_SECRET = process.env.L4YERCAK3_WEBHOOK_SECRET;
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Verify webhook signature
|
|
768
|
+
*/
|
|
769
|
+
function verifySignature(payload: string, signature: string): boolean {
|
|
770
|
+
if (!WEBHOOK_SECRET) return false;
|
|
771
|
+
|
|
772
|
+
const expected = crypto
|
|
773
|
+
.createHmac('sha256', WEBHOOK_SECRET)
|
|
774
|
+
.update(payload)
|
|
775
|
+
.digest('hex');
|
|
776
|
+
|
|
777
|
+
return crypto.timingSafeEqual(
|
|
778
|
+
Buffer.from(signature),
|
|
779
|
+
Buffer.from(expected)
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export async function POST(request: NextRequest) {
|
|
784
|
+
try {
|
|
785
|
+
const payload = await request.text();
|
|
786
|
+
const signature = request.headers.get('x-l4yercak3-signature');
|
|
787
|
+
|
|
788
|
+
// Verify signature
|
|
789
|
+
if (!signature || !verifySignature(payload, signature)) {
|
|
790
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const event = JSON.parse(payload);
|
|
794
|
+
|
|
795
|
+
// Handle events
|
|
796
|
+
switch (event.type) {
|
|
797
|
+
${events
|
|
798
|
+
.map(
|
|
799
|
+
e => ` case '${e}':
|
|
800
|
+
await handle${e.replace(/\./g, '_')}(event);
|
|
801
|
+
break;`
|
|
802
|
+
)
|
|
803
|
+
.join('\n')}
|
|
804
|
+
default:
|
|
805
|
+
console.log('Unhandled event type:', event.type);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return NextResponse.json({ received: true });
|
|
809
|
+
} catch (error) {
|
|
810
|
+
console.error('Webhook error:', error);
|
|
811
|
+
return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 });
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ==================
|
|
816
|
+
// Event Handlers
|
|
817
|
+
// ==================
|
|
818
|
+
|
|
819
|
+
${events
|
|
820
|
+
.map(
|
|
821
|
+
e => `async function handle${e.replace(/\./g, '_')}(event: any) {
|
|
822
|
+
// TODO: Implement ${e} handler
|
|
823
|
+
console.log('Handling ${e}:', event.data);
|
|
824
|
+
}`
|
|
825
|
+
)
|
|
826
|
+
.join('\n\n')}
|
|
827
|
+
`;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Pages router
|
|
831
|
+
return `/**
|
|
832
|
+
* L4YERCAK3 Webhook Handler
|
|
833
|
+
* Generated by L4YERCAK3 CLI
|
|
834
|
+
*
|
|
835
|
+
* Route: pages/api/webhooks/l4yercak3.ts
|
|
836
|
+
*/
|
|
837
|
+
|
|
838
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
839
|
+
import crypto from 'crypto';
|
|
840
|
+
|
|
841
|
+
const WEBHOOK_SECRET = process.env.L4YERCAK3_WEBHOOK_SECRET;
|
|
842
|
+
|
|
843
|
+
function verifySignature(payload: string, signature: string): boolean {
|
|
844
|
+
if (!WEBHOOK_SECRET) return false;
|
|
845
|
+
|
|
846
|
+
const expected = crypto
|
|
847
|
+
.createHmac('sha256', WEBHOOK_SECRET)
|
|
848
|
+
.update(payload)
|
|
849
|
+
.digest('hex');
|
|
850
|
+
|
|
851
|
+
return crypto.timingSafeEqual(
|
|
852
|
+
Buffer.from(signature),
|
|
853
|
+
Buffer.from(expected)
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export default async function handler(
|
|
858
|
+
req: NextApiRequest,
|
|
859
|
+
res: NextApiResponse
|
|
860
|
+
) {
|
|
861
|
+
if (req.method !== 'POST') {
|
|
862
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
const payload = JSON.stringify(req.body);
|
|
867
|
+
const signature = req.headers['x-l4yercak3-signature'] as string;
|
|
868
|
+
|
|
869
|
+
if (!signature || !verifySignature(payload, signature)) {
|
|
870
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const event = req.body;
|
|
874
|
+
|
|
875
|
+
switch (event.type) {
|
|
876
|
+
${events.map(e => ` case '${e}': await handle${e.replace(/\./g, '_')}(event); break;`).join('\n')}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return res.json({ received: true });
|
|
880
|
+
} catch (error) {
|
|
881
|
+
console.error('Webhook error:', error);
|
|
882
|
+
return res.status(500).json({ error: 'Webhook handler failed' });
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
${events
|
|
887
|
+
.map(
|
|
888
|
+
e => `async function handle${e.replace(/\./g, '_')}(event: any) {
|
|
889
|
+
console.log('Handling ${e}:', event.data);
|
|
890
|
+
}`
|
|
891
|
+
)
|
|
892
|
+
.join('\n\n')}
|
|
893
|
+
`;
|
|
894
|
+
}
|