claude-dev-server 1.1.3 → 1.2.0
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 +80 -35
- package/dist/assets/dev.html +739 -0
- package/dist/assets/inspect-bridge.js +47 -0
- package/dist/{chunk-F6IKDNXJ.js → chunk-PAE5WTS2.js} +605 -414
- package/dist/chunk-PAE5WTS2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-F6IKDNXJ.js.map +0 -1
|
@@ -196,87 +196,77 @@ async function handleInspect(msg, ws, projectRoot) {
|
|
|
196
196
|
// src/client/injection.js
|
|
197
197
|
var CLIENT_STYLES = `
|
|
198
198
|
<style id="claude-dev-server-styles">
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
bottom: 20px;
|
|
202
|
-
right: 20px;
|
|
203
|
-
width: 44px;
|
|
204
|
-
height: 44px;
|
|
205
|
-
background: #1e1e1e;
|
|
206
|
-
border: none;
|
|
207
|
-
border-radius: 50%;
|
|
208
|
-
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
|
|
209
|
-
cursor: pointer;
|
|
210
|
-
z-index: 999999;
|
|
211
|
-
display: flex;
|
|
212
|
-
align-items: center;
|
|
213
|
-
justify-content: center;
|
|
214
|
-
font-size: 20px;
|
|
215
|
-
transition: transform 0.2s, background 0.2s;
|
|
199
|
+
* {
|
|
200
|
+
margin: 0;
|
|
216
201
|
padding: 0;
|
|
202
|
+
box-sizing: border-box;
|
|
217
203
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
display: none;
|
|
204
|
+
html, body {
|
|
205
|
+
width: 100%;
|
|
206
|
+
height: 100%;
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
background: transparent;
|
|
224
209
|
}
|
|
225
|
-
.claude-dev-server-
|
|
226
|
-
position: fixed;
|
|
227
|
-
bottom: 74px;
|
|
228
|
-
right: 20px;
|
|
229
|
-
width: 44px;
|
|
230
|
-
height: 44px;
|
|
231
|
-
background: #1e1e1e;
|
|
232
|
-
border: none;
|
|
233
|
-
border-radius: 50%;
|
|
234
|
-
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
|
|
235
|
-
cursor: pointer;
|
|
236
|
-
z-index: 999999;
|
|
210
|
+
.claude-dev-server-container {
|
|
237
211
|
display: flex;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
.claude-dev-server-inspect-btn:hover {
|
|
244
|
-
transform: scale(1.1);
|
|
245
|
-
background: #2d2d2d;
|
|
212
|
+
width: 100vw;
|
|
213
|
+
height: 100vh;
|
|
214
|
+
overflow: hidden;
|
|
215
|
+
background: transparent;
|
|
246
216
|
}
|
|
247
|
-
.claude-dev-server-
|
|
248
|
-
|
|
217
|
+
.claude-dev-server-left {
|
|
218
|
+
width: 40%;
|
|
219
|
+
min-width: 300px;
|
|
220
|
+
max-width: 60%;
|
|
221
|
+
overflow: hidden;
|
|
222
|
+
position: relative;
|
|
223
|
+
background: #fff;
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-direction: column;
|
|
249
226
|
}
|
|
250
|
-
.claude-dev-server-
|
|
251
|
-
|
|
227
|
+
.claude-dev-server-left iframe {
|
|
228
|
+
width: 100%;
|
|
229
|
+
height: 100%;
|
|
230
|
+
border: none;
|
|
252
231
|
}
|
|
253
|
-
.claude-dev-server-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
232
|
+
.claude-dev-server-divider {
|
|
233
|
+
width: 6px;
|
|
234
|
+
background: #3e3e3e;
|
|
235
|
+
position: relative;
|
|
236
|
+
cursor: col-resize;
|
|
237
|
+
transition: background 0.2s;
|
|
257
238
|
flex-shrink: 0;
|
|
258
239
|
}
|
|
259
|
-
.claude-dev-server-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
font-size: 13px;
|
|
269
|
-
box-shadow: -4px 0 20px rgba(0,0,0,0.3);
|
|
270
|
-
transform: translateX(100%);
|
|
271
|
-
transition: transform 0.2s ease;
|
|
272
|
-
z-index: 999999;
|
|
240
|
+
.claude-dev-server-divider:hover,
|
|
241
|
+
.claude-dev-server-divider.dragging {
|
|
242
|
+
background: #d97757;
|
|
243
|
+
}
|
|
244
|
+
.claude-dev-server-right {
|
|
245
|
+
flex: 1;
|
|
246
|
+
min-width: 300px;
|
|
247
|
+
overflow: hidden;
|
|
248
|
+
position: relative;
|
|
273
249
|
display: flex;
|
|
274
250
|
flex-direction: column;
|
|
275
251
|
}
|
|
276
|
-
.claude-dev-server-
|
|
277
|
-
|
|
252
|
+
.claude-dev-server-terminal {
|
|
253
|
+
flex: 1;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
position: relative;
|
|
256
|
+
background: #000;
|
|
278
257
|
}
|
|
279
|
-
.claude-dev-server-
|
|
258
|
+
.claude-dev-server-terminal iframe,
|
|
259
|
+
.claude-dev-server-terminal-iframe {
|
|
260
|
+
width: 100%;
|
|
261
|
+
height: 100%;
|
|
262
|
+
border: none;
|
|
263
|
+
}
|
|
264
|
+
.claude-dev-server-dev-iframe {
|
|
265
|
+
width: 100%;
|
|
266
|
+
height: 100%;
|
|
267
|
+
border: none;
|
|
268
|
+
}
|
|
269
|
+
.claude-dev-server-terminal-header {
|
|
280
270
|
padding: 8px 12px;
|
|
281
271
|
background: #2d2d2d;
|
|
282
272
|
border-bottom: 1px solid #3e3e3e;
|
|
@@ -284,6 +274,7 @@ var CLIENT_STYLES = `
|
|
|
284
274
|
justify-content: space-between;
|
|
285
275
|
align-items: center;
|
|
286
276
|
user-select: none;
|
|
277
|
+
min-height: 40px;
|
|
287
278
|
}
|
|
288
279
|
.claude-dev-server-title {
|
|
289
280
|
font-weight: 600;
|
|
@@ -292,19 +283,21 @@ var CLIENT_STYLES = `
|
|
|
292
283
|
align-items: center;
|
|
293
284
|
gap: 8px;
|
|
294
285
|
font-size: 13px;
|
|
286
|
+
font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Consolas', monospace;
|
|
295
287
|
}
|
|
296
|
-
.claude-dev-server-title
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
.claude-dev-server-title svg {
|
|
289
|
+
width: 14px;
|
|
290
|
+
height: 14px;
|
|
291
|
+
flex-shrink: 0;
|
|
299
292
|
}
|
|
300
293
|
.claude-dev-server-actions {
|
|
301
294
|
display: flex;
|
|
302
295
|
gap: 6px;
|
|
303
296
|
}
|
|
304
297
|
.claude-dev-server-btn {
|
|
305
|
-
background: #
|
|
298
|
+
background: #d97757;
|
|
306
299
|
border: none;
|
|
307
|
-
color: #
|
|
300
|
+
color: #fff;
|
|
308
301
|
cursor: pointer;
|
|
309
302
|
font-family: inherit;
|
|
310
303
|
font-size: 11px;
|
|
@@ -316,42 +309,12 @@ var CLIENT_STYLES = `
|
|
|
316
309
|
gap: 4px;
|
|
317
310
|
}
|
|
318
311
|
.claude-dev-server-btn:hover {
|
|
319
|
-
background: #
|
|
312
|
+
background: #c96a4a;
|
|
320
313
|
}
|
|
321
314
|
.claude-dev-server-btn.active {
|
|
322
|
-
background: #
|
|
315
|
+
background: #b85d3f;
|
|
323
316
|
color: #fff;
|
|
324
317
|
}
|
|
325
|
-
.claude-dev-server-close {
|
|
326
|
-
background: none;
|
|
327
|
-
border: none;
|
|
328
|
-
color: #858585;
|
|
329
|
-
cursor: pointer;
|
|
330
|
-
font-size: 18px;
|
|
331
|
-
padding: 0;
|
|
332
|
-
width: 20px;
|
|
333
|
-
height: 20px;
|
|
334
|
-
display: flex;
|
|
335
|
-
align-items: center;
|
|
336
|
-
justify-content: center;
|
|
337
|
-
border-radius: 3px;
|
|
338
|
-
transition: background 0.15s, color 0.15s;
|
|
339
|
-
}
|
|
340
|
-
.claude-dev-server-close:hover {
|
|
341
|
-
background: #3e3e3e;
|
|
342
|
-
color: #fff;
|
|
343
|
-
}
|
|
344
|
-
.claude-dev-server-terminal {
|
|
345
|
-
flex: 1;
|
|
346
|
-
overflow: hidden;
|
|
347
|
-
position: relative;
|
|
348
|
-
background: #000;
|
|
349
|
-
}
|
|
350
|
-
.claude-dev-server-terminal iframe {
|
|
351
|
-
width: 100%;
|
|
352
|
-
height: 100%;
|
|
353
|
-
border: none;
|
|
354
|
-
}
|
|
355
318
|
.claude-dev-server-inspect-overlay {
|
|
356
319
|
position: fixed;
|
|
357
320
|
top: 0;
|
|
@@ -359,7 +322,7 @@ var CLIENT_STYLES = `
|
|
|
359
322
|
right: 0;
|
|
360
323
|
bottom: 0;
|
|
361
324
|
pointer-events: none;
|
|
362
|
-
z-index:
|
|
325
|
+
z-index: 2147483646;
|
|
363
326
|
display: none;
|
|
364
327
|
}
|
|
365
328
|
.claude-dev-server-inspect-overlay.active {
|
|
@@ -385,39 +348,22 @@ var CLIENT_STYLES = `
|
|
|
385
348
|
font-family: monospace;
|
|
386
349
|
white-space: nowrap;
|
|
387
350
|
}
|
|
388
|
-
/*
|
|
351
|
+
/* Portrait mode */
|
|
389
352
|
@media (orientation: portrait) {
|
|
390
|
-
.claude-dev-server-
|
|
391
|
-
|
|
392
|
-
bottom: 20px;
|
|
393
|
-
right: 10px;
|
|
394
|
-
width: 36px;
|
|
395
|
-
height: 36px;
|
|
353
|
+
.claude-dev-server-container {
|
|
354
|
+
flex-direction: column;
|
|
396
355
|
}
|
|
397
|
-
.claude-dev-server-
|
|
398
|
-
top: auto;
|
|
399
|
-
bottom: 66px;
|
|
400
|
-
right: 10px;
|
|
401
|
-
width: 36px;
|
|
402
|
-
height: 36px;
|
|
403
|
-
}
|
|
404
|
-
.claude-dev-server-panel {
|
|
405
|
-
top: auto;
|
|
406
|
-
bottom: 0;
|
|
407
|
-
right: 0;
|
|
408
|
-
left: 0;
|
|
356
|
+
.claude-dev-server-divider {
|
|
409
357
|
width: 100%;
|
|
410
|
-
height:
|
|
411
|
-
|
|
412
|
-
box-shadow: 0 -4px 20px rgba(0,0,0,0.3);
|
|
413
|
-
}
|
|
414
|
-
.claude-dev-server-panel.open {
|
|
415
|
-
transform: translateY(0);
|
|
358
|
+
height: 6px;
|
|
359
|
+
cursor: row-resize;
|
|
416
360
|
}
|
|
417
|
-
.claude-dev-server-
|
|
418
|
-
|
|
419
|
-
width:
|
|
420
|
-
|
|
361
|
+
.claude-dev-server-right {
|
|
362
|
+
width: 100%;
|
|
363
|
+
min-width: unset;
|
|
364
|
+
max-width: unset;
|
|
365
|
+
height: 50%;
|
|
366
|
+
min-height: 200px;
|
|
421
367
|
}
|
|
422
368
|
}
|
|
423
369
|
</style>
|
|
@@ -425,15 +371,16 @@ var CLIENT_STYLES = `
|
|
|
425
371
|
var CLIENT_SCRIPT = `
|
|
426
372
|
(() => {
|
|
427
373
|
let ws = null
|
|
428
|
-
let
|
|
429
|
-
let
|
|
430
|
-
let inspectBtn = null // \u60AC\u6D6E inspect \u6309\u94AE
|
|
431
|
-
let terminalContainer = null
|
|
432
|
-
let terminalIframe = null
|
|
374
|
+
let ttydIframe = null
|
|
375
|
+
let devIframe = null
|
|
433
376
|
let overlay = null
|
|
434
|
-
let isOpen = false
|
|
435
377
|
let isInspectMode = false
|
|
436
378
|
let ttydWsUrl = null
|
|
379
|
+
let leftPanel = null
|
|
380
|
+
let divider = null
|
|
381
|
+
let rightPanel = null
|
|
382
|
+
let isDragging = false
|
|
383
|
+
let inspectListenersRegistered = false
|
|
437
384
|
|
|
438
385
|
// Fetch the WebSocket port from the server
|
|
439
386
|
async function getWsPort() {
|
|
@@ -448,75 +395,250 @@ var CLIENT_SCRIPT = `
|
|
|
448
395
|
connect(port)
|
|
449
396
|
} catch (err) {
|
|
450
397
|
console.error('[Claude Dev Server] Failed to get port:', err)
|
|
451
|
-
// Retry after 1 second
|
|
452
398
|
setTimeout(initWhenReady, 1000)
|
|
453
399
|
return
|
|
454
400
|
}
|
|
455
|
-
|
|
456
|
-
createInspectBtn()
|
|
401
|
+
// Create overlay first before split layout
|
|
457
402
|
createOverlay()
|
|
458
|
-
|
|
403
|
+
createSplitLayout()
|
|
459
404
|
}
|
|
460
405
|
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
toggleBtn.addEventListener('click', () => togglePanel(true))
|
|
469
|
-
document.body.appendChild(toggleBtn)
|
|
470
|
-
}
|
|
406
|
+
function createSplitLayout() {
|
|
407
|
+
// IMPORTANT: Only create layout in the top-level window, not in iframes
|
|
408
|
+
// This prevents infinite nesting when the dev iframe loads
|
|
409
|
+
if (window.top !== window.self) {
|
|
410
|
+
console.log('[Claude Dev Server] Not in top window, skipping layout creation')
|
|
411
|
+
return
|
|
412
|
+
}
|
|
471
413
|
|
|
472
|
-
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
414
|
+
// Check if already created
|
|
415
|
+
if (document.querySelector('.claude-dev-server-container')) return
|
|
416
|
+
// Also check if we're already in an injected page
|
|
417
|
+
if (window.__CLAUDE_SPLIT_LAYOUT_CREATED__) return
|
|
418
|
+
window.__CLAUDE_SPLIT_LAYOUT_CREATED__ = true
|
|
419
|
+
|
|
420
|
+
// Get original HTML from window variable (set by server)
|
|
421
|
+
// The HTML is Base64 encoded to avoid any escaping issues
|
|
422
|
+
let originalHtml
|
|
423
|
+
if (window.__CLAUDE_ORIGINAL_HTML_BASE64__) {
|
|
424
|
+
// Decode Base64
|
|
425
|
+
try {
|
|
426
|
+
const decoded = atob(window.__CLAUDE_ORIGINAL_HTML_BASE64__)
|
|
427
|
+
// Handle UTF-8 encoding
|
|
428
|
+
const bytes = new Uint8Array(decoded.length)
|
|
429
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
430
|
+
bytes[i] = decoded.charCodeAt(i)
|
|
431
|
+
}
|
|
432
|
+
originalHtml = new TextDecoder().decode(bytes)
|
|
433
|
+
} catch (e) {
|
|
434
|
+
console.error('[Claude Dev Server] Failed to decode Base64 HTML:', e)
|
|
435
|
+
originalHtml = document.documentElement.outerHTML
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
originalHtml = document.documentElement.outerHTML
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Create container
|
|
442
|
+
const container = document.createElement('div')
|
|
443
|
+
container.className = 'claude-dev-server-container'
|
|
444
|
+
|
|
445
|
+
// Left panel - terminal (Claude)
|
|
446
|
+
leftPanel = document.createElement('div')
|
|
447
|
+
leftPanel.className = 'claude-dev-server-left'
|
|
448
|
+
|
|
449
|
+
// Terminal header
|
|
450
|
+
const header = document.createElement('div')
|
|
451
|
+
header.className = 'claude-dev-server-terminal-header'
|
|
452
|
+
header.innerHTML = \`
|
|
453
|
+
<span class="claude-dev-server-title">
|
|
454
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
455
|
+
<path d="M11.376 24L10.776 23.544L10.44 22.8L10.776 21.312L11.16 19.392L11.472 17.856L11.76 15.96L11.928 15.336L11.904 15.288L11.784 15.312L10.344 17.28L8.16 20.232L6.432 22.056L6.024 22.224L5.304 21.864L5.376 21.192L5.784 20.616L8.16 17.568L9.6 15.672L10.536 14.592L10.512 14.448H10.464L4.128 18.576L3 18.72L2.496 18.264L2.568 17.52L2.808 17.28L4.704 15.96L9.432 13.32L9.504 13.08L9.432 12.96H9.192L8.4 12.912L5.712 12.84L3.384 12.744L1.104 12.624L0.528 12.504L0 11.784L0.048 11.424L0.528 11.112L1.224 11.16L2.736 11.28L5.016 11.424L6.672 11.52L9.12 11.784H9.504L9.552 11.616L9.432 11.52L9.336 11.424L6.96 9.84L4.416 8.16L3.072 7.176L2.352 6.672L1.992 6.216L1.848 5.208L2.496 4.488L3.384 4.56L3.6 4.608L4.488 5.304L6.384 6.768L8.88 8.616L9.24 8.904L9.408 8.808V8.736L9.24 8.472L7.896 6.024L6.456 3.528L5.808 2.496L5.64 1.872C5.576 1.656 5.544 1.416 5.544 1.152L6.288 0.144001L6.696 0L7.704 0.144001L8.112 0.504001L8.736 1.92L9.72 4.152L11.28 7.176L11.736 8.088L11.976 8.904L12.072 9.168H12.24V9.024L12.36 7.296L12.6 5.208L12.84 2.52L12.912 1.752L13.296 0.840001L14.04 0.360001L14.616 0.624001L15.096 1.32L15.024 1.752L14.76 3.6L14.184 6.504L13.824 8.472H14.04L14.28 8.208L15.264 6.912L16.92 4.848L17.64 4.032L18.504 3.12L19.056 2.688H20.088L20.832 3.816L20.496 4.992L19.44 6.336L18.552 7.464L17.28 9.168L16.512 10.536L16.584 10.632H16.752L19.608 10.008L21.168 9.744L22.992 9.432L23.832 9.816L23.928 10.2L23.592 11.016L21.624 11.496L19.32 11.952L15.888 12.768L15.84 12.792L15.888 12.864L17.424 13.008L18.096 13.056H19.728L22.752 13.272L23.544 13.8L24 14.424L23.928 14.928L22.704 15.528L21.072 15.144L17.232 14.232L15.936 13.92H15.744V14.016L16.848 15.096L18.84 16.896L21.36 19.224L21.48 19.8L21.168 20.28L20.832 20.232L18.624 18.552L17.76 17.808L15.84 16.2H15.72V16.368L16.152 17.016L18.504 20.544L18.624 21.624L18.456 21.96L17.832 22.176L17.184 22.056L15.792 20.136L14.376 17.952L13.224 16.008L13.104 16.104L12.408 23.352L12.096 23.712L11.376 24Z" fill="#d97757"/>
|
|
456
|
+
</svg>
|
|
457
|
+
Claude Code
|
|
458
|
+
</span>
|
|
459
|
+
<div class="claude-dev-server-actions">
|
|
460
|
+
<button class="claude-dev-server-btn claude-dev-server-btn-inspect" title="Inspect Element">
|
|
461
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
462
|
+
<path d="M19 11V4a2 2 0 00-2-2H4a2 2 0 00-2 2v13a2 2 0 002 2h7"/>
|
|
463
|
+
<path d="M12 12l4.166 10 1.48-4.355L22 16.166 12 12z"/>
|
|
464
|
+
<path d="M18 18l3 3"/>
|
|
465
|
+
</svg>
|
|
466
|
+
Inspect
|
|
467
|
+
</button>
|
|
468
|
+
</div>
|
|
469
|
+
\`
|
|
470
|
+
|
|
471
|
+
const inspectBtn = header.querySelector('.claude-dev-server-btn-inspect')
|
|
479
472
|
inspectBtn.addEventListener('click', () => {
|
|
480
473
|
if (isInspectMode) {
|
|
481
474
|
disableInspectMode()
|
|
482
475
|
} else {
|
|
483
|
-
// \u70B9\u51FB Inspect \u65F6\u5148\u6536\u8D77\u9762\u677F
|
|
484
|
-
if (isOpen) {
|
|
485
|
-
togglePanel(false)
|
|
486
|
-
}
|
|
487
476
|
enableInspectMode()
|
|
488
477
|
}
|
|
489
478
|
})
|
|
490
|
-
document.body.appendChild(inspectBtn)
|
|
491
|
-
}
|
|
492
479
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
480
|
+
// Terminal container
|
|
481
|
+
const terminal = document.createElement('div')
|
|
482
|
+
terminal.className = 'claude-dev-server-terminal'
|
|
483
|
+
|
|
484
|
+
// Ttyd iframe
|
|
485
|
+
ttydIframe = document.createElement('iframe')
|
|
486
|
+
ttydIframe.className = 'claude-dev-server-terminal-iframe'
|
|
487
|
+
ttydIframe.allow = 'clipboard-read; clipboard-write'
|
|
488
|
+
|
|
489
|
+
terminal.appendChild(ttydIframe)
|
|
490
|
+
leftPanel.appendChild(header)
|
|
491
|
+
leftPanel.appendChild(terminal)
|
|
492
|
+
|
|
493
|
+
// Divider - draggable
|
|
494
|
+
divider = document.createElement('div')
|
|
495
|
+
divider.className = 'claude-dev-server-divider'
|
|
496
|
+
setupDraggable(divider)
|
|
497
|
+
|
|
498
|
+
// Right panel - dev server
|
|
499
|
+
rightPanel = document.createElement('div')
|
|
500
|
+
rightPanel.className = 'claude-dev-server-right'
|
|
501
|
+
|
|
502
|
+
// Create dev server iframe with srcdoc
|
|
503
|
+
devIframe = document.createElement('iframe')
|
|
504
|
+
devIframe.className = 'claude-dev-server-dev-iframe'
|
|
505
|
+
// The HTML already has inspect script injected by server
|
|
506
|
+
devIframe.srcdoc = originalHtml
|
|
507
|
+
|
|
508
|
+
rightPanel.appendChild(devIframe)
|
|
509
|
+
|
|
510
|
+
// Assemble layout: Claude (left) | divider | dev server (right)
|
|
511
|
+
container.appendChild(leftPanel)
|
|
512
|
+
container.appendChild(divider)
|
|
513
|
+
container.appendChild(rightPanel)
|
|
514
|
+
|
|
515
|
+
// Replace body content
|
|
516
|
+
document.body.innerHTML = ''
|
|
517
|
+
document.body.appendChild(container)
|
|
497
518
|
document.body.appendChild(overlay)
|
|
519
|
+
|
|
520
|
+
// Wait for iframe to load before setting up communication
|
|
521
|
+
devIframe.onload = () => {
|
|
522
|
+
console.log('[Claude Dev Server] Dev iframe loaded')
|
|
523
|
+
// Setup inspect mode communication
|
|
524
|
+
setupInspectCommunication()
|
|
525
|
+
}
|
|
498
526
|
}
|
|
499
527
|
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const rect = el.getBoundingClientRect()
|
|
504
|
-
const highlight = document.createElement('div')
|
|
505
|
-
highlight.className = 'claude-dev-server-highlight'
|
|
506
|
-
highlight.style.top = rect.top + 'px'
|
|
507
|
-
highlight.style.left = rect.left + 'px'
|
|
508
|
-
highlight.style.width = rect.width + 'px'
|
|
509
|
-
highlight.style.height = rect.height + 'px'
|
|
510
|
-
const className = el.className ? String(el.className) : ''
|
|
511
|
-
highlight.dataset.element = el.tagName.toLowerCase() +
|
|
512
|
-
(el.id ? '#' + el.id : '') +
|
|
513
|
-
(className ? '.' + className.split(' ').join('.') : '')
|
|
514
|
-
overlay.appendChild(highlight)
|
|
528
|
+
function setupInspectCommunication() {
|
|
529
|
+
// Inspect mode is now handled entirely in the outer layer
|
|
530
|
+
// We'll access the iframe's document directly
|
|
515
531
|
}
|
|
516
532
|
|
|
517
|
-
|
|
533
|
+
function setupIframeInspectListeners() {
|
|
534
|
+
if (!devIframe || !devIframe.contentDocument) return
|
|
535
|
+
|
|
536
|
+
// Prevent duplicate listener registration
|
|
537
|
+
if (inspectListenersRegistered) {
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const iframeDoc = devIframe.contentDocument
|
|
542
|
+
const iframeWindow = devIframe.contentWindow
|
|
543
|
+
|
|
544
|
+
const inspectHandler = (e) => {
|
|
545
|
+
if (!isInspectMode) return
|
|
546
|
+
|
|
547
|
+
e.preventDefault()
|
|
548
|
+
e.stopPropagation()
|
|
549
|
+
|
|
550
|
+
if (e.type === 'click') {
|
|
551
|
+
const el = e.target
|
|
552
|
+
const rect = el.getBoundingClientRect()
|
|
553
|
+
const className = el.className ? String(el.className) : ''
|
|
554
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
555
|
+
const classNames = className.split(' ').filter(c => c).slice(0, 3)
|
|
556
|
+
const selector = el.tagName.toLowerCase() +
|
|
557
|
+
(el.id ? '#' + el.id : '') +
|
|
558
|
+
(classNames.length ? '.' + classNames.join('.') : '')
|
|
559
|
+
|
|
560
|
+
// Highlight element
|
|
561
|
+
if (overlay) {
|
|
562
|
+
overlay.innerHTML = ''
|
|
563
|
+
|
|
564
|
+
// Get iframe position on page
|
|
565
|
+
const iframeRect = devIframe.getBoundingClientRect()
|
|
566
|
+
|
|
567
|
+
// Calculate highlight position relative to main document
|
|
568
|
+
// Element rect is relative to iframe viewport, need to add iframe position
|
|
569
|
+
const highlightTop = iframeRect.top + rect.top
|
|
570
|
+
const highlightLeft = iframeRect.left + rect.left
|
|
571
|
+
|
|
572
|
+
const highlight = document.createElement('div')
|
|
573
|
+
highlight.className = 'claude-dev-server-highlight'
|
|
574
|
+
highlight.style.top = highlightTop + 'px'
|
|
575
|
+
highlight.style.left = highlightLeft + 'px'
|
|
576
|
+
highlight.style.width = rect.width + 'px'
|
|
577
|
+
highlight.style.height = rect.height + 'px'
|
|
578
|
+
highlight.dataset.element = selector
|
|
579
|
+
overlay.appendChild(highlight)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Get source location and send to terminal
|
|
583
|
+
getSourceLocationFromElement(el).then(location => {
|
|
584
|
+
if (location) {
|
|
585
|
+
sendToTerminal(location)
|
|
586
|
+
}
|
|
587
|
+
disableInspectMode()
|
|
588
|
+
})
|
|
589
|
+
} else if (e.type === 'mousemove') {
|
|
590
|
+
const el = e.target
|
|
591
|
+
const rect = el.getBoundingClientRect()
|
|
592
|
+
const className = el.className ? String(el.className) : ''
|
|
593
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
594
|
+
const classNames = className.split(' ').filter(c => c).slice(0, 3)
|
|
595
|
+
const selector = el.tagName.toLowerCase() +
|
|
596
|
+
(el.id ? '#' + el.id : '') +
|
|
597
|
+
(classNames.length ? '.' + classNames.join('.') : '')
|
|
598
|
+
|
|
599
|
+
if (overlay) {
|
|
600
|
+
overlay.innerHTML = ''
|
|
601
|
+
|
|
602
|
+
// Get iframe position on page
|
|
603
|
+
const iframeRect = devIframe.getBoundingClientRect()
|
|
604
|
+
|
|
605
|
+
// Calculate highlight position relative to main document
|
|
606
|
+
const highlightTop = iframeRect.top + rect.top
|
|
607
|
+
const highlightLeft = iframeRect.left + rect.left
|
|
608
|
+
|
|
609
|
+
const highlight = document.createElement('div')
|
|
610
|
+
highlight.className = 'claude-dev-server-highlight'
|
|
611
|
+
highlight.style.top = highlightTop + 'px'
|
|
612
|
+
highlight.style.left = highlightLeft + 'px'
|
|
613
|
+
highlight.style.width = rect.width + 'px'
|
|
614
|
+
highlight.style.height = rect.height + 'px'
|
|
615
|
+
highlight.dataset.element = selector
|
|
616
|
+
overlay.appendChild(highlight)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Store handler reference for later removal
|
|
622
|
+
iframeDoc._claudeInspectHandler = inspectHandler
|
|
623
|
+
|
|
624
|
+
// Add event listeners in capture phase
|
|
625
|
+
iframeDoc.addEventListener('click', inspectHandler, true)
|
|
626
|
+
iframeDoc.addEventListener('mousemove', inspectHandler, true)
|
|
627
|
+
|
|
628
|
+
// Mark listeners as registered
|
|
629
|
+
inspectListenersRegistered = true
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async function handleInspectElement(elementInfo) {
|
|
633
|
+
// This is now handled directly in setupIframeInspectListeners
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function getSourceLocationFromElement(element) {
|
|
637
|
+
// Access the iframe's document to detect Next.js chunks
|
|
638
|
+
const iframeDoc = devIframe?.contentDocument || document
|
|
639
|
+
|
|
518
640
|
// Early check: detect Next.js by checking for _next/static chunks
|
|
519
|
-
const hasNextJsChunks = Array.from(
|
|
641
|
+
const hasNextJsChunks = Array.from(iframeDoc.querySelectorAll('script[src]'))
|
|
520
642
|
.some(script => script.getAttribute('src')?.includes('/_next/static/chunks/'))
|
|
521
643
|
|
|
522
644
|
if (hasNextJsChunks) {
|
|
@@ -540,16 +662,37 @@ var CLIENT_SCRIPT = `
|
|
|
540
662
|
const result = await response.json()
|
|
541
663
|
console.log('[Claude Dev Server] Lookup result:', result)
|
|
542
664
|
if (result.file) {
|
|
543
|
-
const elClassName =
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
665
|
+
const elClassName = element.className ? String(element.className) : ''
|
|
666
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
667
|
+
const classNames = elClassName.split(' ').filter(c => c).slice(0, 3)
|
|
668
|
+
const selector = element.tagName.toLowerCase() +
|
|
669
|
+
(element.id ? '#' + element.id : '') +
|
|
670
|
+
(classNames.length ? '.' + classNames.join('.') : '')
|
|
671
|
+
|
|
672
|
+
// Extract text content from element
|
|
673
|
+
let textContent = ''
|
|
674
|
+
if (element.nodeType === Node.TEXT_NODE) {
|
|
675
|
+
textContent = element.textContent ? element.textContent.trim().substring(0, 50) : ''
|
|
676
|
+
} else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
|
|
677
|
+
textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : ''
|
|
678
|
+
} else {
|
|
679
|
+
for (const child of element.childNodes) {
|
|
680
|
+
if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
|
|
681
|
+
textContent = child.textContent.trim().substring(0, 50)
|
|
682
|
+
break
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (textContent && textContent.length >= 50) {
|
|
687
|
+
textContent += '...'
|
|
688
|
+
}
|
|
547
689
|
|
|
548
690
|
return {
|
|
549
691
|
url: result.file,
|
|
550
692
|
line: result.line || undefined,
|
|
551
693
|
column: result.column || undefined,
|
|
552
694
|
selector: selector,
|
|
695
|
+
text: textContent,
|
|
553
696
|
hint: 'File: ' + result.file
|
|
554
697
|
}
|
|
555
698
|
}
|
|
@@ -682,12 +825,12 @@ var CLIENT_SCRIPT = `
|
|
|
682
825
|
}
|
|
683
826
|
|
|
684
827
|
// Try React DevTools - handle React 18's randomized suffix
|
|
685
|
-
const elKeys = Object.keys(
|
|
828
|
+
const elKeys = Object.keys(element)
|
|
686
829
|
let fiberKey = elKeys.find(k => k === '__reactFiber__' || k === '__reactInternalInstance' || k.indexOf('__reactFiber') === 0)
|
|
687
830
|
let propsKey = elKeys.find(k => k === '__reactProps__' || k.indexOf('__reactProps') === 0)
|
|
688
831
|
|
|
689
832
|
if (fiberKey) {
|
|
690
|
-
const fiber =
|
|
833
|
+
const fiber = element[fiberKey]
|
|
691
834
|
console.log('[Claude Dev Server] Found fiber at key:', fiberKey)
|
|
692
835
|
|
|
693
836
|
// Log fiber structure for debugging
|
|
@@ -835,7 +978,7 @@ var CLIENT_SCRIPT = `
|
|
|
835
978
|
|
|
836
979
|
// Try Vue component
|
|
837
980
|
if (!sourceFile) {
|
|
838
|
-
const vueComponent =
|
|
981
|
+
const vueComponent = element.__vueParentComponent || element.__vnode
|
|
839
982
|
if (vueComponent) {
|
|
840
983
|
const type = vueComponent.type || vueComponent.component
|
|
841
984
|
if (type && type.__file) {
|
|
@@ -848,7 +991,7 @@ var CLIENT_SCRIPT = `
|
|
|
848
991
|
// Try Vite's HMR source map
|
|
849
992
|
if (!sourceFile) {
|
|
850
993
|
// Look for data-vite-dev-id or similar attributes
|
|
851
|
-
const viteId =
|
|
994
|
+
const viteId = element.getAttribute('data-vite-dev-id')
|
|
852
995
|
if (viteId) {
|
|
853
996
|
// Extract file path from viteId (remove query params if any)
|
|
854
997
|
const queryIndex = viteId.indexOf('?')
|
|
@@ -859,22 +1002,22 @@ var CLIENT_SCRIPT = `
|
|
|
859
1002
|
|
|
860
1003
|
// Check for inline script
|
|
861
1004
|
if (!sourceFile) {
|
|
862
|
-
const inlineScripts =
|
|
1005
|
+
const inlineScripts = iframeDoc.querySelectorAll('script:not([src])')
|
|
863
1006
|
if (inlineScripts.length > 0) {
|
|
864
1007
|
const pathParts = window.location.pathname.split('/')
|
|
865
1008
|
const fileName = pathParts[pathParts.length - 1] || 'index.html'
|
|
866
1009
|
sourceFile = '/' + fileName
|
|
867
1010
|
|
|
868
|
-
const html =
|
|
869
|
-
const elId =
|
|
870
|
-
const elClass =
|
|
1011
|
+
const html = iframeDoc.documentElement.outerHTML
|
|
1012
|
+
const elId = element.id ? element.id : ''
|
|
1013
|
+
const elClass = element.className ? element.className : ''
|
|
871
1014
|
let elPattern
|
|
872
1015
|
if (elId) {
|
|
873
1016
|
elPattern = 'id="' + elId + '"'
|
|
874
1017
|
} else if (elClass) {
|
|
875
1018
|
elPattern = 'class="' + elClass.split(' ')[0] + '"'
|
|
876
1019
|
} else {
|
|
877
|
-
elPattern =
|
|
1020
|
+
elPattern = element.tagName.toLowerCase()
|
|
878
1021
|
}
|
|
879
1022
|
|
|
880
1023
|
const matchIndex = html.indexOf(elPattern)
|
|
@@ -887,7 +1030,7 @@ var CLIENT_SCRIPT = `
|
|
|
887
1030
|
|
|
888
1031
|
// Find main script
|
|
889
1032
|
if (!sourceFile) {
|
|
890
|
-
const scripts =
|
|
1033
|
+
const scripts = iframeDoc.querySelectorAll('script[src]')
|
|
891
1034
|
for (const script of scripts) {
|
|
892
1035
|
const src = script.getAttribute('src')
|
|
893
1036
|
if (src && !src.includes('cdn') && !src.includes('node_modules') &&
|
|
@@ -905,155 +1048,227 @@ var CLIENT_SCRIPT = `
|
|
|
905
1048
|
sourceFile = '/' + fileName
|
|
906
1049
|
}
|
|
907
1050
|
|
|
908
|
-
const elClassName =
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1051
|
+
const elClassName = element.className ? String(element.className) : ''
|
|
1052
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
1053
|
+
const classNames = elClassName.split(' ').filter(c => c).slice(0, 3)
|
|
1054
|
+
const selector = element.tagName.toLowerCase() +
|
|
1055
|
+
(element.id ? '#' + element.id : '') +
|
|
1056
|
+
(classNames.length ? '.' + classNames.join('.') : '')
|
|
1057
|
+
|
|
1058
|
+
// Get text content for better context
|
|
1059
|
+
let textContent = ''
|
|
1060
|
+
if (element.nodeType === Node.TEXT_NODE) {
|
|
1061
|
+
textContent = element.textContent ? element.textContent.trim().substring(0, 50) : ''
|
|
1062
|
+
} else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
|
|
1063
|
+
textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : ''
|
|
1064
|
+
} else {
|
|
1065
|
+
// Try to get first text node from children
|
|
1066
|
+
for (const child of element.childNodes) {
|
|
1067
|
+
if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
|
|
1068
|
+
textContent = child.textContent.trim().substring(0, 50)
|
|
1069
|
+
break
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (textContent && textContent.length >= 50) {
|
|
1074
|
+
textContent += '...'
|
|
1075
|
+
}
|
|
912
1076
|
|
|
913
1077
|
return {
|
|
914
1078
|
url: sourceFile,
|
|
915
1079
|
line: sourceLine,
|
|
916
1080
|
column: sourceColumn,
|
|
917
1081
|
selector: selector,
|
|
1082
|
+
text: textContent,
|
|
918
1083
|
hint: sourceFile ? 'File: ' + sourceFile : 'Element: ' + selector
|
|
919
1084
|
}
|
|
920
1085
|
}
|
|
921
1086
|
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
//
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1087
|
+
function setupDraggable(divider) {
|
|
1088
|
+
let startX = 0
|
|
1089
|
+
let startWidth = 0
|
|
1090
|
+
let containerWidth = 0
|
|
1091
|
+
|
|
1092
|
+
// Remove existing listeners to avoid duplicates
|
|
1093
|
+
divider.removeEventListener('mousedown', startDrag)
|
|
1094
|
+
divider.removeEventListener('touchstart', startDrag)
|
|
1095
|
+
|
|
1096
|
+
divider.addEventListener('mousedown', startDrag)
|
|
1097
|
+
divider.addEventListener('touchstart', startDrag)
|
|
1098
|
+
|
|
1099
|
+
function startDrag(e) {
|
|
1100
|
+
isDragging = true
|
|
1101
|
+
divider.classList.add('dragging')
|
|
1102
|
+
|
|
1103
|
+
// Disable pointer events on iframes to prevent interference
|
|
1104
|
+
const iframes = document.querySelectorAll('iframe')
|
|
1105
|
+
iframes.forEach(iframe => iframe.style.pointerEvents = 'none')
|
|
1106
|
+
|
|
1107
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX
|
|
1108
|
+
startX = clientX
|
|
1109
|
+
|
|
1110
|
+
const container = document.querySelector('.claude-dev-server-container')
|
|
1111
|
+
const leftPanelEl = document.querySelector('.claude-dev-server-left')
|
|
1112
|
+
|
|
1113
|
+
if (container && leftPanelEl) {
|
|
1114
|
+
containerWidth = container.offsetWidth
|
|
1115
|
+
startWidth = leftPanelEl.offsetWidth
|
|
940
1116
|
}
|
|
941
|
-
|
|
1117
|
+
|
|
1118
|
+
e.preventDefault()
|
|
942
1119
|
}
|
|
943
1120
|
|
|
944
|
-
|
|
945
|
-
if (!
|
|
946
|
-
|
|
947
|
-
//
|
|
948
|
-
if (
|
|
949
|
-
|
|
1121
|
+
function drag(e) {
|
|
1122
|
+
if (!isDragging) return
|
|
1123
|
+
|
|
1124
|
+
// Safety check: if mouse button is not pressed, end drag
|
|
1125
|
+
if (e.buttons === 0 && !e.touches) {
|
|
1126
|
+
endDrag()
|
|
1127
|
+
return
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX
|
|
1131
|
+
const deltaX = clientX - startX
|
|
1132
|
+
|
|
1133
|
+
// Calculate new width in pixels, then convert to percentage
|
|
1134
|
+
let newWidth = startWidth + deltaX
|
|
1135
|
+
let percentage = (newWidth / containerWidth) * 100
|
|
1136
|
+
|
|
1137
|
+
// Clamp between 20% and 80%
|
|
1138
|
+
const clamped = Math.max(20, Math.min(80, percentage))
|
|
1139
|
+
|
|
1140
|
+
const leftPanelEl = document.querySelector('.claude-dev-server-left')
|
|
1141
|
+
if (leftPanelEl) {
|
|
1142
|
+
leftPanelEl.style.flex = 'none'
|
|
1143
|
+
leftPanelEl.style.width = clamped + '%'
|
|
950
1144
|
}
|
|
951
1145
|
}
|
|
952
1146
|
|
|
953
|
-
|
|
954
|
-
if (
|
|
955
|
-
|
|
956
|
-
|
|
1147
|
+
function endDrag() {
|
|
1148
|
+
if (isDragging) {
|
|
1149
|
+
isDragging = false
|
|
1150
|
+
divider.classList.remove('dragging')
|
|
957
1151
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const location = await getSourceLocation(el)
|
|
962
|
-
if (location) {
|
|
963
|
-
await inspectElement(location, el)
|
|
964
|
-
}
|
|
965
|
-
disableInspectMode()
|
|
1152
|
+
// Re-enable pointer events on iframes
|
|
1153
|
+
const iframes = document.querySelectorAll('iframe')
|
|
1154
|
+
iframes.forEach(iframe => iframe.style.pointerEvents = '')
|
|
966
1155
|
}
|
|
967
1156
|
}
|
|
968
1157
|
|
|
969
|
-
|
|
970
|
-
|
|
1158
|
+
// Use capture phase to ensure events are caught
|
|
1159
|
+
// Remove old listeners first to prevent duplicates
|
|
1160
|
+
document.removeEventListener('mousemove', drag, true)
|
|
1161
|
+
document.removeEventListener('touchmove', drag, true)
|
|
1162
|
+
document.removeEventListener('mouseup', endDrag, true)
|
|
1163
|
+
document.removeEventListener('touchend', endDrag, true)
|
|
1164
|
+
|
|
1165
|
+
document.addEventListener('mousemove', drag, true)
|
|
1166
|
+
document.addEventListener('touchmove', drag, { passive: false, capture: true })
|
|
1167
|
+
document.addEventListener('mouseup', endDrag, true)
|
|
1168
|
+
document.addEventListener('touchend', endDrag, true)
|
|
1169
|
+
}
|
|
971
1170
|
|
|
972
|
-
|
|
1171
|
+
function createOverlay() {
|
|
1172
|
+
if (overlay) return
|
|
1173
|
+
overlay = document.createElement('div')
|
|
1174
|
+
overlay.className = 'claude-dev-server-inspect-overlay'
|
|
1175
|
+
document.body.appendChild(overlay)
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function enableInspectMode() {
|
|
1179
|
+
isInspectMode = true
|
|
1180
|
+
if (overlay) overlay.classList.add('active')
|
|
1181
|
+
// Setup inspect listeners on the iframe
|
|
1182
|
+
setupIframeInspectListeners()
|
|
1183
|
+
// Change cursor in iframe
|
|
1184
|
+
if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
|
|
1185
|
+
devIframe.contentDocument.body.style.cursor = 'crosshair'
|
|
1186
|
+
}
|
|
1187
|
+
const btn = document.querySelector('.claude-dev-server-btn-inspect')
|
|
1188
|
+
if (btn) btn.classList.add('active')
|
|
973
1189
|
}
|
|
974
1190
|
|
|
975
1191
|
function disableInspectMode() {
|
|
976
1192
|
isInspectMode = false
|
|
977
|
-
if (overlay)
|
|
978
|
-
|
|
979
|
-
|
|
1193
|
+
if (overlay) {
|
|
1194
|
+
overlay.classList.remove('active')
|
|
1195
|
+
overlay.innerHTML = ''
|
|
1196
|
+
}
|
|
1197
|
+
// Restore cursor in iframe
|
|
1198
|
+
if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
|
|
1199
|
+
devIframe.contentDocument.body.style.cursor = ''
|
|
1200
|
+
}
|
|
1201
|
+
const btn = document.querySelector('.claude-dev-server-btn-inspect')
|
|
1202
|
+
if (btn) btn.classList.remove('active')
|
|
980
1203
|
|
|
981
|
-
//
|
|
982
|
-
|
|
1204
|
+
// Remove inspect listeners to free up resources
|
|
1205
|
+
removeIframeInspectListeners()
|
|
1206
|
+
}
|
|
983
1207
|
|
|
984
|
-
|
|
985
|
-
if (
|
|
1208
|
+
function removeIframeInspectListeners() {
|
|
1209
|
+
if (!devIframe || !devIframe.contentDocument) return
|
|
986
1210
|
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1211
|
+
const iframeDoc = devIframe.contentDocument
|
|
1212
|
+
const existingHandler = iframeDoc._claudeInspectHandler
|
|
1213
|
+
|
|
1214
|
+
if (existingHandler) {
|
|
1215
|
+
iframeDoc.removeEventListener('click', existingHandler, true)
|
|
1216
|
+
iframeDoc.removeEventListener('mousemove', existingHandler, true)
|
|
1217
|
+
delete iframeDoc._claudeInspectHandler
|
|
1218
|
+
inspectListenersRegistered = false
|
|
991
1219
|
}
|
|
992
1220
|
}
|
|
993
1221
|
|
|
994
|
-
async function
|
|
995
|
-
//
|
|
1222
|
+
async function sendToTerminal(location) {
|
|
1223
|
+
// Use format: @filename <selector> "text content" (without line number)
|
|
996
1224
|
const filePath = location.url || location.file || 'unknown'
|
|
997
|
-
const tagName = el.tagName ? el.tagName.toLowerCase() : ''
|
|
998
|
-
const id = el.id ? '#' + el.id : ''
|
|
999
|
-
|
|
1000
|
-
// \u5BF9\u4E8E Tailwind CSS \u7B49\u5927\u91CF class \u7684\u60C5\u51B5\uFF0C\u53EA\u53D6\u524D 2-3 \u4E2A class
|
|
1001
|
-
let className = ''
|
|
1002
|
-
if (el.className) {
|
|
1003
|
-
const classes = String(el.className).split(' ').filter(c => c)
|
|
1004
|
-
// \u5982\u679C class \u592A\u591A\uFF08\u53EF\u80FD\u662F Tailwind\uFF09\uFF0C\u53EA\u53D6\u524D 2 \u4E2A
|
|
1005
|
-
if (classes.length > 5) {
|
|
1006
|
-
className = '.' + classes.slice(0, 2).join('.')
|
|
1007
|
-
} else {
|
|
1008
|
-
className = '.' + classes.join('.')
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
1225
|
|
|
1012
|
-
|
|
1226
|
+
// Build selector - handle Tailwind CSS classes by limiting
|
|
1227
|
+
const tagName = location.selector ? location.selector.split(/[.#]/)[0] : 'div'
|
|
1228
|
+
let selector = location.selector || tagName
|
|
1013
1229
|
|
|
1014
|
-
//
|
|
1230
|
+
// Get text content for better context
|
|
1015
1231
|
let textContent = ''
|
|
1016
|
-
if (
|
|
1017
|
-
textContent =
|
|
1018
|
-
} else if (
|
|
1019
|
-
|
|
1232
|
+
if (location.text) {
|
|
1233
|
+
textContent = location.text
|
|
1234
|
+
} else if (location.hint && location.hint.includes('File:')) {
|
|
1235
|
+
// No text content available
|
|
1020
1236
|
}
|
|
1021
1237
|
|
|
1022
|
-
//
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
prompt += \` "\${textContent}"\`
|
|
1040
|
-
}
|
|
1238
|
+
// Format: @filename <selector> "text content" (no line number)
|
|
1239
|
+
let prompt = \`@\${filePath} <\${selector}>\`
|
|
1240
|
+
if (textContent) {
|
|
1241
|
+
prompt += \` "\${textContent}"\`
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
console.log('[Claude Dev Server] Sending to terminal:', prompt)
|
|
1245
|
+
|
|
1246
|
+
// Store the interval ID so we can clear it if needed
|
|
1247
|
+
if (!window._claudeSendRetryInterval) {
|
|
1248
|
+
window._claudeSendRetryInterval = null
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// Clear any existing retry interval
|
|
1252
|
+
if (window._claudeSendRetryInterval) {
|
|
1253
|
+
clearInterval(window._claudeSendRetryInterval)
|
|
1254
|
+
window._claudeSendRetryInterval = null
|
|
1041
1255
|
}
|
|
1042
1256
|
|
|
1043
|
-
//
|
|
1044
|
-
const
|
|
1045
|
-
if (!
|
|
1257
|
+
// Send to terminal with retry logic
|
|
1258
|
+
const sendToTerminalInternal = () => {
|
|
1259
|
+
if (!ttydIframe || !ttydIframe.contentWindow) {
|
|
1046
1260
|
return false
|
|
1047
1261
|
}
|
|
1048
1262
|
|
|
1049
|
-
const win =
|
|
1050
|
-
|
|
1051
|
-
// Check if sendToTerminal function is available
|
|
1263
|
+
const win = ttydIframe.contentWindow
|
|
1052
1264
|
if (win.sendToTerminal) {
|
|
1053
1265
|
win.sendToTerminal(prompt)
|
|
1054
1266
|
console.log('[Claude Dev Server] Sent via sendToTerminal:', prompt)
|
|
1055
|
-
//
|
|
1056
|
-
|
|
1267
|
+
// Clear interval on success
|
|
1268
|
+
if (window._claudeSendRetryInterval) {
|
|
1269
|
+
clearInterval(window._claudeSendRetryInterval)
|
|
1270
|
+
window._claudeSendRetryInterval = null
|
|
1271
|
+
}
|
|
1057
1272
|
return true
|
|
1058
1273
|
}
|
|
1059
1274
|
|
|
@@ -1061,17 +1276,18 @@ var CLIENT_SCRIPT = `
|
|
|
1061
1276
|
}
|
|
1062
1277
|
|
|
1063
1278
|
// Try immediately
|
|
1064
|
-
if (
|
|
1279
|
+
if (sendToTerminalInternal()) {
|
|
1065
1280
|
return
|
|
1066
1281
|
}
|
|
1067
1282
|
|
|
1068
1283
|
// If not ready, wait and retry
|
|
1069
1284
|
let attempts = 0
|
|
1070
1285
|
const maxAttempts = 50
|
|
1071
|
-
|
|
1286
|
+
window._claudeSendRetryInterval = setInterval(() => {
|
|
1072
1287
|
attempts++
|
|
1073
|
-
if (
|
|
1074
|
-
clearInterval(
|
|
1288
|
+
if (sendToTerminalInternal() || attempts >= maxAttempts) {
|
|
1289
|
+
clearInterval(window._claudeSendRetryInterval)
|
|
1290
|
+
window._claudeSendRetryInterval = null
|
|
1075
1291
|
if (attempts >= maxAttempts) {
|
|
1076
1292
|
console.warn('[Claude Dev Server] Could not send to terminal after retries')
|
|
1077
1293
|
}
|
|
@@ -1080,86 +1296,12 @@ var CLIENT_SCRIPT = `
|
|
|
1080
1296
|
}
|
|
1081
1297
|
|
|
1082
1298
|
function loadTerminalIframe(ttydUrl) {
|
|
1083
|
-
if (!
|
|
1299
|
+
if (!ttydIframe) return
|
|
1084
1300
|
ttydWsUrl = ttydUrl
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
terminalIframe = document.createElement('iframe')
|
|
1088
|
-
// Pass the ttyd WebSocket URL as query param
|
|
1089
|
-
terminalIframe.src = '/ttyd/index.html?ws=' + encodeURIComponent(ttydUrl)
|
|
1090
|
-
terminalIframe.allow = 'clipboard-read; clipboard-write'
|
|
1091
|
-
|
|
1092
|
-
// Load event - iframe is ready
|
|
1093
|
-
terminalIframe.onload = () => {
|
|
1301
|
+
ttydIframe.src = '/ttyd/index.html?ws=' + encodeURIComponent(ttydUrl)
|
|
1302
|
+
ttydIframe.onload = () => {
|
|
1094
1303
|
console.log('[Claude Dev Server] Terminal iframe loaded')
|
|
1095
1304
|
}
|
|
1096
|
-
|
|
1097
|
-
terminalContainer.appendChild(terminalIframe)
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
function createPanel() {
|
|
1101
|
-
if (panel) return
|
|
1102
|
-
|
|
1103
|
-
panel = document.createElement('div')
|
|
1104
|
-
panel.className = 'claude-dev-server-panel'
|
|
1105
|
-
panel.innerHTML = \`
|
|
1106
|
-
<div class="claude-dev-server-header">
|
|
1107
|
-
<span class="claude-dev-server-title">Claude Code</span>
|
|
1108
|
-
<div class="claude-dev-server-actions">
|
|
1109
|
-
<button class="claude-dev-server-btn claude-dev-server-btn-inspect" title="Inspect Element">
|
|
1110
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1111
|
-
<path d="M2 12h20M12 2v20M4.93 4.93l14.14 14.14M19.07 4.93L4.93 19.07"/>
|
|
1112
|
-
</svg>
|
|
1113
|
-
Inspect
|
|
1114
|
-
</button>
|
|
1115
|
-
</div>
|
|
1116
|
-
<button class="claude-dev-server-close" aria-label="Close" title="Minimize to icon">×</button>
|
|
1117
|
-
</div>
|
|
1118
|
-
<div class="claude-dev-server-terminal"></div>
|
|
1119
|
-
\`
|
|
1120
|
-
|
|
1121
|
-
document.body.appendChild(panel)
|
|
1122
|
-
|
|
1123
|
-
terminalContainer = panel.querySelector('.claude-dev-server-terminal')
|
|
1124
|
-
const closeBtn = panel.querySelector('.claude-dev-server-close')
|
|
1125
|
-
const inspectBtn = panel.querySelector('.claude-dev-server-btn-inspect')
|
|
1126
|
-
|
|
1127
|
-
closeBtn && closeBtn.addEventListener('click', () => togglePanel(false))
|
|
1128
|
-
|
|
1129
|
-
inspectBtn && inspectBtn.addEventListener('click', () => {
|
|
1130
|
-
if (isInspectMode) {
|
|
1131
|
-
disableInspectMode()
|
|
1132
|
-
} else {
|
|
1133
|
-
// \u70B9\u51FB Inspect \u65F6\u5148\u6536\u8D77\u9762\u677F
|
|
1134
|
-
if (isOpen) {
|
|
1135
|
-
togglePanel(false)
|
|
1136
|
-
}
|
|
1137
|
-
enableInspectMode()
|
|
1138
|
-
}
|
|
1139
|
-
})
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
function togglePanel(force) {
|
|
1143
|
-
createPanel()
|
|
1144
|
-
if (typeof force === 'boolean') {
|
|
1145
|
-
isOpen = force
|
|
1146
|
-
} else {
|
|
1147
|
-
isOpen = !isOpen
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
if (panel) panel.classList.toggle('open', isOpen)
|
|
1151
|
-
if (toggleBtn) toggleBtn.classList.toggle('hidden', isOpen)
|
|
1152
|
-
|
|
1153
|
-
if (isOpen) {
|
|
1154
|
-
// Focus iframe when panel opens
|
|
1155
|
-
if (terminalIframe && terminalIframe.contentWindow) {
|
|
1156
|
-
setTimeout(() => {
|
|
1157
|
-
if (terminalIframe.contentWindow.terminalInstance) {
|
|
1158
|
-
terminalIframe.contentWindow.terminalInstance.focus()
|
|
1159
|
-
}
|
|
1160
|
-
}, 100)
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
1305
|
}
|
|
1164
1306
|
|
|
1165
1307
|
function connect(port) {
|
|
@@ -1176,10 +1318,6 @@ var CLIENT_SCRIPT = `
|
|
|
1176
1318
|
console.log('[Claude Dev Server] Received message:', msg.type, msg)
|
|
1177
1319
|
if (msg.type === 'ready' && msg.ttydUrl) {
|
|
1178
1320
|
loadTerminalIframe(msg.ttydUrl)
|
|
1179
|
-
} else if (msg.type === 'inspectResult') {
|
|
1180
|
-
if (msg.location) {
|
|
1181
|
-
showContextPanel(msg.location, null, false)
|
|
1182
|
-
}
|
|
1183
1321
|
}
|
|
1184
1322
|
} catch (err) {
|
|
1185
1323
|
console.error('[Claude Dev Server] Message parse error:', err)
|
|
@@ -1199,12 +1337,13 @@ var CLIENT_SCRIPT = `
|
|
|
1199
1337
|
initWhenReady()
|
|
1200
1338
|
|
|
1201
1339
|
document.addEventListener('keydown', (e) => {
|
|
1202
|
-
if ((e.metaKey || e.ctrlKey) && e.
|
|
1340
|
+
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
|
|
1203
1341
|
e.preventDefault()
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1342
|
+
if (isInspectMode) {
|
|
1343
|
+
disableInspectMode()
|
|
1344
|
+
} else {
|
|
1345
|
+
enableInspectMode()
|
|
1346
|
+
}
|
|
1208
1347
|
}
|
|
1209
1348
|
if (e.key === 'Escape' && isInspectMode) {
|
|
1210
1349
|
disableInspectMode()
|
|
@@ -1493,21 +1632,59 @@ async function handleTurbopackLookup(projectRoot, pagePath, targetPort) {
|
|
|
1493
1632
|
return { error: String(err) };
|
|
1494
1633
|
}
|
|
1495
1634
|
}
|
|
1635
|
+
function isHtmlPageRequest(req) {
|
|
1636
|
+
const accept = req.headers.accept || "";
|
|
1637
|
+
const url = req.url || "";
|
|
1638
|
+
if (!accept.includes("text/html")) {
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
if (url.startsWith("/@") || url.startsWith("/_next/") || url.startsWith("/ttyd") || url.startsWith("/dev.html")) {
|
|
1642
|
+
return false;
|
|
1643
|
+
}
|
|
1644
|
+
return true;
|
|
1645
|
+
}
|
|
1496
1646
|
function createProxyServer(targetPort, wsPort, projectRoot) {
|
|
1497
1647
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
1498
1648
|
const assetsPath = join(moduleDir, "assets");
|
|
1499
1649
|
let ttydHtml;
|
|
1500
1650
|
let ttydBridgeJs;
|
|
1651
|
+
let devHtml;
|
|
1501
1652
|
try {
|
|
1502
1653
|
ttydHtml = readFileSync(join(assetsPath, "ttyd-terminal.html"), "utf-8");
|
|
1503
1654
|
ttydBridgeJs = readFileSync(join(assetsPath, "ttyd-bridge.js"), "utf-8");
|
|
1655
|
+
devHtml = readFileSync(join(assetsPath, "dev.html"), "utf-8");
|
|
1504
1656
|
} catch (e) {
|
|
1505
|
-
console.error("[Claude Dev Server] Failed to read
|
|
1657
|
+
console.error("[Claude Dev Server] Failed to read assets from", assetsPath);
|
|
1506
1658
|
console.error("[Claude Dev Server] moduleDir:", moduleDir);
|
|
1507
1659
|
console.error("[Claude Dev Server] Error:", e.message);
|
|
1508
|
-
throw new Error("
|
|
1660
|
+
throw new Error("Assets not found. Please run `npm run build` first.");
|
|
1509
1661
|
}
|
|
1510
1662
|
const server = http.createServer((req, res) => {
|
|
1663
|
+
const referer = req.headers.referer || "";
|
|
1664
|
+
const isFromDevPage = referer.includes("dev.html");
|
|
1665
|
+
if (req.url?.startsWith("/dev.html")) {
|
|
1666
|
+
const urlParams = new URL(req.url || "", `http://${req.headers.host}`);
|
|
1667
|
+
const originalPath = urlParams.searchParams.get("path") || "/";
|
|
1668
|
+
const host = req.headers.host || "localhost:3000";
|
|
1669
|
+
const origin = `http://${host}`;
|
|
1670
|
+
const modifiedDevHtml = devHtml.replace(
|
|
1671
|
+
/__CLAUDE_IFRAME_SRC__/g,
|
|
1672
|
+
`${origin}${originalPath}`
|
|
1673
|
+
).replace(
|
|
1674
|
+
/__CLAUDE_ORIGINAL_PATH__/g,
|
|
1675
|
+
originalPath
|
|
1676
|
+
);
|
|
1677
|
+
res.setHeader("Content-Type", "text/html");
|
|
1678
|
+
res.end(modifiedDevHtml);
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (!isFromDevPage && isHtmlPageRequest(req)) {
|
|
1682
|
+
const currentPath = req.url || "/";
|
|
1683
|
+
const devPageUrl = `/dev.html?path=${encodeURIComponent(currentPath)}`;
|
|
1684
|
+
res.writeHead(302, { "Location": devPageUrl });
|
|
1685
|
+
res.end();
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1511
1688
|
if (req.url === "/@claude-port") {
|
|
1512
1689
|
res.setHeader("Content-Type", "application/json");
|
|
1513
1690
|
res.end(JSON.stringify({ port: wsPort }));
|
|
@@ -1569,6 +1746,7 @@ function createProxyServer(targetPort, wsPort, projectRoot) {
|
|
|
1569
1746
|
}
|
|
1570
1747
|
const proxyHeaders = { ...req.headers };
|
|
1571
1748
|
delete proxyHeaders["accept-encoding"];
|
|
1749
|
+
const shouldInject = !isFromDevPage;
|
|
1572
1750
|
const options = {
|
|
1573
1751
|
hostname: "localhost",
|
|
1574
1752
|
port: targetPort,
|
|
@@ -1577,7 +1755,7 @@ function createProxyServer(targetPort, wsPort, projectRoot) {
|
|
|
1577
1755
|
headers: proxyHeaders
|
|
1578
1756
|
};
|
|
1579
1757
|
const proxyReq = http.request(options, (proxyRes) => {
|
|
1580
|
-
if (proxyRes.headers["content-type"]?.includes("text/html")) {
|
|
1758
|
+
if (proxyRes.headers["content-type"]?.includes("text/html") && shouldInject) {
|
|
1581
1759
|
const body = [];
|
|
1582
1760
|
proxyRes.on("data", (chunk) => body.push(chunk));
|
|
1583
1761
|
proxyRes.on("end", () => {
|
|
@@ -1659,20 +1837,33 @@ function createProxyServer(targetPort, wsPort, projectRoot) {
|
|
|
1659
1837
|
return server;
|
|
1660
1838
|
}
|
|
1661
1839
|
function injectScripts(html, wsPort, projectRoot) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
);
|
|
1840
|
+
if (html.includes("claude-dev-server-container") || html.includes("__CLAUDE_ORIGINAL_HTML__")) {
|
|
1841
|
+
console.log("[Claude Dev Server] HTML already injected, returning as-is");
|
|
1842
|
+
return html;
|
|
1843
|
+
}
|
|
1844
|
+
const hasOurScripts = html.includes("CLIENT_SCRIPT") || html.includes("claude-dev-server-container");
|
|
1845
|
+
if (hasOurScripts) {
|
|
1846
|
+
console.log("[Claude Dev Server] Warning: Original HTML already has our scripts");
|
|
1847
|
+
}
|
|
1848
|
+
const modifiedHtml = html;
|
|
1849
|
+
console.log("[Claude Dev Server] Creating split layout, original HTML length:", html.length);
|
|
1850
|
+
const base64Html = Buffer.from(modifiedHtml, "utf-8").toString("base64");
|
|
1851
|
+
const projectRootScript = `<script>window.__CLAUDE_PROJECT_ROOT__ = ${JSON.stringify(projectRoot)};window.__CLAUDE_ORIGINAL_HTML_BASE64__ = "${base64Html}";</script>`;
|
|
1852
|
+
return `<!DOCTYPE html>
|
|
1853
|
+
<html lang="en">
|
|
1854
|
+
<head>
|
|
1855
|
+
<meta charset="UTF-8">
|
|
1856
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1857
|
+
<title>Claude Dev Server</title>
|
|
1858
|
+
${CLIENT_STYLES}
|
|
1859
|
+
${projectRootScript}
|
|
1860
|
+
</head>
|
|
1861
|
+
<body>
|
|
1862
|
+
<script type="module">${CLIENT_SCRIPT.replace(/wsPort:\s*\d+/, `wsPort: ${wsPort}`)}</script>
|
|
1863
|
+
</body>
|
|
1864
|
+
</html>`;
|
|
1674
1865
|
}
|
|
1675
1866
|
|
|
1676
1867
|
export { startUniversalServer };
|
|
1677
|
-
//# sourceMappingURL=chunk-
|
|
1678
|
-
//# sourceMappingURL=chunk-
|
|
1868
|
+
//# sourceMappingURL=chunk-PAE5WTS2.js.map
|
|
1869
|
+
//# sourceMappingURL=chunk-PAE5WTS2.js.map
|