claude-dev-server 1.2.2 → 1.2.3

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.
@@ -1,1897 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { existsSync, readFileSync } from 'fs';
3
- import { dirname, join, relative, resolve } from 'path';
4
- import http from 'http';
5
- import { fileURLToPath } from 'url';
6
- import { createConnection } from 'net';
7
- import { SourceMapConsumer } from 'source-map';
8
- import { WebSocketServer } from 'ws';
9
- import { readFile } from 'fs/promises';
10
-
11
- // src/universal/index.ts
12
- function spawnClaudeCode(options) {
13
- const port = 5e4 + Math.floor(Math.random() * 1e4);
14
- const ttydProc = spawn("ttyd", [
15
- "--port",
16
- String(port),
17
- "--interface",
18
- "127.0.0.1",
19
- "--writable",
20
- options.claudePath,
21
- ...options.args
22
- ], {
23
- cwd: options.cwd,
24
- env: {
25
- ...process.env,
26
- ...options.env,
27
- TERM: "xterm-256color",
28
- FORCE_COLOR: "1"
29
- }
30
- });
31
- ttydProc.on("exit", (code) => {
32
- console.log(`[claude-dev-server] ttyd exited - code: ${code}`);
33
- });
34
- ttydProc.on("error", (err) => {
35
- console.error(`[claude-dev-server] ttyd error: ${err.message}`);
36
- });
37
- return new Promise((resolve2, reject) => {
38
- ttydProc.stdout?.on("data", (data) => {
39
- const msg = data.toString();
40
- console.log(`[ttyd] ${msg}`);
41
- });
42
- ttydProc.stderr?.on("data", (data) => {
43
- const msg = data.toString();
44
- console.error(`[ttyd stderr] ${msg}`);
45
- });
46
- setTimeout(() => {
47
- resolve2({
48
- wsUrl: `ws://127.0.0.1:${port}`,
49
- process: ttydProc,
50
- port
51
- });
52
- }, 500);
53
- ttydProc.on("error", reject);
54
- });
55
- }
56
- var sourceMapCache = /* @__PURE__ */ new Map();
57
- async function parseSourceMap(sourceMapUrl, content) {
58
- try {
59
- const consumer = await new SourceMapConsumer(content);
60
- sourceMapCache.set(sourceMapUrl, consumer);
61
- } catch (err) {
62
- console.error("Failed to parse source map:", err);
63
- }
64
- }
65
- async function findOriginalPosition(generatedFile, line, column) {
66
- const consumer = sourceMapCache.get(generatedFile);
67
- if (!consumer) {
68
- return null;
69
- }
70
- try {
71
- const position = consumer.originalPositionFor({ line, column });
72
- if (position.source) {
73
- return {
74
- source: position.source,
75
- line: position.line || 1,
76
- column: position.column || 0,
77
- name: position.name
78
- };
79
- }
80
- } catch (err) {
81
- console.error("Failed to find original position:", err);
82
- }
83
- return null;
84
- }
85
- function formatCodeContext(location) {
86
- return `
87
- \u{1F4CD} Code Location:
88
- File: ${location.file}
89
- Line: ${location.line}
90
- Column: ${location.column}
91
- `;
92
- }
93
- function createWebSocketServer(options) {
94
- let wss = null;
95
- let port = 0;
96
- let ttydInfo = null;
97
- const start = async () => {
98
- console.log("[claude-dev-server] Starting ttyd...");
99
- ttydInfo = await spawnClaudeCode({
100
- cwd: options.projectRoot,
101
- claudePath: options.claudePath,
102
- args: options.claudeArgs
103
- });
104
- console.log(`[claude-dev-server] ttyd running at ${ttydInfo.wsUrl}`);
105
- return new Promise((resolve2, reject) => {
106
- wss = new WebSocketServer({ port, host: "127.0.0.1" });
107
- wss.on("listening", () => {
108
- const address = wss.address();
109
- if (address && typeof address === "object") {
110
- port = address.port;
111
- resolve2({ wsPort: port, ttydPort: ttydInfo.port });
112
- }
113
- });
114
- wss.on("error", (err) => {
115
- reject(err);
116
- });
117
- wss.on("connection", (ws) => {
118
- ws.on("message", async (message) => {
119
- try {
120
- const msg = JSON.parse(message.toString());
121
- if (msg.type === "inspect") {
122
- await handleInspect(msg, ws, options.projectRoot);
123
- } else if (msg.type === "loadSourceMap") {
124
- await handleLoadSourceMap(msg, ws, options.projectRoot);
125
- }
126
- } catch (err) {
127
- }
128
- });
129
- ws.send(JSON.stringify({
130
- type: "ready",
131
- ttydUrl: ttydInfo.wsUrl
132
- }));
133
- });
134
- });
135
- };
136
- const stop = () => {
137
- ttydInfo?.process.kill();
138
- wss?.close();
139
- };
140
- return { start, stop };
141
- }
142
- async function handleLoadSourceMap(msg, ws, projectRoot) {
143
- const { sourceMapUrl } = msg;
144
- try {
145
- const mapPath = resolve(projectRoot, sourceMapUrl.replace(/^\//, ""));
146
- const content = await readFile(mapPath, "utf-8");
147
- await parseSourceMap(sourceMapUrl, content);
148
- ws.send(JSON.stringify({
149
- type: "sourceMapLoaded",
150
- sourceMapUrl,
151
- success: true
152
- }));
153
- } catch (err) {
154
- ws.send(JSON.stringify({
155
- type: "sourceMapLoaded",
156
- sourceMapUrl,
157
- success: false,
158
- error: err.message
159
- }));
160
- }
161
- }
162
- async function handleInspect(msg, ws, projectRoot) {
163
- const { url, line, column, sourceMapUrl } = msg;
164
- let location = null;
165
- if (sourceMapUrl) {
166
- const original = await findOriginalPosition(sourceMapUrl, line, column);
167
- if (original) {
168
- location = {
169
- file: resolve(projectRoot, original.source),
170
- line: original.line,
171
- column: original.column
172
- };
173
- }
174
- }
175
- if (!location) {
176
- const match = url.match(/\/@fs\/(.+?)(?:\?|$)|\/@vite\/(.+?)(?:\?|$)/);
177
- if (match) {
178
- location = {
179
- file: decodeURIComponent(match[1] || match[2]),
180
- line,
181
- column
182
- };
183
- }
184
- }
185
- ws.send(JSON.stringify({
186
- type: "inspectResult",
187
- location: location ? {
188
- file: location.file,
189
- line: location.line,
190
- column: location.column,
191
- context: formatCodeContext(location)
192
- } : null
193
- }));
194
- }
195
-
196
- // src/client/injection.js
197
- var CLIENT_STYLES = `
198
- <style id="claude-dev-server-styles">
199
- * {
200
- margin: 0;
201
- padding: 0;
202
- box-sizing: border-box;
203
- }
204
- html, body {
205
- width: 100%;
206
- height: 100%;
207
- overflow: hidden;
208
- background: transparent;
209
- }
210
- .claude-dev-server-container {
211
- display: flex;
212
- width: 100vw;
213
- height: 100vh;
214
- overflow: hidden;
215
- background: transparent;
216
- }
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;
226
- }
227
- .claude-dev-server-left iframe {
228
- width: 100%;
229
- height: 100%;
230
- border: none;
231
- }
232
- .claude-dev-server-divider {
233
- width: 6px;
234
- background: #3e3e3e;
235
- position: relative;
236
- cursor: col-resize;
237
- transition: background 0.2s;
238
- flex-shrink: 0;
239
- }
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;
249
- display: flex;
250
- flex-direction: column;
251
- }
252
- .claude-dev-server-terminal {
253
- flex: 1;
254
- overflow: hidden;
255
- position: relative;
256
- background: #000;
257
- }
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 {
270
- padding: 8px 12px;
271
- background: #2d2d2d;
272
- border-bottom: 1px solid #3e3e3e;
273
- display: flex;
274
- justify-content: space-between;
275
- align-items: center;
276
- user-select: none;
277
- min-height: 40px;
278
- }
279
- .claude-dev-server-title {
280
- font-weight: 600;
281
- color: #fff;
282
- display: flex;
283
- align-items: center;
284
- gap: 8px;
285
- font-size: 13px;
286
- font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Consolas', monospace;
287
- }
288
- .claude-dev-server-title svg {
289
- width: 14px;
290
- height: 14px;
291
- flex-shrink: 0;
292
- }
293
- .claude-dev-server-actions {
294
- display: flex;
295
- gap: 6px;
296
- }
297
- .claude-dev-server-btn {
298
- background: #d97757;
299
- border: none;
300
- color: #fff;
301
- cursor: pointer;
302
- font-family: inherit;
303
- font-size: 11px;
304
- padding: 4px 10px;
305
- border-radius: 3px;
306
- transition: background 0.15s;
307
- display: flex;
308
- align-items: center;
309
- gap: 4px;
310
- }
311
- .claude-dev-server-btn:hover {
312
- background: #c96a4a;
313
- }
314
- .claude-dev-server-btn.active {
315
- background: #b85d3f;
316
- color: #fff;
317
- }
318
- .claude-dev-server-inspect-overlay {
319
- position: fixed;
320
- top: 0;
321
- left: 0;
322
- right: 0;
323
- bottom: 0;
324
- pointer-events: none;
325
- z-index: 2147483646;
326
- display: none;
327
- }
328
- .claude-dev-server-inspect-overlay.active {
329
- display: block;
330
- }
331
- .claude-dev-server-highlight {
332
- position: absolute;
333
- border: 2px solid #007acc;
334
- background: rgba(0, 122, 204, 0.1);
335
- pointer-events: none;
336
- transition: all 0.1s ease;
337
- }
338
- .claude-dev-server-highlight::after {
339
- content: attr(data-element);
340
- position: absolute;
341
- top: -20px;
342
- left: 0;
343
- background: #007acc;
344
- color: #fff;
345
- font-size: 10px;
346
- padding: 2px 4px;
347
- border-radius: 2px 2px 0 0;
348
- font-family: monospace;
349
- white-space: nowrap;
350
- }
351
- /* Portrait mode - dev on top, ttyd on bottom */
352
- @media (orientation: portrait) {
353
- .claude-dev-server-container {
354
- flex-direction: column-reverse;
355
- }
356
- .claude-dev-server-divider {
357
- width: 100%;
358
- height: 6px;
359
- cursor: row-resize;
360
- }
361
- .claude-dev-server-right {
362
- width: 100%;
363
- min-width: unset;
364
- max-width: unset;
365
- height: 70%;
366
- min-height: 200px;
367
- }
368
- .claude-dev-server-left {
369
- width: 100%;
370
- min-width: unset;
371
- max-width: unset;
372
- height: 30%;
373
- min-height: 150px;
374
- }
375
- }
376
- </style>
377
- `;
378
- var CLIENT_SCRIPT = `
379
- (() => {
380
- let ws = null
381
- let ttydIframe = null
382
- let devIframe = null
383
- let overlay = null
384
- let isInspectMode = false
385
- let ttydWsUrl = null
386
- let leftPanel = null
387
- let divider = null
388
- let rightPanel = null
389
- let isDragging = false
390
- let inspectListenersRegistered = false
391
-
392
- // Fetch the WebSocket port from the server
393
- async function getWsPort() {
394
- const res = await fetch('/@claude-port')
395
- const data = await res.json()
396
- return data.port
397
- }
398
-
399
- async function initWhenReady() {
400
- try {
401
- const port = await getWsPort()
402
- connect(port)
403
- } catch (err) {
404
- console.error('[Claude Dev Server] Failed to get port:', err)
405
- setTimeout(initWhenReady, 1000)
406
- return
407
- }
408
- // Create overlay first before split layout
409
- createOverlay()
410
- createSplitLayout()
411
- }
412
-
413
- function createSplitLayout() {
414
- // IMPORTANT: Only create layout in the top-level window, not in iframes
415
- // This prevents infinite nesting when the dev iframe loads
416
- if (window.top !== window.self) {
417
- console.log('[Claude Dev Server] Not in top window, skipping layout creation')
418
- return
419
- }
420
-
421
- // Check if already created
422
- if (document.querySelector('.claude-dev-server-container')) return
423
- // Also check if we're already in an injected page
424
- if (window.__CLAUDE_SPLIT_LAYOUT_CREATED__) return
425
- window.__CLAUDE_SPLIT_LAYOUT_CREATED__ = true
426
-
427
- // Get original HTML from window variable (set by server)
428
- // The HTML is Base64 encoded to avoid any escaping issues
429
- let originalHtml
430
- if (window.__CLAUDE_ORIGINAL_HTML_BASE64__) {
431
- // Decode Base64
432
- try {
433
- const decoded = atob(window.__CLAUDE_ORIGINAL_HTML_BASE64__)
434
- // Handle UTF-8 encoding
435
- const bytes = new Uint8Array(decoded.length)
436
- for (let i = 0; i < decoded.length; i++) {
437
- bytes[i] = decoded.charCodeAt(i)
438
- }
439
- originalHtml = new TextDecoder().decode(bytes)
440
- } catch (e) {
441
- console.error('[Claude Dev Server] Failed to decode Base64 HTML:', e)
442
- originalHtml = document.documentElement.outerHTML
443
- }
444
- } else {
445
- originalHtml = document.documentElement.outerHTML
446
- }
447
-
448
- // Create container
449
- const container = document.createElement('div')
450
- container.className = 'claude-dev-server-container'
451
-
452
- // Left panel - terminal (Claude)
453
- leftPanel = document.createElement('div')
454
- leftPanel.className = 'claude-dev-server-left'
455
-
456
- // Terminal header
457
- const header = document.createElement('div')
458
- header.className = 'claude-dev-server-terminal-header'
459
- header.innerHTML = \`
460
- <span class="claude-dev-server-title">
461
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
462
- <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"/>
463
- </svg>
464
- Claude Code
465
- </span>
466
- <div class="claude-dev-server-actions">
467
- <button class="claude-dev-server-btn claude-dev-server-btn-inspect" title="Inspect Element">
468
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
469
- <path d="M19 11V4a2 2 0 00-2-2H4a2 2 0 00-2 2v13a2 2 0 002 2h7"/>
470
- <path d="M12 12l4.166 10 1.48-4.355L22 16.166 12 12z"/>
471
- <path d="M18 18l3 3"/>
472
- </svg>
473
- Inspect
474
- </button>
475
- </div>
476
- \`
477
-
478
- const inspectBtn = header.querySelector('.claude-dev-server-btn-inspect')
479
- inspectBtn.addEventListener('click', () => {
480
- if (isInspectMode) {
481
- disableInspectMode()
482
- } else {
483
- enableInspectMode()
484
- }
485
- })
486
-
487
- // Terminal container
488
- const terminal = document.createElement('div')
489
- terminal.className = 'claude-dev-server-terminal'
490
-
491
- // Ttyd iframe
492
- ttydIframe = document.createElement('iframe')
493
- ttydIframe.className = 'claude-dev-server-terminal-iframe'
494
- ttydIframe.allow = 'clipboard-read; clipboard-write'
495
-
496
- terminal.appendChild(ttydIframe)
497
- leftPanel.appendChild(header)
498
- leftPanel.appendChild(terminal)
499
-
500
- // Divider - draggable
501
- divider = document.createElement('div')
502
- divider.className = 'claude-dev-server-divider'
503
- setupDraggable(divider)
504
-
505
- // Right panel - dev server
506
- rightPanel = document.createElement('div')
507
- rightPanel.className = 'claude-dev-server-right'
508
-
509
- // Create dev server iframe with srcdoc
510
- devIframe = document.createElement('iframe')
511
- devIframe.className = 'claude-dev-server-dev-iframe'
512
- // The HTML already has inspect script injected by server
513
- devIframe.srcdoc = originalHtml
514
-
515
- rightPanel.appendChild(devIframe)
516
-
517
- // Assemble layout: Claude (left) | divider | dev server (right)
518
- container.appendChild(leftPanel)
519
- container.appendChild(divider)
520
- container.appendChild(rightPanel)
521
-
522
- // Replace body content
523
- document.body.innerHTML = ''
524
- document.body.appendChild(container)
525
- document.body.appendChild(overlay)
526
-
527
- // Wait for iframe to load before setting up communication
528
- devIframe.onload = () => {
529
- console.log('[Claude Dev Server] Dev iframe loaded')
530
- // Setup inspect mode communication
531
- setupInspectCommunication()
532
- }
533
- }
534
-
535
- function setupInspectCommunication() {
536
- // Inspect mode is now handled entirely in the outer layer
537
- // We'll access the iframe's document directly
538
- }
539
-
540
- function setupIframeInspectListeners() {
541
- if (!devIframe || !devIframe.contentDocument) return
542
-
543
- // Prevent duplicate listener registration
544
- if (inspectListenersRegistered) {
545
- return
546
- }
547
-
548
- const iframeDoc = devIframe.contentDocument
549
- const iframeWindow = devIframe.contentWindow
550
-
551
- const inspectHandler = (e) => {
552
- if (!isInspectMode) return
553
-
554
- e.preventDefault()
555
- e.stopPropagation()
556
-
557
- if (e.type === 'click') {
558
- const el = e.target
559
- const rect = el.getBoundingClientRect()
560
- const className = el.className ? String(el.className) : ''
561
- // Limit classnames to first 2-3 to avoid overly long selectors
562
- const classNames = className.split(' ').filter(c => c).slice(0, 3)
563
- const selector = el.tagName.toLowerCase() +
564
- (el.id ? '#' + el.id : '') +
565
- (classNames.length ? '.' + classNames.join('.') : '')
566
-
567
- // Highlight element
568
- if (overlay) {
569
- overlay.innerHTML = ''
570
-
571
- // Get iframe position on page
572
- const iframeRect = devIframe.getBoundingClientRect()
573
-
574
- // Calculate highlight position relative to main document
575
- // Element rect is relative to iframe viewport, need to add iframe position
576
- const highlightTop = iframeRect.top + rect.top
577
- const highlightLeft = iframeRect.left + rect.left
578
-
579
- const highlight = document.createElement('div')
580
- highlight.className = 'claude-dev-server-highlight'
581
- highlight.style.top = highlightTop + 'px'
582
- highlight.style.left = highlightLeft + 'px'
583
- highlight.style.width = rect.width + 'px'
584
- highlight.style.height = rect.height + 'px'
585
- highlight.dataset.element = selector
586
- overlay.appendChild(highlight)
587
- }
588
-
589
- // Get source location and send to terminal
590
- getSourceLocationFromElement(el).then(location => {
591
- if (location) {
592
- sendToTerminal(location)
593
- }
594
- disableInspectMode()
595
- })
596
- } else if (e.type === 'mousemove') {
597
- const el = e.target
598
- const rect = el.getBoundingClientRect()
599
- const className = el.className ? String(el.className) : ''
600
- // Limit classnames to first 2-3 to avoid overly long selectors
601
- const classNames = className.split(' ').filter(c => c).slice(0, 3)
602
- const selector = el.tagName.toLowerCase() +
603
- (el.id ? '#' + el.id : '') +
604
- (classNames.length ? '.' + classNames.join('.') : '')
605
-
606
- if (overlay) {
607
- overlay.innerHTML = ''
608
-
609
- // Get iframe position on page
610
- const iframeRect = devIframe.getBoundingClientRect()
611
-
612
- // Calculate highlight position relative to main document
613
- const highlightTop = iframeRect.top + rect.top
614
- const highlightLeft = iframeRect.left + rect.left
615
-
616
- const highlight = document.createElement('div')
617
- highlight.className = 'claude-dev-server-highlight'
618
- highlight.style.top = highlightTop + 'px'
619
- highlight.style.left = highlightLeft + 'px'
620
- highlight.style.width = rect.width + 'px'
621
- highlight.style.height = rect.height + 'px'
622
- highlight.dataset.element = selector
623
- overlay.appendChild(highlight)
624
- }
625
- }
626
- }
627
-
628
- // Store handler reference for later removal
629
- iframeDoc._claudeInspectHandler = inspectHandler
630
-
631
- // Add event listeners in capture phase
632
- iframeDoc.addEventListener('click', inspectHandler, true)
633
- iframeDoc.addEventListener('mousemove', inspectHandler, true)
634
-
635
- // Mark listeners as registered
636
- inspectListenersRegistered = true
637
- }
638
-
639
- async function handleInspectElement(elementInfo) {
640
- // This is now handled directly in setupIframeInspectListeners
641
- }
642
-
643
- async function getSourceLocationFromElement(element) {
644
- // Access the iframe's document to detect Next.js chunks
645
- const iframeDoc = devIframe?.contentDocument || document
646
-
647
- // Early check: detect Next.js by checking for _next/static chunks
648
- const hasNextJsChunks = Array.from(iframeDoc.querySelectorAll('script[src]'))
649
- .some(script => script.getAttribute('src')?.includes('/_next/static/chunks/'))
650
-
651
- if (hasNextJsChunks) {
652
- console.log('[Claude Dev Server] Next.js Turbopack detected, using specialized lookup')
653
-
654
- const pagePath = window.location.pathname
655
- console.log('[Claude Dev Server] Looking up source for page:', pagePath)
656
-
657
- try {
658
- const params = new URLSearchParams({
659
- page: pagePath,
660
- framework: 'nextjs'
661
- })
662
- const lookupUrl = '/@sourcemap-lookup?' + params.toString()
663
- console.log('[Claude Dev Server] Fetching:', lookupUrl)
664
-
665
- const response = await fetch(lookupUrl)
666
- console.log('[Claude Dev Server] Response status:', response.status)
667
-
668
- if (response.ok) {
669
- const result = await response.json()
670
- console.log('[Claude Dev Server] Lookup result:', result)
671
- if (result.file) {
672
- const elClassName = element.className ? String(element.className) : ''
673
- // Limit classnames to first 2-3 to avoid overly long selectors
674
- const classNames = elClassName.split(' ').filter(c => c).slice(0, 3)
675
- const selector = element.tagName.toLowerCase() +
676
- (element.id ? '#' + element.id : '') +
677
- (classNames.length ? '.' + classNames.join('.') : '')
678
-
679
- // Extract text content from element
680
- let textContent = ''
681
- if (element.nodeType === Node.TEXT_NODE) {
682
- textContent = element.textContent ? element.textContent.trim().substring(0, 50) : ''
683
- } else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
684
- textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : ''
685
- } else {
686
- for (const child of element.childNodes) {
687
- if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
688
- textContent = child.textContent.trim().substring(0, 50)
689
- break
690
- }
691
- }
692
- }
693
- if (textContent && textContent.length >= 50) {
694
- textContent += '...'
695
- }
696
-
697
- return {
698
- url: result.file,
699
- line: result.line || undefined,
700
- column: result.column || undefined,
701
- selector: selector,
702
- text: textContent,
703
- hint: 'File: ' + result.file
704
- }
705
- }
706
- }
707
- } catch (e) {
708
- console.log('[Claude Dev Server] Next.js lookup failed:', e)
709
- }
710
- // If Next.js lookup failed, continue with normal flow
711
- }
712
-
713
- let sourceFile = null
714
- let sourceLine = 1
715
- let sourceColumn = 1
716
-
717
- // Try to get original line number using source map
718
- // Use server-side source map resolution with source-map library
719
- async function resolveSourceMap(filePath, line, column) {
720
- try {
721
- console.log('[Claude Dev Server] Calling server source map resolution for:', filePath, line, column)
722
-
723
- // Convert absolute file path to URL path for Vite
724
- let urlPath = filePath
725
- if (filePath.startsWith('/Users/')) {
726
- // Convert /Users/.../src/App.jsx to /src/App.jsx
727
- const parts = filePath.split('/')
728
- const srcIndex = parts.indexOf('src')
729
- if (srcIndex >= 0) {
730
- urlPath = '/' + parts.slice(srcIndex).join('/')
731
- }
732
- }
733
-
734
- console.log('[Claude Dev Server] Converted path to URL path:', urlPath)
735
-
736
- // Use the server-side source map resolution with URL path
737
- const params = new URLSearchParams({
738
- file: urlPath,
739
- line: String(line),
740
- col: String(column)
741
- })
742
-
743
- const response = await fetch('/@sourcemap?' + params.toString())
744
- if (!response.ok) {
745
- console.log('[Claude Dev Server] Source map request failed:', response.status)
746
- return null
747
- }
748
-
749
- const result = await response.json()
750
- console.log('[Claude Dev Server] Source map result:', result)
751
-
752
- if (result.error) {
753
- console.log('[Claude Dev Server] Source map error:', result.error)
754
- return null
755
- }
756
-
757
- if (result.file) {
758
- return {
759
- file: result.file,
760
- line: result.line,
761
- column: result.column
762
- }
763
- }
764
-
765
- return null
766
- } catch (e) {
767
- console.log('[Claude Dev Server] Source map lookup failed:', e.message)
768
- return null
769
- }
770
- }
771
-
772
- // Try to extract jsxDEV source location from React Fiber
773
- function extractJsxDevLocation(fiber) {
774
- try {
775
- // The jsxDEV location is stored in the element type's _debugInfo or _source
776
- // React 18 stores it differently - let's check multiple locations
777
- const elementType = fiber.elementType || fiber.type
778
-
779
- // Check if this is a jsxDEV call by looking at the string representation
780
- if (elementType && typeof elementType === 'function') {
781
- const fnStr = elementType.toString()
782
- // jsxDEV functions have a specific pattern
783
- if (fnStr.includes('jsxDEV') || fnStr.includes('jsx')) {
784
- console.log('[Claude Dev Server] Found jsxDEV element type')
785
- }
786
- }
787
-
788
- // Try to get _debugSource which might have jsxDEV metadata
789
- const debugSource = fiber._debugSource || fiber._debugInfo
790
- if (debugSource) {
791
- // For jsxDEV, check if there's a _source object with fileName/lineNumber
792
- const source = debugSource._source || debugSource.source || debugSource
793
- if (source && source.fileName && source.lineNumber !== undefined) {
794
- console.log('[Claude Dev Server] Found _debugSource with source location:', source)
795
- // NOTE: Don't use this directly! The lineNumber might be from transpiled code
796
- // We'll let the source map resolver handle it
797
- return null
798
- }
799
- }
800
-
801
- // Check the memoizedState or other properties for source location
802
- if (fiber.memoizedState) {
803
- // React might store jsxDEV location in memoizedState
804
- console.log('[Claude Dev Server] Checking memoizedState:', fiber.memoizedState)
805
- }
806
-
807
- // Check the memoizedProps or other properties for source location
808
- if (fiber.memoizedProps && fiber.memoizedProps.__source) {
809
- const source = fiber.memoizedProps.__source
810
- console.log('[Claude Dev Server] Found __source in memoizedProps:', source)
811
- return {
812
- file: source.fileName,
813
- line: source.lineNumber,
814
- column: source.columnNumber || 1
815
- }
816
- }
817
-
818
- // Check pendingProps for __source
819
- if (fiber.pendingProps && fiber.pendingProps.__source) {
820
- const source = fiber.pendingProps.__source
821
- console.log('[Claude Dev Server] Found __source in pendingProps:', source)
822
- return {
823
- file: source.fileName,
824
- line: source.lineNumber,
825
- column: source.columnNumber || 1
826
- }
827
- }
828
- } catch (e) {
829
- console.log('[Claude Dev Server] Error extracting jsxDEV location:', e)
830
- }
831
- return null
832
- }
833
-
834
- // Try React DevTools - handle React 18's randomized suffix
835
- const elKeys = Object.keys(element)
836
- let fiberKey = elKeys.find(k => k === '__reactFiber__' || k === '__reactInternalInstance' || k.indexOf('__reactFiber') === 0)
837
- let propsKey = elKeys.find(k => k === '__reactProps__' || k.indexOf('__reactProps') === 0)
838
-
839
- if (fiberKey) {
840
- const fiber = element[fiberKey]
841
- console.log('[Claude Dev Server] Found fiber at key:', fiberKey)
842
-
843
- // Log fiber structure for debugging
844
- console.log('[Claude Dev Server] Fiber structure:', {
845
- _debugSource: fiber._debugSource,
846
- elementType: fiber.elementType,
847
- type: fiber.type,
848
- memoizedProps: fiber.memoizedProps,
849
- pendingProps: fiber.pendingProps
850
- })
851
-
852
- // Log _debugSource details
853
- if (fiber._debugSource) {
854
- console.log('[Claude Dev Server] _debugSource details:', JSON.stringify(fiber._debugSource, null, 2))
855
- }
856
-
857
- // For Next.js, try to get component name from elementType
858
- if (!fiber._debugSource && fiber.elementType) {
859
- const elementType = fiber.elementType
860
- console.log('[Claude Dev Server] elementType:', elementType)
861
- console.log('[Claude Dev Server] elementType.name:', elementType.name)
862
- console.log('[Claude Dev Server] elementType.displayName:', elementType.displayName)
863
-
864
- // Try to get the function string to find component name
865
- if (typeof elementType === 'function') {
866
- const fnStr = elementType.toString()
867
- console.log('[Claude Dev Server] elementType function:', fnStr.substring(0, 200))
868
- }
869
- }
870
-
871
- // First, try to extract jsxDEV source location
872
- const jsxDevLocation = extractJsxDevLocation(fiber)
873
- if (jsxDevLocation) {
874
- sourceFile = jsxDevLocation.file
875
- sourceLine = jsxDevLocation.line
876
- sourceColumn = jsxDevLocation.column
877
-
878
- // Convert absolute path to relative path
879
- if (sourceFile.startsWith('/')) {
880
- const projectRoot = window.__CLAUDE_PROJECT_ROOT__
881
- if (projectRoot && sourceFile.startsWith(projectRoot)) {
882
- sourceFile = sourceFile.substring(projectRoot.length + 1)
883
- if (sourceFile.startsWith('/')) {
884
- sourceFile = sourceFile.substring(1)
885
- }
886
- }
887
- }
888
- console.log('[Claude Dev Server] Using jsxDEV location:', sourceFile, sourceLine)
889
- } else {
890
- // Fall back to _debugSource
891
- const debugSource = fiber._debugSource || fiber.elementType?._debugSource || fiber.type?._debugSource || fiber.alternate?._debugSource
892
- if (debugSource && debugSource.fileName) {
893
- sourceFile = debugSource.fileName
894
- sourceLine = debugSource.lineNumber || 1
895
- sourceColumn = debugSource.columnNumber || 1
896
-
897
- // Convert relative path to absolute path for source map resolution
898
- let absolutePath = sourceFile
899
- if (!sourceFile.startsWith('/')) {
900
- // Relative path - resolve relative to project root
901
- const projectRoot = window.__CLAUDE_PROJECT_ROOT__
902
- absolutePath = projectRoot + '/' + sourceFile
903
- }
904
-
905
- console.log('[Claude Dev Server] Resolving source map for:', absolutePath, 'at line:', sourceLine)
906
-
907
- // Use server-side source map resolution
908
- const original = await resolveSourceMap(absolutePath, sourceLine, sourceColumn)
909
- if (original) {
910
- sourceFile = original.file
911
- sourceLine = original.line
912
- sourceColumn = original.column
913
- console.log('[Claude Dev Server] Original location from source map:', sourceFile, sourceLine)
914
- } else {
915
- // Source map resolution failed, use the original file
916
- // Convert absolute path back to relative
917
- if (absolutePath.startsWith('/')) {
918
- const projectRoot = window.__CLAUDE_PROJECT_ROOT__
919
- if (projectRoot && absolutePath.startsWith(projectRoot)) {
920
- sourceFile = absolutePath.substring(projectRoot.length + 1)
921
- if (sourceFile.startsWith('/')) {
922
- sourceFile = sourceFile.substring(1)
923
- }
924
- }
925
- }
926
- }
927
- console.log('[Claude Dev Server] Final React source:', sourceFile, sourceLine)
928
- } else {
929
- // Try going up the fiber tree
930
- let currentFiber = fiber
931
- let depth = 0
932
- while (currentFiber && depth < 20) {
933
- const jsxDevLoc = extractJsxDevLocation(currentFiber)
934
- if (jsxDevLoc) {
935
- sourceFile = jsxDevLoc.file
936
- sourceLine = jsxDevLoc.line
937
- sourceColumn = jsxDevLoc.column
938
-
939
- if (sourceFile.startsWith('/')) {
940
- const projectRoot = window.__CLAUDE_PROJECT_ROOT__
941
- if (projectRoot && sourceFile.startsWith(projectRoot)) {
942
- sourceFile = sourceFile.substring(projectRoot.length + 1)
943
- if (sourceFile.startsWith('/')) {
944
- sourceFile = sourceFile.substring(1)
945
- }
946
- }
947
- }
948
- console.log('[Claude Dev Server] Found jsxDEV location at depth', depth, ':', sourceFile, sourceLine)
949
- break
950
- }
951
-
952
- const ds = currentFiber._debugSource || currentFiber.elementType?._debugSource || currentFiber.type?._debugSource
953
- if (ds && ds.fileName) {
954
- sourceFile = ds.fileName
955
- sourceLine = ds.lineNumber || 1
956
- sourceColumn = ds.columnNumber || 1
957
-
958
- // Use server-side source map resolution
959
- const original = await resolveSourceMap(sourceFile, sourceLine, sourceColumn)
960
- if (original) {
961
- sourceFile = original.file
962
- sourceLine = original.line
963
- sourceColumn = original.column
964
- } else {
965
- // Convert absolute path to relative path using project root
966
- if (sourceFile.startsWith('/')) {
967
- const projectRoot = window.__CLAUDE_PROJECT_ROOT__
968
- if (projectRoot && sourceFile.startsWith(projectRoot)) {
969
- sourceFile = sourceFile.substring(projectRoot.length + 1)
970
- if (sourceFile.startsWith('/')) {
971
- sourceFile = sourceFile.substring(1)
972
- }
973
- }
974
- }
975
- }
976
- console.log('[Claude Dev Server] Found React source at depth', depth, ':', sourceFile, sourceLine)
977
- break
978
- }
979
- currentFiber = currentFiber.return || currentFiber.alternate
980
- depth++
981
- }
982
- }
983
- }
984
- }
985
-
986
- // Try Vue component
987
- if (!sourceFile) {
988
- const vueComponent = element.__vueParentComponent || element.__vnode
989
- if (vueComponent) {
990
- const type = vueComponent.type || vueComponent.component
991
- if (type && type.__file) {
992
- sourceFile = type.__file
993
- console.log('[Claude Dev Server] Found Vue source:', sourceFile)
994
- }
995
- }
996
- }
997
-
998
- // Try Vite's HMR source map
999
- if (!sourceFile) {
1000
- // Look for data-vite-dev-id or similar attributes
1001
- const viteId = element.getAttribute('data-vite-dev-id')
1002
- if (viteId) {
1003
- // Extract file path from viteId (remove query params if any)
1004
- const queryIndex = viteId.indexOf('?')
1005
- sourceFile = '/' + (queryIndex >= 0 ? viteId.substring(0, queryIndex) : viteId)
1006
- console.log('[Claude Dev Server] Found Vite ID:', viteId)
1007
- }
1008
- }
1009
-
1010
- // Check for inline script
1011
- if (!sourceFile) {
1012
- const inlineScripts = iframeDoc.querySelectorAll('script:not([src])')
1013
- if (inlineScripts.length > 0) {
1014
- const pathParts = window.location.pathname.split('/')
1015
- const fileName = pathParts[pathParts.length - 1] || 'index.html'
1016
- sourceFile = '/' + fileName
1017
-
1018
- const html = iframeDoc.documentElement.outerHTML
1019
- const elId = element.id ? element.id : ''
1020
- const elClass = element.className ? element.className : ''
1021
- let elPattern
1022
- if (elId) {
1023
- elPattern = 'id="' + elId + '"'
1024
- } else if (elClass) {
1025
- elPattern = 'class="' + elClass.split(' ')[0] + '"'
1026
- } else {
1027
- elPattern = element.tagName.toLowerCase()
1028
- }
1029
-
1030
- const matchIndex = html.indexOf(elPattern)
1031
- if (matchIndex !== -1) {
1032
- const beforeElement = html.substring(0, matchIndex)
1033
- sourceLine = beforeElement.split('\\n').length
1034
- }
1035
- }
1036
- }
1037
-
1038
- // Find main script
1039
- if (!sourceFile) {
1040
- const scripts = iframeDoc.querySelectorAll('script[src]')
1041
- for (const script of scripts) {
1042
- const src = script.getAttribute('src')
1043
- if (src && !src.includes('cdn') && !src.includes('node_modules') &&
1044
- (src.includes('/src/') || src.includes('/app.') || src.includes('/main.'))) {
1045
- sourceFile = src
1046
- break
1047
- }
1048
- }
1049
- }
1050
-
1051
- // Fallback
1052
- if (!sourceFile) {
1053
- const pathParts = window.location.pathname.split('/')
1054
- const fileName = pathParts[pathParts.length - 1] || 'index.html'
1055
- sourceFile = '/' + fileName
1056
- }
1057
-
1058
- const elClassName = element.className ? String(element.className) : ''
1059
- // Limit classnames to first 2-3 to avoid overly long selectors
1060
- const classNames = elClassName.split(' ').filter(c => c).slice(0, 3)
1061
- const selector = element.tagName.toLowerCase() +
1062
- (element.id ? '#' + element.id : '') +
1063
- (classNames.length ? '.' + classNames.join('.') : '')
1064
-
1065
- // Get text content for better context
1066
- let textContent = ''
1067
- if (element.nodeType === Node.TEXT_NODE) {
1068
- textContent = element.textContent ? element.textContent.trim().substring(0, 50) : ''
1069
- } else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
1070
- textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : ''
1071
- } else {
1072
- // Try to get first text node from children
1073
- for (const child of element.childNodes) {
1074
- if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
1075
- textContent = child.textContent.trim().substring(0, 50)
1076
- break
1077
- }
1078
- }
1079
- }
1080
- if (textContent && textContent.length >= 50) {
1081
- textContent += '...'
1082
- }
1083
-
1084
- return {
1085
- url: sourceFile,
1086
- line: sourceLine,
1087
- column: sourceColumn,
1088
- selector: selector,
1089
- text: textContent,
1090
- hint: sourceFile ? 'File: ' + sourceFile : 'Element: ' + selector
1091
- }
1092
- }
1093
-
1094
- function setupDraggable(divider) {
1095
- let startValue = 0
1096
- let startSize = 0
1097
- let containerSize = 0
1098
- let isPortrait = window.matchMedia('(orientation: portrait)').matches
1099
-
1100
- // Remove existing listeners to avoid duplicates
1101
- divider.removeEventListener('mousedown', startDrag)
1102
- divider.removeEventListener('touchstart', startDrag)
1103
-
1104
- divider.addEventListener('mousedown', startDrag)
1105
- divider.addEventListener('touchstart', startDrag)
1106
-
1107
- // Listen for orientation changes
1108
- window.matchMedia('(orientation: portrait)').addEventListener('change', (e) => {
1109
- isPortrait = e.matches
1110
- })
1111
-
1112
- function startDrag(e) {
1113
- isDragging = true
1114
- divider.classList.add('dragging')
1115
-
1116
- // Disable pointer events on iframes to prevent interference
1117
- const iframes = document.querySelectorAll('iframe')
1118
- iframes.forEach(iframe => iframe.style.pointerEvents = 'none')
1119
-
1120
- const clientX = e.touches ? e.touches[0].clientX : e.clientX
1121
- const clientY = e.touches ? e.touches[0].clientY : e.clientY
1122
-
1123
- // Use X for landscape (horizontal drag), Y for portrait (vertical drag)
1124
- startValue = isPortrait ? clientY : clientX
1125
-
1126
- const container = document.querySelector('.claude-dev-server-container')
1127
- const leftPanelEl = document.querySelector('.claude-dev-server-left')
1128
-
1129
- if (container && leftPanelEl) {
1130
- containerSize = isPortrait ? container.offsetHeight : container.offsetWidth
1131
- startSize = isPortrait ? leftPanelEl.offsetHeight : leftPanelEl.offsetWidth
1132
- }
1133
-
1134
- e.preventDefault()
1135
- }
1136
-
1137
- function drag(e) {
1138
- if (!isDragging) return
1139
-
1140
- // Safety check: if mouse button is not pressed, end drag
1141
- if (e.buttons === 0 && !e.touches) {
1142
- endDrag()
1143
- return
1144
- }
1145
-
1146
- const clientX = e.touches ? e.touches[0].clientX : e.clientX
1147
- const clientY = e.touches ? e.touches[0].clientY : e.clientY
1148
-
1149
- // Use appropriate axis based on orientation
1150
- const currentValue = isPortrait ? clientY : clientX
1151
- const delta = currentValue - startValue
1152
-
1153
- // Calculate new size in pixels, then convert to percentage
1154
- let newSize = startSize + delta
1155
- let percentage = (newSize / containerSize) * 100
1156
-
1157
- // Clamp between 20% and 80%
1158
- const clamped = Math.max(20, Math.min(80, percentage))
1159
-
1160
- const leftPanelEl = document.querySelector('.claude-dev-server-left')
1161
- if (leftPanelEl) {
1162
- leftPanelEl.style.flex = 'none'
1163
- if (isPortrait) {
1164
- // Portrait: adjust height
1165
- leftPanelEl.style.height = clamped + '%'
1166
- leftPanelEl.style.width = ''
1167
- } else {
1168
- // Landscape: adjust width
1169
- leftPanelEl.style.width = clamped + '%'
1170
- leftPanelEl.style.height = ''
1171
- }
1172
- }
1173
- }
1174
-
1175
- function endDrag() {
1176
- if (isDragging) {
1177
- isDragging = false
1178
- divider.classList.remove('dragging')
1179
-
1180
- // Re-enable pointer events on iframes
1181
- const iframes = document.querySelectorAll('iframe')
1182
- iframes.forEach(iframe => iframe.style.pointerEvents = '')
1183
- }
1184
- }
1185
-
1186
- // Use capture phase to ensure events are caught
1187
- // Remove old listeners first to prevent duplicates
1188
- document.removeEventListener('mousemove', drag, true)
1189
- document.removeEventListener('touchmove', drag, true)
1190
- document.removeEventListener('mouseup', endDrag, true)
1191
- document.removeEventListener('touchend', endDrag, true)
1192
-
1193
- document.addEventListener('mousemove', drag, true)
1194
- document.addEventListener('touchmove', drag, { passive: false, capture: true })
1195
- document.addEventListener('mouseup', endDrag, true)
1196
- document.addEventListener('touchend', endDrag, true)
1197
- }
1198
-
1199
- function createOverlay() {
1200
- if (overlay) return
1201
- overlay = document.createElement('div')
1202
- overlay.className = 'claude-dev-server-inspect-overlay'
1203
- document.body.appendChild(overlay)
1204
- }
1205
-
1206
- function enableInspectMode() {
1207
- isInspectMode = true
1208
- if (overlay) overlay.classList.add('active')
1209
- // Setup inspect listeners on the iframe
1210
- setupIframeInspectListeners()
1211
- // Change cursor in iframe
1212
- if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
1213
- devIframe.contentDocument.body.style.cursor = 'crosshair'
1214
- }
1215
- const btn = document.querySelector('.claude-dev-server-btn-inspect')
1216
- if (btn) btn.classList.add('active')
1217
- }
1218
-
1219
- function disableInspectMode() {
1220
- isInspectMode = false
1221
- if (overlay) {
1222
- overlay.classList.remove('active')
1223
- overlay.innerHTML = ''
1224
- }
1225
- // Restore cursor in iframe
1226
- if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
1227
- devIframe.contentDocument.body.style.cursor = ''
1228
- }
1229
- const btn = document.querySelector('.claude-dev-server-btn-inspect')
1230
- if (btn) btn.classList.remove('active')
1231
-
1232
- // Remove inspect listeners to free up resources
1233
- removeIframeInspectListeners()
1234
- }
1235
-
1236
- function removeIframeInspectListeners() {
1237
- if (!devIframe || !devIframe.contentDocument) return
1238
-
1239
- const iframeDoc = devIframe.contentDocument
1240
- const existingHandler = iframeDoc._claudeInspectHandler
1241
-
1242
- if (existingHandler) {
1243
- iframeDoc.removeEventListener('click', existingHandler, true)
1244
- iframeDoc.removeEventListener('mousemove', existingHandler, true)
1245
- delete iframeDoc._claudeInspectHandler
1246
- inspectListenersRegistered = false
1247
- }
1248
- }
1249
-
1250
- async function sendToTerminal(location) {
1251
- // Use format: @filename <selector> "text content" (without line number)
1252
- const filePath = location.url || location.file || 'unknown'
1253
-
1254
- // Build selector - handle Tailwind CSS classes by limiting
1255
- const tagName = location.selector ? location.selector.split(/[.#]/)[0] : 'div'
1256
- let selector = location.selector || tagName
1257
-
1258
- // Get text content for better context
1259
- let textContent = ''
1260
- if (location.text) {
1261
- textContent = location.text
1262
- } else if (location.hint && location.hint.includes('File:')) {
1263
- // No text content available
1264
- }
1265
-
1266
- // Format: @filename <selector> "text content" (no line number)
1267
- let prompt = \`@\${filePath} <\${selector}>\`
1268
- if (textContent) {
1269
- prompt += \` "\${textContent}"\`
1270
- }
1271
-
1272
- console.log('[Claude Dev Server] Sending to terminal:', prompt)
1273
-
1274
- // Store the interval ID so we can clear it if needed
1275
- if (!window._claudeSendRetryInterval) {
1276
- window._claudeSendRetryInterval = null
1277
- }
1278
-
1279
- // Clear any existing retry interval
1280
- if (window._claudeSendRetryInterval) {
1281
- clearInterval(window._claudeSendRetryInterval)
1282
- window._claudeSendRetryInterval = null
1283
- }
1284
-
1285
- // Send to terminal with retry logic
1286
- const sendToTerminalInternal = () => {
1287
- if (!ttydIframe || !ttydIframe.contentWindow) {
1288
- return false
1289
- }
1290
-
1291
- const win = ttydIframe.contentWindow
1292
- if (win.sendToTerminal) {
1293
- win.sendToTerminal(prompt)
1294
- console.log('[Claude Dev Server] Sent via sendToTerminal:', prompt)
1295
- // Clear interval on success
1296
- if (window._claudeSendRetryInterval) {
1297
- clearInterval(window._claudeSendRetryInterval)
1298
- window._claudeSendRetryInterval = null
1299
- }
1300
- return true
1301
- }
1302
-
1303
- return false
1304
- }
1305
-
1306
- // Try immediately
1307
- if (sendToTerminalInternal()) {
1308
- return
1309
- }
1310
-
1311
- // If not ready, wait and retry
1312
- let attempts = 0
1313
- const maxAttempts = 50
1314
- window._claudeSendRetryInterval = setInterval(() => {
1315
- attempts++
1316
- if (sendToTerminalInternal() || attempts >= maxAttempts) {
1317
- clearInterval(window._claudeSendRetryInterval)
1318
- window._claudeSendRetryInterval = null
1319
- if (attempts >= maxAttempts) {
1320
- console.warn('[Claude Dev Server] Could not send to terminal after retries')
1321
- }
1322
- }
1323
- }, 100)
1324
- }
1325
-
1326
- function loadTerminalIframe(ttydUrl) {
1327
- if (!ttydIframe) return
1328
- ttydWsUrl = ttydUrl
1329
- ttydIframe.src = '/ttyd/index.html?ws=' + encodeURIComponent(ttydUrl)
1330
- ttydIframe.onload = () => {
1331
- console.log('[Claude Dev Server] Terminal iframe loaded')
1332
- }
1333
- }
1334
-
1335
- function connect(port) {
1336
- const WS_URL = 'ws://localhost:' + port
1337
- ws = new WebSocket(WS_URL)
1338
-
1339
- ws.onopen = () => {
1340
- console.log('[Claude Dev Server] Connected to control server')
1341
- }
1342
-
1343
- ws.onmessage = (e) => {
1344
- try {
1345
- const msg = JSON.parse(e.data)
1346
- console.log('[Claude Dev Server] Received message:', msg.type, msg)
1347
- if (msg.type === 'ready' && msg.ttydUrl) {
1348
- loadTerminalIframe(msg.ttydUrl)
1349
- }
1350
- } catch (err) {
1351
- console.error('[Claude Dev Server] Message parse error:', err)
1352
- }
1353
- }
1354
-
1355
- ws.onclose = () => {
1356
- console.log('[Claude Dev Server] Control WebSocket disconnected, reconnecting...')
1357
- setTimeout(() => connect(port), 2000)
1358
- }
1359
-
1360
- ws.onerror = (err) => {
1361
- console.error('[Claude Dev Server] Control WebSocket error:', err)
1362
- }
1363
- }
1364
-
1365
- initWhenReady()
1366
-
1367
- document.addEventListener('keydown', (e) => {
1368
- if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
1369
- e.preventDefault()
1370
- if (isInspectMode) {
1371
- disableInspectMode()
1372
- } else {
1373
- enableInspectMode()
1374
- }
1375
- }
1376
- if (e.key === 'Escape' && isInspectMode) {
1377
- disableInspectMode()
1378
- }
1379
- })
1380
- })()
1381
- `;
1382
-
1383
- // src/universal/index.ts
1384
- var __filename$1 = fileURLToPath(import.meta.url);
1385
- dirname(__filename$1);
1386
- async function startUniversalServer(options = {}) {
1387
- const cwd = options.cwd || process.cwd();
1388
- const port = options.port || 3e3;
1389
- const projectType = options.server?.type || detectProjectType(cwd);
1390
- const targetCommand = options.server?.command || getDefaultCommand(projectType);
1391
- console.log(`[Claude Dev Server] Detected project type: ${projectType}`);
1392
- console.log(`[Claude Dev Server] Starting target server...`);
1393
- const targetServer = spawnTargetServer(targetCommand, cwd);
1394
- console.log(`[Claude Dev Server] Waiting for target server to start (PID: ${targetServer.pid})...`);
1395
- const targetPort = await detectServerPort(targetServer, 3e4);
1396
- if (!targetPort) {
1397
- throw new Error("Failed to detect target server port. Please check if the dev server started successfully.");
1398
- }
1399
- console.log(`[Claude Dev Server] Target server is running on port ${targetPort}`);
1400
- const wsServer = createWebSocketServer({
1401
- port: 0,
1402
- // 自动分配端口
1403
- projectRoot: cwd,
1404
- claudePath: options.claudePath || "claude",
1405
- claudeArgs: options.claudeArgs || []
1406
- });
1407
- const { wsPort, ttydPort } = await wsServer.start();
1408
- console.log(`[Claude Dev Server] Control server running on ws://localhost:${wsPort}`);
1409
- console.log(`[Claude Dev Server] ttyd running on ws://localhost:${ttydPort}`);
1410
- const proxyServer = createProxyServer(targetPort, wsPort, cwd);
1411
- proxyServer.listen(port);
1412
- console.log(`[Claude Dev Server] Proxy server running on http://localhost:${port}`);
1413
- console.log(`
1414
- \u{1F680} Ready! Open http://localhost:${port} in your browser`);
1415
- const cleanup = () => {
1416
- console.log("[Claude Dev Server] Shutting down...");
1417
- targetServer.kill();
1418
- wsServer.stop();
1419
- proxyServer.close();
1420
- process.exit(0);
1421
- };
1422
- process.on("SIGINT", cleanup);
1423
- process.on("SIGTERM", cleanup);
1424
- return { proxyServer, targetServer, wsServer };
1425
- }
1426
- function detectProjectType(cwd) {
1427
- if (existsSync(join(cwd, "vite.config.ts")) || existsSync(join(cwd, "vite.config.js"))) {
1428
- return "vite";
1429
- }
1430
- if (existsSync(join(cwd, "next.config.js")) || existsSync(join(cwd, "next.config.mjs"))) {
1431
- return "next";
1432
- }
1433
- if (existsSync(join(cwd, "webpack.config.js")) || existsSync(join(cwd, "webpack.config.ts"))) {
1434
- return "webpack";
1435
- }
1436
- return "custom";
1437
- }
1438
- function getDefaultCommand(projectType) {
1439
- switch (projectType) {
1440
- case "vite":
1441
- return "npm run dev";
1442
- case "next":
1443
- return "npm run dev";
1444
- case "webpack":
1445
- return "npm run dev";
1446
- default:
1447
- return "npm run dev";
1448
- }
1449
- }
1450
- function spawnTargetServer(command, cwd) {
1451
- const [cmd, ...args] = command.split(" ");
1452
- const child = spawn(cmd, args, {
1453
- cwd,
1454
- stdio: "pipe",
1455
- env: process.env
1456
- });
1457
- child.stdout?.pipe(process.stdout);
1458
- child.stderr?.pipe(process.stderr);
1459
- return child;
1460
- }
1461
- async function detectServerPort(childProcess, timeout) {
1462
- return new Promise((resolve2) => {
1463
- const timeoutId = setTimeout(() => {
1464
- cleanup();
1465
- resolve2(null);
1466
- }, timeout);
1467
- let stdout = "";
1468
- const onData = (chunk) => {
1469
- stdout += chunk.toString();
1470
- const patterns = [
1471
- /localhost:(\d{4,5})/,
1472
- /127\.0\.0\.1:(\d{4,5})/,
1473
- /0\.0\.0\.0:(\d{4,5})/
1474
- ];
1475
- for (const pattern of patterns) {
1476
- const match = stdout.match(pattern);
1477
- if (match) {
1478
- const port = parseInt(match[1], 10);
1479
- console.log(`[Claude Dev Server] Detected port from stdout: ${port}`);
1480
- cleanup();
1481
- resolve2(port);
1482
- return;
1483
- }
1484
- }
1485
- };
1486
- const cleanup = () => {
1487
- clearTimeout(timeoutId);
1488
- childProcess.stdout?.off("data", onData);
1489
- };
1490
- childProcess.stdout?.on("data", onData);
1491
- });
1492
- }
1493
- async function handleSourceMapRequest(projectRoot, filePath, line, column, targetPort) {
1494
- try {
1495
- let content;
1496
- let fullPath = filePath;
1497
- if (filePath.startsWith("/") && targetPort) {
1498
- try {
1499
- const response = await fetch(`http://localhost:${targetPort}${filePath}`);
1500
- if (!response.ok) {
1501
- console.log("[Claude Dev Server] Failed to fetch from Dev Server:", response.status);
1502
- return { error: "Failed to fetch from Dev Server" };
1503
- }
1504
- content = await response.text();
1505
- console.log("[Claude Dev Server] Fetched", content.length, "chars from Dev Server");
1506
- } catch (e) {
1507
- console.log("[Claude Dev Server] Fetch error:", e);
1508
- return { error: "Fetch error: " + e.message };
1509
- }
1510
- } else {
1511
- if (!filePath.startsWith("/")) {
1512
- fullPath = join(projectRoot, filePath);
1513
- }
1514
- if (!existsSync(fullPath)) {
1515
- console.log("[Claude Dev Server] File not found:", fullPath);
1516
- return { error: "File not found" };
1517
- }
1518
- content = readFileSync(fullPath, "utf-8");
1519
- console.log("[Claude Dev Server] Resolving source map for:", fullPath, "at line:", line);
1520
- }
1521
- let sourceMapUrl = null;
1522
- const patterns = [
1523
- /\/\/[@#]\s*sourceMappingURL=([^\s]+)/,
1524
- /\/\*[@#]\s*sourceMappingURL=([^\s]+)\s*\*\//
1525
- ];
1526
- for (const pattern of patterns) {
1527
- const match = content.match(pattern);
1528
- if (match) {
1529
- sourceMapUrl = match[1];
1530
- console.log("[Claude Dev Server] Found sourceMappingURL:", sourceMapUrl.substring(0, 100) + "...");
1531
- break;
1532
- }
1533
- }
1534
- if (!sourceMapUrl) {
1535
- console.log("[Claude Dev Server] No source map found in:", fullPath);
1536
- return { file: relative(projectRoot, fullPath), line, column };
1537
- }
1538
- let sourceMapContent;
1539
- let sourceMap;
1540
- if (sourceMapUrl.startsWith("data:application/json;base64,") || sourceMapUrl.startsWith("data:application/json;charset=utf-8;base64,")) {
1541
- console.log("[Claude Dev Server] Found inline source map");
1542
- const base64Data = sourceMapUrl.split(",", 2)[1];
1543
- sourceMapContent = Buffer.from(base64Data, "base64").toString("utf-8");
1544
- try {
1545
- sourceMap = JSON.parse(sourceMapContent);
1546
- } catch (e) {
1547
- console.log("[Claude Dev Server] Failed to parse inline source map:", e);
1548
- return { file: relative(projectRoot, fullPath), line, column };
1549
- }
1550
- } else if (sourceMapUrl.startsWith("http://") || sourceMapUrl.startsWith("https://")) {
1551
- console.log("[Claude Dev Server] Remote source map not supported:", sourceMapUrl);
1552
- return { file: relative(projectRoot, fullPath), line, column };
1553
- } else {
1554
- let sourceMapPath;
1555
- if (sourceMapUrl.startsWith("/")) {
1556
- sourceMapPath = sourceMapUrl;
1557
- } else {
1558
- sourceMapPath = join(dirname(fullPath), sourceMapUrl);
1559
- }
1560
- console.log("[Claude Dev Server] Reading external source map:", sourceMapPath);
1561
- if (!existsSync(sourceMapPath)) {
1562
- console.log("[Claude Dev Server] Source map file not found:", sourceMapPath);
1563
- return { file: relative(projectRoot, fullPath), line, column };
1564
- }
1565
- sourceMapContent = readFileSync(sourceMapPath, "utf-8");
1566
- sourceMap = JSON.parse(sourceMapContent);
1567
- }
1568
- const consumer = await new SourceMapConsumer(sourceMap);
1569
- const original = consumer.originalPositionFor({
1570
- line,
1571
- column
1572
- });
1573
- consumer.destroy();
1574
- if (original.source && original.line !== null) {
1575
- let originalFile = original.source;
1576
- if (originalFile.startsWith("webpack://")) {
1577
- originalFile = originalFile.replace(/^webpack:\/\/[\/\\]?/, "");
1578
- }
1579
- if (!originalFile.startsWith("/")) {
1580
- const possiblePath = join(projectRoot, originalFile);
1581
- if (existsSync(possiblePath)) {
1582
- } else {
1583
- const fileName = originalFile.split("/").pop() || originalFile.split("\\").pop() || originalFile;
1584
- if (fileName === originalFile) {
1585
- if (filePath.startsWith("/")) {
1586
- originalFile = filePath.substring(1);
1587
- console.log("[Claude Dev Server] Source is just a filename, using filePath:", originalFile);
1588
- } else {
1589
- originalFile = relative(projectRoot, fullPath);
1590
- }
1591
- } else {
1592
- originalFile = relative(projectRoot, possiblePath);
1593
- }
1594
- }
1595
- }
1596
- return {
1597
- file: originalFile,
1598
- line: original.line,
1599
- column: original.column || 1,
1600
- original: {
1601
- source: original.source,
1602
- line: original.line,
1603
- column: original.column,
1604
- name: original.name
1605
- }
1606
- };
1607
- }
1608
- return { file: relative(projectRoot, fullPath), line, column };
1609
- } catch (err) {
1610
- console.error("[Claude Dev Server] Source map resolution error:", err);
1611
- return { error: String(err) };
1612
- }
1613
- }
1614
- async function handleTurbopackLookup(projectRoot, pagePath, targetPort) {
1615
- try {
1616
- console.log("[Claude Dev Server] Turbopack lookup for page:", pagePath);
1617
- const pageRes = await fetch(`http://localhost:${targetPort}${pagePath}`);
1618
- if (!pageRes.ok) {
1619
- return { error: "Failed to fetch page" };
1620
- }
1621
- const html = await pageRes.text();
1622
- const chunkUrls = [];
1623
- const scriptMatches = html.matchAll(/<script[^>]*src="([^"]*\/_next\/static\/chunks\/[^"]*)"/g);
1624
- for (const match of scriptMatches) {
1625
- if (match[1]) {
1626
- chunkUrls.push(match[1]);
1627
- }
1628
- }
1629
- console.log("[Claude Dev Server] Found", chunkUrls.length, "chunk URLs");
1630
- for (const chunkUrl of chunkUrls) {
1631
- try {
1632
- const fullUrl = chunkUrl.startsWith("http") ? chunkUrl : `http://localhost:${targetPort}${chunkUrl}`;
1633
- const chunkRes = await fetch(fullUrl);
1634
- if (!chunkRes.ok) continue;
1635
- const chunkContent = await chunkRes.text();
1636
- const modulePathRegex = /\[project\]([^\s"]+\.(tsx?|jsx?))/g;
1637
- const matches = [...chunkContent.matchAll(modulePathRegex)];
1638
- for (const match of matches) {
1639
- if (match[1]) {
1640
- const sourcePath = match[1];
1641
- let relativePath = sourcePath.replace(/^\[project\]/, "");
1642
- const normalizedPagePath = pagePath.replace(/^\/[^/]+/, "");
1643
- if (relativePath.toLowerCase().includes(normalizedPagePath.toLowerCase()) || relativePath.toLowerCase().includes("login")) {
1644
- console.log("[Claude Dev Server] Found source file:", relativePath);
1645
- return {
1646
- file: relativePath,
1647
- line: void 0
1648
- // Turbopack doesn't provide line numbers
1649
- };
1650
- }
1651
- }
1652
- }
1653
- } catch (e) {
1654
- console.log("[Claude Dev Server] Error fetching chunk:", chunkUrl, e);
1655
- }
1656
- }
1657
- return { error: "Source file not found for page: " + pagePath };
1658
- } catch (err) {
1659
- console.error("[Claude Dev Server] Turbopack lookup error:", err);
1660
- return { error: String(err) };
1661
- }
1662
- }
1663
- function isHtmlPageRequest(req) {
1664
- const accept = req.headers.accept || "";
1665
- const url = req.url || "";
1666
- if (!accept.includes("text/html")) {
1667
- return false;
1668
- }
1669
- if (url.startsWith("/@") || url.startsWith("/_next/") || url.startsWith("/ttyd") || url.startsWith("/dev.html")) {
1670
- return false;
1671
- }
1672
- return true;
1673
- }
1674
- function createProxyServer(targetPort, wsPort, projectRoot) {
1675
- const moduleDir = dirname(fileURLToPath(import.meta.url));
1676
- const assetsPath = join(moduleDir, "assets");
1677
- let ttydHtml;
1678
- let ttydBridgeJs;
1679
- let devHtml;
1680
- try {
1681
- ttydHtml = readFileSync(join(assetsPath, "ttyd-terminal.html"), "utf-8");
1682
- ttydBridgeJs = readFileSync(join(assetsPath, "ttyd-bridge.js"), "utf-8");
1683
- devHtml = readFileSync(join(assetsPath, "dev.html"), "utf-8");
1684
- } catch (e) {
1685
- console.error("[Claude Dev Server] Failed to read assets from", assetsPath);
1686
- console.error("[Claude Dev Server] moduleDir:", moduleDir);
1687
- console.error("[Claude Dev Server] Error:", e.message);
1688
- throw new Error("Assets not found. Please run `npm run build` first.");
1689
- }
1690
- const server = http.createServer((req, res) => {
1691
- const referer = req.headers.referer || "";
1692
- const isFromDevPage = referer.includes("dev.html");
1693
- if (req.url?.startsWith("/dev.html")) {
1694
- const urlParams = new URL(req.url || "", `http://${req.headers.host}`);
1695
- const originalPath = urlParams.searchParams.get("path") || "/";
1696
- const host = req.headers.host || "localhost:3000";
1697
- const origin = `http://${host}`;
1698
- const modifiedDevHtml = devHtml.replace(
1699
- /__CLAUDE_IFRAME_SRC__/g,
1700
- `${origin}${originalPath}`
1701
- ).replace(
1702
- /__CLAUDE_ORIGINAL_PATH__/g,
1703
- originalPath
1704
- );
1705
- res.setHeader("Content-Type", "text/html");
1706
- res.end(modifiedDevHtml);
1707
- return;
1708
- }
1709
- if (!isFromDevPage && isHtmlPageRequest(req)) {
1710
- const currentPath = req.url || "/";
1711
- const devPageUrl = `/dev.html?path=${encodeURIComponent(currentPath)}`;
1712
- res.writeHead(302, { "Location": devPageUrl });
1713
- res.end();
1714
- return;
1715
- }
1716
- if (req.url === "/@claude-port") {
1717
- res.setHeader("Content-Type", "application/json");
1718
- res.end(JSON.stringify({ port: wsPort }));
1719
- return;
1720
- }
1721
- if (req.url?.startsWith("/@sourcemap?")) {
1722
- const url = new URL(req.url, `http://localhost:${wsPort}`);
1723
- const file = url.searchParams.get("file");
1724
- const line = url.searchParams.get("line");
1725
- const column = url.searchParams.get("col");
1726
- if (file && line && column) {
1727
- handleSourceMapRequest(projectRoot, file, parseInt(line), parseInt(column || "1"), targetPort).then((result) => {
1728
- res.setHeader("Content-Type", "application/json");
1729
- res.end(JSON.stringify(result));
1730
- }).catch((err) => {
1731
- console.error("[Claude Dev Server] Source map error:", err);
1732
- res.setHeader("Content-Type", "application/json");
1733
- res.end(JSON.stringify({ error: err.message }));
1734
- });
1735
- return;
1736
- }
1737
- }
1738
- if (req.url?.startsWith("/@sourcemap-lookup?")) {
1739
- const url = new URL(req.url, `http://localhost:${wsPort}`);
1740
- const page = url.searchParams.get("page");
1741
- const framework = url.searchParams.get("framework");
1742
- if (page && framework === "nextjs") {
1743
- handleTurbopackLookup(projectRoot, page, targetPort).then((result) => {
1744
- res.setHeader("Content-Type", "application/json");
1745
- res.end(JSON.stringify(result));
1746
- }).catch((err) => {
1747
- console.error("[Claude Dev Server] Turbopack lookup error:", err);
1748
- res.setHeader("Content-Type", "application/json");
1749
- res.end(JSON.stringify({ error: err.message }));
1750
- });
1751
- return;
1752
- }
1753
- }
1754
- if (req.url?.startsWith("/ttyd/")) {
1755
- const urlPath = req.url.split("?")[0];
1756
- if (urlPath === "/ttyd/index.html" || urlPath === "/ttyd/") {
1757
- res.setHeader("Content-Type", "text/html");
1758
- res.end(ttydHtml);
1759
- return;
1760
- }
1761
- if (urlPath === "/ttyd/ttyd-bridge.js") {
1762
- res.setHeader("Content-Type", "application/javascript");
1763
- res.end(ttydBridgeJs);
1764
- return;
1765
- }
1766
- if (urlPath === "/ttyd/token" || urlPath === "/ttyd/index.html/token") {
1767
- res.setHeader("Content-Type", "application/json");
1768
- res.end(JSON.stringify({ token: "" }));
1769
- return;
1770
- }
1771
- res.statusCode = 404;
1772
- res.end("Not found");
1773
- return;
1774
- }
1775
- const proxyHeaders = { ...req.headers };
1776
- delete proxyHeaders["accept-encoding"];
1777
- const shouldInject = !isFromDevPage;
1778
- const options = {
1779
- hostname: "localhost",
1780
- port: targetPort,
1781
- path: req.url,
1782
- method: req.method,
1783
- headers: proxyHeaders
1784
- };
1785
- const proxyReq = http.request(options, (proxyRes) => {
1786
- if (proxyRes.headers["content-type"]?.includes("text/html") && shouldInject) {
1787
- const body = [];
1788
- proxyRes.on("data", (chunk) => body.push(chunk));
1789
- proxyRes.on("end", () => {
1790
- const html = Buffer.concat(body).toString("utf8");
1791
- const injected = injectScripts(html, wsPort, projectRoot);
1792
- const statusCode = proxyRes.statusCode || 200;
1793
- res.writeHead(statusCode, {
1794
- ...proxyRes.headers,
1795
- "content-length": Buffer.byteLength(injected)
1796
- });
1797
- res.end(injected);
1798
- });
1799
- } else {
1800
- const statusCode = proxyRes.statusCode || 200;
1801
- res.writeHead(statusCode, proxyRes.headers);
1802
- proxyRes.pipe(res);
1803
- }
1804
- });
1805
- proxyReq.on("error", (err) => {
1806
- console.error("[Claude Dev Server] Proxy error:", err);
1807
- res.statusCode = 502;
1808
- res.end("Bad Gateway");
1809
- });
1810
- req.pipe(proxyReq);
1811
- });
1812
- server.on("upgrade", (req, socket, head) => {
1813
- if (req.headers["upgrade"]?.toLowerCase() !== "websocket") {
1814
- return;
1815
- }
1816
- console.log("[Claude Dev Server] WebSocket upgrade request:", req.url);
1817
- const targetSocket = createConnection(targetPort, "localhost", () => {
1818
- console.log("[Claude Dev Server] Connected to target WebSocket server");
1819
- const upgradeRequest = [
1820
- `${req.method} ${req.url} HTTP/1.1`,
1821
- `Host: localhost:${targetPort}`,
1822
- "Upgrade: websocket",
1823
- "Connection: Upgrade",
1824
- `Sec-WebSocket-Key: ${req.headers["sec-websocket-key"]}`,
1825
- `Sec-WebSocket-Version: ${req.headers["sec-websocket-version"] || "13"}`
1826
- ];
1827
- if (req.headers["sec-websocket-protocol"]) {
1828
- upgradeRequest.push(`Sec-WebSocket-Protocol: ${req.headers["sec-websocket-protocol"]}`);
1829
- }
1830
- if (req.headers["sec-websocket-extensions"]) {
1831
- upgradeRequest.push(`Sec-WebSocket-Extensions: ${req.headers["sec-websocket-extensions"]}`);
1832
- }
1833
- targetSocket.write(upgradeRequest.join("\r\n") + "\r\n\r\n");
1834
- if (head && head.length > 0) {
1835
- targetSocket.write(head);
1836
- }
1837
- });
1838
- targetSocket.on("data", (data) => {
1839
- if (socket.writable) {
1840
- socket.write(data);
1841
- }
1842
- });
1843
- socket.on("data", (data) => {
1844
- if (targetSocket.writable) {
1845
- targetSocket.write(data);
1846
- }
1847
- });
1848
- socket.on("close", () => {
1849
- console.log("[Claude Dev Server] Client WebSocket closed");
1850
- targetSocket.destroy();
1851
- });
1852
- targetSocket.on("close", () => {
1853
- console.log("[Claude Dev Server] Target WebSocket closed");
1854
- socket.end();
1855
- });
1856
- socket.on("error", (err) => {
1857
- console.error("[Claude Dev Server] Client socket error:", err.message);
1858
- targetSocket.destroy();
1859
- });
1860
- targetSocket.on("error", (err) => {
1861
- console.error("[Claude Dev Server] Target socket error:", err.message);
1862
- socket.end();
1863
- });
1864
- });
1865
- return server;
1866
- }
1867
- function injectScripts(html, wsPort, projectRoot) {
1868
- if (html.includes("claude-dev-server-container") || html.includes("__CLAUDE_ORIGINAL_HTML__")) {
1869
- console.log("[Claude Dev Server] HTML already injected, returning as-is");
1870
- return html;
1871
- }
1872
- const hasOurScripts = html.includes("CLIENT_SCRIPT") || html.includes("claude-dev-server-container");
1873
- if (hasOurScripts) {
1874
- console.log("[Claude Dev Server] Warning: Original HTML already has our scripts");
1875
- }
1876
- const modifiedHtml = html;
1877
- console.log("[Claude Dev Server] Creating split layout, original HTML length:", html.length);
1878
- const base64Html = Buffer.from(modifiedHtml, "utf-8").toString("base64");
1879
- const projectRootScript = `<script>window.__CLAUDE_PROJECT_ROOT__ = ${JSON.stringify(projectRoot)};window.__CLAUDE_ORIGINAL_HTML_BASE64__ = "${base64Html}";</script>`;
1880
- return `<!DOCTYPE html>
1881
- <html lang="en">
1882
- <head>
1883
- <meta charset="UTF-8">
1884
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1885
- <title>Claude Dev Server</title>
1886
- ${CLIENT_STYLES}
1887
- ${projectRootScript}
1888
- </head>
1889
- <body>
1890
- <script type="module">${CLIENT_SCRIPT.replace(/wsPort:\s*\d+/, `wsPort: ${wsPort}`)}</script>
1891
- </body>
1892
- </html>`;
1893
- }
1894
-
1895
- export { startUniversalServer };
1896
- //# sourceMappingURL=chunk-PZVR2URG.js.map
1897
- //# sourceMappingURL=chunk-PZVR2URG.js.map