@goscribe/server 1.1.7 → 1.2.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.
package/.env.example ADDED
@@ -0,0 +1,43 @@
1
+ # ──────────────────────────────────────────────
2
+ # Scribe Server Environment Variables
3
+ # Copy this file to .env and fill in your values
4
+ # ──────────────────────────────────────────────
5
+
6
+ # Auth
7
+ AUTH_SECRET="your-auth-secret-here"
8
+ AUTH_TRUST_HOST=true
9
+ AUTH_URL=http://localhost:3001
10
+
11
+ # Database (PostgreSQL via Supabase)
12
+ DATABASE_URL="postgresql://user:password@host:6543/postgres?pgbouncer=true"
13
+ DIRECT_URL="postgresql://user:password@host:5432/postgres"
14
+
15
+ # AI Inference
16
+ DONT_TEST_INFERENCE=false
17
+ INFERENCE_API_URL=http://localhost:5000
18
+ INFERENCE_API_KEY=your-inference-api-key
19
+ INFERENCE_BASE_URL=https://api.cohere.ai/compatibility/v1
20
+
21
+ # Supabase Storage
22
+ SUPABASE_URL=https://your-project.supabase.co
23
+ SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
24
+
25
+ # Google Cloud Storage (for podcast audio)
26
+ GCP_PROJECT_ID=your-gcp-project
27
+ GCP_BUCKET=your-bucket-name
28
+ GCP_CLIENT_EMAIL=your-service-account@your-project.iam.gserviceaccount.com
29
+ GCP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nyour-private-key\n-----END PRIVATE KEY-----\n"
30
+
31
+ # Pusher (real-time events)
32
+ PUSHER_APP_ID=your-pusher-app-id
33
+ PUSHER_KEY=your-pusher-key
34
+ PUSHER_SECRET=your-pusher-secret
35
+ PUSHER_CLUSTER=your-cluster
36
+
37
+ # Email (Resend)
38
+ RESEND_API_KEY=re_your_resend_api_key
39
+ EMAIL_FROM=Scribe <noreply@yourdomain.com>
40
+
41
+ # Optional
42
+ COHERE_API_KEY=your-cohere-key
43
+ MURF_TTS_KEY=your-murf-tts-key
@@ -4,7 +4,7 @@ import bcrypt from 'bcryptjs';
4
4
  import { serialize } from 'cookie';
5
5
  import crypto from 'node:crypto';
6
6
  import { TRPCError } from '@trpc/server';
7
- import { supabaseClient } from 'src/lib/storage.js';
7
+ import { supabaseClient } from '../lib/storage.js';
8
8
  // Helper to create custom auth token
9
9
  function createCustomAuthToken(userId) {
10
10
  const secret = process.env.AUTH_SECRET;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goscribe/server",
3
- "version": "1.1.7",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -40,6 +40,7 @@
40
40
  "prisma": "^6.14.0",
41
41
  "pusher": "^5.2.0",
42
42
  "pusher-js": "^8.4.0",
43
+ "resend": "^6.9.2",
43
44
  "socket.io": "^4.8.1",
44
45
  "superjson": "^2.2.2",
45
46
  "uuid": "^13.0.0",
@@ -62,6 +62,10 @@ model User {
62
62
  // Invitations
63
63
  sentInvitations WorkspaceInvitation[] @relation("UserInvitations")
64
64
 
65
+ // Study guide annotations
66
+ highlights StudyGuideHighlight[] @relation("UserHighlights")
67
+ studyGuideComments StudyGuideComment[] @relation("UserStudyGuideComments")
68
+
65
69
  notifications Notification[]
66
70
  chats Chat[]
67
71
  createdAt DateTime @default(now())
@@ -270,12 +274,54 @@ model ArtifactVersion {
270
274
  createdById String?
271
275
  createdBy User? @relation("UserArtifactVersions", fields: [createdById], references: [id], onDelete: SetNull)
272
276
 
277
+ // Highlights on this version
278
+ highlights StudyGuideHighlight[]
279
+
273
280
  createdAt DateTime @default(now())
274
281
 
275
282
  @@unique([artifactId, version]) // each artifact has 1,2,3...
276
283
  @@index([artifactId])
277
284
  }
278
285
 
286
+ //
287
+ // Study Guide Highlights and Comments
288
+ //
289
+ model StudyGuideHighlight {
290
+ id String @id @default(cuid())
291
+ artifactVersionId String
292
+ artifactVersion ArtifactVersion @relation(fields: [artifactVersionId], references: [id], onDelete: Cascade)
293
+
294
+ userId String
295
+ user User @relation("UserHighlights", fields: [userId], references: [id], onDelete: Cascade)
296
+
297
+ startOffset Int // character offset in the content
298
+ endOffset Int
299
+ selectedText String // the highlighted text (preserved even if content changes)
300
+ color String @default("#FBBF24") // highlight color
301
+
302
+ comments StudyGuideComment[]
303
+
304
+ createdAt DateTime @default(now())
305
+ updatedAt DateTime @updatedAt
306
+
307
+ @@index([artifactVersionId, userId])
308
+ }
309
+
310
+ model StudyGuideComment {
311
+ id String @id @default(cuid())
312
+ highlightId String
313
+ highlight StudyGuideHighlight @relation(fields: [highlightId], references: [id], onDelete: Cascade)
314
+
315
+ userId String
316
+ user User @relation("UserStudyGuideComments", fields: [userId], references: [id], onDelete: Cascade)
317
+ content String
318
+
319
+ createdAt DateTime @default(now())
320
+ updatedAt DateTime @updatedAt
321
+
322
+ @@index([highlightId])
323
+ }
324
+
279
325
  //
280
326
  // Flashcards (child items of a FLASHCARD_SET Artifact)
281
327
  //
@@ -1,5 +1,6 @@
1
1
  import { TRPCError } from '@trpc/server';
2
2
  import { logger } from './logger.js';
3
+ import { withRetry } from './retry.js';
3
4
  import { MarkScheme, UserMarkScheme } from '../types/index.js';
4
5
 
5
6
  // External AI service configuration
@@ -8,8 +9,8 @@ import { MarkScheme, UserMarkScheme } from '../types/index.js';
8
9
  const AI_SERVICE_URL = process.env.INFERENCE_API_URL + '/upload';
9
10
  const AI_RESPONSE_URL = process.env.INFERENCE_API_URL + '/last_response';
10
11
 
11
- console.log('AI_SERVICE_URL', AI_SERVICE_URL);
12
- console.log('AI_RESPONSE_URL', AI_RESPONSE_URL);
12
+ logger.info(`AI_SERVICE_URL: ${AI_SERVICE_URL}`);
13
+ logger.info(`AI_RESPONSE_URL: ${AI_RESPONSE_URL}`);
13
14
 
14
15
  // Mock mode flag - when true, returns fake responses instead of calling AI service
15
16
  const MOCK_MODE = process.env.DONT_TEST_INFERENCE === 'true';
@@ -49,7 +50,7 @@ export class AISessionService {
49
50
  await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
50
51
  // Mock mode - return fake session
51
52
  if (MOCK_MODE) {
52
- console.log(`🎭 MOCK MODE: Initializing AI session for workspace ${workspaceId}`);
53
+ logger.info(`MOCK MODE: Initializing AI session for workspace ${workspaceId}`);
53
54
  const session: AISession = {
54
55
  id: sessionId,
55
56
  workspaceId,
@@ -73,23 +74,23 @@ export class AISessionService {
73
74
 
74
75
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
75
76
  try {
76
- console.log(`🤖 AI Session init attempt ${attempt}/${maxRetries} for workspace ${workspaceId}`);
77
+ logger.info(`AI Session init attempt ${attempt}/${maxRetries} for workspace ${workspaceId}`);
77
78
 
78
79
  const response = await fetch(AI_SERVICE_URL, {
79
80
  method: 'POST',
80
81
  body: formData,
81
82
  });
82
83
 
83
- console.log(`📡 AI Service response status: ${response.status} ${response.statusText}`);
84
+ logger.info(`AI Service response status: ${response.status} ${response.statusText}`);
84
85
 
85
86
  if (!response.ok) {
86
87
  const errorText = await response.text();
87
- console.error(`❌ AI Service error response:`, errorText);
88
+ logger.error(`AI Service error response: ${errorText}`);
88
89
  throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
89
90
  }
90
91
 
91
92
  const result = await response.json();
92
- console.log(`📋 AI Service result:`, result);
93
+ logger.debug(`AI Service result: ${JSON.stringify(result)}`);
93
94
 
94
95
  // If we get a response with a message, consider it successful
95
96
  if (!result.message) {
@@ -106,22 +107,22 @@ export class AISessionService {
106
107
  };
107
108
 
108
109
  this.sessions.set(sessionId, session);
109
- console.log(`✅ AI Session initialized successfully on attempt ${attempt}`);
110
+ logger.info(`AI Session initialized successfully on attempt ${attempt}`);
110
111
  return session;
111
112
 
112
113
  } catch (error) {
113
114
  lastError = error instanceof Error ? error : new Error('Unknown error');
114
- console.error(`❌ AI Session init attempt ${attempt} failed:`, lastError.message);
115
+ logger.error(`AI Session init attempt ${attempt} failed: ${lastError.message}`);
115
116
 
116
117
  if (attempt < maxRetries) {
117
118
  const delay = Math.pow(2, attempt) * 1000; // Exponential backoff: 2s, 4s, 8s
118
- console.log(`⏳ Retrying in ${delay}ms...`);
119
+ logger.info(`Retrying in ${delay}ms...`);
119
120
  await new Promise(resolve => setTimeout(resolve, delay));
120
121
  }
121
122
  }
122
123
  }
123
124
 
124
- console.error(`💥 All ${maxRetries} attempts failed. Last error:`, lastError?.message);
125
+ logger.error(`All ${maxRetries} attempts failed. Last error: ${lastError?.message}`);
125
126
  throw new TRPCError({
126
127
  code: 'INTERNAL_SERVER_ERROR',
127
128
  message: `Failed to initialize AI session after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`,
@@ -163,7 +164,7 @@ export class AISessionService {
163
164
  formData.append('maxPages', maxPages.toString());
164
165
  }
165
166
 
166
- console.log('formData', formData);
167
+ logger.debug('Processing file with formData');
167
168
 
168
169
  // Retry logic for file processing
169
170
  const maxRetries = 3;
@@ -180,7 +181,7 @@ export class AISessionService {
180
181
  const response = await fetch(AI_SERVICE_URL, {
181
182
  method: 'POST',
182
183
  body: formData,
183
- // signal: controller.signal,
184
+ signal: controller.signal,
184
185
  });
185
186
 
186
187
  clearTimeout(timeoutId);
@@ -230,7 +231,7 @@ export class AISessionService {
230
231
  await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
231
232
  // Mock mode - return fake study guide
232
233
  if (MOCK_MODE) {
233
- console.log(`🎭 MOCK MODE: Generating study guide for session ${sessionId}`);
234
+ logger.info(`MOCK MODE: Generating study guide for session ${sessionId}`);
234
235
  return `# Mock Study Guide
235
236
 
236
237
  ## Overview
@@ -252,11 +253,12 @@ This mock study guide demonstrates the structure and format that would be genera
252
253
  *Note: This is a mock response generated when DONT_TEST_INFERENCE=true*`;
253
254
  }
254
255
 
255
- const formData = new FormData();
256
- formData.append('command', 'generate_study_guide');
257
- formData.append('session', sessionId);
258
- formData.append('user', user);
259
- try {
256
+ return withRetry(async () => {
257
+ const formData = new FormData();
258
+ formData.append('command', 'generate_study_guide');
259
+ formData.append('session', sessionId);
260
+ formData.append('user', user);
261
+
260
262
  const response = await fetch(AI_SERVICE_URL, {
261
263
  method: 'POST',
262
264
  body: formData,
@@ -267,13 +269,11 @@ This mock study guide demonstrates the structure and format that would be genera
267
269
  }
268
270
 
269
271
  const result = await response.json();
272
+ if (!result.markdown) {
273
+ throw new Error('AI service returned empty study guide');
274
+ }
270
275
  return result.markdown;
271
- } catch (error) {
272
- throw new TRPCError({
273
- code: 'INTERNAL_SERVER_ERROR',
274
- message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
275
- });
276
- }
276
+ }, { maxRetries: 3, timeoutMs: 300000, label: 'generateStudyGuide' });
277
277
  }
278
278
 
279
279
  // Generate flashcard questions
@@ -291,14 +291,14 @@ This mock study guide demonstrates the structure and format that would be genera
291
291
  })));
292
292
  }
293
293
 
294
- const formData = new FormData();
295
- formData.append('command', 'generate_flashcard_questions');
296
- formData.append('session', sessionId);
297
- formData.append('user', user);
298
- formData.append('num_questions', numQuestions.toString());
299
- formData.append('difficulty', difficulty);
294
+ return withRetry(async () => {
295
+ const formData = new FormData();
296
+ formData.append('command', 'generate_flashcard_questions');
297
+ formData.append('session', sessionId);
298
+ formData.append('user', user);
299
+ formData.append('num_questions', numQuestions.toString());
300
+ formData.append('difficulty', difficulty);
300
301
 
301
- try {
302
302
  const response = await fetch(AI_SERVICE_URL, {
303
303
  method: 'POST',
304
304
  body: formData,
@@ -309,16 +309,8 @@ This mock study guide demonstrates the structure and format that would be genera
309
309
  }
310
310
 
311
311
  const result = await response.json();
312
-
313
- console.log(JSON.parse(result.flashcards))
314
-
315
312
  return JSON.parse(result.flashcards).flashcards;
316
- } catch (error) {
317
- throw new TRPCError({
318
- code: 'INTERNAL_SERVER_ERROR',
319
- message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
320
- });
321
- }
313
+ }, { maxRetries: 3, timeoutMs: 300000, label: 'generateFlashcardQuestions' });
322
314
  }
323
315
 
324
316
  // Generate worksheet questions
@@ -351,15 +343,14 @@ This mock study guide demonstrates the structure and format that would be genera
351
343
  });
352
344
  }
353
345
 
354
- const formData = new FormData();
355
- formData.append('command', 'generate_worksheet_questions');
356
- formData.append('session', sessionId);
357
- formData.append('user', user);
358
- formData.append('num_questions', numQuestions.toString());
359
- formData.append('difficulty', difficulty);
360
-
346
+ return withRetry(async () => {
347
+ const formData = new FormData();
348
+ formData.append('command', 'generate_worksheet_questions');
349
+ formData.append('session', sessionId);
350
+ formData.append('user', user);
351
+ formData.append('num_questions', numQuestions.toString());
352
+ formData.append('difficulty', difficulty);
361
353
 
362
- try {
363
354
  const response = await fetch(AI_SERVICE_URL, {
364
355
  method: 'POST',
365
356
  body: formData,
@@ -370,16 +361,8 @@ This mock study guide demonstrates the structure and format that would be genera
370
361
  }
371
362
 
372
363
  const result = await response.json();
373
-
374
- console.log(JSON.parse(result.worksheet));
375
-
376
364
  return result.worksheet;
377
- } catch (error) {
378
- throw new TRPCError({
379
- code: 'INTERNAL_SERVER_ERROR',
380
- message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
381
- });
382
- }
365
+ }, { maxRetries: 3, timeoutMs: 300000, label: 'generateWorksheetQuestions' });
383
366
  }
384
367
 
385
368
  async checkWorksheetQuestions(sessionId: string, user: string, question: string, answer: string, mark_scheme: MarkScheme): Promise<UserMarkScheme> {
@@ -402,7 +385,7 @@ This mock study guide demonstrates the structure and format that would be genera
402
385
  }
403
386
 
404
387
  const result = await response.json();
405
- console.log(result.marking);
388
+ logger.debug(`Worksheet marking result received`);
406
389
  return JSON.parse(result.marking);
407
390
  }
408
391
 
@@ -576,6 +559,77 @@ This mock study guide demonstrates the structure and format that would be genera
576
559
  });
577
560
  }
578
561
  }
562
+
563
+ async segmentStudyGuide (sessionId: string, user: string, studyGuide: string): Promise<{
564
+ hint: string;
565
+ content: string
566
+ }[]> {
567
+ // def generate_study_guide_segmentation(request):
568
+ // user = request.form.get("user")
569
+ // session = request.form.get("session")
570
+ // study_guide = request.form.get("study_guide")
571
+
572
+ // if not user or not session:
573
+ // return {"error": "Session not initialized."}, 400
574
+ // if not study_guide:
575
+ // print("Study guide not provided.")
576
+ // return {"error": "Study guide not provided."}, 400
577
+
578
+ // messages = generate_segmentation(study_guide)
579
+ // return {"segmentation": messages}, 200
580
+
581
+ const formData = new FormData();
582
+ formData.append('command', 'generate_study_guide_segmentation');
583
+ formData.append('session', sessionId);
584
+ formData.append('user', user);
585
+ formData.append('study_guide', studyGuide);
586
+ try {
587
+ const response = await fetch(AI_SERVICE_URL, {
588
+ method: 'POST',
589
+ body: formData,
590
+ });
591
+ if (!response.ok) {
592
+ throw new Error(`AI service error: ${response.status} ${response.statusText}`);
593
+ }
594
+ const result = await response.json();
595
+ return result.segmentation;
596
+ } catch (error) {
597
+ throw new TRPCError({
598
+ code: 'INTERNAL_SERVER_ERROR',
599
+ message: `Failed to segment study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
600
+ });
601
+ }
602
+ }
603
+ async validateSegmentSummary(sessionId: string, user: string, segmentContent: string, studentResponse: string, studyGuide: string): Promise<{
604
+ valid: boolean;
605
+ feedback: string;
606
+ }> {
607
+ const formData = new FormData();
608
+ formData.append('command', 'validate_segment_summary');
609
+ formData.append('session', sessionId);
610
+ formData.append('user', user);
611
+
612
+ formData.append('segment_content', segmentContent);
613
+ formData.append('student_response', studentResponse);
614
+ formData.append('study_guide', studyGuide);
615
+ try {
616
+ const response = await fetch(AI_SERVICE_URL, {
617
+ method: 'POST',
618
+ body: formData,
619
+ });
620
+ if (!response.ok) {
621
+ throw new Error(`AI service error: ${response.status} ${response.statusText}`);
622
+ }
623
+ const result = await response.json();
624
+ return result.feedback;
625
+ } catch (error) {
626
+ throw new TRPCError({
627
+ code: 'INTERNAL_SERVER_ERROR',
628
+ message: `Failed to validate segment summary: ${error instanceof Error ? error.message : 'Unknown error'}`,
629
+ });
630
+ }
631
+ }
632
+
579
633
  // Get session by ID
580
634
  getSession(sessionId: string): AISession | undefined {
581
635
  return this.sessions.get(sessionId);
@@ -598,21 +652,21 @@ This mock study guide demonstrates the structure and format that would be genera
598
652
  await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
599
653
  // Mock mode - always return healthy
600
654
  if (MOCK_MODE) {
601
- console.log('🎭 MOCK MODE: AI service health check - returning healthy');
655
+ logger.info('MOCK MODE: AI service health check - returning healthy');
602
656
  return true;
603
657
  }
604
658
 
605
659
  try {
606
- console.log('🏥 Checking AI service health...');
660
+ logger.info('Checking AI service health...');
607
661
  const response = await fetch(AI_SERVICE_URL, {
608
662
  method: 'POST',
609
663
  body: new FormData(), // Empty form data
610
664
  });
611
665
 
612
- console.log(`🏥 AI Service health check status: ${response.status}`);
666
+ logger.info(`AI Service health check status: ${response.status}`);
613
667
  return response.ok;
614
668
  } catch (error) {
615
- console.error('🏥 AI Service health check failed:', error);
669
+ logger.error('AI Service health check failed:', error);
616
670
  return false;
617
671
  }
618
672
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared constants for artifact types.
3
+ * Mirrors the Prisma ArtifactType enum to avoid direct type imports
4
+ * in contexts where Prisma client types may not be available.
5
+ */
6
+ export const ArtifactType = {
7
+ STUDY_GUIDE: 'STUDY_GUIDE',
8
+ FLASHCARD_SET: 'FLASHCARD_SET',
9
+ WORKSHEET: 'WORKSHEET',
10
+ MEETING_SUMMARY: 'MEETING_SUMMARY',
11
+ PODCAST_EPISODE: 'PODCAST_EPISODE',
12
+ } as const;
13
+
14
+ export type ArtifactTypeValue = (typeof ArtifactType)[keyof typeof ArtifactType];
@@ -0,0 +1,46 @@
1
+ import { Resend } from 'resend';
2
+ import { logger } from './logger.js';
3
+
4
+ // const resend = new Resend(process.env.RESEND_API_KEY);
5
+
6
+ const FROM_EMAIL = process.env.EMAIL_FROM || 'Scribe <noreply@goscribe.app>';
7
+ const APP_URL = process.env.AUTH_URL || 'http://localhost:3000';
8
+
9
+ export async function sendVerificationEmail(
10
+ email: string,
11
+ token: string,
12
+ name?: string | null
13
+ ): Promise<boolean> {
14
+ const verifyUrl = APP_URL + '/verify-email?token=' + token;
15
+ const greeting = name ? ', ' + name : '';
16
+
17
+ const html = '<div style="font-family:system-ui,sans-serif;max-width:480px;margin:0 auto;padding:40px 20px;">'
18
+ + '<h2 style="font-size:20px;font-weight:600;margin-bottom:8px;">Welcome to Scribe' + greeting + '!</h2>'
19
+ + '<p style="color:#6b7280;font-size:14px;line-height:1.6;margin-bottom:24px;">'
20
+ + 'Please verify your email address to get the most out of your account.</p>'
21
+ + '<a href="' + verifyUrl + '" style="display:inline-block;background-color:#7c3aed;color:#fff;text-decoration:none;padding:10px 24px;border-radius:6px;font-size:14px;font-weight:500;">Verify Email</a>'
22
+ + '<p style="color:#9ca3af;font-size:12px;margin-top:32px;line-height:1.5;">'
23
+ + 'If you did not create an account on Scribe, you can safely ignore this email.'
24
+ + ' This link expires in 24 hours.</p>'
25
+ + '</div>';
26
+
27
+ try {
28
+ // const { error } = await resend.emails.send({
29
+ // from: FROM_EMAIL,
30
+ // to: email,
31
+ // subject: 'Verify your email - Scribe',
32
+ // html,
33
+ // });
34
+
35
+ // if (error) {
36
+ // logger.error('Failed to send verification email to ' + email + ': ' + error.message);
37
+ // return false;
38
+ // }
39
+
40
+ logger.info('Verification email sent to ' + email);
41
+ return true;
42
+ } catch (err) {
43
+ logger.error('Email send error:', err);
44
+ return false;
45
+ }
46
+ }
@@ -13,7 +13,7 @@ async function inference(prompt: string) {
13
13
  });
14
14
  return response;
15
15
  } catch (error) {
16
- console.error('Inference error:', error);
16
+ // Inference error logged at call site
17
17
  throw error;
18
18
  }
19
19
  }
package/src/lib/logger.ts CHANGED
@@ -163,7 +163,8 @@ class Logger {
163
163
  return `\x1b[90m${time}\x1b[0m`; // Gray color
164
164
  }
165
165
 
166
- private formatContext(context: string): string {
166
+ private formatContext(context: string | unknown): string {
167
+ if (typeof context !== 'string') return '';
167
168
  const icon = CONTEXT_ICONS[context.toUpperCase()] || '📦';
168
169
  return `\x1b[94m${icon}${context}\x1b[0m `; // Blue color
169
170
  }
@@ -248,20 +249,36 @@ class Logger {
248
249
  }
249
250
  }
250
251
 
251
- error(message: string, context?: string, metadata?: Record<string, any>, error?: Error): void {
252
- this.log(LogLevel.ERROR, message, context, metadata, error);
252
+ error(message: string, contextOrError?: string | Error | unknown, metadata?: Record<string, any>, error?: Error): void {
253
+ if (contextOrError instanceof Error) {
254
+ this.log(LogLevel.ERROR, message, undefined, metadata, contextOrError);
255
+ } else {
256
+ this.log(LogLevel.ERROR, message, contextOrError as string | undefined, metadata, error);
257
+ }
253
258
  }
254
259
 
255
- warn(message: string, context?: string, metadata?: Record<string, any>): void {
256
- this.log(LogLevel.WARN, message, context, metadata);
260
+ warn(message: string, contextOrMeta?: string | Record<string, any>, metadata?: Record<string, any>): void {
261
+ if (typeof contextOrMeta === 'object' && contextOrMeta !== null) {
262
+ this.log(LogLevel.WARN, message, undefined, contextOrMeta as Record<string, any>);
263
+ } else {
264
+ this.log(LogLevel.WARN, message, contextOrMeta as string | undefined, metadata);
265
+ }
257
266
  }
258
267
 
259
- info(message: string, context?: string, metadata?: Record<string, any>): void {
260
- this.log(LogLevel.INFO, message, context, metadata);
268
+ info(message: string, contextOrMeta?: string | Record<string, any>, metadata?: Record<string, any>): void {
269
+ if (typeof contextOrMeta === 'object' && contextOrMeta !== null) {
270
+ this.log(LogLevel.INFO, message, undefined, contextOrMeta as Record<string, any>);
271
+ } else {
272
+ this.log(LogLevel.INFO, message, contextOrMeta as string | undefined, metadata);
273
+ }
261
274
  }
262
275
 
263
- debug(message: string, context?: string, metadata?: Record<string, any>): void {
264
- this.log(LogLevel.DEBUG, message, context, metadata);
276
+ debug(message: string, contextOrMeta?: string | Record<string, any>, metadata?: Record<string, any>): void {
277
+ if (typeof contextOrMeta === 'object' && contextOrMeta !== null) {
278
+ this.log(LogLevel.DEBUG, message, undefined, contextOrMeta as Record<string, any>);
279
+ } else {
280
+ this.log(LogLevel.DEBUG, message, contextOrMeta as string | undefined, metadata);
281
+ }
265
282
  }
266
283
 
267
284
  trace(message: string, context?: string, metadata?: Record<string, any>): void {
package/src/lib/pusher.ts CHANGED
@@ -20,7 +20,7 @@ export class PusherService {
20
20
  await pusher.trigger(channel, eventName, data);
21
21
  logger.info(`📡 Pusher notification sent: ${eventName} to ${channel}`);
22
22
  } catch (error) {
23
- console.error('Pusher notification error:', error);
23
+ logger.error('Pusher notification error:', 'PUSHER', { error });
24
24
  }
25
25
  }
26
26
 
@@ -96,7 +96,7 @@ export class PusherService {
96
96
  await pusher.trigger(channel, 'analysis_progress', progress);
97
97
  logger.info(`📡 Analysis progress sent to ${channel}: ${progress.status}`);
98
98
  } catch (error) {
99
- console.error('Pusher progress notification error:', error);
99
+ logger.error('Pusher progress notification error:', 'PUSHER', { error });
100
100
  }
101
101
  }
102
102
 
@@ -106,9 +106,9 @@ export class PusherService {
106
106
  const channel = channelId; // Use channelId directly as channel name
107
107
  const eventName = event;
108
108
  await pusher.trigger(channel, eventName, data);
109
- console.log(`📡 Pusher notification sent: ${eventName} to ${channel}`);
109
+ logger.info(`Pusher notification sent: ${eventName} to ${channel}`);
110
110
  } catch (error) {
111
- console.error('Pusher notification error:', error);
111
+ logger.error('Pusher channel notification error:', 'PUSHER', { error });
112
112
  }
113
113
  }
114
114
  }