dev3000 0.0.44 → 0.0.46
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/LICENSE +21 -0
- package/README.md +13 -1
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +52 -31
- package/dist/cdp-monitor.js.map +1 -1
- package/mcp-server/app/logs/LogsClient.test.ts +1 -1
- package/mcp-server/app/logs/LogsClient.tsx +614 -374
- package/mcp-server/app/logs/page.tsx +124 -2
- package/mcp-server/app/logs/utils.ts +46 -0
- package/package.json +3 -1
|
@@ -1,7 +1,129 @@
|
|
|
1
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';
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
interface PageProps {
|
|
8
|
+
searchParams: { file?: string; mode?: 'head' | 'tail' };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getLogFiles() {
|
|
12
|
+
try {
|
|
13
|
+
const currentLogPath = process.env.LOG_FILE_PATH || '/tmp/dev3000.log';
|
|
14
|
+
|
|
15
|
+
if (!existsSync(currentLogPath)) {
|
|
16
|
+
return { files: [], currentFile: '', projectName: 'unknown' };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const logDir = dirname(currentLogPath);
|
|
20
|
+
const currentLogName = basename(currentLogPath);
|
|
21
|
+
|
|
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);
|
|
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
|
+
|
|
39
|
+
return {
|
|
40
|
+
name: file,
|
|
41
|
+
path: filePath,
|
|
42
|
+
timestamp,
|
|
43
|
+
size: stats.size,
|
|
44
|
+
mtime: stats.mtime,
|
|
45
|
+
isCurrent: file === currentLogName
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
files: logFiles,
|
|
52
|
+
currentFile: currentLogPath,
|
|
53
|
+
projectName
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return { files: [], currentFile: '', projectName: 'unknown' };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getLogData(logPath: string, mode: 'head' | 'tail' = 'tail', lines: number = 100) {
|
|
61
|
+
try {
|
|
62
|
+
if (!existsSync(logPath)) {
|
|
63
|
+
return { logs: '', total: 0 };
|
|
64
|
+
}
|
|
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
|
+
|
|
73
|
+
return {
|
|
74
|
+
logs: selectedLines.join('\n'),
|
|
75
|
+
total: allLines.length
|
|
76
|
+
};
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return { logs: '', total: 0 };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default async function LogsPage({ searchParams }: PageProps) {
|
|
4
83
|
const version = process.env.DEV3000_VERSION || '0.0.0';
|
|
5
84
|
|
|
6
|
-
|
|
85
|
+
// Get available log files
|
|
86
|
+
const { files, currentFile } = await getLogFiles();
|
|
87
|
+
|
|
88
|
+
// If no file specified and we have files, redirect to latest
|
|
89
|
+
if (!searchParams.file && files.length > 0) {
|
|
90
|
+
const latestFile = files[0].name;
|
|
91
|
+
redirect(`/logs?file=${encodeURIComponent(latestFile)}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// If no file specified and no files available, render with empty data
|
|
95
|
+
if (!searchParams.file) {
|
|
96
|
+
return (
|
|
97
|
+
<LogsClient
|
|
98
|
+
version={version}
|
|
99
|
+
initialData={{
|
|
100
|
+
logs: [],
|
|
101
|
+
logFiles: [],
|
|
102
|
+
currentLogFile: '',
|
|
103
|
+
mode: 'tail'
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Find the selected log file
|
|
110
|
+
const selectedFile = files.find(f => f.name === searchParams.file);
|
|
111
|
+
const logPath = selectedFile?.path || currentFile;
|
|
112
|
+
const mode = (searchParams.mode as 'head' | 'tail') || 'tail';
|
|
113
|
+
|
|
114
|
+
// Get initial log data server-side
|
|
115
|
+
const logData = await getLogData(logPath, mode);
|
|
116
|
+
const parsedLogs = parseLogEntries(logData.logs);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<LogsClient
|
|
120
|
+
version={version}
|
|
121
|
+
initialData={{
|
|
122
|
+
logs: parsedLogs,
|
|
123
|
+
logFiles: files,
|
|
124
|
+
currentLogFile: logPath,
|
|
125
|
+
mode
|
|
126
|
+
}}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
7
129
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LogEntry } from '@/types';
|
|
2
|
+
|
|
3
|
+
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
|
+
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
if (!line.trim()) continue;
|
|
13
|
+
|
|
14
|
+
const match = line.match(timestampPattern);
|
|
15
|
+
if (match) {
|
|
16
|
+
// Save previous entry if exists
|
|
17
|
+
if (currentEntry) {
|
|
18
|
+
entries.push(currentEntry);
|
|
19
|
+
}
|
|
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
|
+
|
|
26
|
+
currentEntry = {
|
|
27
|
+
timestamp,
|
|
28
|
+
source,
|
|
29
|
+
message,
|
|
30
|
+
screenshot,
|
|
31
|
+
original: line
|
|
32
|
+
};
|
|
33
|
+
} else if (currentEntry) {
|
|
34
|
+
// Append to current entry's message
|
|
35
|
+
currentEntry.message += '\n' + line;
|
|
36
|
+
currentEntry.original += '\n' + line;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Don't forget the last entry
|
|
41
|
+
if (currentEntry) {
|
|
42
|
+
entries.push(currentEntry);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev3000",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"description": "AI-powered development tools with browser monitoring and MCP server integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"claude"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"biome": "^0.3.3",
|
|
33
34
|
"chalk": "^5.3.0",
|
|
34
35
|
"cli-progress": "^3.12.0",
|
|
35
36
|
"commander": "^11.1.0",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"scripts": {
|
|
60
61
|
"build": "tsc && mkdir -p dist/src && cp src/loading.html dist/src/",
|
|
61
62
|
"dev": "tsc --watch",
|
|
63
|
+
"format": "biome format --write .",
|
|
62
64
|
"test": "vitest run",
|
|
63
65
|
"postinstall": "cd mcp-server && pnpm install --frozen-lockfile",
|
|
64
66
|
"release": "./scripts/release.sh",
|