dev3000 0.0.49 → 0.0.51

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 (36) hide show
  1. package/README.md +55 -6
  2. package/dist/cdp-monitor.d.ts.map +1 -1
  3. package/dist/cdp-monitor.js +54 -48
  4. package/dist/cdp-monitor.js.map +1 -1
  5. package/dist/cli.js +39 -33
  6. package/dist/cli.js.map +1 -1
  7. package/dist/dev-environment.d.ts +2 -0
  8. package/dist/dev-environment.d.ts.map +1 -1
  9. package/dist/dev-environment.js +212 -181
  10. package/dist/dev-environment.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/mcp-server/app/api/config/route.ts +7 -7
  16. package/mcp-server/app/api/logs/append/route.ts +59 -51
  17. package/mcp-server/app/api/logs/head/route.ts +22 -22
  18. package/mcp-server/app/api/logs/list/route.ts +39 -42
  19. package/mcp-server/app/api/logs/rotate/route.ts +28 -38
  20. package/mcp-server/app/api/logs/stream/route.ts +35 -35
  21. package/mcp-server/app/api/logs/tail/route.ts +22 -22
  22. package/mcp-server/app/api/mcp/[transport]/route.ts +189 -188
  23. package/mcp-server/app/api/replay/route.ts +217 -202
  24. package/mcp-server/app/layout.tsx +9 -8
  25. package/mcp-server/app/logs/LogsClient.test.ts +123 -99
  26. package/mcp-server/app/logs/LogsClient.tsx +724 -562
  27. package/mcp-server/app/logs/page.tsx +71 -72
  28. package/mcp-server/app/logs/utils.ts +99 -28
  29. package/mcp-server/app/page.tsx +10 -14
  30. package/mcp-server/app/replay/ReplayClient.tsx +120 -119
  31. package/mcp-server/app/replay/page.tsx +3 -3
  32. package/mcp-server/next.config.ts +2 -0
  33. package/mcp-server/package.json +5 -2
  34. package/mcp-server/pnpm-lock.yaml +37 -5
  35. package/mcp-server/tsconfig.json +4 -17
  36. package/package.json +16 -13
@@ -1,10 +1,10 @@
1
- import { createMcpHandler } from "mcp-handler";
2
- import { z } from "zod";
3
- import { readFileSync, existsSync } from "fs";
4
- import { join } from "path";
5
- import { WebSocket } from "ws";
1
+ import { existsSync, readFileSync } from "fs"
2
+ import { createMcpHandler } from "mcp-handler"
3
+ import { WebSocket } from "ws"
4
+ import { z } from "zod"
6
5
 
7
- const handler = createMcpHandler((server) => {
6
+ const handler = createMcpHandler(
7
+ (server) => {
8
8
  // Healthcheck tool
9
9
  server.tool(
10
10
  "healthcheck",
@@ -20,9 +20,9 @@ const handler = createMcpHandler((server) => {
20
20
  text: `✅ ${message} - Timestamp: ${new Date().toISOString()}`
21
21
  }
22
22
  ]
23
- };
23
+ }
24
24
  }
25
- );
25
+ )
26
26
 
27
27
  // Tool to read consolidated logs
28
28
  server.tool(
@@ -31,7 +31,7 @@ const handler = createMcpHandler((server) => {
31
31
  {
32
32
  lines: z.number().optional().describe("Number of recent lines to read (default: 50)"),
33
33
  filter: z.string().optional().describe("Filter logs by text content"),
34
- logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)"),
34
+ logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
35
35
  },
36
36
  async ({ lines = 50, filter, logPath = "./ai-dev-tools/consolidated.log" }) => {
37
37
  try {
@@ -43,44 +43,40 @@ const handler = createMcpHandler((server) => {
43
43
  text: `No log file found at ${logPath}. Make sure the dev environment is running.`
44
44
  }
45
45
  ]
46
- };
46
+ }
47
47
  }
48
-
49
- const logContent = readFileSync(logPath, "utf-8");
50
- let logLines = logContent.split("\n").filter(line => line.trim());
51
-
48
+
49
+ const logContent = readFileSync(logPath, "utf-8")
50
+ let logLines = logContent.split("\n").filter((line) => line.trim())
51
+
52
52
  // Apply filter if provided
53
53
  if (filter) {
54
- logLines = logLines.filter(line =>
55
- line.toLowerCase().includes(filter.toLowerCase())
56
- );
54
+ logLines = logLines.filter((line) => line.toLowerCase().includes(filter.toLowerCase()))
57
55
  }
58
-
56
+
59
57
  // Get recent lines
60
- const recentLines = logLines.slice(-lines);
61
-
58
+ const recentLines = logLines.slice(-lines)
59
+
62
60
  return {
63
61
  content: [
64
62
  {
65
63
  type: "text",
66
- text: recentLines.length > 0
67
- ? recentLines.join("\n")
68
- : "No matching log entries found."
64
+ text: recentLines.length > 0 ? recentLines.join("\n") : "No matching log entries found."
69
65
  }
70
66
  ]
71
- };
67
+ }
72
68
  } catch (error) {
73
69
  return {
74
70
  content: [
75
71
  {
76
- type: "text",
72
+ type: "text",
77
73
  text: `Error reading logs: ${error instanceof Error ? error.message : String(error)}`
78
74
  }
79
75
  ]
80
- };
76
+ }
81
77
  }
82
78
  }
83
- );
79
+ )
84
80
 
85
81
  // Tool to search logs
86
82
  server.tool(
@@ -89,7 +85,7 @@ const handler = createMcpHandler((server) => {
89
85
  {
90
86
  pattern: z.string().describe("Regex pattern to search for"),
91
87
  context: z.number().optional().describe("Number of lines of context around matches (default: 2)"),
92
- logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)"),
88
+ logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
93
89
  },
94
90
  async ({ pattern, context = 2, logPath = "./ai-dev-tools/consolidated.log" }) => {
95
91
  try {
@@ -101,35 +97,33 @@ const handler = createMcpHandler((server) => {
101
97
  text: `No log file found at ${logPath}.`
102
98
  }
103
99
  ]
104
- };
100
+ }
105
101
  }
106
-
107
- const logContent = readFileSync(logPath, "utf-8");
108
- const logLines = logContent.split("\n");
109
-
110
- const regex = new RegExp(pattern, "gi");
111
- const matches: string[] = [];
112
-
102
+
103
+ const logContent = readFileSync(logPath, "utf-8")
104
+ const logLines = logContent.split("\n")
105
+
106
+ const regex = new RegExp(pattern, "gi")
107
+ const matches: string[] = []
108
+
113
109
  logLines.forEach((line, index) => {
114
110
  if (regex.test(line)) {
115
- const start = Math.max(0, index - context);
116
- const end = Math.min(logLines.length, index + context + 1);
117
- const contextLines = logLines.slice(start, end);
118
-
119
- matches.push(`Match at line ${index + 1}:\n${contextLines.join("\n")}\n---`);
111
+ const start = Math.max(0, index - context)
112
+ const end = Math.min(logLines.length, index + context + 1)
113
+ const contextLines = logLines.slice(start, end)
114
+
115
+ matches.push(`Match at line ${index + 1}:\n${contextLines.join("\n")}\n---`)
120
116
  }
121
- });
122
-
117
+ })
118
+
123
119
  return {
124
120
  content: [
125
121
  {
126
122
  type: "text",
127
- text: matches.length > 0
128
- ? matches.join("\n\n")
129
- : "No matches found for the given pattern."
123
+ text: matches.length > 0 ? matches.join("\n\n") : "No matches found for the given pattern."
130
124
  }
131
125
  ]
132
- };
126
+ }
133
127
  } catch (error) {
134
128
  return {
135
129
  content: [
@@ -138,10 +132,10 @@ const handler = createMcpHandler((server) => {
138
132
  text: `Error searching logs: ${error instanceof Error ? error.message : String(error)}`
139
133
  }
140
134
  ]
141
- };
135
+ }
142
136
  }
143
137
  }
144
- );
138
+ )
145
139
 
146
140
  // Tool to get browser errors
147
141
  server.tool(
@@ -149,7 +143,7 @@ const handler = createMcpHandler((server) => {
149
143
  "Get recent browser errors and page errors from logs",
150
144
  {
151
145
  hours: z.number().optional().describe("Hours to look back (default: 1)"),
152
- logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)"),
146
+ logPath: z.string().optional().describe("Path to log file (default: ./ai-dev-tools/consolidated.log)")
153
147
  },
154
148
  async ({ hours = 1, logPath = "./ai-dev-tools/consolidated.log" }) => {
155
149
  try {
@@ -161,36 +155,37 @@ const handler = createMcpHandler((server) => {
161
155
  text: `No log file found at ${logPath}.`
162
156
  }
163
157
  ]
164
- };
158
+ }
165
159
  }
166
-
167
- const logContent = readFileSync(logPath, "utf-8");
168
- const logLines = logContent.split("\n");
169
-
170
- const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000);
171
- const errorLines = logLines.filter(line => {
172
- if (!line.includes("[BROWSER]")) return false;
173
- if (!(line.includes("ERROR") || line.includes("CONSOLE ERROR") || line.includes("PAGE ERROR"))) return false;
174
-
160
+
161
+ const logContent = readFileSync(logPath, "utf-8")
162
+ const logLines = logContent.split("\n")
163
+
164
+ const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000)
165
+ const errorLines = logLines.filter((line) => {
166
+ if (!line.includes("[BROWSER]")) return false
167
+ if (!(line.includes("ERROR") || line.includes("CONSOLE ERROR") || line.includes("PAGE ERROR"))) return false
168
+
175
169
  // Extract timestamp
176
- const timestampMatch = line.match(/\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\]/);
170
+ const timestampMatch = line.match(/\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\]/)
177
171
  if (timestampMatch) {
178
- const logTime = new Date(timestampMatch[1]);
179
- return logTime > cutoffTime;
172
+ const logTime = new Date(timestampMatch[1])
173
+ return logTime > cutoffTime
180
174
  }
181
- return true; // Include if we can't parse timestamp
182
- });
183
-
175
+ return true // Include if we can't parse timestamp
176
+ })
177
+
184
178
  return {
185
179
  content: [
186
180
  {
187
181
  type: "text",
188
- text: errorLines.length > 0
189
- ? errorLines.join("\n")
190
- : "No browser errors found in the specified time period."
182
+ text:
183
+ errorLines.length > 0
184
+ ? errorLines.join("\n")
185
+ : "No browser errors found in the specified time period."
191
186
  }
192
187
  ]
193
- };
188
+ }
194
189
  } catch (error) {
195
190
  return {
196
191
  content: [
@@ -199,10 +194,10 @@ const handler = createMcpHandler((server) => {
199
194
  text: `Error getting browser errors: ${error instanceof Error ? error.message : String(error)}`
200
195
  }
201
196
  ]
202
- };
197
+ }
203
198
  }
204
199
  }
205
- );
200
+ )
206
201
 
207
202
  // Tool to execute browser actions via CDP
208
203
  server.tool(
@@ -210,83 +205,85 @@ const handler = createMcpHandler((server) => {
210
205
  "Execute safe browser actions via Chrome DevTools Protocol",
211
206
  {
212
207
  action: z.enum(["click", "navigate", "screenshot", "evaluate", "scroll", "type"]).describe("Action to perform"),
213
- params: z.object({
214
- x: z.number().optional().describe("X coordinate for click/scroll"),
215
- y: z.number().optional().describe("Y coordinate for click/scroll"),
216
- url: z.string().optional().describe("URL for navigation"),
217
- selector: z.string().optional().describe("CSS selector for element targeting"),
218
- text: z.string().optional().describe("Text to type"),
219
- expression: z.string().optional().describe("JavaScript expression to evaluate (safe expressions only)"),
220
- deltaX: z.number().optional().describe("Horizontal scroll amount"),
221
- deltaY: z.number().optional().describe("Vertical scroll amount")
222
- }).describe("Parameters for the action")
208
+ params: z
209
+ .object({
210
+ x: z.number().optional().describe("X coordinate for click/scroll"),
211
+ y: z.number().optional().describe("Y coordinate for click/scroll"),
212
+ url: z.string().optional().describe("URL for navigation"),
213
+ selector: z.string().optional().describe("CSS selector for element targeting"),
214
+ text: z.string().optional().describe("Text to type"),
215
+ expression: z.string().optional().describe("JavaScript expression to evaluate (safe expressions only)"),
216
+ deltaX: z.number().optional().describe("Horizontal scroll amount"),
217
+ deltaY: z.number().optional().describe("Vertical scroll amount")
218
+ })
219
+ .describe("Parameters for the action")
223
220
  },
224
221
  async ({ action, params }) => {
225
222
  try {
226
223
  // Connect to CDP on port 9222
227
- const targetsResponse = await fetch('http://localhost:9222/json');
228
- const targets = await targetsResponse.json();
229
-
230
- const pageTarget = targets.find((target: any) => target.type === 'page');
224
+ const targetsResponse = await fetch("http://localhost:9222/json")
225
+ const targets = await targetsResponse.json()
226
+
227
+ const pageTarget = targets.find((target: any) => target.type === "page")
231
228
  if (!pageTarget) {
232
- throw new Error('No browser tab found. Make sure dev3000 is running with CDP monitoring.');
229
+ throw new Error("No browser tab found. Make sure dev3000 is running with CDP monitoring.")
233
230
  }
234
231
 
235
- const wsUrl = pageTarget.webSocketDebuggerUrl;
236
-
232
+ const wsUrl = pageTarget.webSocketDebuggerUrl
233
+
237
234
  const result = await new Promise((resolve, reject) => {
238
235
  // WebSocket imported at top of file
239
- const ws = new WebSocket(wsUrl);
240
- let messageId = 1;
241
-
242
- ws.on('open', async () => {
236
+ const ws = new WebSocket(wsUrl)
237
+ let messageId = 1
238
+
239
+ ws.on("open", async () => {
243
240
  try {
244
- let cdpResult;
245
-
241
+ let cdpResult
242
+
246
243
  switch (action) {
247
- case 'click':
244
+ case "click":
248
245
  if (!params.x || !params.y) {
249
- throw new Error('Click action requires x and y coordinates');
246
+ throw new Error("Click action requires x and y coordinates")
250
247
  }
251
248
  // Send mouse down and up events
252
- await sendCDPCommand(ws, messageId++, 'Input.dispatchMouseEvent', {
253
- type: 'mousePressed',
249
+ await sendCDPCommand(ws, messageId++, "Input.dispatchMouseEvent", {
250
+ type: "mousePressed",
254
251
  x: params.x,
255
252
  y: params.y,
256
- button: 'left',
253
+ button: "left",
257
254
  clickCount: 1
258
- });
259
- await sendCDPCommand(ws, messageId++, 'Input.dispatchMouseEvent', {
260
- type: 'mouseReleased',
255
+ })
256
+ await sendCDPCommand(ws, messageId++, "Input.dispatchMouseEvent", {
257
+ type: "mouseReleased",
261
258
  x: params.x,
262
259
  y: params.y,
263
- button: 'left',
260
+ button: "left",
264
261
  clickCount: 1
265
- });
266
- cdpResult = { action: 'click', coordinates: { x: params.x, y: params.y } };
267
- break;
268
-
269
- case 'navigate':
262
+ })
263
+ cdpResult = { action: "click", coordinates: { x: params.x, y: params.y } }
264
+ break
265
+
266
+ case "navigate":
270
267
  if (!params.url) {
271
- throw new Error('Navigate action requires url parameter');
268
+ throw new Error("Navigate action requires url parameter")
272
269
  }
273
270
  // Basic URL validation
274
- if (!params.url.startsWith('http://') && !params.url.startsWith('https://')) {
275
- throw new Error('Only http:// and https:// URLs are allowed');
271
+ if (!params.url.startsWith("http://") && !params.url.startsWith("https://")) {
272
+ throw new Error("Only http:// and https:// URLs are allowed")
276
273
  }
277
- cdpResult = await sendCDPCommand(ws, messageId++, 'Page.navigate', { url: params.url });
278
- break;
279
-
280
- case 'screenshot':
281
- cdpResult = await sendCDPCommand(ws, messageId++, 'Page.captureScreenshot', {
282
- format: 'png',
274
+ cdpResult = await sendCDPCommand(ws, messageId++, "Page.navigate", { url: params.url })
275
+ break
276
+
277
+ case "screenshot":
278
+ cdpResult = await sendCDPCommand(ws, messageId++, "Page.captureScreenshot", {
279
+ format: "png",
283
280
  quality: 80
284
- });
285
- break;
286
-
287
- case 'evaluate':
281
+ })
282
+ break
283
+
284
+ case "evaluate": {
288
285
  if (!params.expression) {
289
- throw new Error('Evaluate action requires expression parameter');
286
+ throw new Error("Evaluate action requires expression parameter")
290
287
  }
291
288
  // Whitelist safe expressions only
292
289
  const safeExpressions = [
@@ -296,87 +293,89 @@ const handler = createMcpHandler((server) => {
296
293
  /^document\.body\.scrollHeight$/,
297
294
  /^window\.scrollY$/,
298
295
  /^window\.scrollX$/
299
- ];
300
-
301
- if (!safeExpressions.some(regex => regex.test(params.expression!))) {
302
- throw new Error('Expression not in whitelist. Only safe read-only expressions allowed.');
296
+ ]
297
+
298
+ if (!safeExpressions.some((regex) => regex.test(params.expression!))) {
299
+ throw new Error("Expression not in whitelist. Only safe read-only expressions allowed.")
303
300
  }
304
-
305
- cdpResult = await sendCDPCommand(ws, messageId++, 'Runtime.evaluate', {
301
+
302
+ cdpResult = await sendCDPCommand(ws, messageId++, "Runtime.evaluate", {
306
303
  expression: params.expression,
307
304
  returnByValue: true
308
- });
309
- break;
310
-
311
- case 'scroll':
312
- const scrollX = params.deltaX || 0;
313
- const scrollY = params.deltaY || 0;
314
- cdpResult = await sendCDPCommand(ws, messageId++, 'Input.dispatchMouseEvent', {
315
- type: 'mouseWheel',
305
+ })
306
+ break
307
+ }
308
+
309
+ case "scroll": {
310
+ const scrollX = params.deltaX || 0
311
+ const scrollY = params.deltaY || 0
312
+ cdpResult = await sendCDPCommand(ws, messageId++, "Input.dispatchMouseEvent", {
313
+ type: "mouseWheel",
316
314
  x: params.x || 500,
317
315
  y: params.y || 500,
318
316
  deltaX: scrollX,
319
317
  deltaY: scrollY
320
- });
321
- break;
322
-
323
- case 'type':
318
+ })
319
+ break
320
+ }
321
+
322
+ case "type":
324
323
  if (!params.text) {
325
- throw new Error('Type action requires text parameter');
324
+ throw new Error("Type action requires text parameter")
326
325
  }
327
326
  // Type each character
328
327
  for (const char of params.text) {
329
- await sendCDPCommand(ws, messageId++, 'Input.dispatchKeyEvent', {
330
- type: 'char',
328
+ await sendCDPCommand(ws, messageId++, "Input.dispatchKeyEvent", {
329
+ type: "char",
331
330
  text: char
332
- });
331
+ })
333
332
  }
334
- cdpResult = { action: 'type', text: params.text };
335
- break;
336
-
333
+ cdpResult = { action: "type", text: params.text }
334
+ break
335
+
337
336
  default:
338
- throw new Error(`Unsupported action: ${action}`);
337
+ throw new Error(`Unsupported action: ${action}`)
339
338
  }
340
-
341
- ws.close();
342
- resolve(cdpResult);
339
+
340
+ ws.close()
341
+ resolve(cdpResult)
343
342
  } catch (error) {
344
- ws.close();
345
- reject(error);
343
+ ws.close()
344
+ reject(error)
346
345
  }
347
- });
348
-
349
- ws.on('error', reject);
350
-
346
+ })
347
+
348
+ ws.on("error", reject)
349
+
351
350
  // Helper function to send CDP commands
352
351
  async function sendCDPCommand(ws: any, id: number, method: string, params: any): Promise<any> {
353
352
  return new Promise((cmdResolve, cmdReject) => {
354
- const command = { id, method, params };
355
-
353
+ const command = { id, method, params }
354
+
356
355
  const messageHandler = (data: any) => {
357
- const message = JSON.parse(data.toString());
356
+ const message = JSON.parse(data.toString())
358
357
  if (message.id === id) {
359
- ws.removeListener('message', messageHandler);
358
+ ws.removeListener("message", messageHandler)
360
359
  if (message.error) {
361
- cmdReject(new Error(message.error.message));
360
+ cmdReject(new Error(message.error.message))
362
361
  } else {
363
- cmdResolve(message.result);
362
+ cmdResolve(message.result)
364
363
  }
365
364
  }
366
- };
367
-
368
- ws.on('message', messageHandler);
369
- ws.send(JSON.stringify(command));
370
-
365
+ }
366
+
367
+ ws.on("message", messageHandler)
368
+ ws.send(JSON.stringify(command))
369
+
371
370
  // Command timeout
372
371
  setTimeout(() => {
373
- ws.removeListener('message', messageHandler);
374
- cmdReject(new Error(`CDP command timeout: ${method}`));
375
- }, 5000);
376
- });
372
+ ws.removeListener("message", messageHandler)
373
+ cmdReject(new Error(`CDP command timeout: ${method}`))
374
+ }, 5000)
375
+ })
377
376
  }
378
- });
379
-
377
+ })
378
+
380
379
  return {
381
380
  content: [
382
381
  {
@@ -384,8 +383,7 @@ const handler = createMcpHandler((server) => {
384
383
  text: `Browser action '${action}' executed successfully. Result: ${JSON.stringify(result, null, 2)}`
385
384
  }
386
385
  ]
387
- };
388
-
386
+ }
389
387
  } catch (error) {
390
388
  return {
391
389
  content: [
@@ -394,16 +392,19 @@ const handler = createMcpHandler((server) => {
394
392
  text: `Browser action failed: ${error instanceof Error ? error.message : String(error)}`
395
393
  }
396
394
  ]
397
- };
395
+ }
398
396
  }
399
397
  }
400
- );
401
- }, {
402
- // Server options
403
- }, {
404
- basePath: "/api/mcp",
405
- maxDuration: 60,
406
- verboseLogs: true
407
- });
408
-
409
- export { handler as GET, handler as POST };
398
+ )
399
+ },
400
+ {
401
+ // Server options
402
+ },
403
+ {
404
+ basePath: "/api/mcp",
405
+ maxDuration: 60,
406
+ verboseLogs: true
407
+ }
408
+ )
409
+
410
+ export { handler as GET, handler as POST }