@realtimex/email-automator 2.2.0 → 2.3.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 (74) hide show
  1. package/api/server.ts +4 -8
  2. package/api/src/config/index.ts +6 -3
  3. package/bin/email-automator-setup.js +2 -3
  4. package/bin/email-automator.js +7 -11
  5. package/dist/api/server.js +109 -0
  6. package/dist/api/src/config/index.js +88 -0
  7. package/dist/api/src/middleware/auth.js +119 -0
  8. package/dist/api/src/middleware/errorHandler.js +78 -0
  9. package/dist/api/src/middleware/index.js +4 -0
  10. package/dist/api/src/middleware/rateLimit.js +57 -0
  11. package/dist/api/src/middleware/validation.js +111 -0
  12. package/dist/api/src/routes/actions.js +173 -0
  13. package/dist/api/src/routes/auth.js +106 -0
  14. package/dist/api/src/routes/emails.js +100 -0
  15. package/dist/api/src/routes/health.js +33 -0
  16. package/dist/api/src/routes/index.js +19 -0
  17. package/dist/api/src/routes/migrate.js +61 -0
  18. package/dist/api/src/routes/rules.js +104 -0
  19. package/dist/api/src/routes/settings.js +178 -0
  20. package/dist/api/src/routes/sync.js +118 -0
  21. package/dist/api/src/services/eventLogger.js +41 -0
  22. package/dist/api/src/services/gmail.js +350 -0
  23. package/dist/api/src/services/intelligence.js +243 -0
  24. package/dist/api/src/services/microsoft.js +256 -0
  25. package/dist/api/src/services/processor.js +503 -0
  26. package/dist/api/src/services/scheduler.js +210 -0
  27. package/dist/api/src/services/supabase.js +59 -0
  28. package/dist/api/src/utils/contentCleaner.js +94 -0
  29. package/dist/api/src/utils/crypto.js +68 -0
  30. package/dist/api/src/utils/logger.js +119 -0
  31. package/package.json +5 -5
  32. package/src/App.tsx +0 -622
  33. package/src/components/AccountSettings.tsx +0 -310
  34. package/src/components/AccountSettingsPage.tsx +0 -390
  35. package/src/components/Configuration.tsx +0 -1345
  36. package/src/components/Dashboard.tsx +0 -940
  37. package/src/components/ErrorBoundary.tsx +0 -71
  38. package/src/components/LiveTerminal.tsx +0 -308
  39. package/src/components/LoadingSpinner.tsx +0 -39
  40. package/src/components/Login.tsx +0 -371
  41. package/src/components/Logo.tsx +0 -57
  42. package/src/components/SetupWizard.tsx +0 -388
  43. package/src/components/Toast.tsx +0 -109
  44. package/src/components/migration/MigrationBanner.tsx +0 -97
  45. package/src/components/migration/MigrationModal.tsx +0 -458
  46. package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
  47. package/src/components/mode-toggle.tsx +0 -24
  48. package/src/components/theme-provider.tsx +0 -72
  49. package/src/components/ui/alert.tsx +0 -66
  50. package/src/components/ui/button.tsx +0 -57
  51. package/src/components/ui/card.tsx +0 -75
  52. package/src/components/ui/dialog.tsx +0 -133
  53. package/src/components/ui/input.tsx +0 -22
  54. package/src/components/ui/label.tsx +0 -24
  55. package/src/components/ui/otp-input.tsx +0 -184
  56. package/src/context/AppContext.tsx +0 -422
  57. package/src/context/MigrationContext.tsx +0 -53
  58. package/src/context/TerminalContext.tsx +0 -31
  59. package/src/core/actions.ts +0 -76
  60. package/src/core/auth.ts +0 -108
  61. package/src/core/intelligence.ts +0 -76
  62. package/src/core/processor.ts +0 -112
  63. package/src/hooks/useRealtimeEmails.ts +0 -111
  64. package/src/index.css +0 -140
  65. package/src/lib/api-config.ts +0 -42
  66. package/src/lib/api-old.ts +0 -228
  67. package/src/lib/api.ts +0 -421
  68. package/src/lib/migration-check.ts +0 -264
  69. package/src/lib/sounds.ts +0 -120
  70. package/src/lib/supabase-config.ts +0 -117
  71. package/src/lib/supabase.ts +0 -28
  72. package/src/lib/types.ts +0 -166
  73. package/src/lib/utils.ts +0 -6
  74. package/src/main.tsx +0 -10
@@ -1,76 +0,0 @@
1
- import OpenAI from 'openai';
2
- import Instructor from '@instructor-ai/instructor';
3
- import { z } from 'zod';
4
-
5
- // Define the schema for email analysis
6
- export const EmailAnalysisSchema = z.object({
7
- summary: z.string().describe("A brief summary of the email content"),
8
- category: z.enum(['spam', 'newsletter', 'support', 'client', 'internal', 'personal', 'other'])
9
- .describe("The category of the email"),
10
- sentiment: z.enum(['Positive', 'Neutral', 'Negative'])
11
- .describe("The emotional tone of the email"),
12
- is_useless: z.boolean()
13
- .describe("Whether the email is considered useless (spam, newsletter, etc.)"),
14
- suggested_action: z.enum(['none', 'delete', 'archive', 'reply', 'flag'])
15
- .describe("The recommended next action"),
16
- draft_response: z.string().optional()
17
- .describe("A suggested draft response if the action is 'reply'"),
18
- priority: z.enum(['High', 'Medium', 'Low'])
19
- .describe("The urgency of the email")
20
- });
21
-
22
- export type EmailAnalysis = z.infer<typeof EmailAnalysisSchema>;
23
-
24
- export class IntelligenceLayer {
25
- private client;
26
-
27
- constructor() {
28
- if (!process.env.LLM_API_KEY) {
29
- console.warn('LLM_API_KEY is missing. AI analysis will not work.');
30
- this.client = null as any;
31
- return;
32
- }
33
-
34
- const oai = new OpenAI({
35
- apiKey: process.env.LLM_API_KEY,
36
- baseURL: process.env.LLM_BASE_URL,
37
- });
38
-
39
- this.client = Instructor({
40
- client: oai,
41
- mode: 'JSON'
42
- });
43
- }
44
-
45
- async analyzeEmail(content: string, context: { subject: string; sender: string; date: string }): Promise<EmailAnalysis | null> {
46
- try {
47
- const response = await this.client.chat.completions.create({
48
- model: process.env.LLM_MODEL || 'gpt-4o-mini',
49
- messages: [
50
- {
51
- role: 'system',
52
- content: `You are an AI Email Assistant. Analyze the provided email and extract structured information.
53
- Context Date: ${new Date().toISOString()}
54
- Subject: ${context.subject}
55
- From: ${context.sender}
56
- Date: ${context.date}`
57
- },
58
- {
59
- role: 'user',
60
- content: content
61
- }
62
- ],
63
- response_model: {
64
- schema: EmailAnalysisSchema,
65
- name: "EmailAnalysis"
66
- },
67
- max_retries: 3
68
- });
69
-
70
- return response;
71
- } catch (error) {
72
- console.error('AI Analysis Error:', error);
73
- return null;
74
- }
75
- }
76
- }
@@ -1,112 +0,0 @@
1
- import { google } from 'googleapis';
2
- import { IntelligenceLayer } from './intelligence';
3
-
4
- export class EmailProcessor {
5
- private ai: IntelligenceLayer;
6
- private supabase: any;
7
-
8
- constructor(supabaseClient?: any) {
9
- this.ai = new IntelligenceLayer();
10
- this.supabase = supabaseClient;
11
- }
12
-
13
- async syncAccount(accountId: string) {
14
- if (!this.supabase) throw new Error('Supabase client not configured');
15
-
16
- // 1. Fetch account details
17
- const { data: account, error: accError } = await this.supabase
18
- .from('email_accounts')
19
- .select('*')
20
- .eq('id', accountId)
21
- .single();
22
-
23
- if (accError || !account) throw accError || new Error('Account not found');
24
-
25
- // 2. Setup client based on provider
26
- if (account.provider === 'gmail') {
27
- await this.syncGmail(account);
28
- } else {
29
- // TODO: Implement Outlook sync
30
- console.log('Outlook sync not implemented yet');
31
- }
32
- }
33
-
34
- private async syncGmail(account: any) {
35
- const auth = new google.auth.OAuth2(
36
- process.env.GMAIL_CLIENT_ID,
37
- process.env.GMAIL_CLIENT_SECRET
38
- );
39
-
40
- auth.setCredentials({
41
- access_token: account.access_token,
42
- refresh_token: account.refresh_token,
43
- expiry_date: account.token_expires_at ? new Date(account.token_expires_at).getTime() : undefined
44
- });
45
-
46
- const gmail = google.gmail({ version: 'v1', auth });
47
-
48
- // 3. List messages (simplification: last 20)
49
- const response = await gmail.users.messages.list({
50
- userId: 'me',
51
- maxResults: 20
52
- });
53
-
54
- const messages = response.data.messages || [];
55
-
56
- for (const msg of messages) {
57
- if (!msg.id) continue;
58
-
59
- // Check if already processed
60
- const { data: existing } = await this.supabase
61
- .from('emails')
62
- .select('id')
63
- .eq('account_id', account.id)
64
- .eq('external_id', msg.id)
65
- .single();
66
-
67
- if (existing) continue;
68
-
69
- // 4. Get message details
70
- const detail = await gmail.users.messages.get({
71
- userId: 'me',
72
- id: msg.id
73
- });
74
-
75
- const payload = detail.data.payload;
76
- const headers = payload?.headers || [];
77
- const subject = headers.find(h => h.name === 'Subject')?.value || 'No Subject';
78
- const sender = headers.find(h => h.name === 'From')?.value || 'Unknown';
79
- const date = headers.find(h => h.name === 'Date')?.value || '';
80
-
81
- // Extract body (very simplified)
82
- let body = '';
83
- if (payload?.parts) {
84
- body = Buffer.from(payload.parts[0].body?.data || '', 'base64').toString();
85
- } else {
86
- body = Buffer.from(payload?.body?.data || '', 'base64').toString();
87
- }
88
-
89
- // 5. Analyze with AI
90
- const analysis = await this.ai.analyzeEmail(body, { subject, sender, date });
91
-
92
- // 6. Save to Supabase
93
- if (analysis) {
94
- await this.supabase.from('emails').insert({
95
- account_id: account.id,
96
- external_id: msg.id,
97
- subject,
98
- sender,
99
- recipient: 'me', // TODO: extract from headers
100
- date: date ? new Date(date).toISOString() : null,
101
- body_snippet: body.substring(0, 500),
102
- category: analysis.category,
103
- is_useless: analysis.is_useless,
104
- ai_analysis: analysis,
105
- suggested_action: analysis.suggested_action
106
- });
107
-
108
- // TODO: Execute automation rules here
109
- }
110
- }
111
- }
112
- }
@@ -1,111 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { supabase } from '../lib/supabase';
3
- import { Email } from '../lib/types';
4
-
5
- interface UseRealtimeEmailsOptions {
6
- userId?: string;
7
- onInsert?: (email: Email) => void;
8
- onUpdate?: (email: Email) => void;
9
- onDelete?: (emailId: string) => void;
10
- enabled?: boolean;
11
- }
12
-
13
- export function useRealtimeEmails({
14
- userId,
15
- onInsert,
16
- onUpdate,
17
- onDelete,
18
- enabled = true,
19
- }: UseRealtimeEmailsOptions) {
20
- const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);
21
-
22
- useEffect(() => {
23
- if (!enabled || !userId) return;
24
-
25
- // Subscribe to emails table changes
26
- const channel = supabase
27
- .channel('emails-realtime')
28
- .on(
29
- 'postgres_changes',
30
- {
31
- event: 'INSERT',
32
- schema: 'public',
33
- table: 'emails',
34
- },
35
- (payload) => {
36
- onInsert?.(payload.new as Email);
37
- }
38
- )
39
- .on(
40
- 'postgres_changes',
41
- {
42
- event: 'UPDATE',
43
- schema: 'public',
44
- table: 'emails',
45
- },
46
- (payload) => {
47
- onUpdate?.(payload.new as Email);
48
- }
49
- )
50
- .on(
51
- 'postgres_changes',
52
- {
53
- event: 'DELETE',
54
- schema: 'public',
55
- table: 'emails',
56
- },
57
- (payload) => {
58
- onDelete?.(payload.old.id);
59
- }
60
- )
61
- .subscribe();
62
-
63
- channelRef.current = channel;
64
-
65
- return () => {
66
- if (channelRef.current) {
67
- supabase.removeChannel(channelRef.current);
68
- }
69
- };
70
- }, [userId, enabled, onInsert, onUpdate, onDelete]);
71
-
72
- return {
73
- isSubscribed: !!channelRef.current,
74
- };
75
- }
76
-
77
- export function useRealtimeProcessingLogs({
78
- userId,
79
- onNewLog,
80
- enabled = true,
81
- }: {
82
- userId?: string;
83
- onNewLog?: (log: any) => void;
84
- enabled?: boolean;
85
- }) {
86
- useEffect(() => {
87
- if (!enabled || !userId) return;
88
-
89
- const channel = supabase
90
- .channel('processing-logs-realtime')
91
- .on(
92
- 'postgres_changes',
93
- {
94
- event: '*',
95
- schema: 'public',
96
- table: 'processing_logs',
97
- filter: `user_id=eq.${userId}`,
98
- },
99
- (payload) => {
100
- if (payload.eventType === 'INSERT' || payload.eventType === 'UPDATE') {
101
- onNewLog?.(payload.new);
102
- }
103
- }
104
- )
105
- .subscribe();
106
-
107
- return () => {
108
- supabase.removeChannel(channel);
109
- };
110
- }, [userId, enabled, onNewLog]);
111
- }
package/src/index.css DELETED
@@ -1,140 +0,0 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
2
- @import 'tailwindcss';
3
-
4
- @theme {
5
- --color-border: oklch(var(--border));
6
- --color-input: oklch(var(--input));
7
- --color-ring: oklch(var(--ring));
8
- --color-background: oklch(var(--background));
9
- --color-foreground: oklch(var(--foreground));
10
-
11
- --color-primary: oklch(var(--primary));
12
- --color-primary-foreground: oklch(var(--primary-foreground));
13
-
14
- --color-secondary: oklch(var(--secondary));
15
- --color-secondary-foreground: oklch(var(--secondary-foreground));
16
-
17
- --color-destructive: oklch(var(--destructive));
18
- --color-destructive-foreground: oklch(var(--destructive-foreground));
19
-
20
- --color-muted: oklch(var(--muted));
21
- --color-muted-foreground: oklch(var(--muted-foreground));
22
-
23
- --color-accent: oklch(var(--accent));
24
- --color-accent-foreground: oklch(var(--accent-foreground));
25
-
26
- --color-popover: oklch(var(--popover));
27
- --color-popover-foreground: oklch(var(--popover-foreground));
28
-
29
- --color-card: oklch(var(--card));
30
- --color-card-foreground: oklch(var(--card-foreground));
31
-
32
- --radius-lg: var(--radius);
33
- --radius-md: calc(var(--radius) - 2px);
34
- --radius-sm: calc(var(--radius) - 4px);
35
-
36
- --animate-accordion-down: accordion-down 0.2s ease-out;
37
- --animate-accordion-up: accordion-up 0.2s ease-out;
38
-
39
- @keyframes accordion-down {
40
- from {
41
- height: 0;
42
- }
43
-
44
- to {
45
- height: var(--radix-accordion-content-height);
46
- }
47
- }
48
-
49
- @keyframes accordion-up {
50
- from {
51
- height: var(--radix-accordion-content-height);
52
- }
53
-
54
- to {
55
- height: 0;
56
- }
57
- }
58
- }
59
-
60
- @utility container {
61
- margin-inline: auto;
62
- padding-inline: 2rem;
63
-
64
- @media (width >=--theme(--breakpoint-sm)) {
65
- max-width: none;
66
- }
67
-
68
- @media (width >=1400px) {
69
- max-width: 1400px;
70
- }
71
- }
72
-
73
- @layer base {
74
- :root {
75
- /* Light Mode - OKLCH */
76
- --background: 1 0 0;
77
- /* Pure White */
78
- --foreground: 0.145 0 0;
79
- /* Off-Black */
80
- --card: 1 0 0;
81
- --card-foreground: 0.145 0 0;
82
- --popover: 1 0 0;
83
- --popover-foreground: 0.145 0 0;
84
- --primary: 0.205 0 0;
85
- /* Deep Black/Gray */
86
- --primary-foreground: 0.985 0 0;
87
- --secondary: 0.97 0 0;
88
- /* Very Light Gray */
89
- --secondary-foreground: 0.205 0 0;
90
- --muted: 0.97 0 0;
91
- --muted-foreground: 0.556 0 0;
92
- --accent: 0.97 0 0;
93
- --accent-foreground: 0.205 0 0;
94
- --destructive: 0.577 0.245 27.325;
95
- --destructive-foreground: 0.985 0 0;
96
- --border: 0.922 0 0;
97
- --input: 0.922 0 0;
98
- --ring: 0.205 0 0;
99
- --radius: 0.5rem;
100
- }
101
-
102
- .dark {
103
- /* Dark Mode - OKLCH */
104
- --background: 0.145 0 0;
105
- /* Dark Gray/Black */
106
- --foreground: 0.985 0 0;
107
- /* Off-White */
108
- --card: 0.145 0 0;
109
- --card-foreground: 0.985 0 0;
110
- --popover: 0.145 0 0;
111
- --popover-foreground: 0.985 0 0;
112
- --primary: 0.985 0 0;
113
- /* Light Gray/White */
114
- --primary-foreground: 0.145 0 0;
115
- --secondary: 0.269 0 0;
116
- --secondary-foreground: 0.985 0 0;
117
- --muted: 0.269 0 0;
118
- --muted-foreground: 0.708 0 0;
119
- --accent: 0.269 0 0;
120
- --accent-foreground: 0.985 0 0;
121
- --destructive: 0.396 0.141 25.723;
122
- --destructive-foreground: 0.985 0 0;
123
- --border: 0.269 0 0;
124
- --input: 0.269 0 0;
125
- --ring: 0.869 0 0;
126
- }
127
- }
128
-
129
- @layer base {
130
- * {
131
- border-color: var(--color-border);
132
- outline-color: var(--color-ring);
133
- }
134
-
135
- body {
136
- background-color: var(--color-background);
137
- color: var(--color-foreground);
138
- font-family: 'Inter', system-ui, sans-serif;
139
- }
140
- }
@@ -1,42 +0,0 @@
1
- // API Configuration for Hybrid Architecture
2
- // - Edge Functions: Auth, OAuth, Database proxy
3
- // - Express API: Email sync, AI processing (Local App)
4
-
5
- import { getSupabaseConfig } from './supabase-config';
6
-
7
- export interface ApiConfig {
8
- edgeFunctionsUrl: string;
9
- expressApiUrl: string;
10
- anonKey: string;
11
- }
12
-
13
- export function getApiConfig(): ApiConfig {
14
- const supabaseConfig = getSupabaseConfig();
15
-
16
- // Edge Functions URL: https://PROJECT_ID.supabase.co/functions/v1
17
- const edgeFunctionsUrl = supabaseConfig
18
- ? `${supabaseConfig.url}/functions/v1`
19
- : '';
20
-
21
- const anonKey = supabaseConfig ? supabaseConfig.anonKey : '';
22
-
23
- // Express API URL Discovery:
24
- // 1. If we are in Vite Dev Mode (port 5173), use VITE_API_URL or default 3004
25
- // 2. If we are running on the Unified Server (production/npx), use the current window origin
26
- const isViteDev = window.location.port === '5173';
27
- const envApiUrl = import.meta.env.VITE_API_URL;
28
-
29
- let expressApiUrl = '';
30
- if (isViteDev) {
31
- expressApiUrl = envApiUrl || 'http://localhost:3004';
32
- } else {
33
- // Use current window origin (e.g. http://localhost:3008)
34
- expressApiUrl = window.location.origin;
35
- }
36
-
37
- return {
38
- edgeFunctionsUrl,
39
- expressApiUrl,
40
- anonKey,
41
- };
42
- }