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.
@@ -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
- if (lastIndex < message.length) {
112
- parts.push(message.slice(lastIndex));
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
- <div className="flex items-center gap-2 text-xs text-gray-500">
121
- <span className="font-mono">
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
- </span>
124
- <span className={`px-2 py-1 rounded text-xs font-medium ${
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
- </span>
129
- </div>
130
- <div className="mt-1 font-mono text-sm whitespace-pre-wrap break-words overflow-wrap-anywhere">
131
- {renderMessage(entry.message)}
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.39",
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",