@l4yercak3/cli 1.2.16 → 1.2.19

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 (31) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/docs/CRM-PIPELINES-SEQUENCES-SPEC.md +429 -0
  3. package/docs/INTEGRATION_PATHS_ARCHITECTURE.md +1543 -0
  4. package/package.json +1 -1
  5. package/src/commands/login.js +26 -7
  6. package/src/commands/spread.js +251 -10
  7. package/src/detectors/database-detector.js +245 -0
  8. package/src/detectors/expo-detector.js +4 -4
  9. package/src/detectors/index.js +17 -4
  10. package/src/generators/api-only/client.js +683 -0
  11. package/src/generators/api-only/index.js +96 -0
  12. package/src/generators/api-only/types.js +618 -0
  13. package/src/generators/api-only/webhooks.js +377 -0
  14. package/src/generators/env-generator.js +23 -8
  15. package/src/generators/expo-auth-generator.js +1009 -0
  16. package/src/generators/index.js +88 -2
  17. package/src/generators/mcp-guide-generator.js +256 -0
  18. package/src/generators/quickstart/components/index.js +1699 -0
  19. package/src/generators/quickstart/components-mobile/index.js +1440 -0
  20. package/src/generators/quickstart/database/convex.js +1257 -0
  21. package/src/generators/quickstart/database/index.js +34 -0
  22. package/src/generators/quickstart/database/supabase.js +1132 -0
  23. package/src/generators/quickstart/hooks/index.js +1065 -0
  24. package/src/generators/quickstart/index.js +177 -0
  25. package/src/generators/quickstart/pages/index.js +1466 -0
  26. package/src/generators/quickstart/screens/index.js +1498 -0
  27. package/src/mcp/registry/domains/benefits.js +798 -0
  28. package/src/mcp/registry/index.js +2 -0
  29. package/tests/database-detector.test.js +221 -0
  30. package/tests/expo-detector.test.js +3 -4
  31. package/tests/generators-index.test.js +215 -3
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Webhooks Generator
3
+ * Generates webhook handling utilities for the L4YERCAK3 API
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { ensureDir, writeFileWithBackup, checkFileOverwrite } = require('../../utils/file-utils');
9
+
10
+ class WebhooksGenerator {
11
+ /**
12
+ * Generate the webhooks utility file
13
+ * @param {Object} options - Generation options
14
+ * @returns {Promise<string|null>} - Path to generated file or null if skipped
15
+ */
16
+ async generate(options) {
17
+ const { projectPath, isTypeScript } = options;
18
+
19
+ // Determine output directory
20
+ let outputDir;
21
+ if (fs.existsSync(path.join(projectPath, 'src'))) {
22
+ outputDir = path.join(projectPath, 'src', 'lib', 'l4yercak3');
23
+ } else {
24
+ outputDir = path.join(projectPath, 'lib', 'l4yercak3');
25
+ }
26
+
27
+ ensureDir(outputDir);
28
+
29
+ const extension = isTypeScript ? 'ts' : 'js';
30
+ const outputPath = path.join(outputDir, `webhooks.${extension}`);
31
+
32
+ const action = await checkFileOverwrite(outputPath);
33
+ if (action === 'skip') {
34
+ return null;
35
+ }
36
+
37
+ const content = isTypeScript
38
+ ? this.generateTypeScriptWebhooks()
39
+ : this.generateJavaScriptWebhooks();
40
+
41
+ return writeFileWithBackup(outputPath, content, action);
42
+ }
43
+
44
+ generateTypeScriptWebhooks() {
45
+ return `/**
46
+ * L4YERCAK3 Webhook Utilities
47
+ * Auto-generated by @l4yercak3/cli
48
+ *
49
+ * Utilities for handling webhook events from L4YERCAK3.
50
+ */
51
+
52
+ import { createHmac, timingSafeEqual } from 'crypto';
53
+
54
+ export interface WebhookEvent<T = Record<string, unknown>> {
55
+ id: string;
56
+ type: WebhookEventType;
57
+ timestamp: string;
58
+ data: T;
59
+ organizationId: string;
60
+ }
61
+
62
+ export type WebhookEventType =
63
+ | 'contact.created'
64
+ | 'contact.updated'
65
+ | 'contact.deleted'
66
+ | 'organization.created'
67
+ | 'organization.updated'
68
+ | 'event.created'
69
+ | 'event.updated'
70
+ | 'event.published'
71
+ | 'event.cancelled'
72
+ | 'attendee.registered'
73
+ | 'attendee.checked_in'
74
+ | 'attendee.cancelled'
75
+ | 'form.submitted'
76
+ | 'order.created'
77
+ | 'order.paid'
78
+ | 'order.refunded'
79
+ | 'invoice.created'
80
+ | 'invoice.sent'
81
+ | 'invoice.paid'
82
+ | 'invoice.overdue'
83
+ | 'benefit_claim.submitted'
84
+ | 'benefit_claim.approved'
85
+ | 'benefit_claim.rejected'
86
+ | 'benefit_claim.paid'
87
+ | 'commission.calculated'
88
+ | 'commission.paid'
89
+ | 'certificate.issued';
90
+
91
+ export interface VerifyOptions {
92
+ /** The raw request body as a string or Buffer */
93
+ payload: string | Buffer;
94
+ /** The signature from the X-L4yercak3-Signature header */
95
+ signature: string;
96
+ /** Your webhook secret from the L4YERCAK3 dashboard */
97
+ secret: string;
98
+ /** Tolerance in seconds for timestamp validation (default: 300 = 5 minutes) */
99
+ tolerance?: number;
100
+ }
101
+
102
+ export class WebhookVerificationError extends Error {
103
+ constructor(message: string) {
104
+ super(message);
105
+ this.name = 'WebhookVerificationError';
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Verify a webhook signature and parse the event
111
+ *
112
+ * @example
113
+ * \`\`\`ts
114
+ * // In your Next.js API route
115
+ * import { verifyWebhookSignature, WebhookEvent } from '@/lib/l4yercak3';
116
+ *
117
+ * export async function POST(req: Request) {
118
+ * const payload = await req.text();
119
+ * const signature = req.headers.get('x-l4yercak3-signature') || '';
120
+ *
121
+ * try {
122
+ * const event = verifyWebhookSignature({
123
+ * payload,
124
+ * signature,
125
+ * secret: process.env.L4YERCAK3_WEBHOOK_SECRET!,
126
+ * });
127
+ *
128
+ * switch (event.type) {
129
+ * case 'contact.created':
130
+ * // Handle new contact
131
+ * break;
132
+ * case 'order.paid':
133
+ * // Handle paid order
134
+ * break;
135
+ * }
136
+ *
137
+ * return new Response('OK', { status: 200 });
138
+ * } catch (error) {
139
+ * console.error('Webhook error:', error);
140
+ * return new Response('Invalid signature', { status: 400 });
141
+ * }
142
+ * }
143
+ * \`\`\`
144
+ */
145
+ export function verifyWebhookSignature<T = Record<string, unknown>>(
146
+ options: VerifyOptions
147
+ ): WebhookEvent<T> {
148
+ const { payload, signature, secret, tolerance = 300 } = options;
149
+
150
+ if (!signature) {
151
+ throw new WebhookVerificationError('Missing signature header');
152
+ }
153
+
154
+ // Parse the signature header (format: t=timestamp,v1=signature)
155
+ const parts = signature.split(',').reduce((acc, part) => {
156
+ const [key, value] = part.split('=');
157
+ acc[key] = value;
158
+ return acc;
159
+ }, {} as Record<string, string>);
160
+
161
+ const timestamp = parts['t'];
162
+ const signatureHash = parts['v1'];
163
+
164
+ if (!timestamp || !signatureHash) {
165
+ throw new WebhookVerificationError('Invalid signature format');
166
+ }
167
+
168
+ // Check timestamp tolerance
169
+ const timestampNum = parseInt(timestamp, 10);
170
+ const now = Math.floor(Date.now() / 1000);
171
+ if (Math.abs(now - timestampNum) > tolerance) {
172
+ throw new WebhookVerificationError('Timestamp outside tolerance window');
173
+ }
174
+
175
+ // Compute expected signature
176
+ const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf8');
177
+ const signedPayload = \`\${timestamp}.\${payloadStr}\`;
178
+ const expectedSignature = createHmac('sha256', secret)
179
+ .update(signedPayload)
180
+ .digest('hex');
181
+
182
+ // Constant-time comparison
183
+ const expectedBuffer = Buffer.from(expectedSignature);
184
+ const receivedBuffer = Buffer.from(signatureHash);
185
+
186
+ if (expectedBuffer.length !== receivedBuffer.length) {
187
+ throw new WebhookVerificationError('Invalid signature');
188
+ }
189
+
190
+ if (!timingSafeEqual(expectedBuffer, receivedBuffer)) {
191
+ throw new WebhookVerificationError('Invalid signature');
192
+ }
193
+
194
+ // Parse and return the event
195
+ try {
196
+ return JSON.parse(payloadStr) as WebhookEvent<T>;
197
+ } catch {
198
+ throw new WebhookVerificationError('Invalid JSON payload');
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Type guard helpers for specific webhook events
204
+ */
205
+ export function isContactEvent(event: WebhookEvent): event is WebhookEvent<{ contact: unknown }> {
206
+ return event.type.startsWith('contact.');
207
+ }
208
+
209
+ export function isEventEvent(event: WebhookEvent): event is WebhookEvent<{ event: unknown }> {
210
+ return event.type.startsWith('event.');
211
+ }
212
+
213
+ export function isOrderEvent(event: WebhookEvent): event is WebhookEvent<{ order: unknown }> {
214
+ return event.type.startsWith('order.');
215
+ }
216
+
217
+ export function isInvoiceEvent(event: WebhookEvent): event is WebhookEvent<{ invoice: unknown }> {
218
+ return event.type.startsWith('invoice.');
219
+ }
220
+
221
+ export function isBenefitClaimEvent(event: WebhookEvent): event is WebhookEvent<{ claim: unknown }> {
222
+ return event.type.startsWith('benefit_claim.');
223
+ }
224
+ `;
225
+ }
226
+
227
+ generateJavaScriptWebhooks() {
228
+ return `/**
229
+ * L4YERCAK3 Webhook Utilities
230
+ * Auto-generated by @l4yercak3/cli
231
+ *
232
+ * Utilities for handling webhook events from L4YERCAK3.
233
+ */
234
+
235
+ const { createHmac, timingSafeEqual } = require('crypto');
236
+
237
+ class WebhookVerificationError extends Error {
238
+ constructor(message) {
239
+ super(message);
240
+ this.name = 'WebhookVerificationError';
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Verify a webhook signature and parse the event
246
+ *
247
+ * @param {Object} options
248
+ * @param {string|Buffer} options.payload - The raw request body
249
+ * @param {string} options.signature - The signature from X-L4yercak3-Signature header
250
+ * @param {string} options.secret - Your webhook secret
251
+ * @param {number} [options.tolerance=300] - Timestamp tolerance in seconds
252
+ * @returns {Object} The verified webhook event
253
+ * @throws {WebhookVerificationError} If verification fails
254
+ *
255
+ * @example
256
+ * // In your Next.js API route
257
+ * const { verifyWebhookSignature } = require('@/lib/l4yercak3');
258
+ *
259
+ * export default async function handler(req, res) {
260
+ * const payload = req.body;
261
+ * const signature = req.headers['x-l4yercak3-signature'];
262
+ *
263
+ * try {
264
+ * const event = verifyWebhookSignature({
265
+ * payload: JSON.stringify(payload),
266
+ * signature,
267
+ * secret: process.env.L4YERCAK3_WEBHOOK_SECRET,
268
+ * });
269
+ *
270
+ * switch (event.type) {
271
+ * case 'contact.created':
272
+ * // Handle new contact
273
+ * break;
274
+ * case 'order.paid':
275
+ * // Handle paid order
276
+ * break;
277
+ * }
278
+ *
279
+ * res.status(200).json({ received: true });
280
+ * } catch (error) {
281
+ * console.error('Webhook error:', error);
282
+ * res.status(400).json({ error: 'Invalid signature' });
283
+ * }
284
+ * }
285
+ */
286
+ function verifyWebhookSignature(options) {
287
+ const { payload, signature, secret, tolerance = 300 } = options;
288
+
289
+ if (!signature) {
290
+ throw new WebhookVerificationError('Missing signature header');
291
+ }
292
+
293
+ // Parse the signature header (format: t=timestamp,v1=signature)
294
+ const parts = signature.split(',').reduce((acc, part) => {
295
+ const [key, value] = part.split('=');
296
+ acc[key] = value;
297
+ return acc;
298
+ }, {});
299
+
300
+ const timestamp = parts['t'];
301
+ const signatureHash = parts['v1'];
302
+
303
+ if (!timestamp || !signatureHash) {
304
+ throw new WebhookVerificationError('Invalid signature format');
305
+ }
306
+
307
+ // Check timestamp tolerance
308
+ const timestampNum = parseInt(timestamp, 10);
309
+ const now = Math.floor(Date.now() / 1000);
310
+ if (Math.abs(now - timestampNum) > tolerance) {
311
+ throw new WebhookVerificationError('Timestamp outside tolerance window');
312
+ }
313
+
314
+ // Compute expected signature
315
+ const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf8');
316
+ const signedPayload = \`\${timestamp}.\${payloadStr}\`;
317
+ const expectedSignature = createHmac('sha256', secret)
318
+ .update(signedPayload)
319
+ .digest('hex');
320
+
321
+ // Constant-time comparison
322
+ const expectedBuffer = Buffer.from(expectedSignature);
323
+ const receivedBuffer = Buffer.from(signatureHash);
324
+
325
+ if (expectedBuffer.length !== receivedBuffer.length) {
326
+ throw new WebhookVerificationError('Invalid signature');
327
+ }
328
+
329
+ if (!timingSafeEqual(expectedBuffer, receivedBuffer)) {
330
+ throw new WebhookVerificationError('Invalid signature');
331
+ }
332
+
333
+ // Parse and return the event
334
+ try {
335
+ return JSON.parse(payloadStr);
336
+ } catch {
337
+ throw new WebhookVerificationError('Invalid JSON payload');
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Type guard helpers for specific webhook events
343
+ */
344
+ function isContactEvent(event) {
345
+ return event.type && event.type.startsWith('contact.');
346
+ }
347
+
348
+ function isEventEvent(event) {
349
+ return event.type && event.type.startsWith('event.');
350
+ }
351
+
352
+ function isOrderEvent(event) {
353
+ return event.type && event.type.startsWith('order.');
354
+ }
355
+
356
+ function isInvoiceEvent(event) {
357
+ return event.type && event.type.startsWith('invoice.');
358
+ }
359
+
360
+ function isBenefitClaimEvent(event) {
361
+ return event.type && event.type.startsWith('benefit_claim.');
362
+ }
363
+
364
+ module.exports = {
365
+ verifyWebhookSignature,
366
+ WebhookVerificationError,
367
+ isContactEvent,
368
+ isEventEvent,
369
+ isOrderEvent,
370
+ isInvoiceEvent,
371
+ isBenefitClaimEvent,
372
+ };
373
+ `;
374
+ }
375
+ }
376
+
377
+ module.exports = new WebhooksGenerator();
@@ -18,8 +18,12 @@ class EnvGenerator {
18
18
  organizationId,
19
19
  features,
20
20
  oauthProviders,
21
+ isMobile,
21
22
  } = options;
22
23
 
24
+ // Use EXPO_PUBLIC_ for Expo, NEXT_PUBLIC_ for Next.js
25
+ const publicPrefix = isMobile ? 'EXPO_PUBLIC_' : 'NEXT_PUBLIC_';
26
+
23
27
  const envPath = path.join(projectPath, '.env.local');
24
28
  const existingEnv = this.readExistingEnv(envPath);
25
29
 
@@ -31,9 +35,11 @@ class EnvGenerator {
31
35
  L4YERCAK3_API_KEY: apiKey || existingEnv.L4YERCAK3_API_KEY || 'your_api_key_here',
32
36
  L4YERCAK3_BACKEND_URL: backendUrl,
33
37
  L4YERCAK3_ORGANIZATION_ID: organizationId,
34
- NEXT_PUBLIC_L4YERCAK3_BACKEND_URL: backendUrl,
35
38
  };
36
39
 
40
+ // Add public backend URL with appropriate prefix
41
+ envVars[`${publicPrefix}L4YERCAK3_BACKEND_URL`] = backendUrl;
42
+
37
43
  // Add OAuth variables if OAuth is enabled
38
44
  if (features.includes('oauth') && oauthProviders) {
39
45
  if (oauthProviders.includes('google')) {
@@ -55,11 +61,15 @@ class EnvGenerator {
55
61
 
56
62
  // Add Stripe variables if Stripe is enabled
57
63
  if (features.includes('stripe')) {
58
- envVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = existingEnv.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || 'your_stripe_publishable_key_here';
64
+ const stripePublicKey = `${publicPrefix}STRIPE_PUBLISHABLE_KEY`;
65
+ envVars[stripePublicKey] = existingEnv[stripePublicKey] || existingEnv.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || 'your_stripe_publishable_key_here';
59
66
  envVars.STRIPE_SECRET_KEY = existingEnv.STRIPE_SECRET_KEY || 'your_stripe_secret_key_here';
60
67
  envVars.STRIPE_WEBHOOK_SECRET = existingEnv.STRIPE_WEBHOOK_SECRET || 'your_stripe_webhook_secret_here';
61
68
  }
62
69
 
70
+ // Store isMobile for formatting
71
+ envVars._isMobile = isMobile;
72
+
63
73
  // Write env file
64
74
  const envContent = this.formatEnvFile(envVars);
65
75
  fs.writeFileSync(envPath, envContent, 'utf8');
@@ -103,19 +113,23 @@ class EnvGenerator {
103
113
  * Format environment variables as .env file
104
114
  */
105
115
  formatEnvFile(envVars) {
116
+ const isMobile = envVars._isMobile;
117
+ const publicPrefix = isMobile ? 'EXPO_PUBLIC_' : 'NEXT_PUBLIC_';
118
+ const platformName = isMobile ? 'Expo/React Native' : 'Next.js';
119
+ const deploymentPlatforms = isMobile ? 'EAS Build, Expo Go' : 'Vercel, Netlify, etc.';
120
+
106
121
  let content = `# L4YERCAK3 Configuration
107
122
  # Auto-generated by @l4yercak3/cli
108
123
  # DO NOT commit this file to git - it contains sensitive credentials
109
124
  #
110
- # This file is for LOCAL DEVELOPMENT. For production:
111
- # - Set these variables in your hosting platform (Vercel, Netlify, etc.)
112
- # - NEXTAUTH_URL should be your production domain
125
+ # This file is for LOCAL DEVELOPMENT (${platformName}).
126
+ # For production, set these variables in your deployment platform (${deploymentPlatforms}).
113
127
 
114
128
  # Core API Configuration
115
129
  L4YERCAK3_API_KEY=${envVars.L4YERCAK3_API_KEY}
116
130
  L4YERCAK3_BACKEND_URL=${envVars.L4YERCAK3_BACKEND_URL}
117
131
  L4YERCAK3_ORGANIZATION_ID=${envVars.L4YERCAK3_ORGANIZATION_ID}
118
- NEXT_PUBLIC_L4YERCAK3_BACKEND_URL=${envVars.NEXT_PUBLIC_L4YERCAK3_BACKEND_URL}
132
+ ${publicPrefix}L4YERCAK3_BACKEND_URL=${envVars[`${publicPrefix}L4YERCAK3_BACKEND_URL`]}
119
133
 
120
134
  `;
121
135
 
@@ -160,9 +174,10 @@ NEXTAUTH_SECRET=${envVars.NEXTAUTH_SECRET}
160
174
  }
161
175
 
162
176
  // Add Stripe section if present
163
- if (envVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) {
177
+ const stripePublicKey = envVars[`${publicPrefix}STRIPE_PUBLISHABLE_KEY`];
178
+ if (stripePublicKey) {
164
179
  content += `# Stripe Configuration
165
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${envVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
180
+ ${publicPrefix}STRIPE_PUBLISHABLE_KEY=${stripePublicKey}
166
181
  STRIPE_SECRET_KEY=${envVars.STRIPE_SECRET_KEY}
167
182
  STRIPE_WEBHOOK_SECRET=${envVars.STRIPE_WEBHOOK_SECRET}
168
183