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,41 +1,38 @@
1
- import LogsClient from './LogsClient';
2
- import { redirect } from 'next/navigation';
3
- import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
4
- import { join, dirname, basename } from 'path';
5
- import { parseLogEntries } from './utils';
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs"
2
+ import { redirect } from "next/navigation"
3
+ import { basename, dirname, join } from "path"
4
+ import LogsClient from "./LogsClient"
5
+ import { parseLogEntries } from "./utils"
6
6
 
7
7
  interface PageProps {
8
- searchParams: Promise<{ file?: string; mode?: 'head' | 'tail' }>;
8
+ searchParams: Promise<{ file?: string; mode?: "head" | "tail" }>
9
9
  }
10
10
 
11
11
  async function getLogFiles() {
12
12
  try {
13
- const currentLogPath = process.env.LOG_FILE_PATH || '/tmp/dev3000.log';
14
-
13
+ const currentLogPath = process.env.LOG_FILE_PATH || "/tmp/dev3000.log"
14
+
15
15
  if (!existsSync(currentLogPath)) {
16
- return { files: [], currentFile: '', projectName: 'unknown' };
16
+ return { files: [], currentFile: "", projectName: "unknown" }
17
17
  }
18
18
 
19
- const logDir = dirname(currentLogPath);
20
- const currentLogName = basename(currentLogPath);
21
-
19
+ const logDir = dirname(currentLogPath)
20
+ const currentLogName = basename(currentLogPath)
21
+
22
22
  // Extract project name from current log filename
23
- const projectMatch = currentLogName.match(/^dev3000-(.+?)-\d{4}-\d{2}-\d{2}T/);
24
- const projectName = projectMatch ? projectMatch[1] : 'unknown';
25
-
26
- const dirContents = readdirSync(logDir);
23
+ const projectMatch = currentLogName.match(/^dev3000-(.+?)-\d{4}-\d{2}-\d{2}T/)
24
+ const projectName = projectMatch ? projectMatch[1] : "unknown"
25
+
26
+ const dirContents = readdirSync(logDir)
27
27
  const logFiles = dirContents
28
- .filter(file =>
29
- file.startsWith(`dev3000-${projectName}-`) &&
30
- file.endsWith('.log')
31
- )
32
- .map(file => {
33
- const filePath = join(logDir, file);
34
- const stats = statSync(filePath);
35
-
36
- const timestampMatch = file.match(/(\d{4}-\d{2}-\d{2}T[\d-]+Z)/);
37
- const timestamp = timestampMatch ? timestampMatch[1].replace(/-/g, ':') : '';
38
-
28
+ .filter((file) => file.startsWith(`dev3000-${projectName}-`) && file.endsWith(".log"))
29
+ .map((file) => {
30
+ const filePath = join(logDir, file)
31
+ const stats = statSync(filePath)
32
+
33
+ const timestampMatch = file.match(/(\d{4}-\d{2}-\d{2}T[\d-]+Z)/)
34
+ const timestamp = timestampMatch ? timestampMatch[1].replace(/-/g, ":") : ""
35
+
39
36
  return {
40
37
  name: file,
41
38
  path: filePath,
@@ -43,83 +40,85 @@ async function getLogFiles() {
43
40
  size: stats.size,
44
41
  mtime: stats.mtime,
45
42
  isCurrent: file === currentLogName
46
- };
43
+ }
47
44
  })
48
- .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
49
-
45
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
46
+
50
47
  return {
51
48
  files: logFiles,
52
49
  currentFile: currentLogPath,
53
50
  projectName
54
- };
55
- } catch (error) {
56
- return { files: [], currentFile: '', projectName: 'unknown' };
51
+ }
52
+ } catch (_error) {
53
+ return { files: [], currentFile: "", projectName: "unknown" }
57
54
  }
58
55
  }
59
56
 
60
- async function getLogData(logPath: string, mode: 'head' | 'tail' = 'tail', lines: number = 100) {
57
+ async function getLogData(logPath: string, mode: "head" | "tail" = "tail", lines: number = 100) {
61
58
  try {
62
59
  if (!existsSync(logPath)) {
63
- return { logs: '', total: 0 };
60
+ return { logs: "", total: 0 }
64
61
  }
65
-
66
- const logContent = readFileSync(logPath, 'utf-8');
67
- const allLines = logContent.split('\n').filter(line => line.trim());
68
-
69
- const selectedLines = mode === 'head'
70
- ? allLines.slice(0, lines)
71
- : allLines.slice(-lines);
72
-
62
+
63
+ const logContent = readFileSync(logPath, "utf-8")
64
+ const allLines = logContent.split("\n").filter((line) => line.trim())
65
+
66
+ const selectedLines = mode === "head" ? allLines.slice(0, lines) : allLines.slice(-lines)
67
+
73
68
  return {
74
- logs: selectedLines.join('\n'),
69
+ logs: selectedLines.join("\n"),
75
70
  total: allLines.length
76
- };
77
- } catch (error) {
78
- return { logs: '', total: 0 };
71
+ }
72
+ } catch (_error) {
73
+ return { logs: "", total: 0 }
79
74
  }
80
75
  }
81
76
 
82
77
  export default async function LogsPage({ searchParams }: PageProps) {
83
- const version = process.env.DEV3000_VERSION || '0.0.0';
84
-
78
+ const version = process.env.DEV3000_VERSION || "0.0.0"
79
+
85
80
  // Await searchParams (Next.js 15 requirement)
86
- const params = await searchParams;
87
-
81
+ const params = await searchParams
82
+
88
83
  // Get available log files
89
- const { files, currentFile } = await getLogFiles();
90
-
91
- // If no file specified and we have files, redirect to latest
84
+ const { files, currentFile } = await getLogFiles()
85
+
86
+ // If no file specified and we have files, redirect to latest with tail mode
92
87
  if (!params.file && files.length > 0) {
93
- const latestFile = files[0].name;
94
- redirect(`/logs?file=${encodeURIComponent(latestFile)}`);
88
+ const latestFile = files[0].name
89
+ redirect(`/logs?file=${encodeURIComponent(latestFile)}&mode=tail`)
95
90
  }
96
-
91
+
97
92
  // If no file specified and no files available, render with empty data
98
93
  if (!params.file) {
99
94
  return (
100
- <LogsClient
95
+ <LogsClient
101
96
  version={version}
102
97
  initialData={{
103
98
  logs: [],
104
99
  logFiles: [],
105
- currentLogFile: '',
106
- mode: 'tail'
100
+ currentLogFile: "",
101
+ mode: "tail"
107
102
  }}
108
103
  />
109
- );
104
+ )
110
105
  }
111
-
106
+
112
107
  // Find the selected log file
113
- const selectedFile = files.find(f => f.name === params.file);
114
- const logPath = selectedFile?.path || currentFile;
115
- const mode = (params.mode as 'head' | 'tail') || 'tail';
116
-
108
+ const selectedFile = files.find((f) => f.name === params.file)
109
+ const logPath = selectedFile?.path || currentFile
110
+
111
+ // Always default to 'tail' mode for initial loads
112
+ const _isCurrentFile = selectedFile?.isCurrent !== false
113
+ const defaultMode = "tail" // Always start in tail mode
114
+ const mode = (params.mode as "head" | "tail") || defaultMode
115
+
117
116
  // Get initial log data server-side
118
- const logData = await getLogData(logPath, mode);
119
- const parsedLogs = parseLogEntries(logData.logs);
120
-
117
+ const logData = await getLogData(logPath, mode)
118
+ const parsedLogs = parseLogEntries(logData.logs)
119
+
121
120
  return (
122
- <LogsClient
121
+ <LogsClient
123
122
  version={version}
124
123
  initialData={{
125
124
  logs: parsedLogs,
@@ -128,5 +127,5 @@ export default async function LogsPage({ searchParams }: PageProps) {
128
127
  mode
129
128
  }}
130
129
  />
131
- );
132
- }
130
+ )
131
+ }
@@ -1,46 +1,117 @@
1
- import { LogEntry } from '@/types';
1
+ import type { LogEntry } from "@/types"
2
+
3
+ /**
4
+ * Cleans up console log messages that contain CSS formatting directives
5
+ * Example: "%c[Vercel Web Analytics]%c Debug mode... color: rgb(120, 120, 120) color: inherit"
6
+ * Becomes: "[Vercel Web Analytics] Debug mode..."
7
+ */
8
+ function cleanConsoleFormatting(message: string): string {
9
+ // Pattern to match console log entries with CSS formatting
10
+ const consoleLogPattern = /^\[CONSOLE LOG\] (.+)$/
11
+ const match = message.match(consoleLogPattern)
12
+
13
+ if (!match) {
14
+ return message
15
+ }
16
+
17
+ const consoleMessage = match[1]
18
+
19
+ // Check if this message has %c CSS formatting directives
20
+ if (!consoleMessage.includes("%c")) {
21
+ return message // No formatting to clean
22
+ }
23
+
24
+ // Remove CSS formatting directives step by step
25
+ let cleaned = consoleMessage
26
+
27
+ // Remove %c markers
28
+ cleaned = cleaned.replace(/%c/g, "")
29
+
30
+ // Remove trailing CSS color declarations - look for CSS patterns before JSON or at end of string
31
+ // Match CSS color declarations that appear after %c removal
32
+ cleaned = cleaned.replace(/\s+color:\s*[^{}\n]*?(?=\s*[{[]|$)/g, "")
33
+
34
+ // Clean up any extra whitespace
35
+ cleaned = cleaned.replace(/\s+/g, " ").trim()
36
+
37
+ return `[CONSOLE LOG] ${cleaned}`
38
+ }
2
39
 
3
40
  export function parseLogEntries(logContent: string): LogEntry[] {
4
- // Split by timestamp pattern - each timestamp starts a new log entry
5
- const timestampPattern = /\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\] \[([^\]]+)\] /;
6
-
7
- const entries: LogEntry[] = [];
8
- const lines = logContent.split('\n');
9
- let currentEntry: LogEntry | null = null;
10
-
41
+ // Enhanced pattern to handle both formats:
42
+ // Format 1 (CDP): [timestamp] [SOURCE] message
43
+ // Format 2 (Extension): [timestamp] [TAB-id] [SOURCE] [event] message
44
+ const timestampPattern = /\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\] \[([^\]]+)\] /
45
+
46
+ const entries: LogEntry[] = []
47
+ const lines = logContent.split("\n")
48
+ let currentEntry: LogEntry | null = null
49
+
11
50
  for (const line of lines) {
12
- if (!line.trim()) continue;
13
-
14
- const match = line.match(timestampPattern);
51
+ if (!line.trim()) continue
52
+
53
+ const match = line.match(timestampPattern)
15
54
  if (match) {
16
55
  // Save previous entry if exists
17
56
  if (currentEntry) {
18
- entries.push(currentEntry);
57
+ entries.push(currentEntry)
58
+ }
59
+
60
+ const [fullMatch, timestamp, firstBracket] = match
61
+ const remainingLine = line.substring(fullMatch.length)
62
+
63
+ // Check if this is a Chrome extension format with tab identifier
64
+ const isTabIdentifier = /^TAB-\d+\.\d+$/.test(firstBracket)
65
+ let source = firstBracket
66
+ let message = remainingLine
67
+ let tabIdentifier: string | undefined
68
+ let userAgent: string | undefined
69
+
70
+ if (isTabIdentifier) {
71
+ // Chrome extension format: [TAB-id] [SOURCE] [event] message
72
+ tabIdentifier = firstBracket
73
+
74
+ // Look for the next bracketed section which should be the actual source
75
+ const sourceMatch = remainingLine.match(/^\[([^\]]+)\] /)
76
+ if (sourceMatch) {
77
+ source = sourceMatch[1] // This should be "BROWSER"
78
+ message = remainingLine.substring(sourceMatch[0].length)
79
+
80
+ // Extract user agent from INFO entries if present
81
+ if (message.includes("User-Agent:")) {
82
+ const uaMatch = message.match(/User-Agent: ([^,\n]+)/)
83
+ if (uaMatch) {
84
+ userAgent = uaMatch[1]
85
+ }
86
+ }
87
+ }
19
88
  }
20
-
21
- // Start new entry
22
- const [fullMatch, timestamp, source] = match;
23
- const message = line.substring(fullMatch.length);
24
- const screenshot = message.match(/\[SCREENSHOT\] (.+)/)?.[1];
25
-
89
+
90
+ const screenshot = message.match(/\[SCREENSHOT\] (.+)/)?.[1]
91
+
92
+ // Clean up CSS formatting directives in console log messages
93
+ const cleanedMessage = cleanConsoleFormatting(message)
94
+
26
95
  currentEntry = {
27
96
  timestamp,
28
97
  source,
29
- message,
98
+ message: cleanedMessage,
30
99
  screenshot,
31
- original: line
32
- };
100
+ original: line,
101
+ tabIdentifier,
102
+ userAgent
103
+ }
33
104
  } else if (currentEntry) {
34
105
  // Append to current entry's message
35
- currentEntry.message += '\n' + line;
36
- currentEntry.original += '\n' + line;
106
+ currentEntry.message += `\n${line}`
107
+ currentEntry.original += `\n${line}`
37
108
  }
38
109
  }
39
-
110
+
40
111
  // Don't forget the last entry
41
112
  if (currentEntry) {
42
- entries.push(currentEntry);
113
+ entries.push(currentEntry)
43
114
  }
44
-
45
- return entries;
46
- }
115
+
116
+ return entries
117
+ }
@@ -1,9 +1,7 @@
1
- import React from 'react';
2
-
3
1
  export default function HomePage() {
4
2
  return (
5
3
  <div className="min-h-screen bg-gray-50 flex items-center justify-center">
6
- <a
4
+ <a
7
5
  href="https://github.com/vercel-labs/dev3000"
8
6
  target="_blank"
9
7
  rel="noopener noreferrer"
@@ -11,35 +9,33 @@ export default function HomePage() {
11
9
  title="View on GitHub"
12
10
  >
13
11
  <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
14
- <path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/>
12
+ <path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z" />
15
13
  </svg>
16
14
  </a>
17
15
  <div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
18
- <h1 className="text-3xl font-bold text-gray-900 mb-6 text-center">
19
- 🎯 dev3000
20
- </h1>
21
-
16
+ <h1 className="text-3xl font-bold text-gray-900 mb-6 text-center">🎯 dev3000</h1>
17
+
22
18
  <div className="space-y-4">
23
- <a
19
+ <a
24
20
  href="/logs"
25
21
  className="block w-full bg-blue-500 text-white text-center py-3 px-4 rounded hover:bg-blue-600 transition-colors"
26
22
  >
27
23
  📊 View Development Logs
28
24
  </a>
29
-
30
- <a
25
+
26
+ <a
31
27
  href="/api/mcp/mcp"
32
28
  className="block w-full bg-green-500 text-white text-center py-3 px-4 rounded hover:bg-green-600 transition-colors"
33
29
  >
34
30
  🤖 MCP Endpoint
35
31
  </a>
36
32
  </div>
37
-
33
+
38
34
  <div className="mt-6 text-sm text-gray-600 text-center">
39
35
  <p>Real-time development monitoring with visual context</p>
40
36
  <p className="mt-2">Server logs + Browser events + Screenshots</p>
41
37
  </div>
42
38
  </div>
43
39
  </div>
44
- );
45
- }
40
+ )
41
+ }