@realtimex/email-automator 2.1.1 → 2.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.
Files changed (48) hide show
  1. package/api/server.ts +0 -6
  2. package/api/src/config/index.ts +3 -0
  3. package/bin/email-automator-setup.js +2 -3
  4. package/bin/email-automator.js +23 -7
  5. package/package.json +1 -2
  6. package/src/App.tsx +0 -622
  7. package/src/components/AccountSettings.tsx +0 -310
  8. package/src/components/AccountSettingsPage.tsx +0 -390
  9. package/src/components/Configuration.tsx +0 -1345
  10. package/src/components/Dashboard.tsx +0 -940
  11. package/src/components/ErrorBoundary.tsx +0 -71
  12. package/src/components/LiveTerminal.tsx +0 -308
  13. package/src/components/LoadingSpinner.tsx +0 -39
  14. package/src/components/Login.tsx +0 -371
  15. package/src/components/Logo.tsx +0 -57
  16. package/src/components/SetupWizard.tsx +0 -388
  17. package/src/components/Toast.tsx +0 -109
  18. package/src/components/migration/MigrationBanner.tsx +0 -97
  19. package/src/components/migration/MigrationModal.tsx +0 -458
  20. package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
  21. package/src/components/mode-toggle.tsx +0 -24
  22. package/src/components/theme-provider.tsx +0 -72
  23. package/src/components/ui/alert.tsx +0 -66
  24. package/src/components/ui/button.tsx +0 -57
  25. package/src/components/ui/card.tsx +0 -75
  26. package/src/components/ui/dialog.tsx +0 -133
  27. package/src/components/ui/input.tsx +0 -22
  28. package/src/components/ui/label.tsx +0 -24
  29. package/src/components/ui/otp-input.tsx +0 -184
  30. package/src/context/AppContext.tsx +0 -422
  31. package/src/context/MigrationContext.tsx +0 -53
  32. package/src/context/TerminalContext.tsx +0 -31
  33. package/src/core/actions.ts +0 -76
  34. package/src/core/auth.ts +0 -108
  35. package/src/core/intelligence.ts +0 -76
  36. package/src/core/processor.ts +0 -112
  37. package/src/hooks/useRealtimeEmails.ts +0 -111
  38. package/src/index.css +0 -140
  39. package/src/lib/api-config.ts +0 -42
  40. package/src/lib/api-old.ts +0 -228
  41. package/src/lib/api.ts +0 -421
  42. package/src/lib/migration-check.ts +0 -264
  43. package/src/lib/sounds.ts +0 -120
  44. package/src/lib/supabase-config.ts +0 -117
  45. package/src/lib/supabase.ts +0 -28
  46. package/src/lib/types.ts +0 -166
  47. package/src/lib/utils.ts +0 -6
  48. package/src/main.tsx +0 -10
@@ -1,228 +0,0 @@
1
- import { getSupabaseConfig } from './supabase-config';
2
-
3
- const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3002';
4
-
5
- interface ApiOptions extends RequestInit {
6
- auth?: boolean;
7
- }
8
-
9
- interface ApiResponse<T> {
10
- data?: T;
11
- error?: { code: string; message: string };
12
- }
13
-
14
- class ApiClient {
15
- private baseUrl: string;
16
- private token: string | null = null;
17
-
18
- constructor(baseUrl: string) {
19
- this.baseUrl = baseUrl;
20
- }
21
-
22
- setToken(token: string | null) {
23
- this.token = token;
24
- }
25
-
26
- private async request<T>(
27
- endpoint: string,
28
- options: ApiOptions = {}
29
- ): Promise<ApiResponse<T>> {
30
- const { auth = true, ...fetchOptions } = options;
31
-
32
- const headers: HeadersInit = {
33
- 'Content-Type': 'application/json',
34
- ...(options.headers || {}),
35
- };
36
-
37
- if (auth && this.token) {
38
- (headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`;
39
- }
40
-
41
- try {
42
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
43
- ...fetchOptions,
44
- headers,
45
- });
46
-
47
- if (!response.ok) {
48
- const errorData = await response.json().catch(() => ({}));
49
- return {
50
- error: {
51
- code: errorData.error?.code || 'API_ERROR',
52
- message: errorData.error?.message || `Request failed: ${response.status}`,
53
- },
54
- };
55
- }
56
-
57
- const data = await response.json();
58
- return { data };
59
- } catch (error) {
60
- return {
61
- error: {
62
- code: 'NETWORK_ERROR',
63
- message: error instanceof Error ? error.message : 'Network error',
64
- },
65
- };
66
- }
67
- }
68
-
69
- // Auth endpoints
70
- async getGmailAuthUrl() {
71
- return this.request<{ url: string }>('/api/auth/gmail/url', { method: 'GET' });
72
- }
73
-
74
- async connectGmail(code: string) {
75
- return this.request<{ success: boolean; account: any }>('/api/auth/gmail/callback', {
76
- method: 'POST',
77
- body: JSON.stringify({ code }),
78
- });
79
- }
80
-
81
- async startMicrosoftDeviceFlow() {
82
- return this.request<{ userCode: string; verificationUri: string; message: string }>(
83
- '/api/auth/microsoft/device-flow',
84
- { method: 'POST' }
85
- );
86
- }
87
-
88
- async getAccounts() {
89
- return this.request<{ accounts: any[] }>('/api/auth/accounts');
90
- }
91
-
92
- async disconnectAccount(accountId: string) {
93
- return this.request<{ success: boolean }>(`/api/auth/accounts/${accountId}`, {
94
- method: 'DELETE',
95
- });
96
- }
97
-
98
- // Sync endpoints
99
- async triggerSync(accountId: string) {
100
- return this.request<{ message: string }>('/api/sync', {
101
- method: 'POST',
102
- body: JSON.stringify({ accountId }),
103
- });
104
- }
105
-
106
- async syncAll() {
107
- return this.request<{ message: string; accountCount: number }>('/api/sync/all', {
108
- method: 'POST',
109
- });
110
- }
111
-
112
- async getSyncLogs(limit = 10) {
113
- return this.request<{ logs: any[] }>(`/api/sync/logs?limit=${limit}`);
114
- }
115
-
116
- // Email endpoints
117
- async getEmails(params: {
118
- limit?: number;
119
- offset?: number;
120
- category?: string;
121
- search?: string;
122
- } = {}) {
123
- const query = new URLSearchParams();
124
- if (params.limit) query.set('limit', params.limit.toString());
125
- if (params.offset) query.set('offset', params.offset.toString());
126
- if (params.category) query.set('category', params.category);
127
- if (params.search) query.set('search', params.search);
128
-
129
- return this.request<{ emails: any[]; total: number }>(`/api/emails?${query}`);
130
- }
131
-
132
- async getEmail(emailId: string) {
133
- return this.request<{ email: any }>(`/api/emails/${emailId}`);
134
- }
135
-
136
- async getCategorySummary() {
137
- return this.request<{ categories: Record<string, number> }>('/api/emails/summary/categories');
138
- }
139
-
140
- // Action endpoints
141
- async executeAction(emailId: string, action: string, draftContent?: string) {
142
- return this.request<{ success: boolean; details?: string }>('/api/actions/execute', {
143
- method: 'POST',
144
- body: JSON.stringify({ emailId, action, draftContent }),
145
- });
146
- }
147
-
148
- async generateDraft(emailId: string, instructions?: string) {
149
- return this.request<{ draft: string }>(`/api/actions/draft/${emailId}`, {
150
- method: 'POST',
151
- body: JSON.stringify({ instructions }),
152
- });
153
- }
154
-
155
- async bulkAction(emailIds: string[], action: string) {
156
- return this.request<{ success: number; failed: number }>('/api/actions/bulk', {
157
- method: 'POST',
158
- body: JSON.stringify({ emailIds, action }),
159
- });
160
- }
161
-
162
- // Rules endpoints
163
- async getRules() {
164
- return this.request<{ rules: any[] }>('/api/rules');
165
- }
166
-
167
- async createRule(rule: { name: string; condition: any; action: string; is_enabled?: boolean }) {
168
- return this.request<{ rule: any }>('/api/rules', {
169
- method: 'POST',
170
- body: JSON.stringify(rule),
171
- });
172
- }
173
-
174
- async updateRule(ruleId: string, updates: any) {
175
- return this.request<{ rule: any }>(`/api/rules/${ruleId}`, {
176
- method: 'PATCH',
177
- body: JSON.stringify(updates),
178
- });
179
- }
180
-
181
- async deleteRule(ruleId: string) {
182
- return this.request<{ success: boolean }>(`/api/rules/${ruleId}`, {
183
- method: 'DELETE',
184
- });
185
- }
186
-
187
- async toggleRule(ruleId: string) {
188
- return this.request<{ rule: any }>(`/api/rules/${ruleId}/toggle`, {
189
- method: 'POST',
190
- });
191
- }
192
-
193
- // Settings endpoints
194
- async getSettings() {
195
- return this.request<{ settings: any }>('/api/settings');
196
- }
197
-
198
- async updateSettings(settings: any) {
199
- return this.request<{ settings: any }>('/api/settings', {
200
- method: 'PATCH',
201
- body: JSON.stringify(settings),
202
- });
203
- }
204
-
205
- async getStats() {
206
- return this.request<{ stats: any }>('/api/settings/stats');
207
- }
208
-
209
- // Health check
210
- async healthCheck() {
211
- return this.request<{ status: string; services: any }>('/api/health', { auth: false });
212
- }
213
- }
214
-
215
- export const api = new ApiClient(API_BASE_URL);
216
-
217
- // Helper to initialize API with auth token from Supabase session
218
- export async function initializeApi(supabase: any) {
219
- const { data: { session } } = await supabase.auth.getSession();
220
- if (session?.access_token) {
221
- api.setToken(session.access_token);
222
- }
223
-
224
- // Listen for auth changes
225
- supabase.auth.onAuthStateChange((_event: string, session: any) => {
226
- api.setToken(session?.access_token || null);
227
- });
228
- }
package/src/lib/api.ts DELETED
@@ -1,421 +0,0 @@
1
- /**
2
- * Hybrid API Client for Email Automator
3
- *
4
- * Architecture:
5
- * - Edge Functions: Auth, OAuth, Database operations (via Supabase)
6
- * - Express API (Local App): Email sync, AI processing
7
- */
8
-
9
- import { getApiConfig } from './api-config';
10
- import { EmailAccount } from './types';
11
-
12
- interface ApiOptions extends RequestInit {
13
- auth?: boolean;
14
- }
15
-
16
- interface ApiResponse<T> {
17
- data?: T;
18
- error?: { code?: string; message: string } | string;
19
- }
20
-
21
- class HybridApiClient {
22
- private edgeFunctionsUrl: string;
23
- private expressApiUrl: string;
24
- private anonKey: string;
25
- private token: string | null = null;
26
- private supabaseClient: any = null;
27
-
28
- constructor() {
29
- const config = getApiConfig();
30
- this.edgeFunctionsUrl = config.edgeFunctionsUrl;
31
- this.expressApiUrl = config.expressApiUrl;
32
- this.anonKey = config.anonKey;
33
- }
34
-
35
- setSupabaseClient(client: any) {
36
- this.supabaseClient = client;
37
- }
38
-
39
- setToken(token: string | null) {
40
- if (token) {
41
- console.debug('[HybridApiClient] Token updated');
42
- } else {
43
- console.debug('[HybridApiClient] Token cleared');
44
- }
45
- this.token = token;
46
- }
47
-
48
- private async request<T>(
49
- baseUrl: string,
50
- endpoint: string,
51
- options: ApiOptions = {}
52
- ): Promise<ApiResponse<T>> {
53
- const { auth = true, ...fetchOptions } = options;
54
-
55
- const headers: HeadersInit = {
56
- 'Content-Type': 'application/json',
57
- ...(options.headers || {}),
58
- };
59
-
60
- // Fallback: try to get token from supabaseClient if missing
61
- if (auth && !this.token && this.supabaseClient) {
62
- const { data: { session } } = await this.supabaseClient.auth.getSession();
63
- if (session?.access_token) {
64
- this.token = session.access_token;
65
- console.debug('[HybridApiClient] Recovered token from session');
66
- }
67
- }
68
-
69
- if (auth && !this.token) {
70
- console.warn(`[HybridApiClient] Attempted protected request to ${endpoint} without token`);
71
- return {
72
- error: {
73
- code: 'AUTH_REQUIRED',
74
- message: 'You must be logged in to perform this action',
75
- },
76
- };
77
- }
78
-
79
- if (auth && this.token) {
80
- (headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`;
81
- }
82
-
83
- try {
84
- console.debug(`[HybridApiClient] ${options.method || 'GET'} ${baseUrl}${endpoint}`);
85
- const response = await fetch(`${baseUrl}${endpoint}`, {
86
- ...fetchOptions,
87
- headers,
88
- });
89
-
90
- if (!response.ok) {
91
- const errorData = await response.json().catch(() => ({}));
92
- const errorMessage = typeof errorData.error === 'string'
93
- ? errorData.error
94
- : errorData.error?.message || `Request failed: ${response.status}`;
95
-
96
- return {
97
- error: {
98
- code: errorData.error?.code || 'API_ERROR',
99
- message: errorMessage,
100
- },
101
- };
102
- }
103
-
104
- const data = await response.json();
105
- return { data };
106
- } catch (error) {
107
- return {
108
- error: {
109
- code: 'NETWORK_ERROR',
110
- message: error instanceof Error ? error.message : 'Network error',
111
- },
112
- };
113
- }
114
- }
115
-
116
- // Edge Functions requests
117
- private edgeRequest<T>(endpoint: string, options?: ApiOptions) {
118
- // Supabase Gateway requires 'apikey' header for all requests
119
- const headers = {
120
- ...(options?.headers || {}),
121
- apikey: this.anonKey,
122
- };
123
-
124
- return this.request<T>(this.edgeFunctionsUrl, endpoint, {
125
- ...options,
126
- headers,
127
- });
128
- }
129
-
130
- // Express API requests - include Supabase config for backend to use
131
- private expressRequest<T>(endpoint: string, options?: ApiOptions) {
132
- const config = getApiConfig();
133
- const headers = {
134
- ...(options?.headers || {}),
135
- // Pass Supabase config so backend can connect dynamically
136
- 'X-Supabase-Url': config.edgeFunctionsUrl.replace('/functions/v1', ''),
137
- 'X-Supabase-Anon-Key': config.anonKey,
138
- };
139
-
140
- return this.request<T>(this.expressApiUrl, endpoint, {
141
- ...options,
142
- headers,
143
- });
144
- }
145
-
146
- // ============================================================================
147
- // AUTH & OAUTH ENDPOINTS (Edge Functions)
148
- // ============================================================================
149
-
150
- async getGmailAuthUrl() {
151
- return this.edgeRequest<{ url: string }>('/auth-gmail?action=url', {
152
- method: 'GET',
153
- });
154
- }
155
-
156
- async connectGmail(code: string) {
157
- return this.edgeRequest<{ success: boolean; account: any }>('/auth-gmail', {
158
- method: 'POST',
159
- body: JSON.stringify({ code }),
160
- });
161
- }
162
-
163
- async startMicrosoftDeviceFlow() {
164
- return this.edgeRequest<{
165
- userCode: string;
166
- verificationUri: string;
167
- message: string;
168
- deviceCode: string;
169
- expiresIn: number;
170
- interval: number;
171
- }>('/auth-microsoft?action=device-flow', {
172
- method: 'POST',
173
- });
174
- }
175
-
176
- async pollMicrosoftDeviceCode(deviceCode: string) {
177
- return this.edgeRequest<{
178
- status: 'pending' | 'completed';
179
- account?: any
180
- }>('/auth-microsoft?action=poll', {
181
- method: 'POST',
182
- body: JSON.stringify({ deviceCode }),
183
- });
184
- }
185
-
186
- // ============================================================================
187
- // ACCOUNTS ENDPOINTS (Edge Functions)
188
- // ============================================================================
189
-
190
- async getAccounts() {
191
- return this.edgeRequest<{ accounts: any[] }>('/api-v1-accounts');
192
- }
193
-
194
- async disconnectAccount(accountId: string) {
195
- return this.edgeRequest<{ success: boolean }>(`/api-v1-accounts/${accountId}`, {
196
- method: 'DELETE',
197
- });
198
- }
199
-
200
- async updateAccount(accountId: string, updates: Partial<EmailAccount>) {
201
- return this.edgeRequest<{ account: EmailAccount }>(`/api-v1-accounts/${accountId}`, {
202
- method: 'PATCH',
203
- body: JSON.stringify(updates),
204
- });
205
- }
206
-
207
- // ============================================================================
208
- // EMAILS ENDPOINTS (Edge Functions)
209
- // ============================================================================
210
-
211
- async getEmails(params: {
212
- limit?: number;
213
- offset?: number;
214
- category?: string;
215
- search?: string;
216
- } = {}) {
217
- const query = new URLSearchParams();
218
- if (params.limit) query.set('limit', params.limit.toString());
219
- if (params.offset) query.set('offset', params.offset.toString());
220
- if (params.category) query.set('category', params.category);
221
- if (params.search) query.set('search', params.search);
222
-
223
- return this.edgeRequest<{ emails: any[]; total: number }>(`/api-v1-emails?${query}`);
224
- }
225
-
226
- async getEmail(emailId: string) {
227
- return this.edgeRequest<{ email: any }>(`/api-v1-emails/${emailId}`);
228
- }
229
-
230
- async getEmailEvents(emailId: string) {
231
- return this.edgeRequest<{ events: any[] }>(`/api-v1-emails/${emailId}/events`);
232
- }
233
-
234
- async deleteEmail(emailId: string) {
235
- return this.edgeRequest<{ success: boolean }>(`/api-v1-emails/${emailId}`, {
236
- method: 'DELETE',
237
- });
238
- }
239
-
240
- async getCategorySummary() {
241
- return this.edgeRequest<{ categories: Record<string, number> }>('/api-v1-emails/summary/categories');
242
- }
243
-
244
- // ============================================================================
245
- // RULES ENDPOINTS (Express API - Local App)
246
- // ============================================================================
247
-
248
- async getRules() {
249
- return this.expressRequest<{ rules: any[] }>('/api/rules');
250
- }
251
-
252
- async createRule(rule: { name: string; condition: any; action: string; is_enabled?: boolean, instructions?: string, attachments?: any[] }) {
253
- return this.expressRequest<{ rule: any }>('/api/rules', {
254
- method: 'POST',
255
- body: JSON.stringify(rule),
256
- });
257
- }
258
-
259
- async updateRule(ruleId: string, updates: any) {
260
- return this.expressRequest<{ rule: any }>(`/api/rules/${ruleId}`, {
261
- method: 'PATCH',
262
- body: JSON.stringify(updates),
263
- });
264
- }
265
-
266
- async deleteRule(ruleId: string) {
267
- return this.expressRequest<{ success: boolean }>(`/api/rules/${ruleId}`, {
268
- method: 'DELETE',
269
- });
270
- }
271
-
272
- async toggleRule(ruleId: string) {
273
- return this.expressRequest<{ rule: any }>(`/api/rules/${ruleId}/toggle`, {
274
- method: 'POST',
275
- });
276
- }
277
-
278
- // ============================================================================
279
- // SETTINGS ENDPOINTS (Edge Functions)
280
- // ============================================================================
281
-
282
- async getSettings() {
283
- return this.edgeRequest<{ settings: any }>('/api-v1-settings');
284
- }
285
-
286
- async getRunEvents(runId: string) {
287
- return this.edgeRequest<{ events: any[] }>(`/api-v1-settings/logs/${runId}/events`);
288
- }
289
-
290
- async updateSettings(settings: any) {
291
- return this.edgeRequest<{ settings: any }>('/api-v1-settings', {
292
- method: 'PATCH',
293
- body: JSON.stringify(settings),
294
- });
295
- }
296
-
297
- async getStats() {
298
- return this.edgeRequest<{ stats: any }>('/api-v1-settings/stats');
299
- }
300
-
301
- async testLlm(config: { llm_model: string | null; llm_base_url: string | null; llm_api_key: string | null }) {
302
- return this.expressRequest<{ success: boolean; message: string }>('/api/settings/test-llm', {
303
- method: 'POST',
304
- body: JSON.stringify(config),
305
- });
306
- }
307
-
308
- // ============================================================================
309
- // SYNC ENDPOINTS (Express API - Local App)
310
- // ============================================================================
311
-
312
- async triggerSync(accountId: string) {
313
- return this.expressRequest<{ message: string }>('/api/sync', {
314
- method: 'POST',
315
- body: JSON.stringify({ accountId }),
316
- });
317
- }
318
-
319
- async syncAll() {
320
- return this.expressRequest<{ message: string; accountCount: number }>('/api/sync/all', {
321
- method: 'POST',
322
- });
323
- }
324
-
325
- async getSyncLogs(limit = 10) {
326
- return this.expressRequest<{ logs: any[] }>(`/api/sync/logs?limit=${limit}`);
327
- }
328
-
329
- // ============================================================================
330
- // ACTION ENDPOINTS (Express API - Local App)
331
- // ============================================================================
332
-
333
- async executeAction(emailId: string, action: string, draftContent?: string) {
334
- return this.expressRequest<{ success: boolean; details?: string }>('/api/actions/execute', {
335
- method: 'POST',
336
- body: JSON.stringify({ emailId, action, draftContent }),
337
- });
338
- }
339
-
340
- async generateDraft(emailId: string, instructions?: string) {
341
- return this.expressRequest<{ draft: string }>(`/api/actions/draft/${emailId}`, {
342
- method: 'POST',
343
- body: JSON.stringify({ instructions }),
344
- });
345
- }
346
-
347
- async bulkAction(emailIds: string[], action: string) {
348
- return this.expressRequest<{ success: number; failed: number }>('/api/actions/bulk', {
349
- method: 'POST',
350
- body: JSON.stringify({ emailIds, action }),
351
- });
352
- }
353
-
354
- // ============================================================================
355
- // HEALTH CHECK (Express API)
356
- // ============================================================================
357
-
358
- async healthCheck() {
359
- return this.expressRequest<{ status: string; services: any }>('/api/health', {
360
- auth: false
361
- });
362
- }
363
-
364
- // ============================================================================
365
- // PROFILE & SECURITY (Edge Functions / Supabase Auth)
366
- // ============================================================================
367
-
368
- async getProfile() {
369
- if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
370
- const { data: { user } } = await this.supabaseClient.auth.getUser();
371
- if (!user) return { error: 'Not authenticated' };
372
-
373
- const { data, error } = await this.supabaseClient
374
- .from('profiles')
375
- .select('*')
376
- .eq('id', user.id)
377
- .single();
378
-
379
- return { data, error };
380
- }
381
-
382
- async updateProfile(updates: { first_name?: string; last_name?: string; avatar_url?: string }) {
383
- if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
384
- const { data: { user } } = await this.supabaseClient.auth.getUser();
385
- if (!user) return { error: 'Not authenticated' };
386
-
387
- const { data, error } = await this.supabaseClient
388
- .from('profiles')
389
- .update({
390
- ...updates,
391
- updated_at: new Date().toISOString()
392
- })
393
- .eq('id', user.id)
394
- .select()
395
- .single();
396
-
397
- return { data, error };
398
- }
399
-
400
- async changePassword(password: string) {
401
- if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
402
- const { error } = await this.supabaseClient.auth.updateUser({ password });
403
- return { success: !error, error };
404
- }
405
- }
406
-
407
- export const api = new HybridApiClient();
408
-
409
- // Helper to initialize API with auth token from Supabase session
410
- export async function initializeApi(supabase: any) {
411
- api.setSupabaseClient(supabase);
412
- const { data: { session } } = await supabase.auth.getSession();
413
- if (session?.access_token) {
414
- api.setToken(session.access_token);
415
- }
416
-
417
- // Listen for auth changes
418
- supabase.auth.onAuthStateChange((_event: string, session: any) => {
419
- api.setToken(session?.access_token || null);
420
- });
421
- }