dev3000 0.0.39 → 0.0.42
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/dist/cdp-monitor.d.ts +1 -1
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +350 -405
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +6 -4
- package/dist/dev-environment.js.map +1 -1
- package/dist/src/loading.html +206 -0
- package/mcp-server/app/logs/LogsClient.tsx +249 -12
- package/package.json +2 -2
|
@@ -65,6 +65,187 @@ function parseLogLine(line: string): LogEntry | null {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Component to render truncated URLs with click-to-expand
|
|
69
|
+
function URLRenderer({ url, maxLength = 60 }: { url: string, maxLength?: number }) {
|
|
70
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
71
|
+
|
|
72
|
+
if (url.length <= maxLength) {
|
|
73
|
+
return (
|
|
74
|
+
<a
|
|
75
|
+
href={url}
|
|
76
|
+
target="_blank"
|
|
77
|
+
rel="noopener noreferrer"
|
|
78
|
+
className="text-blue-600 hover:text-blue-800 underline"
|
|
79
|
+
>
|
|
80
|
+
{url}
|
|
81
|
+
</a>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const truncated = url.substring(0, maxLength) + '...';
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<span className="inline-block">
|
|
89
|
+
{isExpanded ? (
|
|
90
|
+
<span>
|
|
91
|
+
<a
|
|
92
|
+
href={url}
|
|
93
|
+
target="_blank"
|
|
94
|
+
rel="noopener noreferrer"
|
|
95
|
+
className="text-blue-600 hover:text-blue-800 underline"
|
|
96
|
+
>
|
|
97
|
+
{url}
|
|
98
|
+
</a>
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => setIsExpanded(false)}
|
|
101
|
+
className="ml-2 text-xs text-gray-500 hover:text-gray-700 px-1 py-0.5 rounded hover:bg-gray-100"
|
|
102
|
+
>
|
|
103
|
+
[collapse]
|
|
104
|
+
</button>
|
|
105
|
+
</span>
|
|
106
|
+
) : (
|
|
107
|
+
<span>
|
|
108
|
+
<a
|
|
109
|
+
href={url}
|
|
110
|
+
target="_blank"
|
|
111
|
+
rel="noopener noreferrer"
|
|
112
|
+
className="text-blue-600 hover:text-blue-800 underline"
|
|
113
|
+
>
|
|
114
|
+
{truncated}
|
|
115
|
+
</a>
|
|
116
|
+
<button
|
|
117
|
+
onClick={() => setIsExpanded(true)}
|
|
118
|
+
className="ml-1 text-xs text-gray-500 hover:text-gray-700 px-1 py-0.5 rounded hover:bg-gray-100"
|
|
119
|
+
>
|
|
120
|
+
[expand]
|
|
121
|
+
</button>
|
|
122
|
+
</span>
|
|
123
|
+
)}
|
|
124
|
+
</span>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Component to render Chrome DevTools-style collapsible objects
|
|
129
|
+
function ObjectRenderer({ content }: { content: string }) {
|
|
130
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const obj = JSON.parse(content);
|
|
134
|
+
|
|
135
|
+
// Check if it's a Chrome DevTools object representation
|
|
136
|
+
if (obj && typeof obj === 'object' && obj.type === 'object' && obj.properties) {
|
|
137
|
+
const properties = obj.properties;
|
|
138
|
+
const description = obj.description || 'Object';
|
|
139
|
+
const overflow = obj.overflow;
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className="inline-block">
|
|
143
|
+
<button
|
|
144
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
145
|
+
className="inline-flex items-center gap-1 text-blue-600 hover:text-blue-800 font-mono text-sm"
|
|
146
|
+
>
|
|
147
|
+
<svg
|
|
148
|
+
className={`w-3 h-3 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
|
149
|
+
fill="currentColor"
|
|
150
|
+
viewBox="0 0 20 20"
|
|
151
|
+
>
|
|
152
|
+
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 111.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
|
153
|
+
</svg>
|
|
154
|
+
<span className="text-purple-600">{description}</span>
|
|
155
|
+
{!isExpanded && (
|
|
156
|
+
<span className="text-gray-500">
|
|
157
|
+
{overflow ? '...' : ''} {'{'}
|
|
158
|
+
{properties.slice(0, 3).map((prop: any, idx: number) => (
|
|
159
|
+
<span key={idx}>
|
|
160
|
+
{idx > 0 && ', '}
|
|
161
|
+
<span className="text-red-600">{prop.name}</span>:
|
|
162
|
+
<span className="text-blue-600">
|
|
163
|
+
{prop.type === 'string' ? `"${prop.value}"` :
|
|
164
|
+
prop.type === 'number' ? prop.value :
|
|
165
|
+
prop.type === 'object' ? (prop.subtype === 'array' ? prop.value : '{...}') :
|
|
166
|
+
prop.value}
|
|
167
|
+
</span>
|
|
168
|
+
</span>
|
|
169
|
+
))}
|
|
170
|
+
{properties.length > 3 && ', ...'}
|
|
171
|
+
{'}'}
|
|
172
|
+
</span>
|
|
173
|
+
)}
|
|
174
|
+
</button>
|
|
175
|
+
|
|
176
|
+
{isExpanded && (
|
|
177
|
+
<div className="mt-1 ml-4 border-l-2 border-gray-200 pl-3">
|
|
178
|
+
<div className="font-mono text-sm">
|
|
179
|
+
<div className="text-gray-600">{description} {'{'}
|
|
180
|
+
<div className="ml-4">
|
|
181
|
+
{properties.map((prop: any, idx: number) => (
|
|
182
|
+
<div key={idx} className="py-0.5">
|
|
183
|
+
<span className="text-red-600">{prop.name}</span>
|
|
184
|
+
<span className="text-gray-500">: </span>
|
|
185
|
+
<span className={
|
|
186
|
+
prop.type === 'string' ? 'text-green-600' :
|
|
187
|
+
prop.type === 'number' ? 'text-blue-600' :
|
|
188
|
+
prop.type === 'object' ? 'text-purple-600' :
|
|
189
|
+
'text-orange-600'
|
|
190
|
+
}>
|
|
191
|
+
{prop.type === 'string' ? `"${prop.value}"` :
|
|
192
|
+
prop.type === 'number' ? prop.value :
|
|
193
|
+
prop.type === 'object' ? (prop.subtype === 'array' ? prop.value : '{...}') :
|
|
194
|
+
prop.value}
|
|
195
|
+
</span>
|
|
196
|
+
{idx < properties.length - 1 && <span className="text-gray-500">,</span>}
|
|
197
|
+
</div>
|
|
198
|
+
))}
|
|
199
|
+
{overflow && (
|
|
200
|
+
<div className="text-gray-500 italic">... and more properties</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
<div className="text-gray-600">{'}'}</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// For regular JSON objects, render them nicely too
|
|
213
|
+
return (
|
|
214
|
+
<div className="inline-block">
|
|
215
|
+
<button
|
|
216
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
217
|
+
className="inline-flex items-center gap-1 text-blue-600 hover:text-blue-800 font-mono text-sm"
|
|
218
|
+
>
|
|
219
|
+
<svg
|
|
220
|
+
className={`w-3 h-3 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
|
221
|
+
fill="currentColor"
|
|
222
|
+
viewBox="0 0 20 20"
|
|
223
|
+
>
|
|
224
|
+
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 111.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
|
225
|
+
</svg>
|
|
226
|
+
<span className="text-purple-600">Object</span>
|
|
227
|
+
{!isExpanded && (
|
|
228
|
+
<span className="text-gray-500">
|
|
229
|
+
{'{'}...{'}'}
|
|
230
|
+
</span>
|
|
231
|
+
)}
|
|
232
|
+
</button>
|
|
233
|
+
|
|
234
|
+
{isExpanded && (
|
|
235
|
+
<div className="mt-1 ml-4 border-l-2 border-gray-200 pl-3">
|
|
236
|
+
<pre className="font-mono text-sm text-gray-700 whitespace-pre-wrap">
|
|
237
|
+
{JSON.stringify(obj, null, 2)}
|
|
238
|
+
</pre>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
// If it's not valid JSON, just return the original content
|
|
245
|
+
return <span>{content}</span>;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
68
249
|
function LogEntryComponent({ entry }: { entry: LogEntry }) {
|
|
69
250
|
// Parse log type from message patterns
|
|
70
251
|
const parseLogType = (message: string) => {
|
|
@@ -81,13 +262,17 @@ function LogEntryComponent({ entry }: { entry: LogEntry }) {
|
|
|
81
262
|
|
|
82
263
|
const logTypeInfo = parseLogType(entry.message);
|
|
83
264
|
|
|
84
|
-
// Extract and highlight type tags
|
|
265
|
+
// Extract and highlight type tags, detect JSON objects and URLs
|
|
85
266
|
const renderMessage = (message: string) => {
|
|
86
267
|
const typeTagRegex = /\[([A-Z\s]+)\]/g;
|
|
268
|
+
const jsonRegex = /\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g;
|
|
269
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
270
|
+
|
|
87
271
|
const parts = [];
|
|
88
272
|
let lastIndex = 0;
|
|
89
273
|
let match;
|
|
90
274
|
|
|
275
|
+
// First, handle type tags
|
|
91
276
|
while ((match = typeTagRegex.exec(message)) !== null) {
|
|
92
277
|
// Add text before the tag
|
|
93
278
|
if (match.index > lastIndex) {
|
|
@@ -108,28 +293,80 @@ function LogEntryComponent({ entry }: { entry: LogEntry }) {
|
|
|
108
293
|
}
|
|
109
294
|
|
|
110
295
|
// Add remaining text
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
296
|
+
let remainingText = message.slice(lastIndex);
|
|
297
|
+
|
|
298
|
+
// Process remaining text for JSON objects and URLs
|
|
299
|
+
const processTextForObjects = (text: string, keyPrefix: string) => {
|
|
300
|
+
const jsonMatches = [...text.matchAll(jsonRegex)];
|
|
301
|
+
const urlMatches = [...text.matchAll(urlRegex)];
|
|
302
|
+
const allMatches = [...jsonMatches.map(m => ({ ...m, type: 'json' })), ...urlMatches.map(m => ({ ...m, type: 'url' }))];
|
|
303
|
+
|
|
304
|
+
// Sort matches by index
|
|
305
|
+
allMatches.sort((a, b) => a.index! - b.index!);
|
|
306
|
+
|
|
307
|
+
if (allMatches.length === 0) {
|
|
308
|
+
return [text];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const finalParts = [];
|
|
312
|
+
let textLastIndex = 0;
|
|
313
|
+
|
|
314
|
+
allMatches.forEach((objMatch, idx) => {
|
|
315
|
+
// Add text before match
|
|
316
|
+
if (objMatch.index! > textLastIndex) {
|
|
317
|
+
finalParts.push(text.slice(textLastIndex, objMatch.index));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Add appropriate renderer
|
|
321
|
+
if (objMatch.type === 'json') {
|
|
322
|
+
finalParts.push(
|
|
323
|
+
<ObjectRenderer key={`${keyPrefix}-json-${idx}`} content={objMatch[0]} />
|
|
324
|
+
);
|
|
325
|
+
} else if (objMatch.type === 'url') {
|
|
326
|
+
finalParts.push(
|
|
327
|
+
<URLRenderer key={`${keyPrefix}-url-${idx}`} url={objMatch[0]} />
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
textLastIndex = objMatch.index! + objMatch[0].length;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Add any text after the last match
|
|
335
|
+
if (textLastIndex < text.length) {
|
|
336
|
+
finalParts.push(text.slice(textLastIndex));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return finalParts;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const processedRemaining = processTextForObjects(remainingText, 'main');
|
|
343
|
+
parts.push(...processedRemaining);
|
|
114
344
|
|
|
115
345
|
return parts.length > 0 ? parts : message;
|
|
116
346
|
};
|
|
117
347
|
|
|
118
348
|
return (
|
|
119
349
|
<div className={`border-l-4 ${logTypeInfo.color} pl-4 py-2`}>
|
|
120
|
-
|
|
121
|
-
|
|
350
|
+
{/* Table-like layout using CSS Grid */}
|
|
351
|
+
<div className="grid grid-cols-[auto_auto_1fr] gap-3 items-start">
|
|
352
|
+
{/* Column 1: Timestamp */}
|
|
353
|
+
<div className="text-xs text-gray-500 font-mono whitespace-nowrap">
|
|
122
354
|
{new Date(entry.timestamp).toLocaleTimeString()}
|
|
123
|
-
</
|
|
124
|
-
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
{/* Column 2: Source */}
|
|
358
|
+
<div className={`px-2 py-1 rounded text-xs font-medium whitespace-nowrap ${
|
|
125
359
|
entry.source === 'SERVER' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800'
|
|
126
360
|
}`}>
|
|
127
361
|
{entry.source}
|
|
128
|
-
</
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
{/* Column 3: Message content */}
|
|
365
|
+
<div className="font-mono text-sm min-w-0">
|
|
366
|
+
{renderMessage(entry.message)}
|
|
367
|
+
</div>
|
|
132
368
|
</div>
|
|
369
|
+
|
|
133
370
|
{entry.screenshot && (
|
|
134
371
|
<div className="mt-2">
|
|
135
372
|
<img
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev3000",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42",
|
|
4
4
|
"description": "AI-powered development tools with browser monitoring and MCP server integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"author": "Lindsey Simon <lindsey@vercel.com>",
|
|
57
57
|
"license": "MIT",
|
|
58
58
|
"scripts": {
|
|
59
|
-
"build": "tsc",
|
|
59
|
+
"build": "tsc && mkdir -p dist/src && cp src/loading.html dist/src/",
|
|
60
60
|
"dev": "tsc --watch",
|
|
61
61
|
"test": "vitest run",
|
|
62
62
|
"postinstall": "cd mcp-server && pnpm install --frozen-lockfile",
|