@vue-skuilder/db 0.1.20 → 0.1.21

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 (70) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/{classroomDB-CZdMBiTU.d.ts → contentSource-BP9hznNV.d.ts} +150 -196
  3. package/dist/{classroomDB-PxDZTky3.d.cts → contentSource-DsJadoBU.d.cts} +150 -196
  4. package/dist/core/index.d.cts +3 -3
  5. package/dist/core/index.d.ts +3 -3
  6. package/dist/core/index.js +615 -1758
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/index.mjs +579 -1727
  9. package/dist/core/index.mjs.map +1 -1
  10. package/dist/{dataLayerProvider-D8o6ZnKW.d.ts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
  11. package/dist/{dataLayerProvider-D0MoZMjH.d.cts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
  12. package/dist/impl/couch/index.d.cts +6 -22
  13. package/dist/impl/couch/index.d.ts +6 -22
  14. package/dist/impl/couch/index.js +598 -1769
  15. package/dist/impl/couch/index.js.map +1 -1
  16. package/dist/impl/couch/index.mjs +579 -1755
  17. package/dist/impl/couch/index.mjs.map +1 -1
  18. package/dist/impl/static/index.d.cts +22 -6
  19. package/dist/impl/static/index.d.ts +22 -6
  20. package/dist/impl/static/index.js +617 -1629
  21. package/dist/impl/static/index.js.map +1 -1
  22. package/dist/impl/static/index.mjs +607 -1624
  23. package/dist/impl/static/index.mjs.map +1 -1
  24. package/dist/index.d.cts +64 -56
  25. package/dist/index.d.ts +64 -56
  26. package/dist/index.js +1000 -2161
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +970 -2127
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/pouch/index.js +3 -0
  31. package/dist/pouch/index.js.map +1 -1
  32. package/dist/pouch/index.mjs +3 -0
  33. package/dist/pouch/index.mjs.map +1 -1
  34. package/docs/navigators-architecture.md +2 -9
  35. package/package.json +3 -3
  36. package/src/core/interfaces/classroomDB.ts +5 -13
  37. package/src/core/interfaces/contentSource.ts +6 -66
  38. package/src/core/interfaces/courseDB.ts +2 -7
  39. package/src/core/navigators/Pipeline.ts +24 -53
  40. package/src/core/navigators/PipelineAssembler.ts +1 -1
  41. package/src/core/navigators/defaults.ts +84 -0
  42. package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +11 -25
  43. package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +10 -24
  44. package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +10 -24
  45. package/src/core/navigators/filters/userTagPreference.ts +1 -16
  46. package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
  47. package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
  48. package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
  49. package/src/core/navigators/generators/types.ts +1 -1
  50. package/src/core/navigators/index.ts +36 -91
  51. package/src/impl/couch/classroomDB.ts +100 -103
  52. package/src/impl/couch/courseDB.ts +5 -81
  53. package/src/impl/couch/pouchdb-setup.ts +7 -0
  54. package/src/impl/static/StaticDataUnpacker.ts +50 -1
  55. package/src/impl/static/courseDB.ts +76 -37
  56. package/src/study/SessionController.ts +122 -202
  57. package/src/study/SourceMixer.ts +65 -0
  58. package/src/study/TagFilteredContentSource.ts +49 -92
  59. package/src/study/index.ts +1 -0
  60. package/src/study/services/CardHydrationService.ts +165 -81
  61. package/src/util/dataDirectory.ts +1 -1
  62. package/src/util/index.ts +0 -1
  63. package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
  64. package/tests/core/navigators/Pipeline.test.ts +5 -72
  65. package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
  66. package/tests/core/navigators/navigators.test.ts +118 -151
  67. package/src/core/navigators/hardcodedOrder.ts +0 -163
  68. package/src/util/tuiLogger.ts +0 -139
  69. /package/src/core/navigators/{inferredPreference.ts → filters/inferredPreferenceStub.ts} +0 -0
  70. /package/src/core/navigators/{userGoal.ts → filters/userGoalStub.ts} +0 -0
@@ -1,27 +1,23 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
1
+ import { describe, it, expect } from 'vitest';
2
2
  import { ContentNavigator, WeightedCard, getCardOrigin } from '../../../src/core/navigators/index';
3
- import { StudySessionNewItem, StudySessionReviewItem } from '../../../src/core';
4
- import { ScheduledCard } from '../../../src/core/types/user';
5
3
 
6
- // Mock implementation of ContentNavigator for testing the default getWeightedCards
4
+ // Mock implementation of ContentNavigator for testing base class behavior
7
5
  class MockNavigator extends ContentNavigator {
8
- private mockNewCards: StudySessionNewItem[] = [];
9
- private mockReviews: (StudySessionReviewItem & ScheduledCard)[] = [];
10
-
11
- setMockNewCards(cards: StudySessionNewItem[]) {
12
- this.mockNewCards = cards;
13
- }
6
+ name: string = 'MockNavigator';
7
+ // Base class no longer has legacy methods - subclasses must implement getWeightedCards
8
+ }
14
9
 
15
- setMockReviews(reviews: (StudySessionReviewItem & ScheduledCard)[]) {
16
- this.mockReviews = reviews;
17
- }
10
+ // Mock implementation that properly implements getWeightedCards
11
+ class ProperMockNavigator extends ContentNavigator {
12
+ name: string = 'ProperMockNavigator';
13
+ private cards: WeightedCard[] = [];
18
14
 
19
- async getNewCards(n?: number): Promise<StudySessionNewItem[]> {
20
- return this.mockNewCards.slice(0, n);
15
+ setCards(cards: WeightedCard[]) {
16
+ this.cards = cards;
21
17
  }
22
18
 
23
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
24
- return this.mockReviews;
19
+ async getWeightedCards(limit: number): Promise<WeightedCard[]> {
20
+ return this.cards.slice(0, limit);
25
21
  }
26
22
  }
27
23
 
@@ -77,111 +73,97 @@ describe('WeightedCard', () => {
77
73
  });
78
74
  });
79
75
 
80
- describe('ContentNavigator.getWeightedCards', () => {
81
- let navigator: MockNavigator;
76
+ describe('ContentNavigator base class', () => {
77
+ it('should throw error when getWeightedCards is not implemented', async () => {
78
+ const navigator = new MockNavigator();
82
79
 
83
- beforeEach(() => {
84
- navigator = new MockNavigator();
80
+ await expect(navigator.getWeightedCards(10)).rejects.toThrow(
81
+ 'must implement getWeightedCards()'
82
+ );
85
83
  });
86
84
 
87
- it('should return empty array when no cards available', async () => {
88
- navigator.setMockNewCards([]);
89
- navigator.setMockReviews([]);
90
-
91
- const result = await navigator.getWeightedCards(10);
85
+ it('should throw error mentioning legacy methods have been removed', async () => {
86
+ const navigator = new MockNavigator();
92
87
 
93
- expect(result).toEqual([]);
94
- });
95
-
96
- it('should assign score=1.0 to all cards by default', async () => {
97
- navigator.setMockNewCards([
98
- {
99
- cardID: 'card-1',
100
- courseID: 'course-1',
101
- contentSourceType: 'course',
102
- contentSourceID: 'course-1',
103
- status: 'new',
104
- },
105
- {
106
- cardID: 'card-2',
107
- courseID: 'course-1',
108
- contentSourceType: 'course',
109
- contentSourceID: 'course-1',
110
- status: 'new',
111
- },
112
- ]);
113
-
114
- const result = await navigator.getWeightedCards(10);
115
-
116
- expect(result).toHaveLength(2);
117
- expect(result[0].score).toBe(1.0);
118
- expect(result[1].score).toBe(1.0);
119
- });
120
-
121
- it('should mark new cards with source="new"', async () => {
122
- navigator.setMockNewCards([
123
- {
124
- cardID: 'card-1',
125
- courseID: 'course-1',
126
- contentSourceType: 'course',
127
- contentSourceID: 'course-1',
128
- status: 'new',
129
- },
130
- ]);
131
-
132
- const result = await navigator.getWeightedCards(10);
133
-
134
- expect(getCardOrigin(result[0])).toBe('new');
135
- expect(result[0].provenance[0].reason).toContain('new');
88
+ await expect(navigator.getWeightedCards(10)).rejects.toThrow();
136
89
  });
90
+ });
137
91
 
138
- it('should mark reviews with origin="review"', async () => {
139
- navigator.setMockReviews([
92
+ describe('ContentNavigator.getWeightedCards with proper implementation', () => {
93
+ it('should return cards from implementation', async () => {
94
+ const navigator = new ProperMockNavigator();
95
+ navigator.setCards([
140
96
  {
141
- cardID: 'review-1',
142
- courseID: 'course-1',
143
- contentSourceType: 'course',
144
- contentSourceID: 'course-1',
145
- status: 'review',
146
- reviewID: 'review-id-1',
147
- _id: 'scheduled-1',
148
- cardId: 'review-1',
97
+ cardId: 'card-1',
149
98
  courseId: 'course-1',
150
- scheduledFor: 'course',
151
- schedulingAgentId: 'agent-1',
152
- reviewTime: new Date(),
153
- scheduledAt: new Date(),
154
- } as unknown as StudySessionReviewItem & ScheduledCard,
99
+ score: 0.8,
100
+ provenance: [
101
+ {
102
+ strategy: 'test',
103
+ strategyName: 'Test',
104
+ strategyId: 'TEST',
105
+ action: 'generated',
106
+ score: 0.8,
107
+ reason: 'Test new card',
108
+ },
109
+ ],
110
+ },
155
111
  ]);
156
112
 
157
113
  const result = await navigator.getWeightedCards(10);
158
114
 
159
- expect(getCardOrigin(result[0])).toBe('review');
160
- expect(result[0].provenance[0].reason).toContain('review');
115
+ expect(result).toHaveLength(1);
116
+ expect(result[0].cardId).toBe('card-1');
117
+ expect(result[0].score).toBe(0.8);
161
118
  });
162
119
 
163
120
  it('should respect limit parameter', async () => {
164
- navigator.setMockNewCards([
121
+ const navigator = new ProperMockNavigator();
122
+ navigator.setCards([
165
123
  {
166
- cardID: 'card-1',
167
- courseID: 'course-1',
168
- contentSourceType: 'course',
169
- contentSourceID: 'course-1',
170
- status: 'new',
124
+ cardId: 'card-1',
125
+ courseId: 'course-1',
126
+ score: 0.9,
127
+ provenance: [
128
+ {
129
+ strategy: 'test',
130
+ strategyName: 'Test',
131
+ strategyId: 'TEST',
132
+ action: 'generated',
133
+ score: 0.9,
134
+ reason: 'Test new card',
135
+ },
136
+ ],
171
137
  },
172
138
  {
173
- cardID: 'card-2',
174
- courseID: 'course-1',
175
- contentSourceType: 'course',
176
- contentSourceID: 'course-1',
177
- status: 'new',
139
+ cardId: 'card-2',
140
+ courseId: 'course-1',
141
+ score: 0.8,
142
+ provenance: [
143
+ {
144
+ strategy: 'test',
145
+ strategyName: 'Test',
146
+ strategyId: 'TEST',
147
+ action: 'generated',
148
+ score: 0.8,
149
+ reason: 'Test new card',
150
+ },
151
+ ],
178
152
  },
179
153
  {
180
- cardID: 'card-3',
181
- courseID: 'course-1',
182
- contentSourceType: 'course',
183
- contentSourceID: 'course-1',
184
- status: 'new',
154
+ cardId: 'card-3',
155
+ courseId: 'course-1',
156
+ score: 0.7,
157
+ provenance: [
158
+ {
159
+ strategy: 'test',
160
+ strategyName: 'Test',
161
+ strategyId: 'TEST',
162
+ action: 'generated',
163
+ score: 0.7,
164
+ reason: 'Test new card',
165
+ },
166
+ ],
185
167
  },
186
168
  ]);
187
169
 
@@ -190,57 +172,42 @@ describe('ContentNavigator.getWeightedCards', () => {
190
172
  expect(result).toHaveLength(2);
191
173
  });
192
174
 
193
- it('should combine new cards and reviews', async () => {
194
- navigator.setMockNewCards([
195
- {
196
- cardID: 'new-1',
197
- courseID: 'course-1',
198
- contentSourceType: 'course',
199
- contentSourceID: 'course-1',
200
- status: 'new',
201
- },
202
- ]);
203
- navigator.setMockReviews([
204
- {
205
- cardID: 'review-1',
206
- courseID: 'course-1',
207
- contentSourceType: 'course',
208
- contentSourceID: 'course-1',
209
- status: 'review',
210
- reviewID: 'review-id-1',
211
- _id: 'scheduled-1',
212
- cardId: 'review-1',
213
- courseId: 'course-1',
214
- scheduledFor: 'course',
215
- schedulingAgentId: 'agent-1',
216
- reviewTime: new Date(),
217
- scheduledAt: new Date(),
218
- } as unknown as StudySessionReviewItem & ScheduledCard,
219
- ]);
220
-
221
- const result = await navigator.getWeightedCards(10);
222
-
223
- expect(result).toHaveLength(2);
224
- const origins = result.map((c) => getCardOrigin(c));
225
- expect(origins).toContain('new');
226
- expect(origins).toContain('review');
227
- });
228
-
229
- it('should correctly map cardID to cardId and courseID to courseId', async () => {
230
- navigator.setMockNewCards([
231
- {
232
- cardID: 'CARD-123',
233
- courseID: 'COURSE-456',
234
- contentSourceType: 'course',
235
- contentSourceID: 'COURSE-456',
236
- status: 'new',
237
- },
238
- ]);
175
+ it('should correctly identify card origins from provenance', () => {
176
+ const newCard: WeightedCard = {
177
+ cardId: 'new-1',
178
+ courseId: 'course-1',
179
+ score: 1.0,
180
+ provenance: [
181
+ {
182
+ strategy: 'test',
183
+ strategyName: 'Test',
184
+ strategyId: 'TEST',
185
+ action: 'generated',
186
+ score: 1.0,
187
+ reason: 'ELO distance 50, new card',
188
+ },
189
+ ],
190
+ };
239
191
 
240
- const result = await navigator.getWeightedCards(10);
192
+ const reviewCard: WeightedCard = {
193
+ cardId: 'review-1',
194
+ courseId: 'course-1',
195
+ score: 0.8,
196
+ reviewID: 'SCHEDULED_CARD-123',
197
+ provenance: [
198
+ {
199
+ strategy: 'srs',
200
+ strategyName: 'SRS',
201
+ strategyId: 'SRS',
202
+ action: 'generated',
203
+ score: 0.8,
204
+ reason: '48h overdue (interval: 72h), review',
205
+ },
206
+ ],
207
+ };
241
208
 
242
- expect(result[0].cardId).toBe('CARD-123');
243
- expect(result[0].courseId).toBe('COURSE-456');
209
+ expect(getCardOrigin(newCard)).toBe('new');
210
+ expect(getCardOrigin(reviewCard)).toBe('review');
244
211
  });
245
212
  });
246
213
 
@@ -1,163 +0,0 @@
1
- import type {
2
- CourseDBInterface,
3
- QualifiedCardID,
4
- StudySessionNewItem,
5
- StudySessionReviewItem,
6
- UserDBInterface,
7
- } from '..';
8
- import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
9
- import type { ScheduledCard } from '../types/user';
10
- import { ContentNavigator } from './index';
11
- import type { WeightedCard } from './index';
12
- import type { CardGenerator, GeneratorContext } from './generators/types';
13
- import { logger } from '../../util/logger';
14
-
15
- // ============================================================================
16
- // HARDCODED ORDER NAVIGATOR
17
- // ============================================================================
18
- //
19
- // A generator strategy that presents cards in a fixed, author-defined order.
20
- //
21
- // Use case: When course authors want explicit control over content sequencing,
22
- // e.g., teaching letters in a specific pedagogical order.
23
- //
24
- // The order is defined in serializedData as a JSON array of card IDs.
25
- // Earlier positions in the array get higher scores.
26
- //
27
- // ============================================================================
28
-
29
- /**
30
- * A navigation strategy that presents cards in a fixed order.
31
- *
32
- * Implements CardGenerator for use in Pipeline architecture.
33
- * Also extends ContentNavigator for backward compatibility with legacy code.
34
- *
35
- * Scoring:
36
- * - Earlier cards in the sequence get higher scores
37
- * - Reviews get score 1.0 (highest priority)
38
- * - New cards scored by position: 1.0 - (position / total) * 0.5
39
- */
40
- export default class HardcodedOrderNavigator extends ContentNavigator implements CardGenerator {
41
- /** Human-readable name for CardGenerator interface */
42
- name: string;
43
-
44
- private orderedCardIds: string[] = [];
45
-
46
- constructor(
47
- user: UserDBInterface,
48
- course: CourseDBInterface,
49
- strategyData: ContentNavigationStrategyData
50
- ) {
51
- super(user, course, strategyData);
52
- this.name = strategyData.name || 'Hardcoded Order';
53
-
54
- if (strategyData.serializedData) {
55
- try {
56
- this.orderedCardIds = JSON.parse(strategyData.serializedData);
57
- } catch (e) {
58
- logger.error('Failed to parse serializedData for HardcodedOrderNavigator', e);
59
- }
60
- }
61
- }
62
-
63
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
64
- const reviews = await this.user.getPendingReviews(this.course.getCourseID());
65
- return reviews.map((r) => {
66
- return {
67
- ...r,
68
- contentSourceType: 'course',
69
- contentSourceID: this.course.getCourseID(),
70
- cardID: r.cardId,
71
- courseID: r.courseId,
72
- reviewID: r._id,
73
- status: 'review',
74
- };
75
- });
76
- }
77
-
78
- async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
79
- const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);
80
-
81
- const newCardIds = this.orderedCardIds.filter((cardId) => !activeCardIds.includes(cardId));
82
-
83
- const cardsToReturn = newCardIds.slice(0, limit);
84
-
85
- return cardsToReturn.map((cardId) => {
86
- return {
87
- cardID: cardId,
88
- courseID: this.course.getCourseID(),
89
- contentSourceType: 'course',
90
- contentSourceID: this.course.getCourseID(),
91
- status: 'new',
92
- };
93
- });
94
- }
95
-
96
- /**
97
- * Get cards in hardcoded order with scores based on position.
98
- *
99
- * Earlier cards in the sequence get higher scores.
100
- * Score formula: 1.0 - (position / totalCards) * 0.5
101
- * This ensures scores range from 1.0 (first card) to 0.5+ (last card).
102
- *
103
- * This method supports both the legacy signature (limit only) and the
104
- * CardGenerator interface signature (limit, context).
105
- *
106
- * @param limit - Maximum number of cards to return
107
- * @param _context - Optional GeneratorContext (currently unused, but required for interface)
108
- */
109
- async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {
110
- const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);
111
- const reviews = await this.getPendingReviews();
112
-
113
- // Filter out already-active cards
114
- const newCardIds = this.orderedCardIds.filter((cardId) => !activeCardIds.includes(cardId));
115
-
116
- const totalCards = newCardIds.length;
117
-
118
- // Score new cards by position in sequence
119
- const scoredNew: WeightedCard[] = newCardIds.slice(0, limit).map((cardId, index) => {
120
- const position = index + 1;
121
- const score = Math.max(0.5, 1.0 - (index / totalCards) * 0.5);
122
-
123
- return {
124
- cardId,
125
- courseId: this.course.getCourseID(),
126
- score,
127
- provenance: [
128
- {
129
- strategy: 'hardcodedOrder',
130
- strategyName: this.strategyName || this.name,
131
- strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hardcoded',
132
- action: 'generated',
133
- score,
134
- reason: `Position ${position} of ${totalCards} in fixed sequence, new card`,
135
- },
136
- ],
137
- };
138
- });
139
-
140
- // Score reviews at 1.0 (highest priority)
141
- const scoredReviews: WeightedCard[] = reviews.map((r) => ({
142
- cardId: r.cardID,
143
- courseId: r.courseID,
144
- score: 1.0,
145
- provenance: [
146
- {
147
- strategy: 'hardcodedOrder',
148
- strategyName: this.strategyName || this.name,
149
- strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hardcoded',
150
- action: 'generated',
151
- score: 1.0,
152
- reason: 'Scheduled review, highest priority',
153
- },
154
- ],
155
- }));
156
-
157
- // Combine (reviews already sorted at top due to score=1.0)
158
- const all = [...scoredReviews, ...scoredNew];
159
- all.sort((a, b) => b.score - a.score);
160
-
161
- return all.slice(0, limit);
162
- }
163
- }
@@ -1,139 +0,0 @@
1
- // TUI-aware logging utility that redirects logs to file in Node.js
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import { getAppDataDirectory } from './dataDirectory';
5
-
6
- let logFile: string | null = null;
7
- let isNodeEnvironment = false;
8
-
9
- /**
10
- * Initialize TUI logging - redirect console logs to file in Node.js
11
- */
12
- export function initializeTuiLogging(): void {
13
- // Detect Node.js environment
14
- isNodeEnvironment = typeof window === 'undefined' && typeof process !== 'undefined';
15
-
16
- if (!isNodeEnvironment) {
17
- return; // Browser environment - keep normal console logging
18
- }
19
-
20
- try {
21
- // Set up log file path
22
- logFile = path.join(getAppDataDirectory(), 'lastrun.log');
23
-
24
- // Clear previous log file
25
- if (fs.existsSync(logFile)) {
26
- fs.unlinkSync(logFile);
27
- }
28
-
29
- // Create initial log entry
30
- const startTime = new Date().toISOString();
31
- fs.writeFileSync(logFile, `=== TUI Session Started: ${startTime} ===\n`);
32
-
33
- // Redirect console methods to file
34
- const originalConsole = {
35
- // eslint-disable-next-line no-console
36
- log: console.log,
37
- // eslint-disable-next-line no-console
38
- error: console.error,
39
- // eslint-disable-next-line no-console
40
- warn: console.warn,
41
- // eslint-disable-next-line no-console
42
- info: console.info
43
- };
44
-
45
- const writeToLog = (level: string, args: any[]) => {
46
- const timestamp = new Date().toISOString();
47
- const message = args.map(arg =>
48
- typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
49
- ).join(' ');
50
-
51
- const logEntry = `[${timestamp}] ${level}: ${message}\n`;
52
-
53
- try {
54
- fs.appendFileSync(logFile!, logEntry);
55
- } catch (err) {
56
- // Fallback to original console if file write fails
57
- originalConsole.error('Failed to write to log file:', err);
58
- originalConsole[level.toLowerCase() as keyof typeof originalConsole](...args);
59
- }
60
- };
61
-
62
- // Override console methods
63
- // eslint-disable-next-line no-console
64
- console.log = (...args) => writeToLog('INFO', args);
65
- // eslint-disable-next-line no-console
66
- console.info = (...args) => writeToLog('INFO', args);
67
- // eslint-disable-next-line no-console
68
- console.warn = (...args) => writeToLog('WARN', args);
69
- // eslint-disable-next-line no-console
70
- console.error = (...args) => writeToLog('ERROR', args);
71
-
72
- // Store original methods for potential restoration
73
- (console as any)._originalMethods = originalConsole;
74
-
75
- // eslint-disable-next-line no-console
76
- console.log('TUI logging initialized - logs redirected to', logFile);
77
-
78
- } catch (err) {
79
- // eslint-disable-next-line no-console
80
- console.error('Failed to initialize TUI logging:', err);
81
- }
82
- }
83
-
84
- /**
85
- * Get the current log file path (for debugging)
86
- */
87
- export function getLogFilePath(): string | null {
88
- return logFile;
89
- }
90
-
91
- /**
92
- * Show user-facing message (always visible in TUI)
93
- */
94
- export function showUserMessage(message: string): void {
95
- if (isNodeEnvironment) {
96
- // In Node.js, write directly to stdout to bypass log redirection
97
- process.stdout.write(message + '\n');
98
- } else {
99
- // In browser, use normal console
100
- // eslint-disable-next-line no-console
101
- console.log(message);
102
- }
103
- }
104
-
105
- /**
106
- * Show user-facing error (always visible in TUI)
107
- */
108
- export function showUserError(message: string): void {
109
- if (isNodeEnvironment) {
110
- // In Node.js, write directly to stderr to bypass log redirection
111
- process.stderr.write('Error: ' + message + '\n');
112
- } else {
113
- // In browser, use normal console
114
- // eslint-disable-next-line no-console
115
- console.error(message);
116
- }
117
- }
118
-
119
- /**
120
- * Logger object with standard log levels
121
- */
122
- export const logger = {
123
- debug: (message: string, ...args: any[]) => {
124
- // eslint-disable-next-line no-console
125
- console.log(`[DEBUG] ${message}`, ...args);
126
- },
127
- info: (message: string, ...args: any[]) => {
128
- // eslint-disable-next-line no-console
129
- console.info(`[INFO] ${message}`, ...args);
130
- },
131
- warn: (message: string, ...args: any[]) => {
132
- // eslint-disable-next-line no-console
133
- console.warn(`[WARN] ${message}`, ...args);
134
- },
135
- error: (message: string, ...args: any[]) => {
136
- // eslint-disable-next-line no-console
137
- console.error(`[ERROR] ${message}`, ...args);
138
- },
139
- };