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.
- package/README.md +55 -6
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +54 -48
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +39 -33
- package/dist/cli.js.map +1 -1
- package/dist/dev-environment.d.ts +2 -0
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +212 -181
- package/dist/dev-environment.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/mcp-server/app/api/config/route.ts +7 -7
- package/mcp-server/app/api/logs/append/route.ts +59 -51
- package/mcp-server/app/api/logs/head/route.ts +22 -22
- package/mcp-server/app/api/logs/list/route.ts +39 -42
- package/mcp-server/app/api/logs/rotate/route.ts +28 -38
- package/mcp-server/app/api/logs/stream/route.ts +35 -35
- package/mcp-server/app/api/logs/tail/route.ts +22 -22
- package/mcp-server/app/api/mcp/[transport]/route.ts +189 -188
- package/mcp-server/app/api/replay/route.ts +217 -202
- package/mcp-server/app/layout.tsx +9 -8
- package/mcp-server/app/logs/LogsClient.test.ts +123 -99
- package/mcp-server/app/logs/LogsClient.tsx +724 -562
- package/mcp-server/app/logs/page.tsx +71 -72
- package/mcp-server/app/logs/utils.ts +99 -28
- package/mcp-server/app/page.tsx +10 -14
- package/mcp-server/app/replay/ReplayClient.tsx +120 -119
- package/mcp-server/app/replay/page.tsx +3 -3
- package/mcp-server/next.config.ts +2 -0
- package/mcp-server/package.json +5 -2
- package/mcp-server/pnpm-lock.yaml +37 -5
- package/mcp-server/tsconfig.json +4 -17
- package/package.json +16 -13
|
@@ -1,41 +1,38 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { redirect } from
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { parseLogEntries } from
|
|
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?:
|
|
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 ||
|
|
14
|
-
|
|
13
|
+
const currentLogPath = process.env.LOG_FILE_PATH || "/tmp/dev3000.log"
|
|
14
|
+
|
|
15
15
|
if (!existsSync(currentLogPath)) {
|
|
16
|
-
return { files: [], currentFile:
|
|
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] :
|
|
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
|
-
|
|
30
|
-
file
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
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 (
|
|
56
|
-
return { files: [], currentFile:
|
|
51
|
+
}
|
|
52
|
+
} catch (_error) {
|
|
53
|
+
return { files: [], currentFile: "", projectName: "unknown" }
|
|
57
54
|
}
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
async function getLogData(logPath: string, mode:
|
|
57
|
+
async function getLogData(logPath: string, mode: "head" | "tail" = "tail", lines: number = 100) {
|
|
61
58
|
try {
|
|
62
59
|
if (!existsSync(logPath)) {
|
|
63
|
-
return { logs:
|
|
60
|
+
return { logs: "", total: 0 }
|
|
64
61
|
}
|
|
65
|
-
|
|
66
|
-
const logContent = readFileSync(logPath,
|
|
67
|
-
const allLines = logContent.split(
|
|
68
|
-
|
|
69
|
-
const selectedLines = mode ===
|
|
70
|
-
|
|
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(
|
|
69
|
+
logs: selectedLines.join("\n"),
|
|
75
70
|
total: allLines.length
|
|
76
|
-
}
|
|
77
|
-
} catch (
|
|
78
|
-
return { logs:
|
|
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 ||
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
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 +=
|
|
36
|
-
currentEntry.original +=
|
|
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
|
+
}
|
package/mcp-server/app/page.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|