@realtimex/email-automator 2.3.5 → 2.3.7

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.
package/README.md CHANGED
@@ -60,7 +60,17 @@ npx @realtimex/email-automator-setup
60
60
  npx @realtimex/email-automator-deploy
61
61
 
62
62
  # Start Email Automator
63
- npx @realtimex/email-automator
63
+ npx @realtimex/email-automator --port 3004
64
+ ```
65
+
66
+ ### Option 1b: Global Install
67
+
68
+ ```bash
69
+ # Install globally
70
+ npm install -g @realtimex/email-automator
71
+
72
+ # Then run directly
73
+ email-automator --port 3004
64
74
  ```
65
75
 
66
76
  ### Option 2: Clone and Install
package/api/server.ts CHANGED
@@ -69,23 +69,28 @@ app.use('/api', routes);
69
69
 
70
70
  // Robust resolution for static assets (dist folder)
71
71
  function getDistPath() {
72
- // 1. Check environment variable override
73
- if (process.env.ELECTRON_STATIC_PATH && existsSync(process.env.ELECTRON_STATIC_PATH)) {
72
+ // 1. Check environment variable override (must contain index.html)
73
+ if (process.env.ELECTRON_STATIC_PATH && existsSync(join(process.env.ELECTRON_STATIC_PATH, 'index.html'))) {
74
74
  return process.env.ELECTRON_STATIC_PATH;
75
75
  }
76
76
 
77
- // 2. Try to find dist relative to this file
78
- // In dev: dist is at ../dist
79
- // In npx/dist: dist is at ../../dist
77
+ // 2. Try to find dist relative to packageRoot (from config)
78
+ // This is the most reliable way in compiled app
79
+ const fromRoot = join(config.rootDir || process.cwd(), 'dist');
80
+ if (existsSync(join(fromRoot, 'index.html'))) {
81
+ return fromRoot;
82
+ }
83
+
84
+ // 3. Try to find dist relative to this file
80
85
  let current = __dirname;
81
86
  for (let i = 0; i < 4; i++) {
82
87
  const potential = join(current, 'dist');
83
- if (existsSync(potential)) return potential;
88
+ if (existsSync(join(potential, 'index.html'))) return potential;
84
89
  current = path.dirname(current);
85
90
  }
86
91
 
87
- // 3. Fallback to current working directory
88
- return join(process.cwd(), 'dist');
92
+ // 4. Fallback to current working directory
93
+ return join(config.packageRoot || process.cwd(), 'dist');
89
94
  }
90
95
 
91
96
  const distPath = getDistPath();
@@ -6,11 +6,11 @@ import { existsSync } from 'fs';
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
 
9
- // Robustly find package root (directory containing package.json)
9
+ // Robustly find package root (directory containing package.json and bin folder)
10
10
  function findPackageRoot(startDir: string): string {
11
11
  let current = startDir;
12
12
  while (current !== path.parse(current).root) {
13
- if (existsSync(join(current, 'package.json'))) {
13
+ if (existsSync(join(current, 'package.json')) && existsSync(join(current, 'bin'))) {
14
14
  return current;
15
15
  }
16
16
  current = dirname(current);
@@ -18,9 +18,12 @@ function findPackageRoot(startDir: string): string {
18
18
  return process.cwd();
19
19
  }
20
20
 
21
+ const packageRoot = findPackageRoot(__dirname);
22
+ console.log(`🏠 Package Root: ${packageRoot}`);
23
+
21
24
  function loadEnvironment() {
22
25
  const cwdEnv = join(process.cwd(), '.env');
23
- const rootEnv = join(findPackageRoot(__dirname), '.env');
26
+ const rootEnv = join(packageRoot, '.env');
24
27
 
25
28
  if (existsSync(cwdEnv)) {
26
29
  console.log(`📝 Loading environment from CWD: ${cwdEnv}`);
@@ -55,6 +58,7 @@ const cliArgs = parseArgs(process.argv.slice(2));
55
58
 
56
59
  export const config = {
57
60
  // Server
61
+ packageRoot,
58
62
  // Default port 3004 (RealTimeX Desktop uses 3001/3002)
59
63
  port: cliArgs.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 3004),
60
64
  noUi: cliArgs.noUi,
@@ -62,8 +66,8 @@ export const config = {
62
66
  isProduction: process.env.NODE_ENV === 'production',
63
67
 
64
68
  // Paths - Robust resolution for both TS source and compiled JS in dist/
65
- rootDir: process.cwd(),
66
- scriptsDir: join(process.cwd(), 'scripts'),
69
+ rootDir: packageRoot,
70
+ scriptsDir: join(packageRoot, 'scripts'),
67
71
 
68
72
  // Supabase
69
73
  supabase: {
@@ -1,5 +1,4 @@
1
1
  import OpenAI from 'openai';
2
- import Instructor from '@instructor-ai/instructor';
3
2
  import { z } from 'zod';
4
3
  import { config } from '../config/index.js';
5
4
  import { createLogger } from '../utils/logger.js';
@@ -48,7 +47,7 @@ export interface EmailContext {
48
47
  }
49
48
 
50
49
  export class IntelligenceService {
51
- private client: any;
50
+ private client: OpenAI | null = null;
52
51
  private model: string;
53
52
  private isConfigured: boolean = false;
54
53
 
@@ -67,16 +66,11 @@ export class IntelligenceService {
67
66
  }
68
67
 
69
68
  try {
70
- const oai = new OpenAI({
69
+ this.client = new OpenAI({
71
70
  apiKey: apiKey || 'not-needed-for-local', // Placeholder for local LLMs
72
71
  baseURL: baseUrl,
73
72
  });
74
73
 
75
- this.client = Instructor({
76
- client: oai,
77
- mode: 'MD_JSON',
78
- });
79
-
80
74
  this.isConfigured = true;
81
75
  logger.info('Intelligence service initialized', { model: this.model, baseUrl: baseUrl || 'default' });
82
76
  } catch (error) {
@@ -163,7 +157,7 @@ REQUIRED JSON STRUCTURE:
163
157
  let rawResponse = '';
164
158
  try {
165
159
  // Using raw completion call to handle garbage characters and strip tokens manually
166
- const response = await this.client.client.chat.completions.create({
160
+ const response = await this.client!.chat.completions.create({
167
161
  model: this.model,
168
162
  messages: [
169
163
  { role: 'system', content: systemPrompt },
@@ -223,7 +217,7 @@ REQUIRED JSON STRUCTURE:
223
217
  }
224
218
 
225
219
  try {
226
- const response = await this.client.chat.completions.create({
220
+ const response = await this.client!.chat.completions.create({
227
221
  model: this.model,
228
222
  messages: [
229
223
  {
@@ -257,7 +251,7 @@ Please write a reply.`,
257
251
  }
258
252
 
259
253
  try {
260
- await this.client.chat.completions.create({
254
+ await this.client!.chat.completions.create({
261
255
  model: this.model,
262
256
  messages: [{ role: 'user', content: 'Say "Connection Successful"' }],
263
257
  max_tokens: 5,
@@ -58,22 +58,26 @@ app.use('/api', apiRateLimit);
58
58
  app.use('/api', routes);
59
59
  // Robust resolution for static assets (dist folder)
60
60
  function getDistPath() {
61
- // 1. Check environment variable override
62
- if (process.env.ELECTRON_STATIC_PATH && existsSync(process.env.ELECTRON_STATIC_PATH)) {
61
+ // 1. Check environment variable override (must contain index.html)
62
+ if (process.env.ELECTRON_STATIC_PATH && existsSync(join(process.env.ELECTRON_STATIC_PATH, 'index.html'))) {
63
63
  return process.env.ELECTRON_STATIC_PATH;
64
64
  }
65
- // 2. Try to find dist relative to this file
66
- // In dev: dist is at ../dist
67
- // In npx/dist: dist is at ../../dist
65
+ // 2. Try to find dist relative to packageRoot (from config)
66
+ // This is the most reliable way in compiled app
67
+ const fromRoot = join(config.rootDir || process.cwd(), 'dist');
68
+ if (existsSync(join(fromRoot, 'index.html'))) {
69
+ return fromRoot;
70
+ }
71
+ // 3. Try to find dist relative to this file
68
72
  let current = __dirname;
69
73
  for (let i = 0; i < 4; i++) {
70
74
  const potential = join(current, 'dist');
71
- if (existsSync(potential))
75
+ if (existsSync(join(potential, 'index.html')))
72
76
  return potential;
73
77
  current = path.dirname(current);
74
78
  }
75
- // 3. Fallback to current working directory
76
- return join(process.cwd(), 'dist');
79
+ // 4. Fallback to current working directory
80
+ return join(config.packageRoot || process.cwd(), 'dist');
77
81
  }
78
82
  const distPath = getDistPath();
79
83
  logger.info('Serving static assets', { path: distPath });
@@ -4,20 +4,22 @@ import path, { dirname, join } from 'path';
4
4
  import { existsSync } from 'fs';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
- // Robustly find package root (directory containing package.json)
7
+ // Robustly find package root (directory containing package.json and bin folder)
8
8
  function findPackageRoot(startDir) {
9
9
  let current = startDir;
10
10
  while (current !== path.parse(current).root) {
11
- if (existsSync(join(current, 'package.json'))) {
11
+ if (existsSync(join(current, 'package.json')) && existsSync(join(current, 'bin'))) {
12
12
  return current;
13
13
  }
14
14
  current = dirname(current);
15
15
  }
16
16
  return process.cwd();
17
17
  }
18
+ const packageRoot = findPackageRoot(__dirname);
19
+ console.log(`🏠 Package Root: ${packageRoot}`);
18
20
  function loadEnvironment() {
19
21
  const cwdEnv = join(process.cwd(), '.env');
20
- const rootEnv = join(findPackageRoot(__dirname), '.env');
22
+ const rootEnv = join(packageRoot, '.env');
21
23
  if (existsSync(cwdEnv)) {
22
24
  console.log(`📝 Loading environment from CWD: ${cwdEnv}`);
23
25
  dotenv.config({ path: cwdEnv, override: true });
@@ -47,14 +49,15 @@ function parseArgs(args) {
47
49
  const cliArgs = parseArgs(process.argv.slice(2));
48
50
  export const config = {
49
51
  // Server
52
+ packageRoot,
50
53
  // Default port 3004 (RealTimeX Desktop uses 3001/3002)
51
54
  port: cliArgs.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 3004),
52
55
  noUi: cliArgs.noUi,
53
56
  nodeEnv: process.env.NODE_ENV || 'development',
54
57
  isProduction: process.env.NODE_ENV === 'production',
55
58
  // Paths - Robust resolution for both TS source and compiled JS in dist/
56
- rootDir: process.cwd(),
57
- scriptsDir: join(process.cwd(), 'scripts'),
59
+ rootDir: packageRoot,
60
+ scriptsDir: join(packageRoot, 'scripts'),
58
61
  // Supabase
59
62
  supabase: {
60
63
  url: process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL || '',
@@ -1,5 +1,4 @@
1
1
  import OpenAI from 'openai';
2
- import Instructor from '@instructor-ai/instructor';
3
2
  import { z } from 'zod';
4
3
  import { config } from '../config/index.js';
5
4
  import { createLogger } from '../utils/logger.js';
@@ -26,7 +25,7 @@ export const EmailAnalysisSchema = z.object({
26
25
  .describe('Action items mentioned in the email'),
27
26
  });
28
27
  export class IntelligenceService {
29
- client;
28
+ client = null;
30
29
  model;
31
30
  isConfigured = false;
32
31
  constructor(overrides) {
@@ -41,14 +40,10 @@ export class IntelligenceService {
41
40
  return;
42
41
  }
43
42
  try {
44
- const oai = new OpenAI({
43
+ this.client = new OpenAI({
45
44
  apiKey: apiKey || 'not-needed-for-local', // Placeholder for local LLMs
46
45
  baseURL: baseUrl,
47
46
  });
48
- this.client = Instructor({
49
- client: oai,
50
- mode: 'MD_JSON',
51
- });
52
47
  this.isConfigured = true;
53
48
  logger.info('Intelligence service initialized', { model: this.model, baseUrl: baseUrl || 'default' });
54
49
  }
@@ -134,7 +129,7 @@ REQUIRED JSON STRUCTURE:
134
129
  let rawResponse = '';
135
130
  try {
136
131
  // Using raw completion call to handle garbage characters and strip tokens manually
137
- const response = await this.client.client.chat.completions.create({
132
+ const response = await this.client.chat.completions.create({
138
133
  model: this.model,
139
134
  messages: [
140
135
  { role: 'system', content: systemPrompt },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realtimex/email-automator",
3
- "version": "2.3.5",
3
+ "version": "2.3.7",
4
4
  "type": "module",
5
5
  "main": "dist/api/server.js",
6
6
  "bin": {
@@ -69,7 +69,6 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@azure/msal-node": "^3.8.5",
72
- "@instructor-ai/instructor": "^1.7.0",
73
72
  "@radix-ui/react-alert-dialog": "^1.1.15",
74
73
  "@radix-ui/react-dialog": "^1.1.15",
75
74
  "@radix-ui/react-label": "^2.1.8",