expxagents 0.11.2 → 0.12.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.
Files changed (94) hide show
  1. package/dist/dashboard/assets/{BufferResource-CR4DczHL.js → BufferResource-BcVsF5HP.js} +1 -1
  2. package/dist/dashboard/assets/{CanvasRenderer-DdWLm2t4.js → CanvasRenderer-kA1Maw0x.js} +1 -1
  3. package/dist/dashboard/assets/{JarvisView-yQv964kf.js → JarvisView-DBrCWArD.js} +1 -1
  4. package/dist/dashboard/assets/{RenderTargetSystem-DdTH8Un8.js → RenderTargetSystem-Bp9B4iP8.js} +1 -1
  5. package/dist/dashboard/assets/{WebGLRenderer-C6BYo_WV.js → WebGLRenderer-CNJyeb_W.js} +1 -1
  6. package/dist/dashboard/assets/{WebGPURenderer-C1UxCrJq.js → WebGPURenderer-DCbozzdc.js} +1 -1
  7. package/dist/dashboard/assets/{browserAll-BC0ycs7y.js → browserAll-Bu4cXbCn.js} +1 -1
  8. package/dist/dashboard/assets/index-zfHiMrG2.js +344 -0
  9. package/dist/dashboard/assets/{webworkerAll-D7SccyuO.js → webworkerAll-BFb9bpT-.js} +1 -1
  10. package/dist/dashboard/index.html +1 -1
  11. package/dist/data/opensquad.db +0 -0
  12. package/dist/server/app.d.ts.map +1 -1
  13. package/dist/server/app.js +30 -0
  14. package/dist/server/app.js.map +1 -1
  15. package/dist/server/config.d.ts +6 -0
  16. package/dist/server/config.d.ts.map +1 -1
  17. package/dist/server/config.js +14 -1
  18. package/dist/server/config.js.map +1 -1
  19. package/dist/server/db/__tests__/email-schema.test.d.ts +2 -0
  20. package/dist/server/db/__tests__/email-schema.test.d.ts.map +1 -0
  21. package/dist/server/db/__tests__/email-schema.test.js +53 -0
  22. package/dist/server/db/__tests__/email-schema.test.js.map +1 -0
  23. package/dist/server/db/schema.d.ts +1 -1
  24. package/dist/server/db/schema.d.ts.map +1 -1
  25. package/dist/server/db/schema.js +70 -0
  26. package/dist/server/db/schema.js.map +1 -1
  27. package/dist/server/email/__tests__/campaign-routes.test.d.ts +2 -0
  28. package/dist/server/email/__tests__/campaign-routes.test.d.ts.map +1 -0
  29. package/dist/server/email/__tests__/campaign-routes.test.js +216 -0
  30. package/dist/server/email/__tests__/campaign-routes.test.js.map +1 -0
  31. package/dist/server/email/__tests__/campaign-service.test.d.ts +2 -0
  32. package/dist/server/email/__tests__/campaign-service.test.d.ts.map +1 -0
  33. package/dist/server/email/__tests__/campaign-service.test.js +79 -0
  34. package/dist/server/email/__tests__/campaign-service.test.js.map +1 -0
  35. package/dist/server/email/__tests__/email-queue-worker.test.d.ts +2 -0
  36. package/dist/server/email/__tests__/email-queue-worker.test.d.ts.map +1 -0
  37. package/dist/server/email/__tests__/email-queue-worker.test.js +93 -0
  38. package/dist/server/email/__tests__/email-queue-worker.test.js.map +1 -0
  39. package/dist/server/email/__tests__/email-utils.test.d.ts +2 -0
  40. package/dist/server/email/__tests__/email-utils.test.d.ts.map +1 -0
  41. package/dist/server/email/__tests__/email-utils.test.js +36 -0
  42. package/dist/server/email/__tests__/email-utils.test.js.map +1 -0
  43. package/dist/server/email/__tests__/lead-routes.test.d.ts +2 -0
  44. package/dist/server/email/__tests__/lead-routes.test.d.ts.map +1 -0
  45. package/dist/server/email/__tests__/lead-routes.test.js +180 -0
  46. package/dist/server/email/__tests__/lead-routes.test.js.map +1 -0
  47. package/dist/server/email/__tests__/lead-service.test.d.ts +2 -0
  48. package/dist/server/email/__tests__/lead-service.test.d.ts.map +1 -0
  49. package/dist/server/email/__tests__/lead-service.test.js +113 -0
  50. package/dist/server/email/__tests__/lead-service.test.js.map +1 -0
  51. package/dist/server/email/__tests__/ses-client.test.d.ts +2 -0
  52. package/dist/server/email/__tests__/ses-client.test.d.ts.map +1 -0
  53. package/dist/server/email/__tests__/ses-client.test.js +48 -0
  54. package/dist/server/email/__tests__/ses-client.test.js.map +1 -0
  55. package/dist/server/email/__tests__/sns-webhook.test.d.ts +2 -0
  56. package/dist/server/email/__tests__/sns-webhook.test.d.ts.map +1 -0
  57. package/dist/server/email/__tests__/sns-webhook.test.js +40 -0
  58. package/dist/server/email/__tests__/sns-webhook.test.js.map +1 -0
  59. package/dist/server/email/campaign-routes.d.ts +8 -0
  60. package/dist/server/email/campaign-routes.d.ts.map +1 -0
  61. package/dist/server/email/campaign-routes.js +65 -0
  62. package/dist/server/email/campaign-routes.js.map +1 -0
  63. package/dist/server/email/campaign-service.d.ts +55 -0
  64. package/dist/server/email/campaign-service.d.ts.map +1 -0
  65. package/dist/server/email/campaign-service.js +89 -0
  66. package/dist/server/email/campaign-service.js.map +1 -0
  67. package/dist/server/email/email-queue-worker.d.ts +27 -0
  68. package/dist/server/email/email-queue-worker.d.ts.map +1 -0
  69. package/dist/server/email/email-queue-worker.js +119 -0
  70. package/dist/server/email/email-queue-worker.js.map +1 -0
  71. package/dist/server/email/email-utils.d.ts +5 -0
  72. package/dist/server/email/email-utils.d.ts.map +1 -0
  73. package/dist/server/email/email-utils.js +24 -0
  74. package/dist/server/email/email-utils.js.map +1 -0
  75. package/dist/server/email/lead-routes.d.ts +8 -0
  76. package/dist/server/email/lead-routes.d.ts.map +1 -0
  77. package/dist/server/email/lead-routes.js +56 -0
  78. package/dist/server/email/lead-routes.js.map +1 -0
  79. package/dist/server/email/lead-service.d.ts +66 -0
  80. package/dist/server/email/lead-service.d.ts.map +1 -0
  81. package/dist/server/email/lead-service.js +138 -0
  82. package/dist/server/email/lead-service.js.map +1 -0
  83. package/dist/server/email/ses-client.d.ts +27 -0
  84. package/dist/server/email/ses-client.d.ts.map +1 -0
  85. package/dist/server/email/ses-client.js +44 -0
  86. package/dist/server/email/ses-client.js.map +1 -0
  87. package/dist/server/email/sns-webhook.d.ts +10 -0
  88. package/dist/server/email/sns-webhook.d.ts.map +1 -0
  89. package/dist/server/email/sns-webhook.js +73 -0
  90. package/dist/server/email/sns-webhook.js.map +1 -0
  91. package/package.json +6 -2
  92. package/dist/dashboard/assets/index-3Noclaww.js +0 -344
  93. package/dist/data/opensquad.db-shm +0 -0
  94. package/dist/data/opensquad.db-wal +0 -0
@@ -0,0 +1,56 @@
1
+ import { LeadService } from './lead-service.js';
2
+ export async function leadRoutes(app, opts) {
3
+ const service = new LeadService(opts.db);
4
+ app.post('/api/email/leads', { preHandler: [app.requireAuth] }, async (request, reply) => {
5
+ const lead = service.createLead(request.body);
6
+ return reply.send({ lead });
7
+ });
8
+ app.get('/api/email/leads', { preHandler: [app.requireAuth] }, async (request, reply) => {
9
+ const { tag, list, status, search, page, limit } = request.query;
10
+ const result = service.queryLeads({ tag, list, status, search, page: page ? parseInt(page) : undefined, limit: limit ? parseInt(limit) : undefined });
11
+ return reply.send(result);
12
+ });
13
+ app.post('/api/email/leads/batch', { preHandler: [app.requireAuth] }, async (request, reply) => {
14
+ const result = service.importBatch(request.body.leads, request.body.source_detail, request.body.source);
15
+ return reply.send(result);
16
+ });
17
+ app.patch('/api/email/leads/:id', { preHandler: [app.requireAuth] }, async (request, reply) => {
18
+ const lead = service.updateLead(request.params.id, request.body);
19
+ if (!lead)
20
+ return reply.status(404).send({ error: 'Lead not found' });
21
+ return reply.send({ lead });
22
+ });
23
+ app.delete('/api/email/leads/:id', { preHandler: [app.requireAuth] }, async (request, reply) => {
24
+ service.deleteLead(request.params.id);
25
+ return reply.send({ ok: true });
26
+ });
27
+ app.post('/api/email/leads/:id/tags', { preHandler: [app.requireAuth] }, async (request, reply) => {
28
+ service.addTags(request.params.id, request.body.tags);
29
+ const tags = service.getTagsForLead(request.params.id);
30
+ return reply.send({ tags });
31
+ });
32
+ app.delete('/api/email/leads/:id/tags', { preHandler: [app.requireAuth] }, async (request, reply) => {
33
+ service.removeTags(request.params.id, request.body.tags);
34
+ const tags = service.getTagsForLead(request.params.id);
35
+ return reply.send({ tags });
36
+ });
37
+ app.post('/api/email/leads/:id/lists', { preHandler: [app.requireAuth] }, async (request, reply) => {
38
+ for (const list of request.body.lists)
39
+ service.addToList(request.params.id, list);
40
+ const lists = service.getListsForLead(request.params.id);
41
+ return reply.send({ lists });
42
+ });
43
+ app.delete('/api/email/leads/:id/lists', { preHandler: [app.requireAuth] }, async (request, reply) => {
44
+ for (const list of request.body.lists)
45
+ service.removeFromList(request.params.id, list);
46
+ const lists = service.getListsForLead(request.params.id);
47
+ return reply.send({ lists });
48
+ });
49
+ app.get('/api/email/leads/lists', { preHandler: [app.requireAuth] }, async (_request, reply) => {
50
+ return reply.send({ lists: service.getListsWithCounts() });
51
+ });
52
+ app.get('/api/email/leads/tags', { preHandler: [app.requireAuth] }, async (_request, reply) => {
53
+ return reply.send({ tags: service.getTagsWithCounts() });
54
+ });
55
+ }
56
+ //# sourceMappingURL=lead-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lead-routes.js","sourceRoot":"","sources":["../../src/email/lead-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type Database from 'better-sqlite3';
2
+ export interface CreateLeadData {
3
+ email: string;
4
+ name?: string;
5
+ company?: string;
6
+ metadata?: Record<string, unknown>;
7
+ source?: string;
8
+ source_detail?: string;
9
+ }
10
+ export interface LeadFilters {
11
+ tag?: string;
12
+ list?: string;
13
+ status?: string;
14
+ search?: string;
15
+ source?: string;
16
+ page?: number;
17
+ limit?: number;
18
+ }
19
+ export interface Lead {
20
+ id: string;
21
+ email: string;
22
+ name: string | null;
23
+ company: string | null;
24
+ metadata: string | null;
25
+ status: string;
26
+ source: string | null;
27
+ source_detail: string | null;
28
+ created_at: string;
29
+ updated_at: string;
30
+ }
31
+ export declare class LeadService {
32
+ private db;
33
+ constructor(db: Database.Database);
34
+ createLead(data: CreateLeadData): Lead;
35
+ getLead(id: string): Lead | undefined;
36
+ updateLead(id: string, data: Partial<CreateLeadData>): Lead | undefined;
37
+ deleteLead(id: string): boolean;
38
+ addTags(leadId: string, tags: string[]): void;
39
+ removeTags(leadId: string, tags: string[]): void;
40
+ getTagsForLead(leadId: string): string[];
41
+ addToList(leadId: string, listName: string): void;
42
+ removeFromList(leadId: string, listName: string): void;
43
+ getListsForLead(leadId: string): string[];
44
+ queryLeads(filters: LeadFilters): {
45
+ data: Lead[];
46
+ total: number;
47
+ };
48
+ unsubscribe(email: string): boolean;
49
+ getListsWithCounts(): {
50
+ list_name: string;
51
+ count: number;
52
+ }[];
53
+ getTagsWithCounts(): {
54
+ tag: string;
55
+ count: number;
56
+ }[];
57
+ importBatch(leads: {
58
+ email: string;
59
+ name?: string;
60
+ company?: string;
61
+ metadata?: Record<string, unknown>;
62
+ }[], sourceDetail: string, source: string): {
63
+ imported: number;
64
+ };
65
+ }
66
+ //# sourceMappingURL=lead-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lead-service.d.ts","sourceRoot":"","sources":["../../src/email/lead-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import crypto from 'crypto';
2
+ export class LeadService {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ createLead(data) {
8
+ const id = crypto.randomUUID();
9
+ const meta = data.metadata ? JSON.stringify(data.metadata) : null;
10
+ const now = new Date().toISOString();
11
+ this.db.prepare(`
12
+ INSERT INTO leads (id, email, name, company, metadata, source, source_detail, created_at, updated_at)
13
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
14
+ ON CONFLICT(email) DO UPDATE SET
15
+ name = COALESCE(excluded.name, leads.name),
16
+ company = COALESCE(excluded.company, leads.company),
17
+ metadata = COALESCE(excluded.metadata, leads.metadata),
18
+ source = COALESCE(excluded.source, leads.source),
19
+ source_detail = COALESCE(excluded.source_detail, leads.source_detail),
20
+ updated_at = excluded.updated_at
21
+ `).run(id, data.email, data.name ?? null, data.company ?? null, meta, data.source ?? null, data.source_detail ?? null, now, now);
22
+ return this.db.prepare('SELECT * FROM leads WHERE email = ?').get(data.email);
23
+ }
24
+ getLead(id) {
25
+ return this.db.prepare('SELECT * FROM leads WHERE id = ?').get(id);
26
+ }
27
+ updateLead(id, data) {
28
+ const fields = [];
29
+ const values = [];
30
+ if (data.name !== undefined) {
31
+ fields.push('name = ?');
32
+ values.push(data.name);
33
+ }
34
+ if (data.company !== undefined) {
35
+ fields.push('company = ?');
36
+ values.push(data.company);
37
+ }
38
+ if (data.metadata !== undefined) {
39
+ fields.push('metadata = ?');
40
+ values.push(JSON.stringify(data.metadata));
41
+ }
42
+ if (fields.length === 0)
43
+ return this.getLead(id);
44
+ fields.push('updated_at = ?');
45
+ values.push(new Date().toISOString());
46
+ values.push(id);
47
+ this.db.prepare(`UPDATE leads SET ${fields.join(', ')} WHERE id = ?`).run(...values);
48
+ return this.getLead(id);
49
+ }
50
+ deleteLead(id) {
51
+ const result = this.db.prepare('DELETE FROM leads WHERE id = ?').run(id);
52
+ return result.changes > 0;
53
+ }
54
+ addTags(leadId, tags) {
55
+ const stmt = this.db.prepare('INSERT OR IGNORE INTO lead_tags (lead_id, tag) VALUES (?, ?)');
56
+ const tx = this.db.transaction((items) => {
57
+ for (const tag of items)
58
+ stmt.run(leadId, tag);
59
+ });
60
+ tx(tags);
61
+ }
62
+ removeTags(leadId, tags) {
63
+ const stmt = this.db.prepare('DELETE FROM lead_tags WHERE lead_id = ? AND tag = ?');
64
+ const tx = this.db.transaction((items) => {
65
+ for (const tag of items)
66
+ stmt.run(leadId, tag);
67
+ });
68
+ tx(tags);
69
+ }
70
+ getTagsForLead(leadId) {
71
+ return this.db.prepare('SELECT tag FROM lead_tags WHERE lead_id = ? ORDER BY tag').all(leadId).map(r => r.tag);
72
+ }
73
+ addToList(leadId, listName) {
74
+ this.db.prepare('INSERT OR IGNORE INTO lead_lists (lead_id, list_name) VALUES (?, ?)').run(leadId, listName);
75
+ }
76
+ removeFromList(leadId, listName) {
77
+ this.db.prepare('DELETE FROM lead_lists WHERE lead_id = ? AND list_name = ?').run(leadId, listName);
78
+ }
79
+ getListsForLead(leadId) {
80
+ return this.db.prepare('SELECT list_name FROM lead_lists WHERE lead_id = ? ORDER BY list_name').all(leadId).map(r => r.list_name);
81
+ }
82
+ queryLeads(filters) {
83
+ const page = filters.page ?? 1;
84
+ const limit = filters.limit ?? 50;
85
+ const offset = (page - 1) * limit;
86
+ const conditions = [];
87
+ const params = [];
88
+ if (filters.status) {
89
+ conditions.push('l.status = ?');
90
+ params.push(filters.status);
91
+ }
92
+ if (filters.source) {
93
+ conditions.push('l.source = ?');
94
+ params.push(filters.source);
95
+ }
96
+ if (filters.search) {
97
+ conditions.push('(l.email LIKE ? OR l.name LIKE ?)');
98
+ params.push(`%${filters.search}%`, `%${filters.search}%`);
99
+ }
100
+ if (filters.tag) {
101
+ conditions.push('EXISTS (SELECT 1 FROM lead_tags lt WHERE lt.lead_id = l.id AND lt.tag = ?)');
102
+ params.push(filters.tag);
103
+ }
104
+ if (filters.list) {
105
+ conditions.push('EXISTS (SELECT 1 FROM lead_lists ll WHERE ll.lead_id = l.id AND ll.list_name = ?)');
106
+ params.push(filters.list);
107
+ }
108
+ const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
109
+ const total = this.db.prepare(`SELECT COUNT(*) as count FROM leads l ${where}`).get(...params).count;
110
+ const data = this.db.prepare(`SELECT l.* FROM leads l ${where} ORDER BY l.created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
111
+ return { data, total };
112
+ }
113
+ unsubscribe(email) {
114
+ const result = this.db.prepare("UPDATE leads SET status = 'unsubscribed', updated_at = ? WHERE email = ?").run(new Date().toISOString(), email);
115
+ if (result.changes > 0) {
116
+ this.db.prepare("DELETE FROM email_queue WHERE lead_id = (SELECT id FROM leads WHERE email = ?) AND status = 'pending'").run(email);
117
+ }
118
+ return result.changes > 0;
119
+ }
120
+ getListsWithCounts() {
121
+ return this.db.prepare('SELECT list_name, COUNT(*) as count FROM lead_lists GROUP BY list_name ORDER BY list_name').all();
122
+ }
123
+ getTagsWithCounts() {
124
+ return this.db.prepare('SELECT tag, COUNT(*) as count FROM lead_tags GROUP BY tag ORDER BY tag').all();
125
+ }
126
+ importBatch(leads, sourceDetail, source) {
127
+ let imported = 0;
128
+ const tx = this.db.transaction(() => {
129
+ for (const lead of leads) {
130
+ this.createLead({ ...lead, source, source_detail: sourceDetail });
131
+ imported++;
132
+ }
133
+ });
134
+ tx();
135
+ return { imported };
136
+ }
137
+ }
138
+ //# sourceMappingURL=lead-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lead-service.js","sourceRoot":"","sources":["../../src/email/lead-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export interface SesClientConfig {
2
+ region: string;
3
+ accessKeyId?: string;
4
+ secretAccessKey?: string;
5
+ }
6
+ export interface SendEmailParams {
7
+ to: string;
8
+ from: string;
9
+ fromName: string;
10
+ subject: string;
11
+ htmlBody: string;
12
+ textBody: string;
13
+ headers?: Record<string, string>;
14
+ }
15
+ export interface SesClient {
16
+ isConfigured(): boolean;
17
+ sendEmail(params: SendEmailParams): Promise<{
18
+ messageId: string;
19
+ }>;
20
+ getSendQuota(): Promise<{
21
+ maxSendRate: number;
22
+ max24HourSend: number;
23
+ sentLast24Hours: number;
24
+ }>;
25
+ }
26
+ export declare function createSesClient(config: SesClientConfig): SesClient;
27
+ //# sourceMappingURL=ses-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses-client.d.ts","sourceRoot":"","sources":["../../src/email/ses-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,YAAY,IAAI,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClG;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA2ClE"}
@@ -0,0 +1,44 @@
1
+ import { SESv2Client, SendEmailCommand, GetAccountCommand } from '@aws-sdk/client-sesv2';
2
+ export function createSesClient(config) {
3
+ const configured = !!(config.accessKeyId && config.secretAccessKey);
4
+ const client = configured
5
+ ? new SESv2Client({
6
+ region: config.region,
7
+ credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },
8
+ })
9
+ : null;
10
+ return {
11
+ isConfigured: () => configured,
12
+ async sendEmail(params) {
13
+ if (!client)
14
+ throw new Error('SES not configured');
15
+ const cmd = new SendEmailCommand({
16
+ FromEmailAddress: `${params.fromName} <${params.from}>`,
17
+ Destination: { ToAddresses: [params.to] },
18
+ Content: {
19
+ Simple: {
20
+ Subject: { Data: params.subject, Charset: 'UTF-8' },
21
+ Body: {
22
+ Html: { Data: params.htmlBody, Charset: 'UTF-8' },
23
+ Text: { Data: params.textBody, Charset: 'UTF-8' },
24
+ },
25
+ },
26
+ },
27
+ });
28
+ const res = await client.send(cmd);
29
+ return { messageId: res.MessageId ?? '' };
30
+ },
31
+ async getSendQuota() {
32
+ if (!client)
33
+ throw new Error('SES not configured');
34
+ const res = await client.send(new GetAccountCommand({}));
35
+ const q = res.SendQuota ?? {};
36
+ return {
37
+ maxSendRate: q.MaxSendRate ?? 1,
38
+ max24HourSend: q.Max24HourSend ?? 0,
39
+ sentLast24Hours: q.SentLast24Hours ?? 0,
40
+ };
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=ses-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses-client.js","sourceRoot":"","sources":["../../src/email/ses-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAwBzF,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,eAAe,CAAC,CAAC;IAEpE,MAAM,MAAM,GAAG,UAAU;QACvB,CAAC,CAAC,IAAI,WAAW,CAAC;YACd,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,WAAW,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAY,EAAE,eAAe,EAAE,MAAM,CAAC,eAAgB,EAAE;SAC5F,CAAC;QACJ,CAAC,CAAC,IAAI,CAAC;IAET,OAAO;QACL,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU;QAE9B,KAAK,CAAC,SAAS,CAAC,MAAuB;YACrC,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC;gBAC/B,gBAAgB,EAAE,GAAG,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,IAAI,GAAG;gBACvD,WAAW,EAAE,EAAE,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;gBACzC,OAAO,EAAE;oBACP,MAAM,EAAE;wBACN,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;wBACnD,IAAI,EAAE;4BACJ,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;4BACjD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;yBAClD;qBACF;iBACF;aACF,CAAC,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QAC5C,CAAC;QAED,KAAK,CAAC,YAAY;YAChB,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;YAC9B,OAAO;gBACL,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC;gBAC/B,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC;gBACnC,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,CAAC;aACxC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
2
+ import type Database from 'better-sqlite3';
3
+ export declare function processSnsNotification(db: Database.Database, notification: any): void;
4
+ interface SnsWebhookOptions extends FastifyPluginOptions {
5
+ db: Database.Database;
6
+ jwtSecret: string;
7
+ }
8
+ export declare function snsWebhookRoutes(app: FastifyInstance, opts: SnsWebhookOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=sns-webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sns-webhook.d.ts","sourceRoot":"","sources":["../../src/email/sns-webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,GAAG,IAAI,CA6BrF;AAED,UAAU,iBAAkB,SAAQ,oBAAoB;IACtD,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnG"}
@@ -0,0 +1,73 @@
1
+ import jwt from 'jsonwebtoken';
2
+ export function processSnsNotification(db, notification) {
3
+ const messageId = notification.mail?.messageId;
4
+ if (!messageId)
5
+ return;
6
+ const queueItem = db.prepare('SELECT id, campaign_id, lead_id, status FROM email_queue WHERE ses_message_id = ?').get(messageId);
7
+ if (!queueItem)
8
+ return;
9
+ const type = notification.notificationType;
10
+ if (type === 'Bounce') {
11
+ if (queueItem.status === 'bounced')
12
+ return; // idempotent
13
+ db.transaction(() => {
14
+ db.prepare("UPDATE email_queue SET status = 'bounced' WHERE id = ?").run(queueItem.id);
15
+ db.prepare('UPDATE email_campaigns SET bounce_count = bounce_count + 1 WHERE id = ?').run(queueItem.campaign_id);
16
+ if (queueItem.lead_id) {
17
+ db.prepare("UPDATE leads SET status = 'bounced', updated_at = ? WHERE id = ?").run(new Date().toISOString(), queueItem.lead_id);
18
+ }
19
+ })();
20
+ }
21
+ else if (type === 'Complaint') {
22
+ if (queueItem.status === 'complained')
23
+ return;
24
+ db.transaction(() => {
25
+ db.prepare("UPDATE email_queue SET status = 'complained' WHERE id = ?").run(queueItem.id);
26
+ db.prepare('UPDATE email_campaigns SET complaint_count = complaint_count + 1 WHERE id = ?').run(queueItem.campaign_id);
27
+ if (queueItem.lead_id) {
28
+ db.prepare("UPDATE leads SET status = 'complained', updated_at = ? WHERE id = ?").run(new Date().toISOString(), queueItem.lead_id);
29
+ }
30
+ })();
31
+ }
32
+ // Delivery notifications are ignored (optional metric)
33
+ }
34
+ export async function snsWebhookRoutes(app, opts) {
35
+ const { db, jwtSecret } = opts;
36
+ // SNS webhook — public, no auth
37
+ app.post('/api/email/sns', async (request, reply) => {
38
+ const body = request.body;
39
+ // Handle subscription confirmation
40
+ if (body.Type === 'SubscriptionConfirmation' && body.SubscribeURL) {
41
+ try {
42
+ await fetch(body.SubscribeURL);
43
+ }
44
+ catch { /* ignore */ }
45
+ return reply.send({ ok: true });
46
+ }
47
+ // Process notification
48
+ if (body.Type === 'Notification') {
49
+ try {
50
+ const message = typeof body.Message === 'string' ? JSON.parse(body.Message) : body.Message;
51
+ processSnsNotification(db, message);
52
+ }
53
+ catch { /* ignore malformed */ }
54
+ }
55
+ return reply.send({ ok: true });
56
+ });
57
+ // Unsubscribe — public, token-based
58
+ app.get('/api/email/unsubscribe/:token', async (request, reply) => {
59
+ try {
60
+ const decoded = jwt.verify(request.params.token, jwtSecret);
61
+ if (decoded.action !== 'unsubscribe' || !decoded.email) {
62
+ return reply.status(400).send({ error: 'Invalid token' });
63
+ }
64
+ db.prepare("UPDATE leads SET status = 'unsubscribed', updated_at = ? WHERE email = ?").run(new Date().toISOString(), decoded.email);
65
+ db.prepare("DELETE FROM email_queue WHERE lead_id = (SELECT id FROM leads WHERE email = ?) AND status = 'pending'").run(decoded.email);
66
+ return reply.type('text/html').send('<html><body><h2>You have been unsubscribed.</h2><p>You will no longer receive emails from us.</p></body></html>');
67
+ }
68
+ catch {
69
+ return reply.status(400).send({ error: 'Invalid or expired token' });
70
+ }
71
+ });
72
+ }
73
+ //# sourceMappingURL=sns-webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sns-webhook.js","sourceRoot":"","sources":["../../src/email/sns-webhook.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expxagents",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Multi-agent orchestration platform for AI squads",
5
5
  "author": "ExpxAgents",
6
6
  "license": "MIT",
@@ -49,7 +49,11 @@
49
49
  "dotenv": "^16.4.7",
50
50
  "fastify": "^5.2.1",
51
51
  "jsonwebtoken": "^9.0.2",
52
- "yaml": "^2.7.1"
52
+ "yaml": "^2.7.1",
53
+ "@aws-sdk/client-sesv2": "^3.800.0",
54
+ "@fastify/multipart": "^9.0.3",
55
+ "csv-parse": "^5.6.0",
56
+ "node-cron": "^4.0.7"
53
57
  },
54
58
  "devDependencies": {
55
59
  "@types/js-yaml": "^4.0.9",