@realtimex/email-automator 2.1.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 (139) hide show
  1. package/.env.example +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +247 -0
  4. package/api/server.ts +130 -0
  5. package/api/src/config/index.ts +102 -0
  6. package/api/src/middleware/auth.ts +166 -0
  7. package/api/src/middleware/errorHandler.ts +97 -0
  8. package/api/src/middleware/index.ts +4 -0
  9. package/api/src/middleware/rateLimit.ts +87 -0
  10. package/api/src/middleware/validation.ts +118 -0
  11. package/api/src/routes/actions.ts +214 -0
  12. package/api/src/routes/auth.ts +157 -0
  13. package/api/src/routes/emails.ts +144 -0
  14. package/api/src/routes/health.ts +36 -0
  15. package/api/src/routes/index.ts +22 -0
  16. package/api/src/routes/migrate.ts +76 -0
  17. package/api/src/routes/rules.ts +149 -0
  18. package/api/src/routes/settings.ts +229 -0
  19. package/api/src/routes/sync.ts +152 -0
  20. package/api/src/services/eventLogger.ts +52 -0
  21. package/api/src/services/gmail.ts +456 -0
  22. package/api/src/services/intelligence.ts +288 -0
  23. package/api/src/services/microsoft.ts +368 -0
  24. package/api/src/services/processor.ts +596 -0
  25. package/api/src/services/scheduler.ts +255 -0
  26. package/api/src/services/supabase.ts +144 -0
  27. package/api/src/utils/contentCleaner.ts +114 -0
  28. package/api/src/utils/crypto.ts +80 -0
  29. package/api/src/utils/logger.ts +142 -0
  30. package/bin/email-automator-deploy.js +79 -0
  31. package/bin/email-automator-setup.js +144 -0
  32. package/bin/email-automator.js +61 -0
  33. package/dist/assets/index-BQ1uMdFh.js +97 -0
  34. package/dist/assets/index-Dzi17fx5.css +1 -0
  35. package/dist/email-automator-logo.svg +51 -0
  36. package/dist/favicon.svg +45 -0
  37. package/dist/index.html +14 -0
  38. package/index.html +13 -0
  39. package/package.json +112 -0
  40. package/public/email-automator-logo.svg +51 -0
  41. package/public/favicon.svg +45 -0
  42. package/scripts/deploy-functions.sh +55 -0
  43. package/scripts/migrate.sh +177 -0
  44. package/src/App.tsx +622 -0
  45. package/src/components/AccountSettings.tsx +310 -0
  46. package/src/components/AccountSettingsPage.tsx +390 -0
  47. package/src/components/Configuration.tsx +1345 -0
  48. package/src/components/Dashboard.tsx +940 -0
  49. package/src/components/ErrorBoundary.tsx +71 -0
  50. package/src/components/LiveTerminal.tsx +308 -0
  51. package/src/components/LoadingSpinner.tsx +39 -0
  52. package/src/components/Login.tsx +371 -0
  53. package/src/components/Logo.tsx +57 -0
  54. package/src/components/SetupWizard.tsx +388 -0
  55. package/src/components/Toast.tsx +109 -0
  56. package/src/components/migration/MigrationBanner.tsx +97 -0
  57. package/src/components/migration/MigrationModal.tsx +458 -0
  58. package/src/components/migration/MigrationPulseIndicator.tsx +38 -0
  59. package/src/components/mode-toggle.tsx +24 -0
  60. package/src/components/theme-provider.tsx +72 -0
  61. package/src/components/ui/alert.tsx +66 -0
  62. package/src/components/ui/button.tsx +57 -0
  63. package/src/components/ui/card.tsx +75 -0
  64. package/src/components/ui/dialog.tsx +133 -0
  65. package/src/components/ui/input.tsx +22 -0
  66. package/src/components/ui/label.tsx +24 -0
  67. package/src/components/ui/otp-input.tsx +184 -0
  68. package/src/context/AppContext.tsx +422 -0
  69. package/src/context/MigrationContext.tsx +53 -0
  70. package/src/context/TerminalContext.tsx +31 -0
  71. package/src/core/actions.ts +76 -0
  72. package/src/core/auth.ts +108 -0
  73. package/src/core/intelligence.ts +76 -0
  74. package/src/core/processor.ts +112 -0
  75. package/src/hooks/useRealtimeEmails.ts +111 -0
  76. package/src/index.css +140 -0
  77. package/src/lib/api-config.ts +42 -0
  78. package/src/lib/api-old.ts +228 -0
  79. package/src/lib/api.ts +421 -0
  80. package/src/lib/migration-check.ts +264 -0
  81. package/src/lib/sounds.ts +120 -0
  82. package/src/lib/supabase-config.ts +117 -0
  83. package/src/lib/supabase.ts +28 -0
  84. package/src/lib/types.ts +166 -0
  85. package/src/lib/utils.ts +6 -0
  86. package/src/main.tsx +10 -0
  87. package/supabase/.env.example +15 -0
  88. package/supabase/.temp/cli-latest +1 -0
  89. package/supabase/.temp/gotrue-version +1 -0
  90. package/supabase/.temp/pooler-url +1 -0
  91. package/supabase/.temp/postgres-version +1 -0
  92. package/supabase/.temp/project-ref +1 -0
  93. package/supabase/.temp/rest-version +1 -0
  94. package/supabase/.temp/storage-migration +1 -0
  95. package/supabase/.temp/storage-version +1 -0
  96. package/supabase/config.toml +95 -0
  97. package/supabase/functions/_shared/auth-helper.ts +76 -0
  98. package/supabase/functions/_shared/auth.ts +33 -0
  99. package/supabase/functions/_shared/cors.ts +45 -0
  100. package/supabase/functions/_shared/encryption.ts +70 -0
  101. package/supabase/functions/_shared/supabaseAdmin.ts +14 -0
  102. package/supabase/functions/api-v1-accounts/index.ts +133 -0
  103. package/supabase/functions/api-v1-emails/index.ts +177 -0
  104. package/supabase/functions/api-v1-rules/index.ts +177 -0
  105. package/supabase/functions/api-v1-settings/index.ts +247 -0
  106. package/supabase/functions/auth-gmail/index.ts +197 -0
  107. package/supabase/functions/auth-microsoft/index.ts +215 -0
  108. package/supabase/functions/setup/index.ts +92 -0
  109. package/supabase/migrations/20260114000000_initial_schema.sql +81 -0
  110. package/supabase/migrations/20260115000000_add_user_settings.sql +49 -0
  111. package/supabase/migrations/20260115000001_add_auth_flow.sql +80 -0
  112. package/supabase/migrations/20260115000002_fix_permissions.sql +5 -0
  113. package/supabase/migrations/20260115000003_fix_init_state_permissions.sql +9 -0
  114. package/supabase/migrations/20260115000004_add_migration_rpc.sql +13 -0
  115. package/supabase/migrations/20260115000005_add_provider_creds.sql +7 -0
  116. package/supabase/migrations/20260115000006_backfill_profiles.sql +22 -0
  117. package/supabase/migrations/20260116000000_add_sync_scope.sql +15 -0
  118. package/supabase/migrations/20260116000001_per_account_sync_scope.sql +19 -0
  119. package/supabase/migrations/20260116000002_add_llm_api_key.sql +5 -0
  120. package/supabase/migrations/20260117000000_refactor_integrations.sql +36 -0
  121. package/supabase/migrations/20260117000001_add_processing_events.sql +30 -0
  122. package/supabase/migrations/20260117000002_multi_actions.sql +15 -0
  123. package/supabase/migrations/20260117000003_seed_default_rules.sql +77 -0
  124. package/supabase/migrations/20260117000004_rule_instructions.sql +5 -0
  125. package/supabase/migrations/20260117000005_rule_attachments.sql +7 -0
  126. package/supabase/migrations/20260117000006_setup_storage.sql +32 -0
  127. package/supabase/migrations/20260117000007_add_system_logs.sql +26 -0
  128. package/supabase/migrations/20260117000008_link_logs_to_accounts.sql +8 -0
  129. package/supabase/migrations/20260117000009_convert_toggles_to_rules.sql +28 -0
  130. package/supabase/migrations/20260117000010_add_atomic_action_append.sql +13 -0
  131. package/supabase/migrations/20260117000011_add_profile_avatar.sql +4 -0
  132. package/supabase/migrations/20260117000012_setup_avatars_storage.sql +26 -0
  133. package/supabase/templates/confirmation.html +76 -0
  134. package/supabase/templates/email-change.html +76 -0
  135. package/supabase/templates/invite.html +72 -0
  136. package/supabase/templates/magic-link.html +68 -0
  137. package/supabase/templates/recovery.html +82 -0
  138. package/tsconfig.json +36 -0
  139. package/vite.config.ts +162 -0
@@ -0,0 +1,142 @@
1
+ import { config } from '../config/index.js';
2
+
3
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
4
+
5
+ interface LogMeta {
6
+ [key: string]: unknown;
7
+ }
8
+
9
+ const LOG_COLORS = {
10
+ debug: '\x1b[36m', // cyan
11
+ info: '\x1b[32m', // green
12
+ warn: '\x1b[33m', // yellow
13
+ error: '\x1b[31m', // red
14
+ reset: '\x1b[0m',
15
+ };
16
+
17
+ const LOG_LEVELS: Record<LogLevel, number> = {
18
+ debug: 0,
19
+ info: 1,
20
+ warn: 2,
21
+ error: 3,
22
+ };
23
+
24
+ export class Logger {
25
+ private minLevel: LogLevel;
26
+ private context?: string;
27
+ private static supabaseClient: any = null;
28
+ private static currentUserId: string | null = null;
29
+
30
+ constructor(context?: string) {
31
+ this.minLevel = config.isProduction ? 'info' : 'debug';
32
+ this.context = context;
33
+ }
34
+
35
+ /**
36
+ * Set the Supabase client and current user ID for DB persistence.
37
+ * This is called by the auth middleware or server initialization.
38
+ */
39
+ static setPersistence(client: any, userId: string | null = null): void {
40
+ Logger.supabaseClient = client;
41
+ Logger.currentUserId = userId;
42
+ }
43
+
44
+ private shouldLog(level: LogLevel): boolean {
45
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.minLevel];
46
+ }
47
+
48
+ private async saveToSupabase(level: LogLevel, message: string, meta?: LogMeta): Promise<void> {
49
+ // Only persist warn and error levels to DB to prevent bloat
50
+ // unless explicitly forced via meta
51
+ const persistLevels: LogLevel[] = ['warn', 'error'];
52
+ const shouldPersist = persistLevels.includes(level) || meta?._persist === true;
53
+
54
+ if (shouldPersist && Logger.supabaseClient) {
55
+ try {
56
+ // Remove internal flags from meta before saving
57
+ const { _persist, ...cleanMeta } = meta || {};
58
+
59
+ await Logger.supabaseClient.from('system_logs').insert({
60
+ user_id: Logger.currentUserId,
61
+ level,
62
+ source: this.context || 'System',
63
+ message,
64
+ metadata: cleanMeta,
65
+ created_at: new Date().toISOString()
66
+ });
67
+ } catch (err) {
68
+ // Fail silently to avoid infinite loops if logging fails
69
+ console.error('[Logger] Failed to persist log to Supabase:', err);
70
+ }
71
+ }
72
+ }
73
+
74
+ private formatMessage(level: LogLevel, message: string, meta?: LogMeta): string {
75
+ const timestamp = new Date().toISOString();
76
+ const contextStr = this.context ? `[${this.context}]` : '';
77
+ const { _persist, ...cleanMeta } = meta || {};
78
+ const metaStr = Object.keys(cleanMeta).length > 0 ? ` ${JSON.stringify(cleanMeta)}` : '';
79
+
80
+ if (config.isProduction) {
81
+ return JSON.stringify({
82
+ timestamp,
83
+ level,
84
+ context: this.context,
85
+ message,
86
+ ...cleanMeta,
87
+ });
88
+ }
89
+
90
+ const color = LOG_COLORS[level];
91
+ const reset = LOG_COLORS.reset;
92
+ return `${timestamp} ${color}${level.toUpperCase().padEnd(5)}${reset} ${contextStr} ${message}${metaStr}`;
93
+ }
94
+
95
+ debug(message: string, meta?: LogMeta): void {
96
+ if (this.shouldLog('debug')) {
97
+ console.debug(this.formatMessage('debug', message, meta));
98
+ this.saveToSupabase('debug', message, meta);
99
+ }
100
+ }
101
+
102
+ info(message: string, meta?: LogMeta): void {
103
+ if (this.shouldLog('info')) {
104
+ console.info(this.formatMessage('info', message, meta));
105
+ this.saveToSupabase('info', message, meta);
106
+ }
107
+ }
108
+
109
+ warn(message: string, meta?: LogMeta): void {
110
+ if (this.shouldLog('warn')) {
111
+ console.warn(this.formatMessage('warn', message, meta));
112
+ this.saveToSupabase('warn', message, meta);
113
+ }
114
+ }
115
+
116
+ error(message: string, error?: Error | unknown, meta?: LogMeta): void {
117
+ if (this.shouldLog('error')) {
118
+ const errorMeta: LogMeta = { ...meta };
119
+ if (error instanceof Error) {
120
+ errorMeta.errorName = error.name;
121
+ errorMeta.errorMessage = error.message;
122
+ errorMeta.stack = error.stack;
123
+ } else if (error) {
124
+ errorMeta.error = error;
125
+ }
126
+ console.error(this.formatMessage('error', message, errorMeta));
127
+ this.saveToSupabase('error', message, errorMeta);
128
+ }
129
+ }
130
+
131
+ child(context: string): Logger {
132
+ return new Logger(this.context ? `${this.context}:${context}` : context);
133
+ }
134
+ }
135
+
136
+ // Default logger instance
137
+ export const logger = new Logger();
138
+
139
+ // Factory for creating contextual loggers
140
+ export function createLogger(context: string): Logger {
141
+ return new Logger(context);
142
+ }
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Email Automator Deploy CLI
5
+ * Deploy Edge Functions to Supabase
6
+ */
7
+
8
+ import { spawn } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+ import { existsSync } from 'fs';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ console.log('🚀 Email Automator - Edge Functions Deployment');
17
+ console.log('===============================================');
18
+ console.log('');
19
+
20
+ // Check if supabase CLI is available
21
+ const checkSupabase = spawn('supabase', ['--version'], { stdio: 'pipe' });
22
+
23
+ checkSupabase.on('error', () => {
24
+ console.error('❌ Supabase CLI not found');
25
+ console.error('');
26
+ console.error('Install it with:');
27
+ console.error(' npm install -g supabase');
28
+ console.error('');
29
+ console.error('Or via Homebrew (macOS):');
30
+ console.error(' brew install supabase/tap/supabase');
31
+ console.error('');
32
+ process.exit(1);
33
+ });
34
+
35
+ checkSupabase.on('close', (code) => {
36
+ if (code !== 0) {
37
+ console.error('❌ Supabase CLI check failed');
38
+ process.exit(1);
39
+ }
40
+
41
+ console.log('✅ Supabase CLI detected');
42
+ console.log('');
43
+
44
+ // Run deployment script
45
+ const deployScript = join(__dirname, '..', 'scripts', 'deploy-functions.sh');
46
+
47
+ if (!existsSync(deployScript)) {
48
+ console.error('❌ Deployment script not found:', deployScript);
49
+ process.exit(1);
50
+ }
51
+
52
+ console.log('📦 Deploying Edge Functions...');
53
+ console.log('');
54
+
55
+ const deploy = spawn('bash', [deployScript], {
56
+ stdio: 'inherit',
57
+ cwd: join(__dirname, '..'),
58
+ });
59
+
60
+ deploy.on('error', (error) => {
61
+ console.error('❌ Deployment failed:', error.message);
62
+ process.exit(1);
63
+ });
64
+
65
+ deploy.on('close', (code) => {
66
+ if (code !== 0) {
67
+ console.error('\n❌ Deployment failed with code', code);
68
+ process.exit(code);
69
+ }
70
+
71
+ console.log('');
72
+ console.log('✅ Deployment complete!');
73
+ console.log('');
74
+ console.log('🔐 Don\'t forget to configure secrets in Supabase Dashboard:');
75
+ console.log(' Settings → Edge Functions → Add secrets');
76
+ console.log('');
77
+ process.exit(0);
78
+ });
79
+ });
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Email Automator Setup CLI
5
+ * Interactive setup for Email Automator
6
+ */
7
+
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, join } from 'path';
10
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
11
+ import readline from 'readline';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ const rl = readline.createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout,
19
+ });
20
+
21
+ function question(query) {
22
+ return new Promise((resolve) => {
23
+ rl.question(query, resolve);
24
+ });
25
+ }
26
+
27
+ async function setup() {
28
+ console.log('');
29
+ console.log('🎯 Email Automator Setup');
30
+ console.log('========================');
31
+ console.log('');
32
+
33
+ const envPath = join(__dirname, '..', '.env');
34
+ const envExamplePath = join(__dirname, '..', '.env.example');
35
+
36
+ // Check if .env already exists
37
+ if (existsSync(envPath)) {
38
+ const overwrite = await question('⚠️ .env file already exists. Overwrite? (y/N): ');
39
+ if (overwrite.toLowerCase() !== 'y') {
40
+ console.log('Setup cancelled.');
41
+ rl.close();
42
+ return;
43
+ }
44
+ }
45
+
46
+ console.log('');
47
+ console.log('📝 Let\'s configure your Email Automator...');
48
+ console.log('');
49
+
50
+ // Supabase Configuration
51
+ console.log('1️⃣ Supabase Configuration');
52
+ console.log(' (Get these from: https://supabase.com/dashboard/project/_/settings/api)');
53
+ console.log('');
54
+ const supabaseUrl = await question(' Supabase URL: ');
55
+ const supabaseAnonKey = await question(' Supabase Anon Key: ');
56
+
57
+ // API Configuration
58
+ console.log('');
59
+ console.log('2️⃣ API Configuration');
60
+ const port = await question(' API Port [3004]: ') || '3004';
61
+
62
+ // LLM Configuration
63
+ console.log('');
64
+ console.log('3️⃣ LLM Configuration');
65
+ const llmApiKey = await question(' OpenAI API Key: ');
66
+ const llmModel = await question(' Model [gpt-4o-mini]: ') || 'gpt-4o-mini';
67
+
68
+ // OAuth Configuration (optional)
69
+ console.log('');
70
+ console.log('4️⃣ OAuth Configuration (Optional - skip for now if not ready)');
71
+ const gmailClientId = await question(' Gmail Client ID [skip]: ') || 'your_gmail_client_id';
72
+ const gmailClientSecret = await question(' Gmail Client Secret [skip]: ') || 'your_gmail_client_secret';
73
+
74
+ // Generate encryption key
75
+ const encryptionKey = Array.from({ length: 32 }, () =>
76
+ Math.random().toString(36).substring(2, 3)
77
+ ).join('');
78
+
79
+ // Create .env content
80
+ const envContent = `# Supabase Configuration
81
+ VITE_SUPABASE_URL=${supabaseUrl}
82
+ VITE_SUPABASE_ANON_KEY=${supabaseAnonKey}
83
+
84
+ # API Configuration
85
+ VITE_API_URL=http://localhost:${port}
86
+ PORT=${port}
87
+
88
+ # OpenAI / LLM Configuration
89
+ LLM_API_KEY=${llmApiKey}
90
+ LLM_BASE_URL=https://api.openai.com/v1
91
+ LLM_MODEL=${llmModel}
92
+
93
+ # Security
94
+ JWT_SECRET="dev-secret-change-in-production"
95
+ TOKEN_ENCRYPTION_KEY="${encryptionKey}"
96
+
97
+ # Development
98
+ DISABLE_AUTH=true
99
+
100
+ # Gmail OAuth (optional)
101
+ GMAIL_CLIENT_ID=${gmailClientId}
102
+ GMAIL_CLIENT_SECRET=${gmailClientSecret}
103
+
104
+ # Microsoft Graph (optional)
105
+ MS_GRAPH_CLIENT_ID=your_ms_graph_client_id
106
+ MS_GRAPH_TENANT_ID=common
107
+ MS_GRAPH_CLIENT_SECRET=your_ms_graph_client_secret
108
+
109
+ # Processing
110
+ EMAIL_BATCH_SIZE=20
111
+ SYNC_INTERVAL_MS=300000
112
+ `;
113
+
114
+ // Write .env file
115
+ writeFileSync(envPath, envContent);
116
+
117
+ console.log('');
118
+ console.log('✅ Configuration saved to .env');
119
+ console.log('');
120
+ console.log('📚 Next Steps:');
121
+ console.log('');
122
+ console.log(' 1. Deploy Edge Functions:');
123
+ console.log(' $ supabase login');
124
+ console.log(' $ ./scripts/deploy-functions.sh');
125
+ console.log('');
126
+ console.log(' 2. Configure Edge Function secrets in Supabase Dashboard:');
127
+ console.log(' Settings → Edge Functions → Add secrets');
128
+ console.log('');
129
+ console.log(' 3. Start Email Automator:');
130
+ console.log(' $ npx email-automator');
131
+ console.log(' or');
132
+ console.log(' $ npm run dev:api');
133
+ console.log('');
134
+ console.log('📖 Documentation: https://github.com/therealtimex/email-automator');
135
+ console.log('');
136
+
137
+ rl.close();
138
+ }
139
+
140
+ setup().catch((error) => {
141
+ console.error('❌ Setup failed:', error.message);
142
+ rl.close();
143
+ process.exit(1);
144
+ });
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Email Automator CLI
5
+ * Main command to run the Email Automator API server
6
+ */
7
+
8
+ import { spawn } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ // Parse command line arguments
16
+ const args = process.argv.slice(2);
17
+
18
+ // Default port
19
+ let port = '3004';
20
+ const portIndex = args.indexOf('--port');
21
+ if (portIndex !== -1 && args[portIndex + 1]) {
22
+ port = args[portIndex + 1];
23
+ }
24
+
25
+ const noUi = args.includes('--no-ui');
26
+
27
+ console.log('🚀 Starting Email Automator...');
28
+ console.log(`📡 Port: ${port}`);
29
+ if (noUi) console.log('🖥️ Mode: No-UI');
30
+ console.log('');
31
+
32
+ // Path to server
33
+ const serverPath = join(__dirname, '..', 'api', 'server.ts');
34
+
35
+ // Start server with tsx
36
+ const server = spawn('npx', ['tsx', serverPath, ...args], {
37
+ stdio: 'inherit',
38
+ env: { ...process.env, PORT: port },
39
+ });
40
+
41
+ server.on('error', (error) => {
42
+ console.error('❌ Failed to start Email Automator:', error.message);
43
+ process.exit(1);
44
+ });
45
+
46
+ server.on('close', (code) => {
47
+ if (code !== 0) {
48
+ console.log(`\n⚠️ Email Automator stopped with code ${code}`);
49
+ }
50
+ process.exit(code || 0);
51
+ });
52
+
53
+ // Handle graceful shutdown
54
+ process.on('SIGINT', () => {
55
+ console.log('\n\n⏹️ Shutting down Email Automator...');
56
+ server.kill('SIGINT');
57
+ });
58
+
59
+ process.on('SIGTERM', () => {
60
+ server.kill('SIGTERM');
61
+ });