fraim 2.0.179 → 2.0.182

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 (64) hide show
  1. package/dist/src/ai-hub/desktop-main.js +2 -2
  2. package/dist/src/api/admin/payments.js +33 -0
  3. package/dist/src/api/admin/sales-leads.js +21 -0
  4. package/dist/src/api/payment/create-session.js +338 -0
  5. package/dist/src/api/payment/dashboard-link.js +149 -0
  6. package/dist/src/api/payment/session-details.js +31 -0
  7. package/dist/src/api/payment/webhook.js +587 -0
  8. package/dist/src/api/personas/me.js +29 -0
  9. package/dist/src/api/pricing/get-config.js +25 -0
  10. package/dist/src/api/sales/contact.js +44 -0
  11. package/dist/src/cli/commands/add-ide.js +9 -2
  12. package/dist/src/cli/commands/setup.js +14 -44
  13. package/dist/src/cli/distribution/marketplace-bundles.js +5 -1
  14. package/dist/src/cli/setup/ide-detector.js +7 -2
  15. package/dist/src/core/config-loader.js +10 -8
  16. package/dist/src/core/types.js +2 -1
  17. package/dist/src/db/payment-repository.js +61 -0
  18. package/dist/src/fraim/config-loader.js +11 -0
  19. package/dist/src/fraim/db-service.js +2387 -0
  20. package/dist/src/fraim/issues.js +152 -0
  21. package/dist/src/fraim/template-processor.js +184 -0
  22. package/dist/src/fraim/utils/request-utils.js +23 -0
  23. package/dist/src/middleware/auth.js +266 -0
  24. package/dist/src/middleware/cors-config.js +111 -0
  25. package/dist/src/middleware/logger.js +116 -0
  26. package/dist/src/middleware/rate-limit.js +110 -0
  27. package/dist/src/middleware/reject-query-api-key.js +45 -0
  28. package/dist/src/middleware/security-headers.js +41 -0
  29. package/dist/src/middleware/telemetry.js +134 -0
  30. package/dist/src/models/payment.js +2 -0
  31. package/dist/src/routes/analytics.js +1447 -0
  32. package/dist/src/routes/app-routes.js +32 -0
  33. package/dist/src/routes/auth-routes.js +505 -0
  34. package/dist/src/routes/oauth-routes.js +325 -0
  35. package/dist/src/routes/payment-routes.js +186 -0
  36. package/dist/src/routes/persona-catalog-routes.js +84 -0
  37. package/dist/src/services/admin-service.js +229 -0
  38. package/dist/src/services/audit-log-persistence.js +60 -0
  39. package/dist/src/services/audit-log.js +69 -0
  40. package/dist/src/services/cookie-service.js +129 -0
  41. package/dist/src/services/dashboard-access.js +27 -0
  42. package/dist/src/services/demo-seed-service.js +139 -0
  43. package/dist/src/services/email-code.js +23 -0
  44. package/dist/src/services/email-service-clean.js +782 -0
  45. package/dist/src/services/email-service.js +951 -0
  46. package/dist/src/services/installer-service.js +131 -0
  47. package/dist/src/services/mcp-oauth-store.js +33 -0
  48. package/dist/src/services/mcp-service.js +823 -0
  49. package/dist/src/services/oauth-helpers.js +127 -0
  50. package/dist/src/services/org-service.js +89 -0
  51. package/dist/src/services/persona-entitlement-service.js +288 -0
  52. package/dist/src/services/provider-service.js +215 -0
  53. package/dist/src/services/registry-service.js +628 -0
  54. package/dist/src/services/session-service.js +86 -0
  55. package/dist/src/services/trial-reminder-service.js +120 -0
  56. package/dist/src/services/usage-analytics-service.js +419 -0
  57. package/dist/src/services/workspace-identity.js +21 -0
  58. package/dist/src/types/analytics.js +2 -0
  59. package/dist/src/utils/payment-calculator.js +52 -0
  60. package/extensions/office-word/favicon.ico +0 -0
  61. package/extensions/office-word/icon-64.png +0 -0
  62. package/extensions/office-word/manifest.xml +33 -0
  63. package/extensions/office-word/taskpane.html +242 -0
  64. package/package.json +12 -2
@@ -0,0 +1,782 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmailService = void 0;
4
+ const resend_1 = require("resend");
5
+ class EmailService {
6
+ constructor() {
7
+ const apiKey = process.env.RESEND_API_KEY;
8
+ if (!apiKey) {
9
+ throw new Error('RESEND_API_KEY environment variable is required');
10
+ }
11
+ this.resend = new resend_1.Resend(apiKey);
12
+ // Use custom domain if configured, otherwise use Resend default
13
+ this.fromEmail = process.env.RESEND_FROM_EMAIL || 'FRAIM <onboarding@resend.dev>';
14
+ this.baseUrl = (process.env.BASE_URL || 'https://fraimworks.ai').replace(/\/$/, '');
15
+ }
16
+ /**
17
+ * Send magic link for email verification (14-day trial)
18
+ */
19
+ async sendMagicLink(to, verificationUrl) {
20
+ const template = this.generateMagicLinkTemplate(verificationUrl);
21
+ try {
22
+ const result = await this.resend.emails.send({
23
+ from: this.fromEmail,
24
+ to,
25
+ subject: template.subject,
26
+ html: template.html,
27
+ text: template.text
28
+ });
29
+ if (result.error) {
30
+ console.error(`❌ Resend API error (magic link to ${to}):`, JSON.stringify(result.error));
31
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
32
+ }
33
+ console.log(`✅ Magic link email sent to ${to}`, { id: result.data?.id });
34
+ }
35
+ catch (error) {
36
+ console.error(`❌ Failed to send magic link email to ${to}:`, error);
37
+ throw error;
38
+ }
39
+ }
40
+ /**
41
+ * Send dashboard access link for returning users who already have a key
42
+ */
43
+ async sendDashboardLink(to, dashboardUrl) {
44
+ const dashboardAccess = this.generateDashboardAccessSection(dashboardUrl, 'Go to Dashboard');
45
+ try {
46
+ const result = await this.resend.emails.send({
47
+ from: this.fromEmail,
48
+ to,
49
+ subject: 'Your FRAIM Dashboard Access Link',
50
+ html: `<!DOCTYPE html><html><head><style>
51
+ body{font-family:Arial,sans-serif;line-height:1.6;color:#333}
52
+ .container{max-width:600px;margin:0 auto;padding:20px}
53
+ .button{display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold}
54
+ .footer{font-size:12px;color:#666;margin-top:30px;padding-top:20px;border-top:1px solid #eee}
55
+ </style></head><body><div class="container">
56
+ <h2>Access Your FRAIM Dashboard</h2>
57
+ <p>${dashboardAccess.lead}</p>
58
+ ${dashboardAccess.buttonHtml}
59
+ <p>This link expires in 15 minutes. If you didn't request this, you can safely ignore this email.</p>
60
+ <div class="footer"><p>FRAIM - Framework for Rigor-based AI Management<br><a href="${this.baseUrl}">fraimworks.ai</a></p></div>
61
+ </div></body></html>`,
62
+ text: `Access Your FRAIM Dashboard\n\n${dashboardAccess.lead}\n\n${dashboardAccess.buttonText}\n\nThis link expires in 15 minutes.\n\nFRAIM - Framework for Rigor-based AI Management\n${this.baseUrl}`
63
+ });
64
+ if (result.error) {
65
+ console.error(`[ERROR] Resend API error (dashboard link to ${to}):`, JSON.stringify(result.error));
66
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
67
+ }
68
+ console.log(`[OK] Dashboard link email sent to ${to}`, { id: result.data?.id });
69
+ }
70
+ catch (error) {
71
+ console.error(`[ERROR] Failed to send dashboard link email to ${to}:`, error);
72
+ throw error;
73
+ }
74
+ }
75
+ /**
76
+ * Send welcome email after trial key created
77
+ */
78
+ async sendWelcomeEmail(to, apiKey, expiresAt) {
79
+ const template = this.generateWelcomeTemplate(apiKey, expiresAt);
80
+ try {
81
+ const result = await this.resend.emails.send({
82
+ from: this.fromEmail,
83
+ to,
84
+ subject: template.subject,
85
+ html: template.html,
86
+ text: template.text
87
+ });
88
+ if (result.error) {
89
+ console.error(`❌ Resend API error (welcome email to ${to}):`, JSON.stringify(result.error));
90
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
91
+ }
92
+ console.log(`✅ Welcome email sent to ${to}`, { id: result.data?.id });
93
+ }
94
+ catch (error) {
95
+ console.error(`❌ Failed to send welcome email to ${to}:`, error);
96
+ throw error;
97
+ }
98
+ }
99
+ /**
100
+ * Send trial check-in email (Day 7)
101
+ */
102
+ async sendTrialCheckIn(to, expiresAt) {
103
+ const template = this.generateTrialCheckInTemplate(expiresAt);
104
+ await this.resend.emails.send({
105
+ from: this.fromEmail,
106
+ to,
107
+ subject: template.subject,
108
+ html: template.html,
109
+ text: template.text
110
+ });
111
+ console.log(`✅ Trial check-in email sent to ${to}`);
112
+ }
113
+ /**
114
+ * Send trial expiration warning (Day 12)
115
+ */
116
+ async sendTrialExpiring(to, expiresAt, daysRemaining) {
117
+ const template = this.generateTrialExpiringTemplate(expiresAt, daysRemaining);
118
+ await this.resend.emails.send({
119
+ from: this.fromEmail,
120
+ to,
121
+ subject: template.subject,
122
+ html: template.html,
123
+ text: template.text
124
+ });
125
+ console.log(`✅ Trial expiring email sent to ${to}`);
126
+ }
127
+ /**
128
+ * Send trial expired notification
129
+ */
130
+ async sendTrialExpired(to, expiredAt) {
131
+ const template = this.generateTrialExpiredTemplate(expiredAt);
132
+ await this.resend.emails.send({
133
+ from: this.fromEmail,
134
+ to,
135
+ subject: template.subject,
136
+ html: template.html,
137
+ text: template.text
138
+ });
139
+ console.log(`✅ Trial expired email sent to ${to}`);
140
+ }
141
+ /**
142
+ * Send subscription activated confirmation
143
+ */
144
+ async sendSubscriptionActivated(to, plan, amountCents, billingCycle, nextBillingDate, dashboardUrl, founderDiscount = false) {
145
+ const template = this.generateSubscriptionActivatedTemplate(plan, amountCents, billingCycle, nextBillingDate, dashboardUrl, founderDiscount);
146
+ try {
147
+ const result = await this.resend.emails.send({
148
+ from: this.fromEmail,
149
+ to,
150
+ subject: template.subject,
151
+ html: template.html,
152
+ text: template.text
153
+ });
154
+ if (result.error) {
155
+ console.error(`❌ Resend API error (subscription activated to ${to}):`, JSON.stringify(result.error));
156
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
157
+ }
158
+ console.log(`✅ Subscription activated email sent to ${to}`, { id: result.data?.id });
159
+ }
160
+ catch (error) {
161
+ console.error(`❌ Failed to send subscription activated email to ${to}:`, error);
162
+ throw error;
163
+ }
164
+ }
165
+ /**
166
+ * Send monthly renewal receipt
167
+ */
168
+ async sendRenewalReceipt(to, amount, nextBillingDate, invoiceUrl) {
169
+ const template = this.generateRenewalReceiptTemplate(amount, nextBillingDate, invoiceUrl);
170
+ const result = await this.resend.emails.send({
171
+ from: this.fromEmail,
172
+ to,
173
+ subject: template.subject,
174
+ html: template.html,
175
+ text: template.text
176
+ });
177
+ if (result.error) {
178
+ console.error(`❌ Resend API error (renewal receipt to ${to}):`, JSON.stringify(result.error));
179
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
180
+ }
181
+ console.log(`✅ Renewal receipt sent to ${to}`, { id: result.data?.id });
182
+ }
183
+ /**
184
+ * Send payment failed alert
185
+ */
186
+ async sendPaymentFailed(to, billingPortalUrl, gracePeriodEndsAt) {
187
+ const template = this.generatePaymentFailedTemplate(billingPortalUrl, gracePeriodEndsAt);
188
+ const result = await this.resend.emails.send({
189
+ from: this.fromEmail,
190
+ to,
191
+ subject: template.subject,
192
+ html: template.html,
193
+ text: template.text
194
+ });
195
+ if (result.error) {
196
+ console.error(`❌ Resend API error (payment failed alert to ${to}):`, JSON.stringify(result.error));
197
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
198
+ }
199
+ console.log(`✅ Payment failed alert sent to ${to}`, { id: result.data?.id });
200
+ }
201
+ /**
202
+ * Send payment retry succeeded
203
+ */
204
+ async sendPaymentRestored(to, nextBillingDate) {
205
+ const template = this.generatePaymentRestoredTemplate(nextBillingDate);
206
+ const result = await this.resend.emails.send({
207
+ from: this.fromEmail,
208
+ to,
209
+ subject: template.subject,
210
+ html: template.html,
211
+ text: template.text
212
+ });
213
+ if (result.error) {
214
+ console.error(`❌ Resend API error (payment restored to ${to}):`, JSON.stringify(result.error));
215
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
216
+ }
217
+ console.log(`✅ Payment restored email sent to ${to}`, { id: result.data?.id });
218
+ }
219
+ /**
220
+ * Send subscription cancelled notification
221
+ */
222
+ async sendSubscriptionCancelled(to, endsAt) {
223
+ const template = this.generateSubscriptionCancelledTemplate(endsAt);
224
+ const result = await this.resend.emails.send({
225
+ from: this.fromEmail,
226
+ to,
227
+ subject: template.subject,
228
+ html: template.html,
229
+ text: template.text
230
+ });
231
+ if (result.error) {
232
+ console.error(`❌ Resend API error (subscription cancelled to ${to}):`, JSON.stringify(result.error));
233
+ throw new Error(`Resend error: ${result.error.message || JSON.stringify(result.error)}`);
234
+ }
235
+ console.log(`✅ Subscription cancelled email sent to ${to}`, { id: result.data?.id });
236
+ }
237
+ // ========== Email Template Generators ==========
238
+ generateMagicLinkTemplate(verificationUrl) {
239
+ const expiresInMinutes = 15;
240
+ return {
241
+ subject: 'Verify your email for FRAIM',
242
+ html: `
243
+ <!DOCTYPE html>
244
+ <html>
245
+ <head>
246
+ <style>
247
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
248
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
249
+ .button {
250
+ display: inline-block;
251
+ padding: 12px 24px;
252
+ background-color: #10b981;
253
+ color: #ffffff !important;
254
+ text-decoration: none;
255
+ border-radius: 5px;
256
+ margin: 20px 0;
257
+ }
258
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
259
+ </style>
260
+ </head>
261
+ <body>
262
+ <div class="container">
263
+ <h2>Welcome to FRAIM!</h2>
264
+ <p>Click the button below to verify your email and start your 14-day free trial:</p>
265
+ <a href="${verificationUrl}" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Verify Email &amp; Start Trial</a>
266
+ <p>This link expires in ${expiresInMinutes} minutes.</p>
267
+ <p>If you didn't request this, you can safely ignore this email.</p>
268
+ <div class="footer">
269
+ <p>FRAIM - Framework for Rigor-based AI Management<br>
270
+ <a href="${this.baseUrl}">fraimworks.ai</a></p>
271
+ </div>
272
+ </div>
273
+ </body>
274
+ </html>
275
+ `,
276
+ text: `Welcome to FRAIM!\n\nClick the link below to verify your email and start your 14-day free trial:\n\n${verificationUrl}\n\nThis link expires in ${expiresInMinutes} minutes.\n\nIf you didn't request this, you can safely ignore this email.\n\nFRAIM - Framework for Rigor-based AI Management\n${this.baseUrl}`
277
+ };
278
+ }
279
+ generateWelcomeTemplate(apiKey, expiresAt) {
280
+ const daysRemaining = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
281
+ return {
282
+ subject: 'Welcome to FRAIM! Your 14-day trial starts now',
283
+ html: `
284
+ <!DOCTYPE html>
285
+ <html>
286
+ <head>
287
+ <style>
288
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
289
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
290
+ .api-key {
291
+ background: #f5f5f5;
292
+ padding: 15px;
293
+ border-radius: 5px;
294
+ font-family: monospace;
295
+ word-break: break-all;
296
+ margin: 20px 0;
297
+ }
298
+ .button {
299
+ display: inline-block;
300
+ padding: 12px 24px;
301
+ background-color: #10b981;
302
+ color: #ffffff !important;
303
+ text-decoration: none;
304
+ border-radius: 5px;
305
+ margin: 20px 0;
306
+ }
307
+ .highlight { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0; }
308
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
309
+ </style>
310
+ </head>
311
+ <body>
312
+ <div class="container">
313
+ <h2>🎉 Welcome to FRAIM!</h2>
314
+ <p>Your 14-day free trial is now active. Here's your API key:</p>
315
+ <div class="api-key">${apiKey}</div>
316
+
317
+ <div class="highlight">
318
+ <strong>Trial expires:</strong> ${expiresAt.toLocaleDateString()} (${daysRemaining} days remaining)
319
+ </div>
320
+
321
+ <h3>Get Started:</h3>
322
+ <ol>
323
+ <li><a href="${this.baseUrl}/dashboard">Access your dashboard</a></li>
324
+ <li>Download the installer for your platform</li>
325
+ <li>Run your first AI-managed workflow</li>
326
+ </ol>
327
+
328
+ <a href="${this.baseUrl}/docs/quickstart" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">View Quickstart Guide</a>
329
+
330
+ <div class="footer">
331
+ <p>Questions? Reply to this email or visit <a href="${this.baseUrl}/docs">our docs</a>.</p>
332
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
333
+ </div>
334
+ </div>
335
+ </body>
336
+ </html>
337
+ `,
338
+ text: `Welcome to FRAIM!\n\nYour 14-day free trial is now active. Here's your API key:\n\n${apiKey}\n\nTrial expires: ${expiresAt.toLocaleDateString()} (${daysRemaining} days remaining)\n\nGet Started:\n1. Access your dashboard: ${this.baseUrl}/dashboard\n2. Download the installer for your platform\n3. Run your first AI-managed workflow\n\nView Quickstart Guide: ${this.baseUrl}/docs/quickstart\n\nQuestions? Reply to this email or visit our docs.`
339
+ };
340
+ }
341
+ generateTrialCheckInTemplate(expiresAt) {
342
+ const daysRemaining = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
343
+ return {
344
+ subject: "You've been using FRAIM for a week!",
345
+ html: `
346
+ <!DOCTYPE html>
347
+ <html>
348
+ <head>
349
+ <style>
350
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
351
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
352
+ .button {
353
+ display: inline-block;
354
+ padding: 12px 24px;
355
+ background-color: #10b981;
356
+ color: #ffffff !important;
357
+ text-decoration: none;
358
+ border-radius: 5px;
359
+ margin: 20px 0;
360
+ }
361
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
362
+ </style>
363
+ </head>
364
+ <body>
365
+ <div class="container">
366
+ <h2>How's FRAIM working for you?</h2>
367
+ <p>You've been using FRAIM for a week now. We hope it's helping you manage your AI agents more effectively!</p>
368
+
369
+ <p><strong>Your trial ends in ${daysRemaining} days</strong> (${expiresAt.toLocaleDateString()})</p>
370
+
371
+ <h3>Upgrade to Pro for:</h3>
372
+ <ul>
373
+ <li>Unlimited usage (no expiration)</li>
374
+ <li>Priority support</li>
375
+ <li>Advanced workflows</li>
376
+ <li>Team collaboration features</li>
377
+ </ul>
378
+
379
+ <p><strong>Special offer for early adopters:</strong> 90% off for pre-seed, pre-revenue founders!</p>
380
+
381
+ <a href="${this.baseUrl}/upgrade" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Upgrade to Pro</a>
382
+
383
+ <div class="footer">
384
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
385
+ </div>
386
+ </div>
387
+ </body>
388
+ </html>
389
+ `,
390
+ text: `How's FRAIM working for you?\n\nYou've been using FRAIM for a week now. We hope it's helping you manage your AI agents more effectively!\n\nYour trial ends in ${daysRemaining} days (${expiresAt.toLocaleDateString()})\n\nUpgrade to Pro for:\n- Unlimited usage (no expiration)\n- Priority support\n- Advanced workflows\n- Team collaboration features\n\nSpecial offer for early adopters: 90% off for pre-seed, pre-revenue founders!\n\nUpgrade: ${this.baseUrl}/upgrade`
391
+ };
392
+ }
393
+ generateTrialExpiringTemplate(expiresAt, daysRemaining) {
394
+ const urgency = daysRemaining <= 1 ? '🚨 Last day' : '⚠️ Trial ending soon';
395
+ return {
396
+ subject: `${urgency} - Your FRAIM trial expires in ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''}`,
397
+ html: `
398
+ <!DOCTYPE html>
399
+ <html>
400
+ <head>
401
+ <style>
402
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
403
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
404
+ .button {
405
+ display: inline-block;
406
+ padding: 12px 24px;
407
+ background-color: #dc3545;
408
+ color: #ffffff !important;
409
+ text-decoration: none;
410
+ border-radius: 5px;
411
+ margin: 20px 0;
412
+ }
413
+ .warning { background: #fff3cd; padding: 20px; border-left: 4px solid #ffc107; margin: 20px 0; }
414
+ .pricing { background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0; }
415
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
416
+ </style>
417
+ </head>
418
+ <body>
419
+ <div class="container">
420
+ <h2>${urgency}</h2>
421
+
422
+ <div class="warning">
423
+ <strong>Your FRAIM trial expires on ${expiresAt.toLocaleDateString()}</strong><br>
424
+ (${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining)
425
+ </div>
426
+
427
+ <p><strong>What happens when your trial ends:</strong></p>
428
+ <ul>
429
+ <li>Your API key will stop working</li>
430
+ <li>You won't be able to run workflows</li>
431
+ <li>Your data is preserved - upgrade anytime to reactivate</li>
432
+ </ul>
433
+
434
+ <div class="pricing">
435
+ <h3>Choose your plan:</h3>
436
+ <p><strong>Regular pricing:</strong> $200/month or $2,000/year (save 17%)</p>
437
+ <p><strong>Founder discount (90% off):</strong> $20/month or $200/year<br>
438
+ <em>Available to pre-seed, pre-revenue founders with corporate email</em></p>
439
+ </div>
440
+
441
+ <a href="${this.baseUrl}/upgrade" style="display:inline-block;padding:12px 24px;background-color:#dc3545;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Upgrade Now - Don't Lose Access</a>
442
+
443
+ <div class="footer">
444
+ <p>Questions? Reply to this email.</p>
445
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
446
+ </div>
447
+ </div>
448
+ </body>
449
+ </html>
450
+ `,
451
+ text: `${urgency}\n\nYour FRAIM trial expires on ${expiresAt.toLocaleDateString()} (${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining)\n\nWhat happens when your trial ends:\n- Your API key will stop working\n- You won't be able to run workflows\n- Your data is preserved - upgrade anytime to reactivate\n\nChoose your plan:\n\nRegular pricing: $200/month or $2,000/year (save 17%)\n\nFounder discount (90% off): $20/month or $200/year\nAvailable to pre-seed, pre-revenue founders with corporate email\n\nUpgrade now: ${this.baseUrl}/upgrade`
452
+ };
453
+ }
454
+ generateTrialExpiredTemplate(expiredAt) {
455
+ return {
456
+ subject: 'Your FRAIM trial has ended',
457
+ html: `
458
+ <!DOCTYPE html>
459
+ <html>
460
+ <head>
461
+ <style>
462
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
463
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
464
+ .button {
465
+ display: inline-block;
466
+ padding: 12px 24px;
467
+ background-color: #10b981;
468
+ color: #ffffff !important;
469
+ text-decoration: none;
470
+ border-radius: 5px;
471
+ margin: 20px 0;
472
+ }
473
+ .highlight { background: #e7f3ff; padding: 20px; border-left: 4px solid #0066cc; margin: 20px 0; }
474
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
475
+ </style>
476
+ </head>
477
+ <body>
478
+ <div class="container">
479
+ <h2>Your FRAIM trial has ended</h2>
480
+ <p>Your 14-day trial expired on ${expiredAt.toLocaleDateString()}.</p>
481
+
482
+ <div class="highlight">
483
+ <p><strong>Your data is safe and preserved.</strong></p>
484
+ <p>Upgrade now to reactivate your account and continue using FRAIM.</p>
485
+ </div>
486
+
487
+ <h3>Pricing plans:</h3>
488
+ <ul>
489
+ <li><strong>Regular:</strong> $200/month or $2,000/year</li>
490
+ <li><strong>Founders (90% off):</strong> $20/month or $200/year</li>
491
+ </ul>
492
+
493
+ <a href="${this.baseUrl}/upgrade" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Reactivate Your Account</a>
494
+
495
+ <div class="footer">
496
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
497
+ </div>
498
+ </div>
499
+ </body>
500
+ </html>
501
+ `,
502
+ text: `Your FRAIM trial has ended\n\nYour 14-day trial expired on ${expiredAt.toLocaleDateString()}.\n\nYour data is safe and preserved. Upgrade now to reactivate your account and continue using FRAIM.\n\nPricing plans:\n- Regular: $200/month or $2,000/year\n- Founders (90% off): $20/month or $200/year\n\nReactivate your account: ${this.baseUrl}/upgrade`
503
+ };
504
+ }
505
+ generateSubscriptionActivatedTemplate(plan, amountCents, billingCycle, nextBillingDate, dashboardUrl, founderDiscount) {
506
+ const formattedAmount = (amountCents / 100).toFixed(2);
507
+ const cycleText = billingCycle === 'monthly' ? 'month' : 'year';
508
+ const dashboardAccess = this.generateDashboardAccessSection(dashboardUrl, 'Access Your Dashboard');
509
+ return {
510
+ subject: '🎉 Welcome to FRAIM Pro!',
511
+ html: `
512
+ <!DOCTYPE html>
513
+ <html>
514
+ <head>
515
+ <style>
516
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
517
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
518
+ .success { background: #d4edda; padding: 20px; border-left: 4px solid #28a745; margin: 20px 0; }
519
+ .api-key {
520
+ background: #f5f5f5;
521
+ padding: 15px;
522
+ border-radius: 5px;
523
+ font-family: monospace;
524
+ word-break: break-all;
525
+ margin: 20px 0;
526
+ }
527
+ .button {
528
+ display: inline-block;
529
+ padding: 12px 24px;
530
+ background-color: #10b981;
531
+ color: #ffffff !important;
532
+ text-decoration: none;
533
+ border-radius: 5px;
534
+ margin: 20px 0;
535
+ }
536
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
537
+ </style>
538
+ </head>
539
+ <body>
540
+ <div class="container">
541
+ <h2>🎉 Welcome to FRAIM Pro!</h2>
542
+
543
+ <div class="success">
544
+ <strong>Payment confirmed!</strong><br>
545
+ Your subscription is now active.
546
+ </div>
547
+
548
+ <h3>Subscription Details:</h3>
549
+ <ul>
550
+ <li><strong>Plan:</strong> ${plan}${founderDiscount ? ' (Founder Discount - 90% off)' : ''}</li>
551
+ <li><strong>Amount:</strong> $${formattedAmount}/${cycleText}</li>
552
+ <li><strong>Next billing date:</strong> ${nextBillingDate.toLocaleDateString()}</li>
553
+ </ul>
554
+
555
+ <p>${dashboardAccess.lead}</p>
556
+ ${dashboardAccess.buttonHtml}
557
+
558
+ <p>Manage your subscription (update payment method, change plan, etc.):</p>
559
+ <a href="${this.baseUrl}/billing">Billing Portal</a>
560
+
561
+ <div class="footer">
562
+ <p>Questions? Reply to this email.</p>
563
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
564
+ </div>
565
+ </div>
566
+ </body>
567
+ </html>
568
+ `,
569
+ text: `Welcome to FRAIM Pro!\n\nPayment confirmed! Your subscription is now active.\n\nSubscription Details:\n- Plan: ${plan}${founderDiscount ? ' (Founder Discount - 90% off)' : ''}\n- Amount: $${formattedAmount}/${cycleText}\n- Next billing date: ${nextBillingDate.toLocaleDateString()}\n\n${dashboardAccess.lead}\n${dashboardAccess.buttonText}\n\nManage subscription: ${this.baseUrl}/billing`
570
+ };
571
+ }
572
+ generateDashboardAccessSection(dashboardUrl, ctaLabel) {
573
+ const lead = 'Your API key is available in your dashboard.';
574
+ return {
575
+ lead,
576
+ buttonHtml: `<a href="${dashboardUrl}" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">${ctaLabel}</a>`,
577
+ buttonText: `${ctaLabel}: ${dashboardUrl}`
578
+ };
579
+ }
580
+ generateRenewalReceiptTemplate(amount, nextBillingDate, invoiceUrl) {
581
+ const formattedAmount = (amount / 100).toFixed(2);
582
+ return {
583
+ subject: `Receipt for FRAIM Pro - $${formattedAmount}`,
584
+ html: `
585
+ <!DOCTYPE html>
586
+ <html>
587
+ <head>
588
+ <style>
589
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
590
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
591
+ .receipt { background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0; }
592
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
593
+ </style>
594
+ </head>
595
+ <body>
596
+ <div class="container">
597
+ <h2>Payment Receipt</h2>
598
+ <p>Thank you for your continued subscription to FRAIM Pro!</p>
599
+
600
+ <div class="receipt">
601
+ <h3>Receipt Details:</h3>
602
+ <p><strong>Amount charged:</strong> $${formattedAmount}</p>
603
+ <p><strong>Next billing date:</strong> ${nextBillingDate.toLocaleDateString()}</p>
604
+ ${invoiceUrl ? `<p><a href="${invoiceUrl}">Download Invoice</a></p>` : ''}
605
+ </div>
606
+
607
+ <p>Your subscription remains active with no changes.</p>
608
+
609
+ <div class="footer">
610
+ <p>Manage your subscription: <a href="${this.baseUrl}/billing">Billing Portal</a></p>
611
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
612
+ </div>
613
+ </div>
614
+ </body>
615
+ </html>
616
+ `,
617
+ text: `Payment Receipt\n\nThank you for your continued subscription to FRAIM Pro!\n\nReceipt Details:\n- Amount charged: $${formattedAmount}\n- Next billing date: ${nextBillingDate.toLocaleDateString()}\n${invoiceUrl ? `- Invoice: ${invoiceUrl}\n` : ''}\nYour subscription remains active with no changes.\n\nManage subscription: ${this.baseUrl}/billing`
618
+ };
619
+ }
620
+ generatePaymentFailedTemplate(billingPortalUrl, gracePeriodEndsAt) {
621
+ const daysRemaining = Math.ceil((gracePeriodEndsAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
622
+ return {
623
+ subject: '🚨 FRAIM: Payment Failed - Action Required',
624
+ html: `
625
+ <!DOCTYPE html>
626
+ <html>
627
+ <head>
628
+ <style>
629
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
630
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
631
+ .alert { background: #f8d7da; padding: 20px; border-left: 4px solid #dc3545; margin: 20px 0; }
632
+ .button {
633
+ display: inline-block;
634
+ padding: 12px 24px;
635
+ background-color: #dc3545;
636
+ color: #ffffff !important;
637
+ text-decoration: none;
638
+ border-radius: 5px;
639
+ margin: 20px 0;
640
+ }
641
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
642
+ </style>
643
+ </head>
644
+ <body>
645
+ <div class="container">
646
+ <h2>⚠️ Payment Failed</h2>
647
+
648
+ <div class="alert">
649
+ <strong>Your FRAIM subscription payment was declined.</strong><br>
650
+ Your access has been suspended.
651
+ </div>
652
+
653
+ <p><strong>What this means:</strong></p>
654
+ <ul>
655
+ <li>Your API key has been suspended and won't work</li>
656
+ <li>You have ${daysRemaining} days to update your payment method</li>
657
+ <li>After ${gracePeriodEndsAt.toLocaleDateString()}, your subscription will be cancelled</li>
658
+ </ul>
659
+
660
+ <p><strong>To restore access:</strong></p>
661
+ <ol>
662
+ <li>Click the button below to update your payment method</li>
663
+ <li>Your subscription will be automatically renewed</li>
664
+ <li>Your API key will be reactivated immediately</li>
665
+ </ol>
666
+
667
+ <a href="${billingPortalUrl}" style="display:inline-block;padding:12px 24px;background-color:#dc3545;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Update Payment Method</a>
668
+
669
+ <div class="footer">
670
+ <p>Need help? Reply to this email.</p>
671
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
672
+ </div>
673
+ </div>
674
+ </body>
675
+ </html>
676
+ `,
677
+ text: `⚠️ Payment Failed\n\nYour FRAIM subscription payment was declined. Your access has been suspended.\n\nWhat this means:\n- Your API key has been suspended and won't work\n- You have ${daysRemaining} days to update your payment method\n- After ${gracePeriodEndsAt.toLocaleDateString()}, your subscription will be cancelled\n\nTo restore access:\n1. Update your payment method: ${billingPortalUrl}\n2. Your subscription will be automatically renewed\n3. Your API key will be reactivated immediately\n\nNeed help? Reply to this email.`
678
+ };
679
+ }
680
+ generatePaymentRestoredTemplate(nextBillingDate) {
681
+ return {
682
+ subject: '✅ Payment successful - FRAIM access restored',
683
+ html: `
684
+ <!DOCTYPE html>
685
+ <html>
686
+ <head>
687
+ <style>
688
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
689
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
690
+ .success { background: #d4edda; padding: 20px; border-left: 4px solid #28a745; margin: 20px 0; }
691
+ .button {
692
+ display: inline-block;
693
+ padding: 12px 24px;
694
+ background-color: #10b981;
695
+ color: #ffffff !important;
696
+ text-decoration: none;
697
+ border-radius: 5px;
698
+ margin: 20px 0;
699
+ }
700
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
701
+ </style>
702
+ </head>
703
+ <body>
704
+ <div class="container">
705
+ <h2>✅ Access Restored!</h2>
706
+
707
+ <div class="success">
708
+ <strong>Payment successful!</strong><br>
709
+ Your FRAIM subscription has been renewed.
710
+ </div>
711
+
712
+ <p>Your API key is now active again and you can continue using FRAIM.</p>
713
+ <p><strong>Next billing date:</strong> ${nextBillingDate.toLocaleDateString()}</p>
714
+
715
+ <a href="${this.baseUrl}/dashboard" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Continue Using FRAIM</a>
716
+
717
+ <div class="footer">
718
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
719
+ </div>
720
+ </div>
721
+ </body>
722
+ </html>
723
+ `,
724
+ text: `✅ Access Restored!\n\nPayment successful! Your FRAIM subscription has been renewed.\n\nYour API key is now active again and you can continue using FRAIM.\n\nNext billing date: ${nextBillingDate.toLocaleDateString()}\n\nContinue using FRAIM: ${this.baseUrl}/dashboard`
725
+ };
726
+ }
727
+ generateSubscriptionCancelledTemplate(endsAt) {
728
+ return {
729
+ subject: 'FRAIM subscription cancelled',
730
+ html: `
731
+ <!DOCTYPE html>
732
+ <html>
733
+ <head>
734
+ <style>
735
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
736
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
737
+ .info { background: #fff3cd; padding: 20px; border-left: 4px solid #ffc107; margin: 20px 0; }
738
+ .button {
739
+ display: inline-block;
740
+ padding: 12px 24px;
741
+ background-color: #10b981;
742
+ color: #ffffff !important;
743
+ text-decoration: none;
744
+ border-radius: 5px;
745
+ margin: 20px 0;
746
+ }
747
+ .footer { font-size: 12px; color: #666; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
748
+ </style>
749
+ </head>
750
+ <body>
751
+ <div class="container">
752
+ <h2>Subscription Cancelled</h2>
753
+
754
+ <div class="info">
755
+ <p><strong>Your subscription has been cancelled.</strong></p>
756
+ <p>Your access remains active until: ${endsAt.toLocaleDateString()}</p>
757
+ </div>
758
+
759
+ <p>After this date:</p>
760
+ <ul>
761
+ <li>Your API key will stop working</li>
762
+ <li>You won't be able to run workflows</li>
763
+ <li>Your data will be preserved for 30 days</li>
764
+ </ul>
765
+
766
+ <p><strong>Changed your mind?</strong> You can reactivate your subscription anytime before ${endsAt.toLocaleDateString()}.</p>
767
+
768
+ <a href="${this.baseUrl}/billing" style="display:inline-block;padding:12px 24px;background-color:#10b981;color:#ffffff !important;text-decoration:none;border-radius:5px;margin:20px 0;font-weight:bold">Reactivate Subscription</a>
769
+
770
+ <div class="footer">
771
+ <p>We're sorry to see you go. Reply to this email to let us know how we can improve.</p>
772
+ <p>FRAIM - Framework for Rigor-based AI Management</p>
773
+ </div>
774
+ </div>
775
+ </body>
776
+ </html>
777
+ `,
778
+ text: `Subscription Cancelled\n\nYour subscription has been cancelled.\n\nYour access remains active until: ${endsAt.toLocaleDateString()}\n\nAfter this date:\n- Your API key will stop working\n- You won't be able to run workflows\n- Your data will be preserved for 30 days\n\nChanged your mind? You can reactivate your subscription anytime before ${endsAt.toLocaleDateString()}.\n\nReactivate: ${this.baseUrl}/billing\n\nWe're sorry to see you go. Reply to this email to let us know how we can improve.`
779
+ };
780
+ }
781
+ }
782
+ exports.EmailService = EmailService;