claude-dev-server 1.1.4 → 1.2.1
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/assets/ttyd-terminal.html +1 -1
- package/dist/{chunk-RFPIOREI.js → chunk-PAE5WTS2.js} +596 -413
- 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-RFPIOREI.js.map +0 -1
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="claude-dev-server-wrapper" content="true">
|
|
7
|
+
<title>Claude Dev Server</title>
|
|
8
|
+
<style id="claude-dev-server-styles">
|
|
9
|
+
* {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
html, body {
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
background: transparent;
|
|
19
|
+
}
|
|
20
|
+
.claude-dev-server-container {
|
|
21
|
+
display: flex;
|
|
22
|
+
width: 100vw;
|
|
23
|
+
height: 100vh;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
background: transparent;
|
|
26
|
+
}
|
|
27
|
+
.claude-dev-server-left {
|
|
28
|
+
width: 40%;
|
|
29
|
+
min-width: 300px;
|
|
30
|
+
max-width: 60%;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
position: relative;
|
|
33
|
+
background: #fff;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
}
|
|
37
|
+
.claude-dev-server-left iframe {
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
border: none;
|
|
41
|
+
}
|
|
42
|
+
.claude-dev-server-divider {
|
|
43
|
+
width: 6px;
|
|
44
|
+
background: #3e3e3e;
|
|
45
|
+
position: relative;
|
|
46
|
+
cursor: col-resize;
|
|
47
|
+
transition: background 0.2s;
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
}
|
|
50
|
+
.claude-dev-server-divider:hover,
|
|
51
|
+
.claude-dev-server-divider.dragging {
|
|
52
|
+
background: #d97757;
|
|
53
|
+
}
|
|
54
|
+
.claude-dev-server-right {
|
|
55
|
+
flex: 1;
|
|
56
|
+
min-width: 300px;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
position: relative;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
}
|
|
62
|
+
.claude-dev-server-terminal {
|
|
63
|
+
flex: 1;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
position: relative;
|
|
66
|
+
background: #000;
|
|
67
|
+
}
|
|
68
|
+
.claude-dev-server-terminal iframe,
|
|
69
|
+
.claude-dev-server-terminal-iframe {
|
|
70
|
+
width: 100%;
|
|
71
|
+
height: 100%;
|
|
72
|
+
border: none;
|
|
73
|
+
}
|
|
74
|
+
.claude-dev-server-dev-iframe {
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: 100%;
|
|
77
|
+
border: none;
|
|
78
|
+
}
|
|
79
|
+
.claude-dev-server-terminal-header {
|
|
80
|
+
padding: 8px 12px;
|
|
81
|
+
background: #2d2d2d;
|
|
82
|
+
border-bottom: 1px solid #3e3e3e;
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
align-items: center;
|
|
86
|
+
user-select: none;
|
|
87
|
+
min-height: 40px;
|
|
88
|
+
}
|
|
89
|
+
.claude-dev-server-title {
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
color: #fff;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 8px;
|
|
95
|
+
font-size: 13px;
|
|
96
|
+
font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Consolas', monospace;
|
|
97
|
+
}
|
|
98
|
+
.claude-dev-server-title svg {
|
|
99
|
+
width: 14px;
|
|
100
|
+
height: 14px;
|
|
101
|
+
flex-shrink: 0;
|
|
102
|
+
}
|
|
103
|
+
.claude-dev-server-actions {
|
|
104
|
+
display: flex;
|
|
105
|
+
gap: 6px;
|
|
106
|
+
}
|
|
107
|
+
.claude-dev-server-btn {
|
|
108
|
+
background: #d97757;
|
|
109
|
+
border: none;
|
|
110
|
+
color: #fff;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
font-family: inherit;
|
|
113
|
+
font-size: 11px;
|
|
114
|
+
padding: 4px 10px;
|
|
115
|
+
border-radius: 3px;
|
|
116
|
+
transition: background 0.15s;
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 4px;
|
|
120
|
+
}
|
|
121
|
+
.claude-dev-server-btn:hover {
|
|
122
|
+
background: #c96a4a;
|
|
123
|
+
}
|
|
124
|
+
.claude-dev-server-btn.active {
|
|
125
|
+
background: #b85d3f;
|
|
126
|
+
color: #fff;
|
|
127
|
+
}
|
|
128
|
+
.claude-dev-server-inspect-overlay {
|
|
129
|
+
position: fixed;
|
|
130
|
+
top: 0;
|
|
131
|
+
left: 0;
|
|
132
|
+
right: 0;
|
|
133
|
+
bottom: 0;
|
|
134
|
+
pointer-events: none;
|
|
135
|
+
z-index: 2147483646;
|
|
136
|
+
display: none;
|
|
137
|
+
}
|
|
138
|
+
.claude-dev-server-inspect-overlay.active {
|
|
139
|
+
display: block;
|
|
140
|
+
}
|
|
141
|
+
.claude-dev-server-highlight {
|
|
142
|
+
position: absolute;
|
|
143
|
+
border: 2px solid #007acc;
|
|
144
|
+
background: rgba(0, 122, 204, 0.1);
|
|
145
|
+
pointer-events: none;
|
|
146
|
+
transition: all 0.1s ease;
|
|
147
|
+
}
|
|
148
|
+
.claude-dev-server-highlight::after {
|
|
149
|
+
content: attr(data-element);
|
|
150
|
+
position: absolute;
|
|
151
|
+
top: -20px;
|
|
152
|
+
left: 0;
|
|
153
|
+
background: #007acc;
|
|
154
|
+
color: #fff;
|
|
155
|
+
font-size: 10px;
|
|
156
|
+
padding: 2px 4px;
|
|
157
|
+
border-radius: 2px 2px 0 0;
|
|
158
|
+
font-family: monospace;
|
|
159
|
+
white-space: nowrap;
|
|
160
|
+
}
|
|
161
|
+
/* Portrait mode */
|
|
162
|
+
@media (orientation: portrait) {
|
|
163
|
+
.claude-dev-server-container {
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
}
|
|
166
|
+
.claude-dev-server-divider {
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 6px;
|
|
169
|
+
cursor: row-resize;
|
|
170
|
+
}
|
|
171
|
+
.claude-dev-server-right {
|
|
172
|
+
width: 100%;
|
|
173
|
+
min-width: unset;
|
|
174
|
+
max-width: unset;
|
|
175
|
+
height: 50%;
|
|
176
|
+
min-height: 200px;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<script>
|
|
183
|
+
// Server will replace these placeholders
|
|
184
|
+
const originalPath = '__CLAUDE_ORIGINAL_PATH__';
|
|
185
|
+
const iframeSrc = '__CLAUDE_IFRAME_SRC__';
|
|
186
|
+
|
|
187
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
188
|
+
const wsPort = urlParams.get('wsPort');
|
|
189
|
+
|
|
190
|
+
console.log('[Claude Dev Server Wrapper] Original path:', originalPath);
|
|
191
|
+
console.log('[Claude Dev Server Wrapper] Dev iframe src:', iframeSrc);
|
|
192
|
+
|
|
193
|
+
// Global state
|
|
194
|
+
let ws = null;
|
|
195
|
+
let ttydIframe = null;
|
|
196
|
+
let devIframe = null;
|
|
197
|
+
let overlay = null;
|
|
198
|
+
let isInspectMode = false;
|
|
199
|
+
let ttydWsUrl = null;
|
|
200
|
+
let leftPanel = null;
|
|
201
|
+
let divider = null;
|
|
202
|
+
let rightPanel = null;
|
|
203
|
+
let isDragging = false;
|
|
204
|
+
let inspectListenersRegistered = false;
|
|
205
|
+
|
|
206
|
+
// Create the split layout
|
|
207
|
+
function createSplitLayout() {
|
|
208
|
+
// Create container
|
|
209
|
+
const container = document.createElement('div');
|
|
210
|
+
container.className = 'claude-dev-server-container';
|
|
211
|
+
|
|
212
|
+
// Left panel - terminal (Claude)
|
|
213
|
+
leftPanel = document.createElement('div');
|
|
214
|
+
leftPanel.className = 'claude-dev-server-left';
|
|
215
|
+
|
|
216
|
+
// Terminal header
|
|
217
|
+
const header = document.createElement('div');
|
|
218
|
+
header.className = 'claude-dev-server-terminal-header';
|
|
219
|
+
header.innerHTML = `
|
|
220
|
+
<span class="claude-dev-server-title">
|
|
221
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
222
|
+
<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"/>
|
|
223
|
+
</svg>
|
|
224
|
+
Claude Code
|
|
225
|
+
</span>
|
|
226
|
+
<div class="claude-dev-server-actions">
|
|
227
|
+
<button class="claude-dev-server-btn claude-dev-server-btn-inspect" title="Inspect Element">
|
|
228
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
229
|
+
<path d="M19 11V4a2 2 0 00-2-2H4a2 2 0 00-2 2v13a2 2 0 002 2h7"/>
|
|
230
|
+
<path d="M12 12l4.166 10 1.48-4.355L22 16.166 12 12z"/>
|
|
231
|
+
<path d="M18 18l3 3"/>
|
|
232
|
+
</svg>
|
|
233
|
+
Inspect
|
|
234
|
+
</button>
|
|
235
|
+
</div>
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
const inspectBtn = header.querySelector('.claude-dev-server-btn-inspect');
|
|
239
|
+
inspectBtn.addEventListener('click', () => {
|
|
240
|
+
if (isInspectMode) {
|
|
241
|
+
disableInspectMode();
|
|
242
|
+
} else {
|
|
243
|
+
enableInspectMode();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Terminal container
|
|
248
|
+
const terminal = document.createElement('div');
|
|
249
|
+
terminal.className = 'claude-dev-server-terminal';
|
|
250
|
+
|
|
251
|
+
// Ttyd iframe
|
|
252
|
+
ttydIframe = document.createElement('iframe');
|
|
253
|
+
ttydIframe.className = 'claude-dev-server-terminal-iframe';
|
|
254
|
+
ttydIframe.allow = 'clipboard-read; clipboard-write';
|
|
255
|
+
|
|
256
|
+
terminal.appendChild(ttydIframe);
|
|
257
|
+
leftPanel.appendChild(header);
|
|
258
|
+
leftPanel.appendChild(terminal);
|
|
259
|
+
|
|
260
|
+
// Divider - draggable
|
|
261
|
+
divider = document.createElement('div');
|
|
262
|
+
divider.className = 'claude-dev-server-divider';
|
|
263
|
+
setupDraggable(divider);
|
|
264
|
+
|
|
265
|
+
// Right panel - dev server
|
|
266
|
+
rightPanel = document.createElement('div');
|
|
267
|
+
rightPanel.className = 'claude-dev-server-right';
|
|
268
|
+
|
|
269
|
+
// Create dev server iframe with REAL src (not srcdoc!)
|
|
270
|
+
devIframe = document.createElement('iframe');
|
|
271
|
+
devIframe.className = 'claude-dev-server-dev-iframe';
|
|
272
|
+
devIframe.src = iframeSrc;
|
|
273
|
+
|
|
274
|
+
rightPanel.appendChild(devIframe);
|
|
275
|
+
|
|
276
|
+
// Assemble layout: Claude (left) | divider | dev server (right)
|
|
277
|
+
container.appendChild(leftPanel);
|
|
278
|
+
container.appendChild(divider);
|
|
279
|
+
container.appendChild(rightPanel);
|
|
280
|
+
|
|
281
|
+
// Create overlay
|
|
282
|
+
overlay = document.createElement('div');
|
|
283
|
+
overlay.className = 'claude-dev-server-inspect-overlay';
|
|
284
|
+
|
|
285
|
+
// Replace body content
|
|
286
|
+
document.body.innerHTML = '';
|
|
287
|
+
document.body.appendChild(container);
|
|
288
|
+
document.body.appendChild(overlay);
|
|
289
|
+
|
|
290
|
+
// Wait for iframe to load before setting up communication
|
|
291
|
+
devIframe.onload = () => {
|
|
292
|
+
console.log('[Claude Dev Server Wrapper] Dev iframe loaded, setting up inspect communication');
|
|
293
|
+
setupInspectCommunication();
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function setupInspectCommunication() {
|
|
298
|
+
console.log('[Claude Dev Server Wrapper] Inspect communication ready');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function setupIframeInspectListeners() {
|
|
302
|
+
if (!devIframe || !devIframe.contentDocument) return;
|
|
303
|
+
|
|
304
|
+
// Prevent duplicate listener registration
|
|
305
|
+
if (inspectListenersRegistered) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const iframeDoc = devIframe.contentDocument;
|
|
310
|
+
|
|
311
|
+
const inspectHandler = (e) => {
|
|
312
|
+
if (!isInspectMode) return;
|
|
313
|
+
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
e.stopPropagation();
|
|
316
|
+
|
|
317
|
+
if (e.type === 'click') {
|
|
318
|
+
const el = e.target;
|
|
319
|
+
const rect = el.getBoundingClientRect();
|
|
320
|
+
const className = el.className ? String(el.className) : '';
|
|
321
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
322
|
+
const classNames = className.split(' ').filter(c => c).slice(0, 3);
|
|
323
|
+
const selector = el.tagName.toLowerCase() +
|
|
324
|
+
(el.id ? '#' + el.id : '') +
|
|
325
|
+
(classNames.length ? '.' + classNames.join('.') : '');
|
|
326
|
+
|
|
327
|
+
// Highlight element
|
|
328
|
+
if (overlay) {
|
|
329
|
+
overlay.innerHTML = '';
|
|
330
|
+
|
|
331
|
+
const iframeRect = devIframe.getBoundingClientRect();
|
|
332
|
+
const highlightTop = iframeRect.top + rect.top;
|
|
333
|
+
const highlightLeft = iframeRect.left + rect.left;
|
|
334
|
+
|
|
335
|
+
const highlight = document.createElement('div');
|
|
336
|
+
highlight.className = 'claude-dev-server-highlight';
|
|
337
|
+
highlight.style.top = highlightTop + 'px';
|
|
338
|
+
highlight.style.left = highlightLeft + 'px';
|
|
339
|
+
highlight.style.width = rect.width + 'px';
|
|
340
|
+
highlight.style.height = rect.height + 'px';
|
|
341
|
+
highlight.dataset.element = selector;
|
|
342
|
+
overlay.appendChild(highlight);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Get source location and send to terminal
|
|
346
|
+
getSourceLocationFromElement(el).then(location => {
|
|
347
|
+
if (location) {
|
|
348
|
+
sendToTerminal(location);
|
|
349
|
+
}
|
|
350
|
+
disableInspectMode();
|
|
351
|
+
});
|
|
352
|
+
} else if (e.type === 'mousemove') {
|
|
353
|
+
const el = e.target;
|
|
354
|
+
const rect = el.getBoundingClientRect();
|
|
355
|
+
const className = el.className ? String(el.className) : '';
|
|
356
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
357
|
+
const classNames = className.split(' ').filter(c => c).slice(0, 3);
|
|
358
|
+
const selector = el.tagName.toLowerCase() +
|
|
359
|
+
(el.id ? '#' + el.id : '') +
|
|
360
|
+
(classNames.length ? '.' + classNames.join('.') : '');
|
|
361
|
+
|
|
362
|
+
if (overlay) {
|
|
363
|
+
overlay.innerHTML = '';
|
|
364
|
+
|
|
365
|
+
const iframeRect = devIframe.getBoundingClientRect();
|
|
366
|
+
const highlightTop = iframeRect.top + rect.top;
|
|
367
|
+
const highlightLeft = iframeRect.left + rect.left;
|
|
368
|
+
|
|
369
|
+
const highlight = document.createElement('div');
|
|
370
|
+
highlight.className = 'claude-dev-server-highlight';
|
|
371
|
+
highlight.style.top = highlightTop + 'px';
|
|
372
|
+
highlight.style.left = highlightLeft + 'px';
|
|
373
|
+
highlight.style.width = rect.width + 'px';
|
|
374
|
+
highlight.style.height = rect.height + 'px';
|
|
375
|
+
highlight.dataset.element = selector;
|
|
376
|
+
overlay.appendChild(highlight);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// Store handler reference for later removal
|
|
382
|
+
iframeDoc._claudeInspectHandler = inspectHandler;
|
|
383
|
+
|
|
384
|
+
// Add event listeners in capture phase
|
|
385
|
+
iframeDoc.addEventListener('click', inspectHandler, true);
|
|
386
|
+
iframeDoc.addEventListener('mousemove', inspectHandler, true);
|
|
387
|
+
|
|
388
|
+
// Mark listeners as registered
|
|
389
|
+
inspectListenersRegistered = true;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function getSourceLocationFromElement(element) {
|
|
393
|
+
const iframeDoc = devIframe?.contentDocument || document;
|
|
394
|
+
|
|
395
|
+
// Check for Next.js
|
|
396
|
+
const hasNextJsChunks = Array.from(iframeDoc.querySelectorAll('script[src]'))
|
|
397
|
+
.some(script => script.getAttribute('src')?.includes('/_next/static/chunks/'));
|
|
398
|
+
|
|
399
|
+
if (hasNextJsChunks) {
|
|
400
|
+
console.log('[Claude Dev Server Wrapper] Next.js detected, using specialized lookup');
|
|
401
|
+
const pagePath = devIframe.contentWindow.location.pathname;
|
|
402
|
+
console.log('[Claude Dev Server Wrapper] Looking up source for page:', pagePath);
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const params = new URLSearchParams({
|
|
406
|
+
page: pagePath,
|
|
407
|
+
framework: 'nextjs'
|
|
408
|
+
});
|
|
409
|
+
const lookupUrl = '/@sourcemap-lookup?' + params.toString();
|
|
410
|
+
const response = await fetch(lookupUrl);
|
|
411
|
+
|
|
412
|
+
if (response.ok) {
|
|
413
|
+
const result = await response.json();
|
|
414
|
+
if (result.file) {
|
|
415
|
+
const elClassName = element.className ? String(element.className) : '';
|
|
416
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
417
|
+
const classNames = elClassName.split(' ').filter(c => c).slice(0, 3);
|
|
418
|
+
const selector = element.tagName.toLowerCase() +
|
|
419
|
+
(element.id ? '#' + element.id : '') +
|
|
420
|
+
(classNames.length ? '.' + classNames.join('.') : '');
|
|
421
|
+
|
|
422
|
+
// Extract text content from element
|
|
423
|
+
let textContent = '';
|
|
424
|
+
if (element.nodeType === Node.TEXT_NODE) {
|
|
425
|
+
textContent = element.textContent ? element.textContent.trim().substring(0, 50) : '';
|
|
426
|
+
} else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
|
|
427
|
+
textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : '';
|
|
428
|
+
} else {
|
|
429
|
+
for (const child of element.childNodes) {
|
|
430
|
+
if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
|
|
431
|
+
textContent = child.textContent.trim().substring(0, 50);
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (textContent && textContent.length >= 50) {
|
|
437
|
+
textContent += '...';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
url: result.file,
|
|
442
|
+
selector: selector,
|
|
443
|
+
text: textContent,
|
|
444
|
+
hint: 'File: ' + result.file
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} catch (e) {
|
|
449
|
+
console.log('[Claude Dev Server Wrapper] Next.js lookup failed:', e);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Default fallback
|
|
454
|
+
const elClassName = element.className ? String(element.className) : '';
|
|
455
|
+
// Limit classnames to first 2-3 to avoid overly long selectors
|
|
456
|
+
const classNames = elClassName.split(' ').filter(c => c).slice(0, 3);
|
|
457
|
+
const selector = element.tagName.toLowerCase() +
|
|
458
|
+
(element.id ? '#' + element.id : '') +
|
|
459
|
+
(classNames.length ? '.' + classNames.join('.') : '');
|
|
460
|
+
|
|
461
|
+
let textContent = '';
|
|
462
|
+
if (element.nodeType === Node.TEXT_NODE) {
|
|
463
|
+
textContent = element.textContent ? element.textContent.trim().substring(0, 50) : '';
|
|
464
|
+
} else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
|
|
465
|
+
textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : '';
|
|
466
|
+
} else {
|
|
467
|
+
for (const child of element.childNodes) {
|
|
468
|
+
if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
|
|
469
|
+
textContent = child.textContent.trim().substring(0, 50);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (textContent && textContent.length >= 50) {
|
|
475
|
+
textContent += '...';
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
url: window.location.pathname,
|
|
480
|
+
selector: selector,
|
|
481
|
+
text: textContent,
|
|
482
|
+
hint: 'Element: ' + selector
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function setupDraggable(divider) {
|
|
487
|
+
let startX = 0;
|
|
488
|
+
let startWidth = 0;
|
|
489
|
+
let containerWidth = 0;
|
|
490
|
+
|
|
491
|
+
divider.addEventListener('mousedown', startDrag);
|
|
492
|
+
divider.addEventListener('touchstart', startDrag);
|
|
493
|
+
|
|
494
|
+
function startDrag(e) {
|
|
495
|
+
isDragging = true;
|
|
496
|
+
divider.classList.add('dragging');
|
|
497
|
+
|
|
498
|
+
const iframes = document.querySelectorAll('iframe');
|
|
499
|
+
iframes.forEach(iframe => iframe.style.pointerEvents = 'none');
|
|
500
|
+
|
|
501
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
502
|
+
startX = clientX;
|
|
503
|
+
|
|
504
|
+
const container = document.querySelector('.claude-dev-server-container');
|
|
505
|
+
const leftPanelEl = document.querySelector('.claude-dev-server-left');
|
|
506
|
+
|
|
507
|
+
if (container && leftPanelEl) {
|
|
508
|
+
containerWidth = container.offsetWidth;
|
|
509
|
+
startWidth = leftPanelEl.offsetWidth;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
e.preventDefault();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function drag(e) {
|
|
516
|
+
if (!isDragging) return;
|
|
517
|
+
|
|
518
|
+
if (e.buttons === 0 && !e.touches) {
|
|
519
|
+
endDrag();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
524
|
+
const deltaX = clientX - startX;
|
|
525
|
+
|
|
526
|
+
let newWidth = startWidth + deltaX;
|
|
527
|
+
let percentage = (newWidth / containerWidth) * 100;
|
|
528
|
+
const clamped = Math.max(20, Math.min(80, percentage));
|
|
529
|
+
|
|
530
|
+
const leftPanelEl = document.querySelector('.claude-dev-server-left');
|
|
531
|
+
if (leftPanelEl) {
|
|
532
|
+
leftPanelEl.style.flex = 'none';
|
|
533
|
+
leftPanelEl.style.width = clamped + '%';
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function endDrag() {
|
|
538
|
+
if (isDragging) {
|
|
539
|
+
isDragging = false;
|
|
540
|
+
divider.classList.remove('dragging');
|
|
541
|
+
|
|
542
|
+
const iframes = document.querySelectorAll('iframe');
|
|
543
|
+
iframes.forEach(iframe => iframe.style.pointerEvents = '');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
document.removeEventListener('mousemove', drag, true);
|
|
548
|
+
document.removeEventListener('touchmove', drag, true);
|
|
549
|
+
document.removeEventListener('mouseup', endDrag, true);
|
|
550
|
+
document.removeEventListener('touchend', endDrag, true);
|
|
551
|
+
|
|
552
|
+
document.addEventListener('mousemove', drag, true);
|
|
553
|
+
document.addEventListener('touchmove', drag, { passive: false, capture: true });
|
|
554
|
+
document.addEventListener('mouseup', endDrag, true);
|
|
555
|
+
document.addEventListener('touchend', endDrag, true);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function enableInspectMode() {
|
|
559
|
+
isInspectMode = true;
|
|
560
|
+
if (overlay) overlay.classList.add('active');
|
|
561
|
+
setupIframeInspectListeners();
|
|
562
|
+
if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
|
|
563
|
+
devIframe.contentDocument.body.style.cursor = 'crosshair';
|
|
564
|
+
}
|
|
565
|
+
const btn = document.querySelector('.claude-dev-server-btn-inspect');
|
|
566
|
+
if (btn) btn.classList.add('active');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function disableInspectMode() {
|
|
570
|
+
isInspectMode = false;
|
|
571
|
+
if (overlay) {
|
|
572
|
+
overlay.classList.remove('active');
|
|
573
|
+
overlay.innerHTML = '';
|
|
574
|
+
}
|
|
575
|
+
if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
|
|
576
|
+
devIframe.contentDocument.body.style.cursor = '';
|
|
577
|
+
}
|
|
578
|
+
const btn = document.querySelector('.claude-dev-server-btn-inspect');
|
|
579
|
+
if (btn) btn.classList.remove('active');
|
|
580
|
+
|
|
581
|
+
// Remove inspect listeners to free up resources
|
|
582
|
+
removeIframeInspectListeners();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function removeIframeInspectListeners() {
|
|
586
|
+
if (!devIframe || !devIframe.contentDocument) return;
|
|
587
|
+
|
|
588
|
+
const iframeDoc = devIframe.contentDocument;
|
|
589
|
+
const existingHandler = iframeDoc._claudeInspectHandler;
|
|
590
|
+
|
|
591
|
+
if (existingHandler) {
|
|
592
|
+
iframeDoc.removeEventListener('click', existingHandler, true);
|
|
593
|
+
iframeDoc.removeEventListener('mousemove', existingHandler, true);
|
|
594
|
+
delete iframeDoc._claudeInspectHandler;
|
|
595
|
+
inspectListenersRegistered = false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function sendToTerminal(location) {
|
|
600
|
+
const filePath = location.url || location.file || 'unknown';
|
|
601
|
+
const tagName = location.selector ? location.selector.split(/[.#]/)[0] : 'div';
|
|
602
|
+
let selector = location.selector || tagName;
|
|
603
|
+
|
|
604
|
+
let textContent = '';
|
|
605
|
+
if (location.text) {
|
|
606
|
+
textContent = location.text;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let prompt = `@${filePath} <${selector}>`;
|
|
610
|
+
if (textContent) {
|
|
611
|
+
prompt += ` "${textContent}"`;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log('[Claude Dev Server Wrapper] Sending to terminal:', prompt);
|
|
615
|
+
|
|
616
|
+
// Store the interval ID so we can clear it if needed
|
|
617
|
+
if (!window._claudeSendRetryInterval) {
|
|
618
|
+
window._claudeSendRetryInterval = null;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Clear any existing retry interval
|
|
622
|
+
if (window._claudeSendRetryInterval) {
|
|
623
|
+
clearInterval(window._claudeSendRetryInterval);
|
|
624
|
+
window._claudeSendRetryInterval = null;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const sendToTerminalInternal = () => {
|
|
628
|
+
if (!ttydIframe || !ttydIframe.contentWindow) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const win = ttydIframe.contentWindow;
|
|
633
|
+
if (win.sendToTerminal) {
|
|
634
|
+
win.sendToTerminal(prompt);
|
|
635
|
+
console.log('[Claude Dev Server Wrapper] Sent via sendToTerminal:', prompt);
|
|
636
|
+
// Clear interval on success
|
|
637
|
+
if (window._claudeSendRetryInterval) {
|
|
638
|
+
clearInterval(window._claudeSendRetryInterval);
|
|
639
|
+
window._claudeSendRetryInterval = null;
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return false;
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
if (sendToTerminalInternal()) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
let attempts = 0;
|
|
652
|
+
const maxAttempts = 50;
|
|
653
|
+
window._claudeSendRetryInterval = setInterval(() => {
|
|
654
|
+
attempts++;
|
|
655
|
+
if (sendToTerminalInternal() || attempts >= maxAttempts) {
|
|
656
|
+
clearInterval(window._claudeSendRetryInterval);
|
|
657
|
+
window._claudeSendRetryInterval = null;
|
|
658
|
+
if (attempts >= maxAttempts) {
|
|
659
|
+
console.warn('[Claude Dev Server Wrapper] Could not send to terminal after retries');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}, 100);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function loadTerminalIframe(ttydUrl) {
|
|
666
|
+
if (!ttydIframe) return;
|
|
667
|
+
ttydWsUrl = ttydUrl;
|
|
668
|
+
ttydIframe.src = '/ttyd/index.html?ws=' + encodeURIComponent(ttydUrl);
|
|
669
|
+
ttydIframe.onload = () => {
|
|
670
|
+
console.log('[Claude Dev Server Wrapper] Terminal iframe loaded');
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function connect(port) {
|
|
675
|
+
const WS_URL = 'ws://localhost:' + port;
|
|
676
|
+
ws = new WebSocket(WS_URL);
|
|
677
|
+
|
|
678
|
+
ws.onopen = () => {
|
|
679
|
+
console.log('[Claude Dev Server Wrapper] Connected to control server');
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
ws.onmessage = (e) => {
|
|
683
|
+
try {
|
|
684
|
+
const msg = JSON.parse(e.data);
|
|
685
|
+
console.log('[Claude Dev Server Wrapper] Received message:', msg.type, msg);
|
|
686
|
+
if (msg.type === 'ready' && msg.ttydUrl) {
|
|
687
|
+
loadTerminalIframe(msg.ttydUrl);
|
|
688
|
+
}
|
|
689
|
+
} catch (err) {
|
|
690
|
+
console.error('[Claude Dev Server Wrapper] Message parse error:', err);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
ws.onclose = () => {
|
|
695
|
+
console.log('[Claude Dev Server Wrapper] Control WebSocket disconnected, reconnecting...');
|
|
696
|
+
setTimeout(() => connect(port), 2000);
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
ws.onerror = (err) => {
|
|
700
|
+
console.error('[Claude Dev Server Wrapper] Control WebSocket error:', err);
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function getWsPort() {
|
|
705
|
+
const res = await fetch('/@claude-port');
|
|
706
|
+
const data = await res.json();
|
|
707
|
+
return data.port;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function initWhenReady() {
|
|
711
|
+
try {
|
|
712
|
+
const port = await getWsPort();
|
|
713
|
+
connect(port);
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.error('[Claude Dev Server Wrapper] Failed to get port:', err);
|
|
716
|
+
setTimeout(initWhenReady, 1000);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
createSplitLayout();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
initWhenReady();
|
|
723
|
+
|
|
724
|
+
document.addEventListener('keydown', (e) => {
|
|
725
|
+
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
|
|
726
|
+
e.preventDefault();
|
|
727
|
+
if (isInspectMode) {
|
|
728
|
+
disableInspectMode();
|
|
729
|
+
} else {
|
|
730
|
+
enableInspectMode();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (e.key === 'Escape' && isInspectMode) {
|
|
734
|
+
disableInspectMode();
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
</script>
|
|
738
|
+
</body>
|
|
739
|
+
</html>
|