@graphty/remote-logger 1.1.1 → 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/README.md +318 -10
- package/dist/client/RemoteLogClient.d.ts +2 -0
- package/dist/client/RemoteLogClient.d.ts.map +1 -1
- package/dist/client/RemoteLogClient.js +35 -4
- package/dist/client/RemoteLogClient.js.map +1 -1
- package/dist/client/types.d.ts +13 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +32 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +270 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +14 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +14 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/logs-clear.d.ts +76 -0
- package/dist/mcp/tools/logs-clear.d.ts.map +1 -0
- package/dist/mcp/tools/logs-clear.js +58 -0
- package/dist/mcp/tools/logs-clear.js.map +1 -0
- package/dist/mcp/tools/logs-get-all.d.ts +60 -0
- package/dist/mcp/tools/logs-get-all.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-all.js +50 -0
- package/dist/mcp/tools/logs-get-all.js.map +1 -0
- package/dist/mcp/tools/logs-get-errors.d.ts +65 -0
- package/dist/mcp/tools/logs-get-errors.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-errors.js +46 -0
- package/dist/mcp/tools/logs-get-errors.js.map +1 -0
- package/dist/mcp/tools/logs-get-file-path.d.ts +75 -0
- package/dist/mcp/tools/logs-get-file-path.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-file-path.js +90 -0
- package/dist/mcp/tools/logs-get-file-path.js.map +1 -0
- package/dist/mcp/tools/logs-get-recent.d.ts +89 -0
- package/dist/mcp/tools/logs-get-recent.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-recent.js +74 -0
- package/dist/mcp/tools/logs-get-recent.js.map +1 -0
- package/dist/mcp/tools/logs-list-sessions.d.ts +64 -0
- package/dist/mcp/tools/logs-list-sessions.d.ts.map +1 -0
- package/dist/mcp/tools/logs-list-sessions.js +48 -0
- package/dist/mcp/tools/logs-list-sessions.js.map +1 -0
- package/dist/mcp/tools/logs-receive.d.ts +150 -0
- package/dist/mcp/tools/logs-receive.d.ts.map +1 -0
- package/dist/mcp/tools/logs-receive.js +68 -0
- package/dist/mcp/tools/logs-receive.js.map +1 -0
- package/dist/mcp/tools/logs-search.d.ts +91 -0
- package/dist/mcp/tools/logs-search.d.ts.map +1 -0
- package/dist/mcp/tools/logs-search.js +68 -0
- package/dist/mcp/tools/logs-search.js.map +1 -0
- package/dist/mcp/tools/logs-status.d.ts +45 -0
- package/dist/mcp/tools/logs-status.d.ts.map +1 -0
- package/dist/mcp/tools/logs-status.js +45 -0
- package/dist/mcp/tools/logs-status.js.map +1 -0
- package/dist/server/dual-server.d.ts +76 -0
- package/dist/server/dual-server.d.ts.map +1 -0
- package/dist/server/dual-server.js +214 -0
- package/dist/server/dual-server.js.map +1 -0
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/jsonl-writer.d.ts +93 -0
- package/dist/server/jsonl-writer.d.ts.map +1 -0
- package/dist/server/jsonl-writer.js +205 -0
- package/dist/server/jsonl-writer.js.map +1 -0
- package/dist/server/log-server.d.ts +62 -11
- package/dist/server/log-server.d.ts.map +1 -1
- package/dist/server/log-server.js +237 -101
- package/dist/server/log-server.js.map +1 -1
- package/dist/server/log-storage.d.ts +301 -0
- package/dist/server/log-storage.d.ts.map +1 -0
- package/dist/server/log-storage.js +408 -0
- package/dist/server/log-storage.js.map +1 -0
- package/dist/server/marker-utils.d.ts +69 -0
- package/dist/server/marker-utils.d.ts.map +1 -0
- package/dist/server/marker-utils.js +118 -0
- package/dist/server/marker-utils.js.map +1 -0
- package/dist/vite/index.d.ts +8 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +8 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/plugin.d.ts +42 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/vite/plugin.js +46 -0
- package/dist/vite/plugin.js.map +1 -0
- package/package.json +12 -2
- package/src/client/RemoteLogClient.ts +52 -4
- package/src/client/types.ts +13 -0
- package/src/mcp/index.ts +25 -0
- package/src/mcp/mcp-server.ts +364 -0
- package/src/mcp/tools/index.ts +69 -0
- package/src/mcp/tools/logs-clear.ts +86 -0
- package/src/mcp/tools/logs-get-all.ts +78 -0
- package/src/mcp/tools/logs-get-errors.ts +71 -0
- package/src/mcp/tools/logs-get-file-path.ts +121 -0
- package/src/mcp/tools/logs-get-recent.ts +104 -0
- package/src/mcp/tools/logs-list-sessions.ts +71 -0
- package/src/mcp/tools/logs-receive.ts +96 -0
- package/src/mcp/tools/logs-search.ts +95 -0
- package/src/mcp/tools/logs-status.ts +69 -0
- package/src/server/dual-server.ts +308 -0
- package/src/server/index.ts +37 -0
- package/src/server/jsonl-writer.ts +277 -0
- package/src/server/log-server.ts +311 -119
- package/src/server/log-storage.ts +651 -0
- package/src/server/marker-utils.ts +144 -0
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin.ts +59 -0
package/src/server/log-server.ts
CHANGED
|
@@ -16,9 +16,51 @@
|
|
|
16
16
|
import * as fs from "fs";
|
|
17
17
|
import * as http from "http";
|
|
18
18
|
import * as https from "https";
|
|
19
|
+
import * as os from "os";
|
|
20
|
+
import * as path from "path";
|
|
19
21
|
import { URL } from "url";
|
|
20
22
|
|
|
21
|
-
import {
|
|
23
|
+
import { JsonlWriter } from "./jsonl-writer.js";
|
|
24
|
+
import { type LogEntry, LogStorage } from "./log-storage.js";
|
|
25
|
+
import { certFilesExist, readCertFiles } from "./self-signed-cert.js";
|
|
26
|
+
|
|
27
|
+
// Shared log storage instance
|
|
28
|
+
let sharedStorage: LogStorage | null = null;
|
|
29
|
+
// Shared JSONL writer instance
|
|
30
|
+
let sharedJsonlWriter: JsonlWriter | null = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the shared LogStorage instance, creating it if needed.
|
|
34
|
+
* Creates a JsonlWriter for JSONL file streaming by default.
|
|
35
|
+
* @returns The shared LogStorage instance
|
|
36
|
+
*/
|
|
37
|
+
export function getLogStorage(): LogStorage {
|
|
38
|
+
if (!sharedStorage) {
|
|
39
|
+
// Create JSONL writer for file streaming
|
|
40
|
+
const jsonlBaseDir = path.join(os.tmpdir(), "remote-logger");
|
|
41
|
+
sharedJsonlWriter = new JsonlWriter(jsonlBaseDir);
|
|
42
|
+
|
|
43
|
+
// Create storage with JSONL writer
|
|
44
|
+
sharedStorage = new LogStorage({ jsonlWriter: sharedJsonlWriter });
|
|
45
|
+
}
|
|
46
|
+
return sharedStorage;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the shared JsonlWriter instance.
|
|
51
|
+
* @returns The shared JsonlWriter instance or null if not initialized
|
|
52
|
+
*/
|
|
53
|
+
export function getJsonlWriter(): JsonlWriter | null {
|
|
54
|
+
return sharedJsonlWriter;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set the shared LogStorage instance (for testing or external injection).
|
|
59
|
+
* @param storage - The LogStorage instance to use
|
|
60
|
+
*/
|
|
61
|
+
export function setLogStorage(storage: LogStorage): void {
|
|
62
|
+
sharedStorage = storage;
|
|
63
|
+
}
|
|
22
64
|
|
|
23
65
|
// ANSI color codes for terminal output
|
|
24
66
|
const colors = {
|
|
@@ -41,32 +83,33 @@ export interface LogServerOptions {
|
|
|
41
83
|
port?: number;
|
|
42
84
|
/** Hostname to bind to (default: localhost) */
|
|
43
85
|
host?: string;
|
|
44
|
-
/** Path to SSL certificate file */
|
|
86
|
+
/** Path to SSL certificate file (HTTPS only used if both certPath and keyPath provided) */
|
|
45
87
|
certPath?: string;
|
|
46
|
-
/** Path to SSL private key file */
|
|
88
|
+
/** Path to SSL private key file (HTTPS only used if both certPath and keyPath provided) */
|
|
47
89
|
keyPath?: string;
|
|
48
90
|
/** Path to file for writing logs (optional) */
|
|
49
91
|
logFile?: string;
|
|
50
|
-
/**
|
|
51
|
-
|
|
92
|
+
/** Start in MCP server mode (default: false) @deprecated Use mcpOnly instead */
|
|
93
|
+
mcp?: boolean;
|
|
94
|
+
/** Start only MCP server (no HTTP) */
|
|
95
|
+
mcpOnly?: boolean;
|
|
96
|
+
/** Start only HTTP server (no MCP) - legacy mode */
|
|
97
|
+
httpOnly?: boolean;
|
|
52
98
|
/** Suppress startup banner (default: false) */
|
|
53
99
|
quiet?: boolean;
|
|
54
100
|
}
|
|
55
101
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
level: string;
|
|
59
|
-
message: string;
|
|
60
|
-
}
|
|
102
|
+
// Re-export LogEntry from log-storage for backward compatibility
|
|
103
|
+
export type { LogEntry } from "./log-storage.js";
|
|
61
104
|
|
|
62
105
|
interface LogBatch {
|
|
63
106
|
sessionId: string;
|
|
64
107
|
logs: LogEntry[];
|
|
108
|
+
projectMarker?: string;
|
|
109
|
+
worktreePath?: string;
|
|
110
|
+
pageUrl?: string;
|
|
65
111
|
}
|
|
66
112
|
|
|
67
|
-
// Store for remote logs by session
|
|
68
|
-
const remoteLogs = new Map<string, LogEntry[]>();
|
|
69
|
-
|
|
70
113
|
// File stream for log file
|
|
71
114
|
let logFileStream: fs.WriteStream | null = null;
|
|
72
115
|
|
|
@@ -75,7 +118,7 @@ let logFileStream: fs.WriteStream | null = null;
|
|
|
75
118
|
* Useful for testing.
|
|
76
119
|
*/
|
|
77
120
|
export function clearLogs(): void {
|
|
78
|
-
|
|
121
|
+
getLogStorage().clear();
|
|
79
122
|
}
|
|
80
123
|
|
|
81
124
|
/**
|
|
@@ -144,6 +187,7 @@ function displayLog(sessionId: string, log: LogEntry, quiet: boolean): void {
|
|
|
144
187
|
* @param port - The server port number
|
|
145
188
|
* @param useHttps - Whether HTTPS is being used
|
|
146
189
|
* @param quiet - If true, suppress terminal output
|
|
190
|
+
* @param logReceiveOnly - If true, only serve /log POST and /health GET endpoints
|
|
147
191
|
*/
|
|
148
192
|
function handleRequest(
|
|
149
193
|
req: http.IncomingMessage,
|
|
@@ -152,6 +196,7 @@ function handleRequest(
|
|
|
152
196
|
port: number,
|
|
153
197
|
useHttps: boolean,
|
|
154
198
|
quiet: boolean,
|
|
199
|
+
logReceiveOnly: boolean = false,
|
|
155
200
|
): void {
|
|
156
201
|
// CORS headers
|
|
157
202
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -176,36 +221,30 @@ function handleRequest(
|
|
|
176
221
|
req.on("end", () => {
|
|
177
222
|
try {
|
|
178
223
|
const data = JSON.parse(body) as LogBatch;
|
|
179
|
-
const { sessionId, logs } = data;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
224
|
+
const { sessionId, logs, projectMarker, worktreePath, pageUrl } = data;
|
|
225
|
+
|
|
226
|
+
const storage = getLogStorage();
|
|
227
|
+
const isNewSession = !storage.hasSession(sessionId);
|
|
228
|
+
|
|
229
|
+
// Show new session banner
|
|
230
|
+
if (isNewSession && !quiet) {
|
|
231
|
+
// eslint-disable-next-line no-console
|
|
232
|
+
console.log(
|
|
233
|
+
`\n${colors.bright}${colors.magenta}═══════════════════════════════════════════════════════════${colors.reset}`,
|
|
234
|
+
);
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
236
|
+
console.log(`${colors.bright}${colors.magenta} NEW SESSION: ${sessionId}${colors.reset}`);
|
|
237
|
+
// eslint-disable-next-line no-console
|
|
238
|
+
console.log(
|
|
239
|
+
`${colors.bright}${colors.magenta}═══════════════════════════════════════════════════════════${colors.reset}\n`,
|
|
240
|
+
);
|
|
196
241
|
}
|
|
197
242
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Should not happen since we just set it above, but satisfy TypeScript
|
|
201
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
202
|
-
res.end(JSON.stringify({ error: "Internal error" }));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
243
|
+
// Add logs to storage
|
|
244
|
+
storage.addLogs(sessionId, logs, { projectMarker, worktreePath, pageUrl });
|
|
205
245
|
|
|
206
|
-
// Display
|
|
246
|
+
// Display each log in terminal
|
|
207
247
|
for (const log of logs) {
|
|
208
|
-
sessionLogs.push(log);
|
|
209
248
|
displayLog(sessionId, log, quiet);
|
|
210
249
|
}
|
|
211
250
|
|
|
@@ -222,12 +261,24 @@ function handleRequest(
|
|
|
222
261
|
return;
|
|
223
262
|
}
|
|
224
263
|
|
|
264
|
+
// Health check endpoint (always available)
|
|
265
|
+
if (url === "/health" && req.method === "GET") {
|
|
266
|
+
const health = getLogStorage().getHealth();
|
|
267
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
268
|
+
res.end(JSON.stringify({ status: health.status, sessions: health.sessionCount }));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// In logReceiveOnly mode, only /log and /health are available
|
|
273
|
+
if (logReceiveOnly) {
|
|
274
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
275
|
+
res.end(JSON.stringify({ error: "Not found (log receive only mode)" }));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
225
279
|
// Handle logs viewer endpoint - GET all logs
|
|
226
280
|
if (url === "/logs" && req.method === "GET") {
|
|
227
|
-
const allLogs
|
|
228
|
-
for (const [sessionId, logs] of remoteLogs) {
|
|
229
|
-
allLogs[sessionId] = logs;
|
|
230
|
-
}
|
|
281
|
+
const allLogs = getLogStorage().getAllLogsBySession();
|
|
231
282
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
232
283
|
res.end(JSON.stringify(allLogs, null, 2));
|
|
233
284
|
return;
|
|
@@ -239,25 +290,16 @@ function handleRequest(
|
|
|
239
290
|
const count = parseInt(urlObj.searchParams.get("n") ?? "50", 10);
|
|
240
291
|
const errorsOnly = urlObj.searchParams.get("errors") === "true";
|
|
241
292
|
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (!errorsOnly || log.level.toUpperCase() === "ERROR") {
|
|
247
|
-
allLogs.push({ sessionId, ...log });
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Sort by time descending and take last N
|
|
253
|
-
allLogs.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
|
|
254
|
-
const recentLogs = allLogs.slice(0, count).reverse(); // Reverse to show oldest first
|
|
293
|
+
const storage = getLogStorage();
|
|
294
|
+
const filter = errorsOnly ? { level: "ERROR" } : {};
|
|
295
|
+
const recentLogs = storage.getRecentLogs(count, filter);
|
|
296
|
+
const totalLogs = storage.getLogs(filter).length;
|
|
255
297
|
|
|
256
298
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
257
299
|
res.end(
|
|
258
300
|
JSON.stringify(
|
|
259
301
|
{
|
|
260
|
-
total:
|
|
302
|
+
total: totalLogs,
|
|
261
303
|
showing: recentLogs.length,
|
|
262
304
|
logs: recentLogs,
|
|
263
305
|
},
|
|
@@ -270,15 +312,7 @@ function handleRequest(
|
|
|
270
312
|
|
|
271
313
|
// Handle errors-only endpoint
|
|
272
314
|
if (url === "/logs/errors" && req.method === "GET") {
|
|
273
|
-
const errorLogs
|
|
274
|
-
for (const [sessionId, logs] of remoteLogs) {
|
|
275
|
-
for (const log of logs) {
|
|
276
|
-
if (log.level.toUpperCase() === "ERROR") {
|
|
277
|
-
errorLogs.push({ sessionId, ...log });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
errorLogs.sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
315
|
+
const errorLogs = getLogStorage().getErrors();
|
|
282
316
|
|
|
283
317
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
284
318
|
res.end(
|
|
@@ -296,7 +330,7 @@ function handleRequest(
|
|
|
296
330
|
|
|
297
331
|
// Handle clear logs endpoint
|
|
298
332
|
if (url === "/logs/clear" && req.method === "POST") {
|
|
299
|
-
|
|
333
|
+
getLogStorage().clear();
|
|
300
334
|
if (!quiet) {
|
|
301
335
|
// eslint-disable-next-line no-console
|
|
302
336
|
console.log(`\n${colors.yellow}Logs cleared${colors.reset}\n`);
|
|
@@ -306,13 +340,6 @@ function handleRequest(
|
|
|
306
340
|
return;
|
|
307
341
|
}
|
|
308
342
|
|
|
309
|
-
// Health check endpoint
|
|
310
|
-
if (url === "/health" && req.method === "GET") {
|
|
311
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
312
|
-
res.end(JSON.stringify({ status: "ok", sessions: remoteLogs.size }));
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
343
|
// Default: 404
|
|
317
344
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
318
345
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
@@ -369,6 +396,80 @@ function printBanner(host: string, port: number, useHttps: boolean): void {
|
|
|
369
396
|
console.log(`${colors.cyan}────────────────────────────────────────────────────────────${colors.reset}`);
|
|
370
397
|
}
|
|
371
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Options for creating a log server without starting it.
|
|
401
|
+
*/
|
|
402
|
+
export interface CreateLogServerOptions {
|
|
403
|
+
/** Port to listen on */
|
|
404
|
+
port: number;
|
|
405
|
+
/** Hostname to bind to */
|
|
406
|
+
host: string;
|
|
407
|
+
/** Shared log storage instance */
|
|
408
|
+
storage: LogStorage;
|
|
409
|
+
/** Suppress terminal output (default: false) */
|
|
410
|
+
quiet?: boolean;
|
|
411
|
+
/** Only serve /log POST and /health GET endpoints (default: false) */
|
|
412
|
+
logReceiveOnly?: boolean;
|
|
413
|
+
/** Path to SSL certificate file (HTTPS only used if both certPath and keyPath provided) */
|
|
414
|
+
certPath?: string;
|
|
415
|
+
/** Path to SSL private key file (HTTPS only used if both certPath and keyPath provided) */
|
|
416
|
+
keyPath?: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Result of creating a log server.
|
|
421
|
+
*/
|
|
422
|
+
export interface CreateLogServerResult {
|
|
423
|
+
/** The HTTP or HTTPS server instance (not yet listening) */
|
|
424
|
+
server: http.Server | https.Server;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create a log server with shared storage.
|
|
429
|
+
* The server is not started - call server.listen() to start it.
|
|
430
|
+
* @param options - Server configuration options
|
|
431
|
+
* @returns The server instance
|
|
432
|
+
*/
|
|
433
|
+
export function createLogServer(options: CreateLogServerOptions): CreateLogServerResult {
|
|
434
|
+
const {
|
|
435
|
+
port,
|
|
436
|
+
host,
|
|
437
|
+
storage,
|
|
438
|
+
quiet = true,
|
|
439
|
+
logReceiveOnly = false,
|
|
440
|
+
certPath,
|
|
441
|
+
keyPath,
|
|
442
|
+
} = options;
|
|
443
|
+
|
|
444
|
+
// Set the shared storage
|
|
445
|
+
setLogStorage(storage);
|
|
446
|
+
|
|
447
|
+
let server: http.Server | https.Server;
|
|
448
|
+
|
|
449
|
+
// Use HTTPS only if valid certificate files are provided
|
|
450
|
+
const useHttps = certPath && keyPath && certFilesExist(certPath, keyPath);
|
|
451
|
+
|
|
452
|
+
if (useHttps) {
|
|
453
|
+
// HTTPS server with provided certificates
|
|
454
|
+
const { cert, key } = readCertFiles(certPath, keyPath);
|
|
455
|
+
if (!quiet) {
|
|
456
|
+
// eslint-disable-next-line no-console
|
|
457
|
+
console.log(`${colors.green}Using SSL certificates from: ${certPath}${colors.reset}`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
server = https.createServer({ cert, key }, (req, res) => {
|
|
461
|
+
handleRequest(req, res, host, port, true, quiet, logReceiveOnly);
|
|
462
|
+
});
|
|
463
|
+
} else {
|
|
464
|
+
// HTTP server (default - no self-signed certs as browsers reject them)
|
|
465
|
+
server = http.createServer((req, res) => {
|
|
466
|
+
handleRequest(req, res, host, port, false, quiet, logReceiveOnly);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return { server };
|
|
471
|
+
}
|
|
472
|
+
|
|
372
473
|
/**
|
|
373
474
|
* Start the log server.
|
|
374
475
|
* @param options - Server configuration options
|
|
@@ -377,7 +478,6 @@ function printBanner(host: string, port: number, useHttps: boolean): void {
|
|
|
377
478
|
export function startLogServer(options: LogServerOptions = {}): http.Server | https.Server {
|
|
378
479
|
const port = options.port ?? 9080;
|
|
379
480
|
const host = options.host ?? "localhost";
|
|
380
|
-
const useHttp = options.useHttp ?? false;
|
|
381
481
|
const quiet = options.quiet ?? false;
|
|
382
482
|
|
|
383
483
|
// Set up log file if specified
|
|
@@ -390,50 +490,34 @@ export function startLogServer(options: LogServerOptions = {}): http.Server | ht
|
|
|
390
490
|
}
|
|
391
491
|
|
|
392
492
|
// Determine SSL configuration
|
|
493
|
+
// Use HTTPS only if valid certificate files are provided
|
|
393
494
|
let server: https.Server | http.Server;
|
|
495
|
+
let useHttps = false;
|
|
496
|
+
const { certPath, keyPath } = options;
|
|
394
497
|
|
|
395
|
-
if (
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
let cert: string;
|
|
403
|
-
let key: string;
|
|
404
|
-
|
|
405
|
-
if (options.certPath && options.keyPath && certFilesExist(options.certPath, options.keyPath)) {
|
|
406
|
-
// Use provided certificates
|
|
407
|
-
({ cert, key } = readCertFiles(options.certPath, options.keyPath));
|
|
408
|
-
if (!quiet) {
|
|
409
|
-
// eslint-disable-next-line no-console
|
|
410
|
-
console.log(`${colors.green}Using SSL certificates from: ${options.certPath}${colors.reset}`);
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
// Generate self-signed certificate
|
|
414
|
-
if (!quiet) {
|
|
415
|
-
// eslint-disable-next-line no-console
|
|
416
|
-
console.log(`${colors.yellow}Generating self-signed certificate for ${host}...${colors.reset}`);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
({ cert, key } = generateSelfSignedCert(host));
|
|
420
|
-
if (!quiet) {
|
|
421
|
-
// eslint-disable-next-line no-console
|
|
422
|
-
console.log(
|
|
423
|
-
`${colors.yellow}Note: Browser will show certificate warning - this is expected for self-signed certs${colors.reset}`,
|
|
424
|
-
);
|
|
425
|
-
}
|
|
498
|
+
if (certPath && keyPath && certFilesExist(certPath, keyPath)) {
|
|
499
|
+
// HTTPS server with provided certificates
|
|
500
|
+
useHttps = true;
|
|
501
|
+
const { cert, key } = readCertFiles(certPath, keyPath);
|
|
502
|
+
if (!quiet) {
|
|
503
|
+
// eslint-disable-next-line no-console
|
|
504
|
+
console.log(`${colors.green}Using SSL certificates from: ${certPath}${colors.reset}`);
|
|
426
505
|
}
|
|
427
506
|
|
|
428
507
|
server = https.createServer({ cert, key }, (req, res) => {
|
|
429
508
|
handleRequest(req, res, host, port, true, quiet);
|
|
430
509
|
});
|
|
510
|
+
} else {
|
|
511
|
+
// HTTP server (default - no self-signed certs as browsers reject them)
|
|
512
|
+
server = http.createServer((req, res) => {
|
|
513
|
+
handleRequest(req, res, host, port, false, quiet);
|
|
514
|
+
});
|
|
431
515
|
}
|
|
432
516
|
|
|
433
517
|
// Start listening
|
|
434
518
|
server.listen(port, host, () => {
|
|
435
519
|
if (!quiet) {
|
|
436
|
-
printBanner(host, port,
|
|
520
|
+
printBanner(host, port, useHttps);
|
|
437
521
|
}
|
|
438
522
|
});
|
|
439
523
|
|
|
@@ -445,6 +529,12 @@ export function startLogServer(options: LogServerOptions = {}): http.Server | ht
|
|
|
445
529
|
logFileStream.end();
|
|
446
530
|
}
|
|
447
531
|
|
|
532
|
+
// Close JSONL writer to flush pending writes
|
|
533
|
+
const jsonlWriter = getJsonlWriter();
|
|
534
|
+
if (jsonlWriter) {
|
|
535
|
+
void jsonlWriter.close();
|
|
536
|
+
}
|
|
537
|
+
|
|
448
538
|
server.close(() => {
|
|
449
539
|
process.exit(0);
|
|
450
540
|
});
|
|
@@ -466,18 +556,29 @@ Usage:
|
|
|
466
556
|
Options:
|
|
467
557
|
--port, -p <port> Port to listen on (default: 9080)
|
|
468
558
|
--host, -h <host> Hostname to bind to (default: localhost)
|
|
469
|
-
--cert, -c <path> Path to SSL certificate file
|
|
470
|
-
--key, -k <path> Path to SSL private key file
|
|
559
|
+
--cert, -c <path> Path to SSL certificate file (enables HTTPS)
|
|
560
|
+
--key, -k <path> Path to SSL private key file (enables HTTPS)
|
|
471
561
|
--log-file, -l <path> Write logs to file
|
|
472
|
-
--
|
|
562
|
+
--mcp-only Start only MCP server (no HTTP)
|
|
563
|
+
--http-only Start only HTTP server (legacy mode)
|
|
564
|
+
--mcp Alias for --mcp-only (deprecated)
|
|
473
565
|
--quiet, -q Suppress startup banner
|
|
474
566
|
--help Show this help message
|
|
475
567
|
|
|
568
|
+
Protocol:
|
|
569
|
+
HTTP is used by default. To use HTTPS, provide both --cert and --key.
|
|
570
|
+
|
|
571
|
+
Modes:
|
|
572
|
+
Default (no flags) Dual mode: HTTP + MCP running together
|
|
573
|
+
--mcp-only MCP only: For Claude Code integration
|
|
574
|
+
--http-only HTTP only: Legacy mode for browser debugging
|
|
575
|
+
|
|
476
576
|
Examples:
|
|
477
|
-
npx remote-log-server
|
|
478
|
-
npx remote-log-server --port 9085
|
|
479
|
-
npx remote-log-server --
|
|
480
|
-
npx remote-log-server --
|
|
577
|
+
npx remote-log-server # Start dual mode (HTTP + MCP)
|
|
578
|
+
npx remote-log-server --port 9085 # Custom port
|
|
579
|
+
npx remote-log-server --mcp-only # MCP server only (for Claude Code)
|
|
580
|
+
npx remote-log-server --http-only # HTTP server only (legacy)
|
|
581
|
+
npx remote-log-server --cert cert.crt --key key.key # Use HTTPS with custom certs
|
|
481
582
|
npx remote-log-server --log-file ./tmp/logs.jsonl # Also write to file
|
|
482
583
|
`;
|
|
483
584
|
|
|
@@ -532,8 +633,15 @@ export function parseArgs(args: string[]): ParseArgsResult {
|
|
|
532
633
|
options.logFile = nextArg;
|
|
533
634
|
i++;
|
|
534
635
|
break;
|
|
535
|
-
case "--
|
|
536
|
-
|
|
636
|
+
case "--mcp":
|
|
637
|
+
// Legacy alias for --mcp-only. Only set mcpOnly now.
|
|
638
|
+
options.mcpOnly = true;
|
|
639
|
+
break;
|
|
640
|
+
case "--mcp-only":
|
|
641
|
+
options.mcpOnly = true;
|
|
642
|
+
break;
|
|
643
|
+
case "--http-only":
|
|
644
|
+
options.httpOnly = true;
|
|
537
645
|
break;
|
|
538
646
|
case "--quiet":
|
|
539
647
|
case "-q":
|
|
@@ -552,7 +660,7 @@ export function parseArgs(args: string[]): ParseArgsResult {
|
|
|
552
660
|
/**
|
|
553
661
|
* Parse command line arguments and start the server.
|
|
554
662
|
*/
|
|
555
|
-
export function main(): void {
|
|
663
|
+
export async function main(): Promise<void> {
|
|
556
664
|
const args = process.argv.slice(2);
|
|
557
665
|
const result = parseArgs(args);
|
|
558
666
|
|
|
@@ -567,5 +675,89 @@ export function main(): void {
|
|
|
567
675
|
process.exit(1);
|
|
568
676
|
}
|
|
569
677
|
|
|
570
|
-
|
|
678
|
+
const { options } = result;
|
|
679
|
+
|
|
680
|
+
// Determine mode: mcp-only, http-only, or dual (default)
|
|
681
|
+
// All modes now use createDualServer with different options
|
|
682
|
+
const { createDualServer } = await import("./dual-server.js");
|
|
683
|
+
|
|
684
|
+
if (options.mcpOnly) {
|
|
685
|
+
// MCP-only mode: HTTP only serves /log endpoint, MCP enabled
|
|
686
|
+
const dualServer = await createDualServer({
|
|
687
|
+
httpPort: options.port ?? 9080,
|
|
688
|
+
httpHost: options.host ?? "localhost",
|
|
689
|
+
httpEnabled: true,
|
|
690
|
+
mcpEnabled: true,
|
|
691
|
+
quiet: options.quiet ?? false,
|
|
692
|
+
logReceiveOnly: true, // Only serve /log and /health endpoints
|
|
693
|
+
certPath: options.certPath,
|
|
694
|
+
keyPath: options.keyPath,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Handle graceful shutdown
|
|
698
|
+
process.on("SIGINT", () => {
|
|
699
|
+
// eslint-disable-next-line no-console
|
|
700
|
+
console.log("\nShutting down...");
|
|
701
|
+
void dualServer.shutdown().then(() => {
|
|
702
|
+
process.exit(0);
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
if (!options.quiet) {
|
|
707
|
+
// eslint-disable-next-line no-console
|
|
708
|
+
console.log("MCP mode: Log receive endpoint and MCP tools running");
|
|
709
|
+
}
|
|
710
|
+
} else if (options.httpOnly) {
|
|
711
|
+
// HTTP-only mode: All HTTP endpoints, no MCP
|
|
712
|
+
const dualServer = await createDualServer({
|
|
713
|
+
httpPort: options.port ?? 9080,
|
|
714
|
+
httpHost: options.host ?? "localhost",
|
|
715
|
+
httpEnabled: true,
|
|
716
|
+
mcpEnabled: false,
|
|
717
|
+
quiet: options.quiet ?? false,
|
|
718
|
+
certPath: options.certPath,
|
|
719
|
+
keyPath: options.keyPath,
|
|
720
|
+
logFile: options.logFile,
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// Handle graceful shutdown
|
|
724
|
+
process.on("SIGINT", () => {
|
|
725
|
+
// eslint-disable-next-line no-console
|
|
726
|
+
console.log("\nShutting down...");
|
|
727
|
+
void dualServer.shutdown().then(() => {
|
|
728
|
+
process.exit(0);
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
if (!options.quiet) {
|
|
733
|
+
// eslint-disable-next-line no-console
|
|
734
|
+
console.log("HTTP-only mode: All HTTP endpoints running");
|
|
735
|
+
}
|
|
736
|
+
} else {
|
|
737
|
+
// Dual mode (default): All HTTP endpoints and MCP
|
|
738
|
+
const dualServer = await createDualServer({
|
|
739
|
+
httpPort: options.port ?? 9080,
|
|
740
|
+
httpHost: options.host ?? "localhost",
|
|
741
|
+
httpEnabled: true,
|
|
742
|
+
mcpEnabled: true,
|
|
743
|
+
quiet: options.quiet ?? false,
|
|
744
|
+
certPath: options.certPath,
|
|
745
|
+
keyPath: options.keyPath,
|
|
746
|
+
logFile: options.logFile,
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// Handle graceful shutdown
|
|
750
|
+
process.on("SIGINT", () => {
|
|
751
|
+
// eslint-disable-next-line no-console
|
|
752
|
+
console.log("\nShutting down...");
|
|
753
|
+
void dualServer.shutdown().then(() => {
|
|
754
|
+
process.exit(0);
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
if (!options.quiet) {
|
|
759
|
+
// eslint-disable-next-line no-console
|
|
760
|
+
console.log("Dual mode: HTTP and MCP servers running");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
571
763
|
}
|