@chinchillaenterprises/mcp-dev-logger 1.2.0 → 2.0.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.
- package/README.md +111 -22
- package/dist/index.js +781 -154
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,27 +3,46 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { spawn } from "child_process";
|
|
7
|
-
import { writeFileSync, readFileSync, existsSync, appendFileSync, copyFileSync,
|
|
6
|
+
import { spawn, exec } from "child_process";
|
|
7
|
+
import { writeFileSync, readFileSync, existsSync, appendFileSync, copyFileSync, readdirSync, statSync, mkdirSync } from "fs";
|
|
8
8
|
import { join, resolve } from "path";
|
|
9
9
|
import { tmpdir } from "os";
|
|
10
|
+
import { promisify } from "util";
|
|
11
|
+
const execAsync = promisify(exec);
|
|
10
12
|
// Schema definitions
|
|
11
13
|
const StartLogStreamingArgsSchema = z.object({
|
|
12
14
|
command: z.string().optional().describe("Dev command to run (default: npm run dev)"),
|
|
13
|
-
outputFile: z.string().optional().describe("File to write logs to (
|
|
15
|
+
outputFile: z.string().optional().describe("File to write logs to (DEPRECATED: use structuredLogging instead)"),
|
|
14
16
|
cwd: z.string().optional().describe("Working directory"),
|
|
15
|
-
env: z.record(z.string()).optional().describe("Environment variables")
|
|
17
|
+
env: z.record(z.string()).optional().describe("Environment variables"),
|
|
18
|
+
processId: z.string().optional().describe("Custom process ID (auto-generated if not provided)"),
|
|
19
|
+
structuredLogging: z.boolean().optional().describe("Use organized logging with date-based sessions (default: true)"),
|
|
20
|
+
sessionDate: z.string().optional().describe("Session date for organized logging (YYYY-MM-DD, defaults to today)"),
|
|
21
|
+
logType: z.enum(["frontend", "backend", "amplify", "custom"]).optional().describe("Process type for standardized naming (auto-detected if not provided)")
|
|
22
|
+
});
|
|
23
|
+
const StopLogStreamingArgsSchema = z.object({
|
|
24
|
+
processId: z.string().optional().describe("Process ID to stop (if not provided, stops all processes)")
|
|
16
25
|
});
|
|
17
26
|
const RestartLogStreamingArgsSchema = z.object({
|
|
27
|
+
processId: z.string().describe("Process ID to restart"),
|
|
18
28
|
clearLogs: z.boolean().optional().describe("Clear logs on restart (default: false)")
|
|
19
29
|
});
|
|
20
30
|
const TailLogsArgsSchema = z.object({
|
|
31
|
+
processId: z.string().optional().describe("Process ID to tail logs from (if not provided, tries to find single process)"),
|
|
21
32
|
lines: z.number().optional().describe("Number of lines to return (default: 50)"),
|
|
22
33
|
filter: z.string().optional().describe("Grep pattern to filter logs")
|
|
23
34
|
});
|
|
24
35
|
const ClearLogsArgsSchema = z.object({
|
|
36
|
+
processId: z.string().optional().describe("Process ID to clear logs for (if not provided, tries to find single process)"),
|
|
25
37
|
backup: z.boolean().optional().describe("Backup logs before clearing (default: false)")
|
|
26
38
|
});
|
|
39
|
+
const CheckRunningProcessesArgsSchema = z.object({
|
|
40
|
+
processType: z.enum(["frontend", "backend", "amplify", "custom"]).optional().describe("Type of process to check for conflicts"),
|
|
41
|
+
port: z.number().optional().describe("Specific port to check")
|
|
42
|
+
});
|
|
43
|
+
const DiscoverLogsArgsSchema = z.object({
|
|
44
|
+
sessionDate: z.string().optional().describe("Specific session date (YYYY-MM-DD) to discover logs for (defaults to most recent)")
|
|
45
|
+
});
|
|
27
46
|
class DevLoggerServer {
|
|
28
47
|
server;
|
|
29
48
|
activeServers;
|
|
@@ -64,7 +83,8 @@ class DevLoggerServer {
|
|
|
64
83
|
cwd: info.cwd,
|
|
65
84
|
outputFile: info.outputFile,
|
|
66
85
|
startTime: info.startTime,
|
|
67
|
-
pid: info.pid
|
|
86
|
+
pid: info.pid,
|
|
87
|
+
processId: info.processId
|
|
68
88
|
};
|
|
69
89
|
}
|
|
70
90
|
writeFileSync(this.pidFile, JSON.stringify(state, null, 2));
|
|
@@ -73,12 +93,317 @@ class DevLoggerServer {
|
|
|
73
93
|
// Ignore errors saving state
|
|
74
94
|
}
|
|
75
95
|
}
|
|
96
|
+
generateProcessId(command, outputFile) {
|
|
97
|
+
// Create a readable process ID based on command and output file
|
|
98
|
+
const commandPart = command.split(' ')[0].replace(/[^a-zA-Z0-9]/g, '');
|
|
99
|
+
const filePart = outputFile.split('/').pop()?.replace(/[^a-zA-Z0-9]/g, '').replace(/\.(txt|log)$/, '') || 'default';
|
|
100
|
+
return `${commandPart}-${filePart}`;
|
|
101
|
+
}
|
|
102
|
+
findProcessOrDefault(processId) {
|
|
103
|
+
if (processId) {
|
|
104
|
+
return this.activeServers.has(processId) ? processId : null;
|
|
105
|
+
}
|
|
106
|
+
// If no processId provided and only one process running, use that
|
|
107
|
+
const activeIds = Array.from(this.activeServers.keys());
|
|
108
|
+
if (activeIds.length === 1) {
|
|
109
|
+
return activeIds[0];
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
async checkPortInUse(port) {
|
|
114
|
+
try {
|
|
115
|
+
// Use lsof to check what's using the port
|
|
116
|
+
const { stdout } = await execAsync(`lsof -i :${port} -P -t`);
|
|
117
|
+
const pid = parseInt(stdout.trim());
|
|
118
|
+
if (pid) {
|
|
119
|
+
// Get command for this PID
|
|
120
|
+
try {
|
|
121
|
+
const { stdout: cmdStdout } = await execAsync(`ps -p ${pid} -o command=`);
|
|
122
|
+
return {
|
|
123
|
+
inUse: true,
|
|
124
|
+
pid: pid,
|
|
125
|
+
command: cmdStdout.trim()
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return { inUse: true, pid: pid };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// Port is not in use or lsof failed
|
|
135
|
+
}
|
|
136
|
+
return { inUse: false };
|
|
137
|
+
}
|
|
138
|
+
detectProcessType(command) {
|
|
139
|
+
const cmd = command.toLowerCase();
|
|
140
|
+
if (cmd.includes('next dev') || cmd.includes('npm run dev') || cmd.includes('yarn dev') || cmd.includes('pnpm dev') || cmd.includes('vite')) {
|
|
141
|
+
return 'frontend';
|
|
142
|
+
}
|
|
143
|
+
if (cmd.includes('ampx sandbox') || cmd.includes('amplify sandbox')) {
|
|
144
|
+
return 'amplify';
|
|
145
|
+
}
|
|
146
|
+
if (cmd.includes('node server') || cmd.includes('express') || cmd.includes('fastify') || cmd.includes('api')) {
|
|
147
|
+
return 'backend';
|
|
148
|
+
}
|
|
149
|
+
return 'custom';
|
|
150
|
+
}
|
|
151
|
+
getCommonPorts(processType) {
|
|
152
|
+
switch (processType) {
|
|
153
|
+
case 'frontend':
|
|
154
|
+
return [3000, 3001, 5173, 5174, 8080, 8081];
|
|
155
|
+
case 'backend':
|
|
156
|
+
return [3001, 8000, 8080, 4000, 5000];
|
|
157
|
+
case 'amplify':
|
|
158
|
+
return []; // Amplify doesn't typically use fixed ports
|
|
159
|
+
default:
|
|
160
|
+
return [3000, 3001, 5173, 5174, 8000, 8080, 8081, 4000, 5000];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async checkRunningProcesses(processType, specificPort) {
|
|
164
|
+
const conflicts = [];
|
|
165
|
+
const recommendations = [];
|
|
166
|
+
// Ports to check
|
|
167
|
+
const portsToCheck = specificPort ? [specificPort] : this.getCommonPorts(processType);
|
|
168
|
+
// Check each port
|
|
169
|
+
for (const port of portsToCheck) {
|
|
170
|
+
const portCheck = await this.checkPortInUse(port);
|
|
171
|
+
if (portCheck.inUse) {
|
|
172
|
+
const detectedType = portCheck.command ? this.detectProcessType(portCheck.command) : 'unknown';
|
|
173
|
+
conflicts.push({
|
|
174
|
+
port: port,
|
|
175
|
+
pid: portCheck.pid || 0,
|
|
176
|
+
command: portCheck.command || 'Unknown process',
|
|
177
|
+
processType: detectedType
|
|
178
|
+
});
|
|
179
|
+
recommendations.push(`Port ${port} is busy with ${detectedType} server (PID ${portCheck.pid})`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Find available ports
|
|
183
|
+
const allCommonPorts = [3000, 3001, 3002, 5173, 5174, 8000, 8080, 8081, 4000, 5000];
|
|
184
|
+
const busyPorts = conflicts.map(c => c.port);
|
|
185
|
+
const availablePorts = allCommonPorts.filter(port => !busyPorts.includes(port));
|
|
186
|
+
// Add recommendations
|
|
187
|
+
if (conflicts.length > 0 && availablePorts.length > 0) {
|
|
188
|
+
recommendations.push(`Consider using port ${availablePorts[0]} instead`);
|
|
189
|
+
recommendations.push(`Or stop the existing process first`);
|
|
190
|
+
}
|
|
191
|
+
if (conflicts.length === 0) {
|
|
192
|
+
recommendations.push('No conflicts detected - safe to start new processes');
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
status: 'success',
|
|
196
|
+
conflicts,
|
|
197
|
+
availablePorts: availablePorts.slice(0, 5), // Limit to first 5 available
|
|
198
|
+
recommendations
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
detectLogProcessType(fileName) {
|
|
202
|
+
const name = fileName.toLowerCase();
|
|
203
|
+
if (name.includes('frontend') || name.includes('next') || name.includes('react') || name.includes('vite')) {
|
|
204
|
+
return 'frontend';
|
|
205
|
+
}
|
|
206
|
+
if (name.includes('backend') || name.includes('api') || name.includes('server')) {
|
|
207
|
+
return 'backend';
|
|
208
|
+
}
|
|
209
|
+
if (name.includes('amplify') || name.includes('ampx')) {
|
|
210
|
+
return 'amplify';
|
|
211
|
+
}
|
|
212
|
+
return 'custom';
|
|
213
|
+
}
|
|
214
|
+
getLogFilesFromDirectory(dirPath) {
|
|
215
|
+
if (!existsSync(dirPath)) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
const logFiles = [];
|
|
219
|
+
try {
|
|
220
|
+
const files = readdirSync(dirPath);
|
|
221
|
+
for (const file of files) {
|
|
222
|
+
// Skip non-log files and hidden files
|
|
223
|
+
if (!file.endsWith('.log') && !file.endsWith('.txt') || file.startsWith('.')) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const filePath = join(dirPath, file);
|
|
227
|
+
const stats = statSync(filePath);
|
|
228
|
+
// Get line count for log files
|
|
229
|
+
let lineCount;
|
|
230
|
+
try {
|
|
231
|
+
const content = readFileSync(filePath, 'utf8');
|
|
232
|
+
lineCount = content.split('\n').filter(line => line.trim()).length;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Ignore errors reading file content
|
|
236
|
+
}
|
|
237
|
+
logFiles.push({
|
|
238
|
+
fileName: file,
|
|
239
|
+
filePath: filePath,
|
|
240
|
+
processType: this.detectLogProcessType(file),
|
|
241
|
+
size: stats.size,
|
|
242
|
+
lastModified: stats.mtime,
|
|
243
|
+
lineCount
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
// Directory read error - return empty array
|
|
249
|
+
}
|
|
250
|
+
return logFiles.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
251
|
+
}
|
|
252
|
+
async discoverLogs(sessionDate) {
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
const logsDir = join(cwd, 'logs');
|
|
255
|
+
// If logs directory doesn't exist, check current directory for log files
|
|
256
|
+
if (!existsSync(logsDir)) {
|
|
257
|
+
const currentDirLogs = this.getLogFilesFromDirectory(cwd);
|
|
258
|
+
if (currentDirLogs.length === 0) {
|
|
259
|
+
return {
|
|
260
|
+
status: 'no_logs_found',
|
|
261
|
+
availableSessions: [],
|
|
262
|
+
recommendedLogs: ['No log files found. Start a dev server to create logs.'],
|
|
263
|
+
totalSessions: 0
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Return current directory logs as a session
|
|
267
|
+
return {
|
|
268
|
+
status: 'success',
|
|
269
|
+
mostRecentSession: {
|
|
270
|
+
sessionDate: new Date().toISOString().split('T')[0],
|
|
271
|
+
logFiles: currentDirLogs,
|
|
272
|
+
totalFiles: currentDirLogs.length,
|
|
273
|
+
sessionPath: cwd
|
|
274
|
+
},
|
|
275
|
+
availableSessions: ['current'],
|
|
276
|
+
recommendedLogs: currentDirLogs.map(f => `${f.processType}: ${f.fileName} (${f.lineCount || 0} lines)`),
|
|
277
|
+
totalSessions: 1
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// Discover date-based sessions in logs directory
|
|
281
|
+
const sessions = [];
|
|
282
|
+
let mostRecentSession;
|
|
283
|
+
try {
|
|
284
|
+
const entries = readdirSync(logsDir);
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
const entryPath = join(logsDir, entry);
|
|
287
|
+
const stats = statSync(entryPath);
|
|
288
|
+
if (stats.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry)) {
|
|
289
|
+
sessions.push(entry);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Sort sessions by date (most recent first)
|
|
293
|
+
sessions.sort((a, b) => b.localeCompare(a));
|
|
294
|
+
// Get the target session (specified or most recent)
|
|
295
|
+
const targetSession = sessionDate || sessions[0];
|
|
296
|
+
if (targetSession && sessions.includes(targetSession)) {
|
|
297
|
+
const sessionPath = join(logsDir, targetSession);
|
|
298
|
+
const logFiles = this.getLogFilesFromDirectory(sessionPath);
|
|
299
|
+
mostRecentSession = {
|
|
300
|
+
sessionDate: targetSession,
|
|
301
|
+
logFiles,
|
|
302
|
+
totalFiles: logFiles.length,
|
|
303
|
+
sessionPath
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
// Directory read error
|
|
309
|
+
}
|
|
310
|
+
const recommendedLogs = [];
|
|
311
|
+
if (mostRecentSession) {
|
|
312
|
+
if (mostRecentSession.logFiles.length === 0) {
|
|
313
|
+
recommendedLogs.push(`Session ${mostRecentSession.sessionDate} has no log files`);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
recommendedLogs.push(`Most recent session: ${mostRecentSession.sessionDate}`);
|
|
317
|
+
recommendedLogs.push(...mostRecentSession.logFiles.map(f => `${f.processType}: ${f.fileName} (${f.lineCount || 0} lines, ${Math.round(f.size / 1024)}KB)`));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else if (sessions.length === 0) {
|
|
321
|
+
recommendedLogs.push('No organized log sessions found. Logs will be created in logs/YYYY-MM-DD/ when you start dev servers.');
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
recommendedLogs.push('No logs found in recent sessions. Start dev servers to generate logs.');
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
status: sessions.length > 0 ? 'success' : 'no_sessions_found',
|
|
328
|
+
mostRecentSession,
|
|
329
|
+
availableSessions: sessions,
|
|
330
|
+
recommendedLogs,
|
|
331
|
+
totalSessions: sessions.length
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
createStructuredLogPath(command, sessionDate, logType) {
|
|
335
|
+
const cwd = process.cwd();
|
|
336
|
+
const today = new Date().toISOString().split('T')[0];
|
|
337
|
+
const targetDate = sessionDate || today;
|
|
338
|
+
// Create logs directory if it doesn't exist
|
|
339
|
+
const logsDir = join(cwd, 'logs');
|
|
340
|
+
const sessionDir = join(logsDir, targetDate);
|
|
341
|
+
if (!existsSync(logsDir)) {
|
|
342
|
+
mkdirSync(logsDir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
if (!existsSync(sessionDir)) {
|
|
345
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
346
|
+
}
|
|
347
|
+
// Determine log type and generate standardized filename
|
|
348
|
+
const detectedType = logType || this.detectProcessType(command);
|
|
349
|
+
const standardizedName = this.generateStandardizedLogName(detectedType, command);
|
|
350
|
+
return join(sessionDir, standardizedName);
|
|
351
|
+
}
|
|
352
|
+
generateStandardizedLogName(logType, command) {
|
|
353
|
+
// Standardized naming conventions to prevent confusion
|
|
354
|
+
const timestamp = new Date().toTimeString().split(' ')[0].replace(/:/g, '-');
|
|
355
|
+
switch (logType) {
|
|
356
|
+
case 'frontend':
|
|
357
|
+
if (command.includes('next'))
|
|
358
|
+
return `frontend-nextjs-${timestamp}.log`;
|
|
359
|
+
if (command.includes('vite'))
|
|
360
|
+
return `frontend-vite-${timestamp}.log`;
|
|
361
|
+
if (command.includes('react'))
|
|
362
|
+
return `frontend-react-${timestamp}.log`;
|
|
363
|
+
return `frontend-dev-${timestamp}.log`;
|
|
364
|
+
case 'backend':
|
|
365
|
+
if (command.includes('express'))
|
|
366
|
+
return `backend-express-${timestamp}.log`;
|
|
367
|
+
if (command.includes('fastify'))
|
|
368
|
+
return `backend-fastify-${timestamp}.log`;
|
|
369
|
+
if (command.includes('node'))
|
|
370
|
+
return `backend-node-${timestamp}.log`;
|
|
371
|
+
return `backend-api-${timestamp}.log`;
|
|
372
|
+
case 'amplify':
|
|
373
|
+
if (command.includes('sandbox'))
|
|
374
|
+
return `amplify-sandbox-${timestamp}.log`;
|
|
375
|
+
if (command.includes('deploy'))
|
|
376
|
+
return `amplify-deploy-${timestamp}.log`;
|
|
377
|
+
return `amplify-dev-${timestamp}.log`;
|
|
378
|
+
default:
|
|
379
|
+
// Extract first word of command for custom processes
|
|
380
|
+
const cmdName = command.split(' ')[0].replace(/[^a-zA-Z0-9]/g, '');
|
|
381
|
+
return `custom-${cmdName}-${timestamp}.log`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
shouldUseStructuredLogging(args) {
|
|
385
|
+
// Use structured logging by default unless explicitly disabled
|
|
386
|
+
// or if legacy outputFile is provided without structuredLogging flag
|
|
387
|
+
if (args.structuredLogging === false)
|
|
388
|
+
return false;
|
|
389
|
+
if (args.outputFile && args.structuredLogging !== true)
|
|
390
|
+
return false;
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
76
393
|
killAllProcesses() {
|
|
77
394
|
for (const [id, info] of this.activeServers) {
|
|
78
395
|
try {
|
|
79
|
-
if (info.process
|
|
80
|
-
//
|
|
81
|
-
process.
|
|
396
|
+
if (info.process) {
|
|
397
|
+
// Remove all event listeners to prevent memory leaks
|
|
398
|
+
info.process.stdout?.removeAllListeners('data');
|
|
399
|
+
info.process.stderr?.removeAllListeners('data');
|
|
400
|
+
info.process.removeAllListeners('exit');
|
|
401
|
+
info.process.removeAllListeners('error');
|
|
402
|
+
// Kill the process
|
|
403
|
+
if (info.pid) {
|
|
404
|
+
// Kill the process group (negative PID) to kill all children
|
|
405
|
+
process.kill(-info.pid, 'SIGTERM');
|
|
406
|
+
}
|
|
82
407
|
}
|
|
83
408
|
}
|
|
84
409
|
catch (error) {
|
|
@@ -100,7 +425,7 @@ class DevLoggerServer {
|
|
|
100
425
|
const tools = [
|
|
101
426
|
{
|
|
102
427
|
name: "dev_start_log_streaming",
|
|
103
|
-
description: "Start development server
|
|
428
|
+
description: "Start development server with organized logging (logs/YYYY-MM-DD/). Supports structured sessions and standardized file naming.",
|
|
104
429
|
inputSchema: {
|
|
105
430
|
type: "object",
|
|
106
431
|
properties: {
|
|
@@ -110,7 +435,7 @@ class DevLoggerServer {
|
|
|
110
435
|
},
|
|
111
436
|
outputFile: {
|
|
112
437
|
type: "string",
|
|
113
|
-
description: "File to write logs to (
|
|
438
|
+
description: "File to write logs to (DEPRECATED: use structuredLogging instead)"
|
|
114
439
|
},
|
|
115
440
|
cwd: {
|
|
116
441
|
type: "string",
|
|
@@ -122,37 +447,77 @@ class DevLoggerServer {
|
|
|
122
447
|
additionalProperties: {
|
|
123
448
|
type: "string"
|
|
124
449
|
}
|
|
450
|
+
},
|
|
451
|
+
processId: {
|
|
452
|
+
type: "string",
|
|
453
|
+
description: "Custom process ID (auto-generated if not provided)"
|
|
454
|
+
},
|
|
455
|
+
structuredLogging: {
|
|
456
|
+
type: "boolean",
|
|
457
|
+
description: "Use organized logging with date-based sessions (default: true)"
|
|
458
|
+
},
|
|
459
|
+
sessionDate: {
|
|
460
|
+
type: "string",
|
|
461
|
+
description: "Session date for organized logging (YYYY-MM-DD, defaults to today)"
|
|
462
|
+
},
|
|
463
|
+
logType: {
|
|
464
|
+
type: "string",
|
|
465
|
+
enum: ["frontend", "backend", "amplify", "custom"],
|
|
466
|
+
description: "Process type for standardized naming (auto-detected if not provided)"
|
|
125
467
|
}
|
|
126
468
|
}
|
|
127
469
|
}
|
|
128
470
|
},
|
|
129
471
|
{
|
|
130
|
-
name: "
|
|
131
|
-
description: "
|
|
472
|
+
name: "dev_list_processes",
|
|
473
|
+
description: "List all running development processes",
|
|
132
474
|
inputSchema: {
|
|
133
475
|
type: "object",
|
|
134
476
|
properties: {}
|
|
135
477
|
}
|
|
136
478
|
},
|
|
479
|
+
{
|
|
480
|
+
name: "dev_stop_log_streaming",
|
|
481
|
+
description: "Stop development server(s) and logging",
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {
|
|
485
|
+
processId: {
|
|
486
|
+
type: "string",
|
|
487
|
+
description: "Process ID to stop (if not provided, stops all processes)"
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
},
|
|
137
492
|
{
|
|
138
493
|
name: "dev_restart_log_streaming",
|
|
139
|
-
description: "Restart
|
|
494
|
+
description: "Restart a specific development server",
|
|
140
495
|
inputSchema: {
|
|
141
496
|
type: "object",
|
|
142
497
|
properties: {
|
|
498
|
+
processId: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "Process ID to restart"
|
|
501
|
+
},
|
|
143
502
|
clearLogs: {
|
|
144
503
|
type: "boolean",
|
|
145
504
|
description: "Clear logs on restart (default: false)"
|
|
146
505
|
}
|
|
147
|
-
}
|
|
506
|
+
},
|
|
507
|
+
required: ["processId"]
|
|
148
508
|
}
|
|
149
509
|
},
|
|
150
510
|
{
|
|
151
511
|
name: "dev_get_status",
|
|
152
|
-
description: "Get status of development server",
|
|
512
|
+
description: "Get status of development server(s)",
|
|
153
513
|
inputSchema: {
|
|
154
514
|
type: "object",
|
|
155
|
-
properties: {
|
|
515
|
+
properties: {
|
|
516
|
+
processId: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "Process ID to get status for (if not provided, shows all processes)"
|
|
519
|
+
}
|
|
520
|
+
}
|
|
156
521
|
}
|
|
157
522
|
},
|
|
158
523
|
{
|
|
@@ -161,6 +526,10 @@ class DevLoggerServer {
|
|
|
161
526
|
inputSchema: {
|
|
162
527
|
type: "object",
|
|
163
528
|
properties: {
|
|
529
|
+
processId: {
|
|
530
|
+
type: "string",
|
|
531
|
+
description: "Process ID to tail logs from (if not provided, tries to find single process)"
|
|
532
|
+
},
|
|
164
533
|
lines: {
|
|
165
534
|
type: "number",
|
|
166
535
|
description: "Number of lines to return (default: 50)"
|
|
@@ -178,12 +547,47 @@ class DevLoggerServer {
|
|
|
178
547
|
inputSchema: {
|
|
179
548
|
type: "object",
|
|
180
549
|
properties: {
|
|
550
|
+
processId: {
|
|
551
|
+
type: "string",
|
|
552
|
+
description: "Process ID to clear logs for (if not provided, tries to find single process)"
|
|
553
|
+
},
|
|
181
554
|
backup: {
|
|
182
555
|
type: "boolean",
|
|
183
556
|
description: "Backup logs before clearing (default: false)"
|
|
184
557
|
}
|
|
185
558
|
}
|
|
186
559
|
}
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "dev_check_running_processes",
|
|
563
|
+
description: "Check for running development processes that might conflict with new servers",
|
|
564
|
+
inputSchema: {
|
|
565
|
+
type: "object",
|
|
566
|
+
properties: {
|
|
567
|
+
processType: {
|
|
568
|
+
type: "string",
|
|
569
|
+
enum: ["frontend", "backend", "amplify", "custom"],
|
|
570
|
+
description: "Type of process to check for conflicts"
|
|
571
|
+
},
|
|
572
|
+
port: {
|
|
573
|
+
type: "number",
|
|
574
|
+
description: "Specific port to check"
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "dev_discover_logs",
|
|
581
|
+
description: "Auto-discover available log files and sessions in organized directory structure",
|
|
582
|
+
inputSchema: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: {
|
|
585
|
+
sessionDate: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "Specific session date (YYYY-MM-DD) to discover logs for (defaults to most recent)"
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
187
591
|
}
|
|
188
592
|
];
|
|
189
593
|
return { tools };
|
|
@@ -194,63 +598,29 @@ class DevLoggerServer {
|
|
|
194
598
|
switch (name) {
|
|
195
599
|
case "dev_start_log_streaming": {
|
|
196
600
|
const validatedArgs = StartLogStreamingArgsSchema.parse(args);
|
|
197
|
-
// Kill any existing process first
|
|
198
|
-
this.killAllProcesses();
|
|
199
|
-
// Wait a moment for processes to die
|
|
200
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
201
601
|
const command = validatedArgs.command || "npm run dev";
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const logDir = join('logs', year.toString(), month, day);
|
|
208
|
-
// Ensure log directory exists
|
|
209
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
210
|
-
// Auto-detect appropriate log filename based on command
|
|
211
|
-
let autoFileName;
|
|
212
|
-
if (command.includes('ampx sandbox --stream-function-logs')) {
|
|
213
|
-
autoFileName = 'amplify-functions.txt';
|
|
214
|
-
}
|
|
215
|
-
else if (command.includes('ampx sandbox')) {
|
|
216
|
-
autoFileName = 'amplify-sandbox.txt';
|
|
217
|
-
}
|
|
218
|
-
else if (command.includes('npm run dev')) {
|
|
219
|
-
autoFileName = 'npm-dev.txt';
|
|
220
|
-
}
|
|
221
|
-
else if (command.includes('yarn dev')) {
|
|
222
|
-
autoFileName = 'yarn-dev.txt';
|
|
223
|
-
}
|
|
224
|
-
else if (command.includes('next dev')) {
|
|
225
|
-
autoFileName = 'nextjs-dev.txt';
|
|
226
|
-
}
|
|
227
|
-
else if (command.includes('npm start')) {
|
|
228
|
-
autoFileName = 'npm-start.txt';
|
|
229
|
-
}
|
|
230
|
-
else if (command.includes('yarn start')) {
|
|
231
|
-
autoFileName = 'yarn-start.txt';
|
|
232
|
-
}
|
|
233
|
-
else if (command.includes('vite')) {
|
|
234
|
-
autoFileName = 'vite-dev.txt';
|
|
235
|
-
}
|
|
236
|
-
else if (command.includes('remix dev')) {
|
|
237
|
-
autoFileName = 'remix-dev.txt';
|
|
238
|
-
}
|
|
239
|
-
else if (command.includes('astro dev')) {
|
|
240
|
-
autoFileName = 'astro-dev.txt';
|
|
602
|
+
const cwd = resolve(validatedArgs.cwd || process.cwd());
|
|
603
|
+
// Determine output file path using structured logging or legacy approach
|
|
604
|
+
let outputFile;
|
|
605
|
+
if (this.shouldUseStructuredLogging(validatedArgs)) {
|
|
606
|
+
outputFile = this.createStructuredLogPath(command, validatedArgs.sessionDate, validatedArgs.logType);
|
|
241
607
|
}
|
|
242
608
|
else {
|
|
243
|
-
|
|
244
|
-
|
|
609
|
+
outputFile = resolve(validatedArgs.outputFile || "dev-server-logs.txt");
|
|
610
|
+
}
|
|
611
|
+
// Generate or use provided process ID
|
|
612
|
+
let processId = validatedArgs.processId || this.generateProcessId(command, outputFile);
|
|
613
|
+
// Ensure unique process ID
|
|
614
|
+
let counter = 1;
|
|
615
|
+
const originalProcessId = processId;
|
|
616
|
+
while (this.activeServers.has(processId)) {
|
|
617
|
+
processId = `${originalProcessId}-${counter}`;
|
|
618
|
+
counter++;
|
|
245
619
|
}
|
|
246
|
-
// Use provided filename or auto-detected one
|
|
247
|
-
const baseFileName = validatedArgs.outputFile || autoFileName;
|
|
248
|
-
const outputFile = resolve(join(logDir, baseFileName.replace(/^logs[\/\\]/, '')));
|
|
249
|
-
const cwd = resolve(validatedArgs.cwd || process.cwd());
|
|
250
620
|
// Parse command into program and args
|
|
251
621
|
const [program, ...cmdArgs] = command.split(' ');
|
|
252
622
|
// Create or clear the output file
|
|
253
|
-
writeFileSync(outputFile, `[${new Date().toISOString()}] Starting: ${command}\n`);
|
|
623
|
+
writeFileSync(outputFile, `[${new Date().toISOString()}] Starting: ${command} (Process ID: ${processId})\n`);
|
|
254
624
|
try {
|
|
255
625
|
// Spawn the process (detached to avoid signal propagation)
|
|
256
626
|
const devProcess = spawn(program, cmdArgs, {
|
|
@@ -259,39 +629,40 @@ class DevLoggerServer {
|
|
|
259
629
|
detached: true,
|
|
260
630
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
261
631
|
});
|
|
262
|
-
const serverId = "default";
|
|
263
632
|
const serverInfo = {
|
|
264
633
|
process: devProcess,
|
|
265
634
|
command: command,
|
|
266
635
|
cwd: cwd,
|
|
267
636
|
outputFile: outputFile,
|
|
268
637
|
startTime: new Date(),
|
|
269
|
-
pid: devProcess.pid
|
|
638
|
+
pid: devProcess.pid,
|
|
639
|
+
processId: processId
|
|
270
640
|
};
|
|
271
|
-
this.activeServers.set(
|
|
641
|
+
this.activeServers.set(processId, serverInfo);
|
|
272
642
|
// Stream stdout to file
|
|
273
643
|
devProcess.stdout?.on('data', (data) => {
|
|
274
644
|
const timestamp = new Date().toISOString();
|
|
275
|
-
const logEntry = `[${timestamp}] ${data}`;
|
|
645
|
+
const logEntry = `[${timestamp}] [${processId}] ${data}`;
|
|
276
646
|
appendFileSync(outputFile, logEntry);
|
|
277
647
|
});
|
|
278
648
|
// Stream stderr to file
|
|
279
649
|
devProcess.stderr?.on('data', (data) => {
|
|
280
650
|
const timestamp = new Date().toISOString();
|
|
281
|
-
const logEntry = `[${timestamp}] [ERROR] ${data}`;
|
|
651
|
+
const logEntry = `[${timestamp}] [${processId}] [ERROR] ${data}`;
|
|
282
652
|
appendFileSync(outputFile, logEntry);
|
|
283
653
|
});
|
|
284
654
|
// Handle process exit
|
|
285
655
|
devProcess.on('exit', (code, signal) => {
|
|
286
656
|
const timestamp = new Date().toISOString();
|
|
287
|
-
const exitMessage = `[${timestamp}] Process exited with code ${code} and signal ${signal}\n`;
|
|
657
|
+
const exitMessage = `[${timestamp}] [${processId}] Process exited with code ${code} and signal ${signal}\n`;
|
|
288
658
|
appendFileSync(outputFile, exitMessage);
|
|
289
|
-
this.activeServers.delete(
|
|
659
|
+
this.activeServers.delete(processId);
|
|
660
|
+
this.saveState();
|
|
290
661
|
});
|
|
291
662
|
// Handle process errors
|
|
292
663
|
devProcess.on('error', (error) => {
|
|
293
664
|
const timestamp = new Date().toISOString();
|
|
294
|
-
const errorMessage = `[${timestamp}] Process error: ${error.message}\n`;
|
|
665
|
+
const errorMessage = `[${timestamp}] [${processId}] Process error: ${error.message}\n`;
|
|
295
666
|
appendFileSync(outputFile, errorMessage);
|
|
296
667
|
});
|
|
297
668
|
this.saveState();
|
|
@@ -301,12 +672,12 @@ class DevLoggerServer {
|
|
|
301
672
|
type: "text",
|
|
302
673
|
text: JSON.stringify({
|
|
303
674
|
status: "started",
|
|
675
|
+
processId: processId,
|
|
304
676
|
pid: devProcess.pid,
|
|
305
677
|
command: command,
|
|
306
678
|
cwd: cwd,
|
|
307
679
|
outputFile: outputFile,
|
|
308
|
-
|
|
309
|
-
message: `Dev server started. Logs streaming to ${outputFile.replace(cwd + '/', '')}`
|
|
680
|
+
message: `Dev server started with process ID: ${processId}. Logs streaming to ${outputFile}`
|
|
310
681
|
}, null, 2)
|
|
311
682
|
}
|
|
312
683
|
]
|
|
@@ -316,27 +687,112 @@ class DevLoggerServer {
|
|
|
316
687
|
throw new Error(`Failed to start dev server: ${error instanceof Error ? error.message : String(error)}`);
|
|
317
688
|
}
|
|
318
689
|
}
|
|
690
|
+
case "dev_list_processes": {
|
|
691
|
+
const processes = Array.from(this.activeServers.values()).map(info => ({
|
|
692
|
+
processId: info.processId,
|
|
693
|
+
pid: info.pid,
|
|
694
|
+
command: info.command,
|
|
695
|
+
cwd: info.cwd,
|
|
696
|
+
outputFile: info.outputFile,
|
|
697
|
+
startTime: info.startTime,
|
|
698
|
+
uptime: new Date().getTime() - info.startTime.getTime()
|
|
699
|
+
}));
|
|
700
|
+
return {
|
|
701
|
+
content: [
|
|
702
|
+
{
|
|
703
|
+
type: "text",
|
|
704
|
+
text: JSON.stringify({
|
|
705
|
+
status: "success",
|
|
706
|
+
totalProcesses: processes.length,
|
|
707
|
+
processes: processes
|
|
708
|
+
}, null, 2)
|
|
709
|
+
}
|
|
710
|
+
]
|
|
711
|
+
};
|
|
712
|
+
}
|
|
319
713
|
case "dev_stop_log_streaming": {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
714
|
+
const validatedArgs = StopLogStreamingArgsSchema.parse(args);
|
|
715
|
+
if (validatedArgs.processId) {
|
|
716
|
+
// Stop specific process
|
|
717
|
+
const serverInfo = this.activeServers.get(validatedArgs.processId);
|
|
718
|
+
if (!serverInfo) {
|
|
719
|
+
return {
|
|
720
|
+
content: [
|
|
721
|
+
{
|
|
722
|
+
type: "text",
|
|
723
|
+
text: JSON.stringify({
|
|
724
|
+
status: "not_found",
|
|
725
|
+
message: `Process with ID '${validatedArgs.processId}' not found`
|
|
726
|
+
}, null, 2)
|
|
727
|
+
}
|
|
728
|
+
]
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
// Remove all event listeners to prevent memory leaks
|
|
733
|
+
if (serverInfo.process) {
|
|
734
|
+
serverInfo.process.stdout?.removeAllListeners('data');
|
|
735
|
+
serverInfo.process.stderr?.removeAllListeners('data');
|
|
736
|
+
serverInfo.process.removeAllListeners('exit');
|
|
737
|
+
serverInfo.process.removeAllListeners('error');
|
|
738
|
+
}
|
|
739
|
+
if (serverInfo.pid) {
|
|
740
|
+
process.kill(serverInfo.pid, 'SIGTERM');
|
|
741
|
+
}
|
|
742
|
+
this.activeServers.delete(validatedArgs.processId);
|
|
743
|
+
this.saveState();
|
|
744
|
+
return {
|
|
745
|
+
content: [
|
|
746
|
+
{
|
|
747
|
+
type: "text",
|
|
748
|
+
text: JSON.stringify({
|
|
749
|
+
status: "stopped",
|
|
750
|
+
processId: validatedArgs.processId,
|
|
751
|
+
message: `Process '${validatedArgs.processId}' stopped successfully`
|
|
752
|
+
}, null, 2)
|
|
753
|
+
}
|
|
754
|
+
]
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
throw new Error(`Failed to stop process '${validatedArgs.processId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
759
|
+
}
|
|
334
760
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
761
|
+
else {
|
|
762
|
+
// Stop all processes
|
|
763
|
+
if (this.activeServers.size === 0) {
|
|
764
|
+
return {
|
|
765
|
+
content: [
|
|
766
|
+
{
|
|
767
|
+
type: "text",
|
|
768
|
+
text: JSON.stringify({
|
|
769
|
+
status: "not_running",
|
|
770
|
+
message: "No dev servers are currently running"
|
|
771
|
+
}, null, 2)
|
|
772
|
+
}
|
|
773
|
+
]
|
|
774
|
+
};
|
|
338
775
|
}
|
|
339
|
-
|
|
776
|
+
const stoppedProcesses = [];
|
|
777
|
+
for (const [processId, serverInfo] of this.activeServers) {
|
|
778
|
+
try {
|
|
779
|
+
// Remove all event listeners to prevent memory leaks
|
|
780
|
+
if (serverInfo.process) {
|
|
781
|
+
serverInfo.process.stdout?.removeAllListeners('data');
|
|
782
|
+
serverInfo.process.stderr?.removeAllListeners('data');
|
|
783
|
+
serverInfo.process.removeAllListeners('exit');
|
|
784
|
+
serverInfo.process.removeAllListeners('error');
|
|
785
|
+
}
|
|
786
|
+
if (serverInfo.pid) {
|
|
787
|
+
process.kill(serverInfo.pid, 'SIGTERM');
|
|
788
|
+
}
|
|
789
|
+
stoppedProcesses.push(processId);
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
// Continue with other processes even if one fails
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
this.activeServers.clear();
|
|
340
796
|
this.saveState();
|
|
341
797
|
return {
|
|
342
798
|
content: [
|
|
@@ -344,28 +800,26 @@ class DevLoggerServer {
|
|
|
344
800
|
type: "text",
|
|
345
801
|
text: JSON.stringify({
|
|
346
802
|
status: "stopped",
|
|
347
|
-
|
|
803
|
+
stoppedProcesses: stoppedProcesses,
|
|
804
|
+
message: `Stopped ${stoppedProcesses.length} processes`
|
|
348
805
|
}, null, 2)
|
|
349
806
|
}
|
|
350
807
|
]
|
|
351
808
|
};
|
|
352
809
|
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
throw new Error(`Failed to stop dev server: ${error instanceof Error ? error.message : String(error)}`);
|
|
355
|
-
}
|
|
356
810
|
}
|
|
357
811
|
case "dev_restart_log_streaming": {
|
|
358
812
|
const validatedArgs = RestartLogStreamingArgsSchema.parse(args);
|
|
359
|
-
const
|
|
360
|
-
const serverInfo = this.activeServers.get(
|
|
813
|
+
const processId = validatedArgs.processId;
|
|
814
|
+
const serverInfo = this.activeServers.get(processId);
|
|
361
815
|
if (!serverInfo) {
|
|
362
816
|
return {
|
|
363
817
|
content: [
|
|
364
818
|
{
|
|
365
819
|
type: "text",
|
|
366
820
|
text: JSON.stringify({
|
|
367
|
-
status: "
|
|
368
|
-
message:
|
|
821
|
+
status: "not_found",
|
|
822
|
+
message: `Process with ID '${processId}' not found. Use dev_list_processes to see available processes.`
|
|
369
823
|
}, null, 2)
|
|
370
824
|
}
|
|
371
825
|
]
|
|
@@ -375,6 +829,13 @@ class DevLoggerServer {
|
|
|
375
829
|
const { command, cwd, outputFile } = serverInfo;
|
|
376
830
|
// Stop current process
|
|
377
831
|
try {
|
|
832
|
+
// Remove all event listeners to prevent memory leaks
|
|
833
|
+
if (serverInfo.process) {
|
|
834
|
+
serverInfo.process.stdout?.removeAllListeners('data');
|
|
835
|
+
serverInfo.process.stderr?.removeAllListeners('data');
|
|
836
|
+
serverInfo.process.removeAllListeners('exit');
|
|
837
|
+
serverInfo.process.removeAllListeners('error');
|
|
838
|
+
}
|
|
378
839
|
if (serverInfo.pid) {
|
|
379
840
|
process.kill(serverInfo.pid, 'SIGTERM');
|
|
380
841
|
}
|
|
@@ -386,22 +847,24 @@ class DevLoggerServer {
|
|
|
386
847
|
if (validatedArgs.clearLogs && existsSync(outputFile)) {
|
|
387
848
|
writeFileSync(outputFile, '');
|
|
388
849
|
}
|
|
850
|
+
// Remove the old server info to ensure complete cleanup
|
|
851
|
+
this.activeServers.delete(processId);
|
|
389
852
|
// Wait a moment for process to die
|
|
390
853
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
391
|
-
// Restart with same configuration
|
|
392
|
-
const startArgs = {
|
|
393
|
-
command,
|
|
394
|
-
outputFile,
|
|
395
|
-
cwd
|
|
396
|
-
};
|
|
397
854
|
// Start new process
|
|
398
855
|
const [program, ...cmdArgs] = command.split(' ');
|
|
399
|
-
|
|
856
|
+
const restartMessage = `[${new Date().toISOString()}] Restarting: ${command} (Process ID: ${processId})\n`;
|
|
857
|
+
if (validatedArgs.clearLogs) {
|
|
858
|
+
writeFileSync(outputFile, restartMessage);
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
appendFileSync(outputFile, restartMessage);
|
|
862
|
+
}
|
|
400
863
|
const devProcess = spawn(program, cmdArgs, {
|
|
401
864
|
cwd: cwd,
|
|
402
865
|
env: process.env,
|
|
403
|
-
|
|
404
|
-
|
|
866
|
+
detached: true,
|
|
867
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
405
868
|
});
|
|
406
869
|
const newServerInfo = {
|
|
407
870
|
process: devProcess,
|
|
@@ -409,27 +872,35 @@ class DevLoggerServer {
|
|
|
409
872
|
cwd: cwd,
|
|
410
873
|
outputFile: outputFile,
|
|
411
874
|
startTime: new Date(),
|
|
412
|
-
pid: devProcess.pid
|
|
875
|
+
pid: devProcess.pid,
|
|
876
|
+
processId: processId
|
|
413
877
|
};
|
|
414
|
-
this.activeServers.set(
|
|
878
|
+
this.activeServers.set(processId, newServerInfo);
|
|
415
879
|
// Stream stdout to file
|
|
416
880
|
devProcess.stdout?.on('data', (data) => {
|
|
417
881
|
const timestamp = new Date().toISOString();
|
|
418
|
-
const logEntry = `[${timestamp}] ${data}`;
|
|
882
|
+
const logEntry = `[${timestamp}] [${processId}] ${data}`;
|
|
419
883
|
appendFileSync(outputFile, logEntry);
|
|
420
884
|
});
|
|
421
885
|
// Stream stderr to file
|
|
422
886
|
devProcess.stderr?.on('data', (data) => {
|
|
423
887
|
const timestamp = new Date().toISOString();
|
|
424
|
-
const logEntry = `[${timestamp}] [ERROR] ${data}`;
|
|
888
|
+
const logEntry = `[${timestamp}] [${processId}] [ERROR] ${data}`;
|
|
425
889
|
appendFileSync(outputFile, logEntry);
|
|
426
890
|
});
|
|
427
891
|
// Handle process exit
|
|
428
892
|
devProcess.on('exit', (code, signal) => {
|
|
429
893
|
const timestamp = new Date().toISOString();
|
|
430
|
-
const exitMessage = `[${timestamp}] Process exited with code ${code} and signal ${signal}\n`;
|
|
894
|
+
const exitMessage = `[${timestamp}] [${processId}] Process exited with code ${code} and signal ${signal}\n`;
|
|
431
895
|
appendFileSync(outputFile, exitMessage);
|
|
432
|
-
this.activeServers.delete(
|
|
896
|
+
this.activeServers.delete(processId);
|
|
897
|
+
this.saveState();
|
|
898
|
+
});
|
|
899
|
+
// Handle process errors
|
|
900
|
+
devProcess.on('error', (error) => {
|
|
901
|
+
const timestamp = new Date().toISOString();
|
|
902
|
+
const errorMessage = `[${timestamp}] [${processId}] Process error: ${error.message}\n`;
|
|
903
|
+
appendFileSync(outputFile, errorMessage);
|
|
433
904
|
});
|
|
434
905
|
this.saveState();
|
|
435
906
|
return {
|
|
@@ -438,62 +909,148 @@ class DevLoggerServer {
|
|
|
438
909
|
type: "text",
|
|
439
910
|
text: JSON.stringify({
|
|
440
911
|
status: "restarted",
|
|
912
|
+
processId: processId,
|
|
441
913
|
pid: devProcess.pid,
|
|
442
914
|
command: command,
|
|
443
915
|
outputFile: outputFile,
|
|
444
|
-
message:
|
|
916
|
+
message: `Process '${processId}' restarted successfully`
|
|
445
917
|
}, null, 2)
|
|
446
918
|
}
|
|
447
919
|
]
|
|
448
920
|
};
|
|
449
921
|
}
|
|
450
922
|
case "dev_get_status": {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
923
|
+
const validatedArgs = { processId: args?.processId };
|
|
924
|
+
if (validatedArgs.processId) {
|
|
925
|
+
// Get status for specific process
|
|
926
|
+
const serverInfo = this.activeServers.get(validatedArgs.processId);
|
|
927
|
+
if (!serverInfo) {
|
|
928
|
+
return {
|
|
929
|
+
content: [
|
|
930
|
+
{
|
|
931
|
+
type: "text",
|
|
932
|
+
text: JSON.stringify({
|
|
933
|
+
status: "not_found",
|
|
934
|
+
message: `Process with ID '${validatedArgs.processId}' not found`
|
|
935
|
+
}, null, 2)
|
|
936
|
+
}
|
|
937
|
+
]
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
const uptime = new Date().getTime() - serverInfo.startTime.getTime();
|
|
941
|
+
const uptimeSeconds = Math.floor(uptime / 1000);
|
|
942
|
+
const uptimeMinutes = Math.floor(uptimeSeconds / 60);
|
|
943
|
+
const uptimeHours = Math.floor(uptimeMinutes / 60);
|
|
454
944
|
return {
|
|
455
945
|
content: [
|
|
456
946
|
{
|
|
457
947
|
type: "text",
|
|
458
948
|
text: JSON.stringify({
|
|
459
|
-
status: "
|
|
460
|
-
|
|
949
|
+
status: "running",
|
|
950
|
+
processId: serverInfo.processId,
|
|
951
|
+
pid: serverInfo.pid,
|
|
952
|
+
command: serverInfo.command,
|
|
953
|
+
cwd: serverInfo.cwd,
|
|
954
|
+
outputFile: serverInfo.outputFile,
|
|
955
|
+
startTime: serverInfo.startTime,
|
|
956
|
+
uptime: {
|
|
957
|
+
hours: uptimeHours,
|
|
958
|
+
minutes: uptimeMinutes % 60,
|
|
959
|
+
seconds: uptimeSeconds % 60
|
|
960
|
+
}
|
|
461
961
|
}, null, 2)
|
|
462
962
|
}
|
|
463
963
|
]
|
|
464
964
|
};
|
|
465
965
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
command: serverInfo.command,
|
|
478
|
-
cwd: serverInfo.cwd,
|
|
479
|
-
outputFile: serverInfo.outputFile,
|
|
480
|
-
startTime: serverInfo.startTime,
|
|
481
|
-
uptime: {
|
|
482
|
-
hours: uptimeHours,
|
|
483
|
-
minutes: uptimeMinutes % 60,
|
|
484
|
-
seconds: uptimeSeconds % 60
|
|
966
|
+
else {
|
|
967
|
+
// Get status for all processes
|
|
968
|
+
if (this.activeServers.size === 0) {
|
|
969
|
+
return {
|
|
970
|
+
content: [
|
|
971
|
+
{
|
|
972
|
+
type: "text",
|
|
973
|
+
text: JSON.stringify({
|
|
974
|
+
status: "not_running",
|
|
975
|
+
message: "No dev servers are currently running"
|
|
976
|
+
}, null, 2)
|
|
485
977
|
}
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
|
|
978
|
+
]
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
const processes = Array.from(this.activeServers.values()).map(serverInfo => {
|
|
982
|
+
const uptime = new Date().getTime() - serverInfo.startTime.getTime();
|
|
983
|
+
const uptimeSeconds = Math.floor(uptime / 1000);
|
|
984
|
+
const uptimeMinutes = Math.floor(uptimeSeconds / 60);
|
|
985
|
+
const uptimeHours = Math.floor(uptimeMinutes / 60);
|
|
986
|
+
return {
|
|
987
|
+
processId: serverInfo.processId,
|
|
988
|
+
pid: serverInfo.pid,
|
|
989
|
+
command: serverInfo.command,
|
|
990
|
+
cwd: serverInfo.cwd,
|
|
991
|
+
outputFile: serverInfo.outputFile,
|
|
992
|
+
startTime: serverInfo.startTime,
|
|
993
|
+
uptime: {
|
|
994
|
+
hours: uptimeHours,
|
|
995
|
+
minutes: uptimeMinutes % 60,
|
|
996
|
+
seconds: uptimeSeconds % 60
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
});
|
|
1000
|
+
return {
|
|
1001
|
+
content: [
|
|
1002
|
+
{
|
|
1003
|
+
type: "text",
|
|
1004
|
+
text: JSON.stringify({
|
|
1005
|
+
status: "running",
|
|
1006
|
+
totalProcesses: processes.length,
|
|
1007
|
+
processes: processes
|
|
1008
|
+
}, null, 2)
|
|
1009
|
+
}
|
|
1010
|
+
]
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
490
1013
|
}
|
|
491
1014
|
case "dev_tail_logs": {
|
|
492
1015
|
const validatedArgs = TailLogsArgsSchema.parse(args);
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
1016
|
+
// Find the process or use the default behavior
|
|
1017
|
+
const processId = this.findProcessOrDefault(validatedArgs.processId);
|
|
1018
|
+
if (!processId && validatedArgs.processId) {
|
|
1019
|
+
return {
|
|
1020
|
+
content: [
|
|
1021
|
+
{
|
|
1022
|
+
type: "text",
|
|
1023
|
+
text: JSON.stringify({
|
|
1024
|
+
status: "not_found",
|
|
1025
|
+
message: `Process with ID '${validatedArgs.processId}' not found`
|
|
1026
|
+
}, null, 2)
|
|
1027
|
+
}
|
|
1028
|
+
]
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
if (!processId && this.activeServers.size > 1) {
|
|
1032
|
+
return {
|
|
1033
|
+
content: [
|
|
1034
|
+
{
|
|
1035
|
+
type: "text",
|
|
1036
|
+
text: JSON.stringify({
|
|
1037
|
+
status: "ambiguous",
|
|
1038
|
+
message: "Multiple processes running. Please specify processId or use dev_list_processes to see options.",
|
|
1039
|
+
availableProcesses: Array.from(this.activeServers.keys())
|
|
1040
|
+
}, null, 2)
|
|
1041
|
+
}
|
|
1042
|
+
]
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
// Get the output file
|
|
1046
|
+
let outputFile;
|
|
1047
|
+
if (processId) {
|
|
1048
|
+
const serverInfo = this.activeServers.get(processId);
|
|
1049
|
+
outputFile = serverInfo.outputFile;
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
outputFile = "dev-server-logs.txt"; // fallback
|
|
1053
|
+
}
|
|
497
1054
|
if (!existsSync(outputFile)) {
|
|
498
1055
|
return {
|
|
499
1056
|
content: [
|
|
@@ -526,6 +1083,7 @@ class DevLoggerServer {
|
|
|
526
1083
|
type: "text",
|
|
527
1084
|
text: JSON.stringify({
|
|
528
1085
|
status: "success",
|
|
1086
|
+
processId: processId || "default",
|
|
529
1087
|
file: outputFile,
|
|
530
1088
|
totalLines: lines.length,
|
|
531
1089
|
filteredLines: filteredLines.length,
|
|
@@ -542,10 +1100,44 @@ class DevLoggerServer {
|
|
|
542
1100
|
}
|
|
543
1101
|
case "dev_clear_logs": {
|
|
544
1102
|
const validatedArgs = ClearLogsArgsSchema.parse(args);
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
1103
|
+
// Find the process or use the default behavior
|
|
1104
|
+
const processId = this.findProcessOrDefault(validatedArgs.processId);
|
|
1105
|
+
if (!processId && validatedArgs.processId) {
|
|
1106
|
+
return {
|
|
1107
|
+
content: [
|
|
1108
|
+
{
|
|
1109
|
+
type: "text",
|
|
1110
|
+
text: JSON.stringify({
|
|
1111
|
+
status: "not_found",
|
|
1112
|
+
message: `Process with ID '${validatedArgs.processId}' not found`
|
|
1113
|
+
}, null, 2)
|
|
1114
|
+
}
|
|
1115
|
+
]
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (!processId && this.activeServers.size > 1) {
|
|
1119
|
+
return {
|
|
1120
|
+
content: [
|
|
1121
|
+
{
|
|
1122
|
+
type: "text",
|
|
1123
|
+
text: JSON.stringify({
|
|
1124
|
+
status: "ambiguous",
|
|
1125
|
+
message: "Multiple processes running. Please specify processId or use dev_list_processes to see options.",
|
|
1126
|
+
availableProcesses: Array.from(this.activeServers.keys())
|
|
1127
|
+
}, null, 2)
|
|
1128
|
+
}
|
|
1129
|
+
]
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
// Get the output file
|
|
1133
|
+
let outputFile;
|
|
1134
|
+
if (processId) {
|
|
1135
|
+
const serverInfo = this.activeServers.get(processId);
|
|
1136
|
+
outputFile = serverInfo.outputFile;
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
outputFile = "dev-server-logs.txt"; // fallback
|
|
1140
|
+
}
|
|
549
1141
|
if (!existsSync(outputFile)) {
|
|
550
1142
|
return {
|
|
551
1143
|
content: [
|
|
@@ -573,6 +1165,7 @@ class DevLoggerServer {
|
|
|
573
1165
|
type: "text",
|
|
574
1166
|
text: JSON.stringify({
|
|
575
1167
|
status: "success",
|
|
1168
|
+
processId: processId || "default",
|
|
576
1169
|
message: `Log file cleared: ${outputFile}`,
|
|
577
1170
|
backup: validatedArgs.backup ? "Created backup" : "No backup created"
|
|
578
1171
|
}, null, 2)
|
|
@@ -584,6 +1177,40 @@ class DevLoggerServer {
|
|
|
584
1177
|
throw new Error(`Failed to clear logs: ${error instanceof Error ? error.message : String(error)}`);
|
|
585
1178
|
}
|
|
586
1179
|
}
|
|
1180
|
+
case "dev_check_running_processes": {
|
|
1181
|
+
const validatedArgs = CheckRunningProcessesArgsSchema.parse(args);
|
|
1182
|
+
try {
|
|
1183
|
+
const result = await this.checkRunningProcesses(validatedArgs.processType, validatedArgs.port);
|
|
1184
|
+
return {
|
|
1185
|
+
content: [
|
|
1186
|
+
{
|
|
1187
|
+
type: "text",
|
|
1188
|
+
text: JSON.stringify(result, null, 2)
|
|
1189
|
+
}
|
|
1190
|
+
]
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
catch (error) {
|
|
1194
|
+
throw new Error(`Failed to check running processes: ${error instanceof Error ? error.message : String(error)}`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
case "dev_discover_logs": {
|
|
1198
|
+
const validatedArgs = DiscoverLogsArgsSchema.parse(args);
|
|
1199
|
+
try {
|
|
1200
|
+
const result = await this.discoverLogs(validatedArgs.sessionDate);
|
|
1201
|
+
return {
|
|
1202
|
+
content: [
|
|
1203
|
+
{
|
|
1204
|
+
type: "text",
|
|
1205
|
+
text: JSON.stringify(result, null, 2)
|
|
1206
|
+
}
|
|
1207
|
+
]
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
catch (error) {
|
|
1211
|
+
throw new Error(`Failed to discover logs: ${error instanceof Error ? error.message : String(error)}`);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
587
1214
|
default:
|
|
588
1215
|
throw new Error(`Unknown tool: ${name}`);
|
|
589
1216
|
}
|