@graphty/remote-logger 1.1.1 → 1.2.1

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 (110) hide show
  1. package/README.md +318 -10
  2. package/dist/client/RemoteLogClient.d.ts +2 -0
  3. package/dist/client/RemoteLogClient.d.ts.map +1 -1
  4. package/dist/client/RemoteLogClient.js +35 -4
  5. package/dist/client/RemoteLogClient.js.map +1 -1
  6. package/dist/client/types.d.ts +13 -0
  7. package/dist/client/types.d.ts.map +1 -1
  8. package/dist/mcp/index.d.ts +9 -0
  9. package/dist/mcp/index.d.ts.map +1 -0
  10. package/dist/mcp/index.js +9 -0
  11. package/dist/mcp/index.js.map +1 -0
  12. package/dist/mcp/mcp-server.d.ts +32 -0
  13. package/dist/mcp/mcp-server.d.ts.map +1 -0
  14. package/dist/mcp/mcp-server.js +270 -0
  15. package/dist/mcp/mcp-server.js.map +1 -0
  16. package/dist/mcp/tools/index.d.ts +14 -0
  17. package/dist/mcp/tools/index.d.ts.map +1 -0
  18. package/dist/mcp/tools/index.js +14 -0
  19. package/dist/mcp/tools/index.js.map +1 -0
  20. package/dist/mcp/tools/logs-clear.d.ts +76 -0
  21. package/dist/mcp/tools/logs-clear.d.ts.map +1 -0
  22. package/dist/mcp/tools/logs-clear.js +58 -0
  23. package/dist/mcp/tools/logs-clear.js.map +1 -0
  24. package/dist/mcp/tools/logs-get-all.d.ts +60 -0
  25. package/dist/mcp/tools/logs-get-all.d.ts.map +1 -0
  26. package/dist/mcp/tools/logs-get-all.js +50 -0
  27. package/dist/mcp/tools/logs-get-all.js.map +1 -0
  28. package/dist/mcp/tools/logs-get-errors.d.ts +65 -0
  29. package/dist/mcp/tools/logs-get-errors.d.ts.map +1 -0
  30. package/dist/mcp/tools/logs-get-errors.js +46 -0
  31. package/dist/mcp/tools/logs-get-errors.js.map +1 -0
  32. package/dist/mcp/tools/logs-get-file-path.d.ts +75 -0
  33. package/dist/mcp/tools/logs-get-file-path.d.ts.map +1 -0
  34. package/dist/mcp/tools/logs-get-file-path.js +90 -0
  35. package/dist/mcp/tools/logs-get-file-path.js.map +1 -0
  36. package/dist/mcp/tools/logs-get-recent.d.ts +89 -0
  37. package/dist/mcp/tools/logs-get-recent.d.ts.map +1 -0
  38. package/dist/mcp/tools/logs-get-recent.js +74 -0
  39. package/dist/mcp/tools/logs-get-recent.js.map +1 -0
  40. package/dist/mcp/tools/logs-list-sessions.d.ts +64 -0
  41. package/dist/mcp/tools/logs-list-sessions.d.ts.map +1 -0
  42. package/dist/mcp/tools/logs-list-sessions.js +48 -0
  43. package/dist/mcp/tools/logs-list-sessions.js.map +1 -0
  44. package/dist/mcp/tools/logs-receive.d.ts +150 -0
  45. package/dist/mcp/tools/logs-receive.d.ts.map +1 -0
  46. package/dist/mcp/tools/logs-receive.js +68 -0
  47. package/dist/mcp/tools/logs-receive.js.map +1 -0
  48. package/dist/mcp/tools/logs-search.d.ts +91 -0
  49. package/dist/mcp/tools/logs-search.d.ts.map +1 -0
  50. package/dist/mcp/tools/logs-search.js +68 -0
  51. package/dist/mcp/tools/logs-search.js.map +1 -0
  52. package/dist/mcp/tools/logs-status.d.ts +45 -0
  53. package/dist/mcp/tools/logs-status.d.ts.map +1 -0
  54. package/dist/mcp/tools/logs-status.js +45 -0
  55. package/dist/mcp/tools/logs-status.js.map +1 -0
  56. package/dist/server/dual-server.d.ts +76 -0
  57. package/dist/server/dual-server.d.ts.map +1 -0
  58. package/dist/server/dual-server.js +214 -0
  59. package/dist/server/dual-server.js.map +1 -0
  60. package/dist/server/index.d.ts +5 -1
  61. package/dist/server/index.d.ts.map +1 -1
  62. package/dist/server/index.js +5 -1
  63. package/dist/server/index.js.map +1 -1
  64. package/dist/server/jsonl-writer.d.ts +93 -0
  65. package/dist/server/jsonl-writer.d.ts.map +1 -0
  66. package/dist/server/jsonl-writer.js +205 -0
  67. package/dist/server/jsonl-writer.js.map +1 -0
  68. package/dist/server/log-server.d.ts +62 -11
  69. package/dist/server/log-server.d.ts.map +1 -1
  70. package/dist/server/log-server.js +237 -101
  71. package/dist/server/log-server.js.map +1 -1
  72. package/dist/server/log-storage.d.ts +301 -0
  73. package/dist/server/log-storage.d.ts.map +1 -0
  74. package/dist/server/log-storage.js +408 -0
  75. package/dist/server/log-storage.js.map +1 -0
  76. package/dist/server/marker-utils.d.ts +69 -0
  77. package/dist/server/marker-utils.d.ts.map +1 -0
  78. package/dist/server/marker-utils.js +118 -0
  79. package/dist/server/marker-utils.js.map +1 -0
  80. package/dist/vite/index.d.ts +8 -0
  81. package/dist/vite/index.d.ts.map +1 -0
  82. package/dist/vite/index.js +8 -0
  83. package/dist/vite/index.js.map +1 -0
  84. package/dist/vite/plugin.d.ts +42 -0
  85. package/dist/vite/plugin.d.ts.map +1 -0
  86. package/dist/vite/plugin.js +46 -0
  87. package/dist/vite/plugin.js.map +1 -0
  88. package/package.json +12 -2
  89. package/src/client/RemoteLogClient.ts +52 -4
  90. package/src/client/types.ts +13 -0
  91. package/src/mcp/index.ts +25 -0
  92. package/src/mcp/mcp-server.ts +364 -0
  93. package/src/mcp/tools/index.ts +69 -0
  94. package/src/mcp/tools/logs-clear.ts +86 -0
  95. package/src/mcp/tools/logs-get-all.ts +78 -0
  96. package/src/mcp/tools/logs-get-errors.ts +71 -0
  97. package/src/mcp/tools/logs-get-file-path.ts +121 -0
  98. package/src/mcp/tools/logs-get-recent.ts +104 -0
  99. package/src/mcp/tools/logs-list-sessions.ts +71 -0
  100. package/src/mcp/tools/logs-receive.ts +96 -0
  101. package/src/mcp/tools/logs-search.ts +95 -0
  102. package/src/mcp/tools/logs-status.ts +69 -0
  103. package/src/server/dual-server.ts +308 -0
  104. package/src/server/index.ts +37 -0
  105. package/src/server/jsonl-writer.ts +277 -0
  106. package/src/server/log-server.ts +311 -119
  107. package/src/server/log-storage.ts +651 -0
  108. package/src/server/marker-utils.ts +144 -0
  109. package/src/vite/index.ts +8 -0
  110. package/src/vite/plugin.ts +59 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * JSONL file writer for streaming logs to disk.
3
+ *
4
+ * Organizes logs by project marker in the temp directory:
5
+ * {baseDir}/{marker}/logs.jsonl
6
+ * @module server/jsonl-writer
7
+ */
8
+
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+
12
+ /**
13
+ * A JSONL log entry.
14
+ */
15
+ export interface JsonlEntry {
16
+ /** ISO 8601 timestamp when the log was created */
17
+ time: string;
18
+ /** Log level (e.g., "INFO", "DEBUG", "WARN", "ERROR") */
19
+ level: string;
20
+ /** The log message */
21
+ message: string;
22
+ /** Session ID this log belongs to */
23
+ sessionId: string;
24
+ /** Optional additional data */
25
+ data?: Record<string, unknown>;
26
+ }
27
+
28
+ /**
29
+ * File statistics.
30
+ */
31
+ export interface FileStats {
32
+ /** Whether the file exists */
33
+ exists: boolean;
34
+ /** File size in bytes */
35
+ size: number;
36
+ }
37
+
38
+ /**
39
+ * Internal state for a marker's file handle.
40
+ */
41
+ interface MarkerState {
42
+ /** File descriptor */
43
+ fd: number;
44
+ /** Pending writes buffer */
45
+ buffer: string[];
46
+ /** Write lock to serialize writes */
47
+ writeLock: Promise<void>;
48
+ }
49
+
50
+ /**
51
+ * JSONL file writer that streams log entries to disk.
52
+ *
53
+ * Each project marker gets its own log file in the structure:
54
+ * {baseDir}/{marker}/logs.jsonl
55
+ */
56
+ export class JsonlWriter {
57
+ private readonly baseDir: string;
58
+ private readonly markerStates = new Map<string, MarkerState>();
59
+ private closed = false;
60
+
61
+ /**
62
+ * Create a new JsonlWriter.
63
+ * @param baseDir - Base directory for log files (e.g., os.tmpdir()/remote-logger)
64
+ */
65
+ constructor(baseDir: string) {
66
+ this.baseDir = baseDir;
67
+ }
68
+
69
+ /**
70
+ * Get the file path for a project marker.
71
+ * @param projectMarker - The project marker
72
+ * @returns Full path to the JSONL file
73
+ */
74
+ getFilePath(projectMarker: string): string {
75
+ return path.join(this.baseDir, projectMarker, "logs.jsonl");
76
+ }
77
+
78
+ /**
79
+ * Get file statistics for a project marker's log file.
80
+ * @param projectMarker - The project marker
81
+ * @returns File stats or null if file doesn't exist
82
+ */
83
+ getFileStats(projectMarker: string): FileStats | null {
84
+ const filePath = this.getFilePath(projectMarker);
85
+
86
+ try {
87
+ const stats = fs.statSync(filePath);
88
+ return {
89
+ exists: true,
90
+ size: stats.size,
91
+ };
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Write a log entry to the appropriate file.
99
+ * @param projectMarker - The project marker
100
+ * @param entry - The log entry to write
101
+ * @returns Promise that resolves when the write is queued
102
+ */
103
+ write(projectMarker: string, entry: JsonlEntry): Promise<void> {
104
+ if (this.closed) {
105
+ return Promise.resolve();
106
+ }
107
+
108
+ const state = this.ensureMarkerState(projectMarker);
109
+ const line = `${JSON.stringify(entry)}\n`;
110
+
111
+ // Add to buffer and trigger write
112
+ state.buffer.push(line);
113
+
114
+ // Chain the write to maintain order
115
+ state.writeLock = state.writeLock.then(() => {
116
+ this.flushBuffer(state);
117
+ });
118
+
119
+ return Promise.resolve();
120
+ }
121
+
122
+ /**
123
+ * Flush all pending writes to disk.
124
+ */
125
+ async flush(): Promise<void> {
126
+ const flushPromises: Promise<void>[] = [];
127
+
128
+ for (const state of this.markerStates.values()) {
129
+ // Wait for pending writes and flush buffer
130
+ flushPromises.push(state.writeLock.then(() => {
131
+ this.flushBuffer(state);
132
+ }));
133
+ }
134
+
135
+ await Promise.all(flushPromises);
136
+
137
+ // Sync all file descriptors
138
+ for (const state of this.markerStates.values()) {
139
+ try {
140
+ fs.fsyncSync(state.fd);
141
+ } catch {
142
+ // Ignore sync errors
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Close all file handles.
149
+ */
150
+ async close(): Promise<void> {
151
+ if (this.closed) {
152
+ return;
153
+ }
154
+
155
+ this.closed = true;
156
+
157
+ // Flush all pending writes
158
+ await this.flush();
159
+
160
+ // Close all file handles
161
+ for (const state of this.markerStates.values()) {
162
+ try {
163
+ fs.closeSync(state.fd);
164
+ } catch {
165
+ // Ignore close errors
166
+ }
167
+ }
168
+
169
+ this.markerStates.clear();
170
+ }
171
+
172
+ /**
173
+ * Ensure the marker state exists, creating file if needed.
174
+ * @param projectMarker - The project marker
175
+ * @returns The marker state
176
+ */
177
+ private ensureMarkerState(projectMarker: string): MarkerState {
178
+ let state = this.markerStates.get(projectMarker);
179
+
180
+ if (!state) {
181
+ const filePath = this.getFilePath(projectMarker);
182
+ const dir = path.dirname(filePath);
183
+
184
+ // Create directory if it doesn't exist
185
+ fs.mkdirSync(dir, { recursive: true });
186
+
187
+ // Open file for appending
188
+ const fd = fs.openSync(filePath, "a");
189
+
190
+ state = {
191
+ fd,
192
+ buffer: [],
193
+ writeLock: Promise.resolve(),
194
+ };
195
+
196
+ this.markerStates.set(projectMarker, state);
197
+ }
198
+
199
+ return state;
200
+ }
201
+
202
+ /**
203
+ * Flush the buffer for a marker state.
204
+ * @param state - The marker state to flush
205
+ */
206
+ private flushBuffer(state: MarkerState): void {
207
+ if (state.buffer.length === 0) {
208
+ return;
209
+ }
210
+
211
+ // Take all buffered entries
212
+ const toWrite = state.buffer.join("");
213
+ state.buffer = [];
214
+
215
+ // Write to file
216
+ fs.writeSync(state.fd, toWrite);
217
+ }
218
+
219
+ /**
220
+ * Clean up old project log files based on directory modification time.
221
+ * Removes entire project directories that haven't been modified within the retention period.
222
+ * @param retentionDays - Number of days to retain logs
223
+ * @returns Number of project directories removed
224
+ */
225
+ cleanupOldFiles(retentionDays: number): number {
226
+ // Check if base directory exists
227
+ if (!fs.existsSync(this.baseDir)) {
228
+ return 0;
229
+ }
230
+
231
+ const cutoffTime = new Date();
232
+ cutoffTime.setDate(cutoffTime.getDate() - retentionDays);
233
+ const cutoffMs = cutoffTime.getTime();
234
+
235
+ let removed = 0;
236
+
237
+ try {
238
+ const entries = fs.readdirSync(this.baseDir, { withFileTypes: true });
239
+
240
+ for (const entry of entries) {
241
+ if (!entry.isDirectory()) {
242
+ continue;
243
+ }
244
+
245
+ const dirPath = path.join(this.baseDir, entry.name);
246
+
247
+ try {
248
+ const stats = fs.statSync(dirPath);
249
+
250
+ // Check if directory is older than retention period
251
+ if (stats.mtimeMs < cutoffMs) {
252
+ // Close file handle if we have one open for this marker
253
+ const state = this.markerStates.get(entry.name);
254
+ if (state) {
255
+ try {
256
+ fs.closeSync(state.fd);
257
+ } catch {
258
+ // Ignore close errors
259
+ }
260
+ this.markerStates.delete(entry.name);
261
+ }
262
+
263
+ // Remove the directory
264
+ fs.rmSync(dirPath, { recursive: true, force: true });
265
+ removed++;
266
+ }
267
+ } catch {
268
+ // Ignore errors for individual directories
269
+ }
270
+ }
271
+ } catch {
272
+ // Ignore errors reading base directory
273
+ }
274
+
275
+ return removed;
276
+ }
277
+ }