yo-bug 0.1.3 → 0.1.4

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 +1 @@
1
- {"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA2ElD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
1
+ {"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwGlD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
@@ -3,8 +3,8 @@ import cors from 'cors';
3
3
  import httpProxy from 'http-proxy';
4
4
  import { createFeedbackRouter } from './feedback-api.js';
5
5
  import { createSdkRouter } from './sdk-serve.js';
6
- import { createHtmlInjector } from './html-injector.js';
7
6
  const PROXY_PORT = 3695;
7
+ const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
8
8
  let server = null;
9
9
  let proxy = null;
10
10
  export async function startProxyServer(targetPort, store) {
@@ -14,46 +14,77 @@ export async function startProxyServer(targetPort, store) {
14
14
  const targetUrl = `http://localhost:${targetPort}`;
15
15
  const proxyUrl = `http://localhost:${PROXY_PORT}`;
16
16
  const app = express();
17
- // CORS for SDK to submit feedback
18
17
  app.use(cors());
19
- // Parse JSON for feedback API
20
18
  app.use(express.json({ limit: '10mb' }));
21
- // Serve SDK JS file
19
+ // Our own routes — served directly, not proxied
22
20
  app.use(createSdkRouter());
23
- // Feedback API routes
24
21
  app.use(createFeedbackRouter(store));
25
- // Create proxy
22
+ // Create proxy — let it handle responses by default (no selfHandleResponse)
26
23
  proxy = httpProxy.createProxyServer({
27
24
  target: targetUrl,
28
- ws: true, // WebSocket support for HMR
25
+ ws: true,
29
26
  changeOrigin: true,
30
27
  });
31
28
  proxy.on('error', (_err, _req, res) => {
32
29
  if (res && 'writeHead' in res) {
33
- res.writeHead?.(502, { 'Content-Type': 'text/plain' });
34
- res.end?.('Dev server not responding');
30
+ try {
31
+ res.writeHead?.(502, { 'Content-Type': 'text/plain' });
32
+ res.end?.('Dev server not responding');
33
+ }
34
+ catch { }
35
35
  }
36
36
  });
37
- // Intercept proxy responses to inject SDK into HTML
37
+ // Inject SDK into HTML responses using the modifyResponse approach
38
+ // We check Accept header on REQUEST to decide if we need to intercept
39
+ app.all('*', (req, res) => {
40
+ const accept = req.headers['accept'] || '';
41
+ const isHtmlRequest = accept.includes('text/html');
42
+ if (isHtmlRequest) {
43
+ // Potentially an HTML page — intercept to inject SDK
44
+ proxy.web(req, res, { selfHandleResponse: true });
45
+ }
46
+ else {
47
+ // Static asset (JS/CSS/images/fonts/etc) — let proxy handle directly
48
+ proxy.web(req, res);
49
+ }
50
+ });
51
+ // Handle HTML injection in proxyRes (only fires for selfHandleResponse requests)
38
52
  proxy.on('proxyRes', (proxyRes, _req, res) => {
39
- const injector = createHtmlInjector(proxyRes, res);
40
- if (injector) {
41
- // HTML response buffer and inject
42
- proxyRes.on('data', (chunk) => injector.onData(chunk));
43
- proxyRes.on('end', () => injector.onEnd());
53
+ const statusCode = proxyRes.statusCode || 200;
54
+ const contentType = proxyRes.headers['content-type'] || '';
55
+ // Only inject into 2xx HTML responses
56
+ if (statusCode >= 200 && statusCode < 300 && contentType.includes('text/html')) {
57
+ // Buffer response, inject SDK, send
58
+ const chunks = [];
59
+ proxyRes.on('data', (chunk) => chunks.push(chunk));
60
+ proxyRes.on('end', () => {
61
+ let html = Buffer.concat(chunks).toString('utf-8');
62
+ if (html.includes('</body>')) {
63
+ html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
64
+ }
65
+ else if (html.includes('</html>')) {
66
+ html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
67
+ }
68
+ else {
69
+ html += `\n${INJECT_SCRIPT}`;
70
+ }
71
+ // Copy headers except content-length (body size changed)
72
+ const headers = { ...proxyRes.headers };
73
+ delete headers['content-length'];
74
+ // Remove content-encoding since we decoded the buffer as utf-8
75
+ delete headers['content-encoding'];
76
+ delete headers['transfer-encoding'];
77
+ headers['content-length'] = String(Buffer.byteLength(html));
78
+ res.writeHead(statusCode, headers);
79
+ res.end(html);
80
+ });
44
81
  }
45
82
  else {
46
- // Non-HTML or redirectpipe through with original status + headers
47
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
83
+ // Redirect or non-HTMLpass through as-is
84
+ res.writeHead(statusCode, proxyRes.headers);
48
85
  proxyRes.pipe(res);
49
86
  }
50
87
  });
51
- // All other requests → proxy to dev server
52
- app.all('*', (req, res) => {
53
- proxy.web(req, res, {
54
- selfHandleResponse: true, // We handle response in proxyRes event
55
- });
56
- });
57
88
  // Start server
58
89
  await new Promise((resolve, reject) => {
59
90
  server = app.listen(PROXY_PORT, () => resolve());
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,SAAS,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB,IAAI,MAAM,GAAkB,IAAI,CAAC;AACjC,IAAI,KAAK,GAAqB,IAAI,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB,EAClB,KAAoB;IAEpB,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,SAAS,GAAG,oBAAoB,UAAU,EAAE,CAAC;IACnD,MAAM,QAAQ,GAAG,oBAAoB,UAAU,EAAE,CAAC;IAElD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IAEtB,kCAAkC;IAClC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAEhB,8BAA8B;IAC9B,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAEzC,oBAAoB;IACpB,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC;IAE3B,sBAAsB;IACtB,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;IAErC,eAAe;IACf,KAAK,GAAG,SAAS,CAAC,iBAAiB,CAAC;QAClC,MAAM,EAAE,SAAS;QACjB,EAAE,EAAE,IAAI,EAAE,4BAA4B;QACtC,YAAY,EAAE,IAAI;KACnB,CAAC,CAAC;IAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,IAAI,GAAG,IAAI,WAAW,IAAI,GAAG,EAAE,CAAC;YAC7B,GAAW,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;YAC/D,GAAW,CAAC,GAAG,EAAE,CAAC,2BAA2B,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,oDAAoD;IACpD,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QAC3C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,GAAU,CAAC,CAAC;QAC1D,IAAI,QAAQ,EAAE,CAAC;YACb,oCAAoC;YACpC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACvD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,qEAAqE;YACrE,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,GAAU,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,2CAA2C;IAC3C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,KAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE;YACnB,kBAAkB,EAAE,IAAI,EAAE,uCAAuC;SAClE,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,eAAe;IACf,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACjD,MAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;YACjD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC9B,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,UAAU,wDAAwD,CAAC,CAAC,CAAC;YAChG,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,mCAAmC;IACnC,MAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAQ,EAAE,MAAW,EAAE,IAAS,EAAE,EAAE;QACzD,KAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,KAAK,EAAE,CAAC;QACV,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IACD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,MAAM,KAAK,IAAI,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yo-bug",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "MCP Server for visual test feedback in vibe coding — QA capability as a protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,13 +1,13 @@
1
1
  import express from 'express';
2
2
  import cors from 'cors';
3
3
  import httpProxy from 'http-proxy';
4
- import type { Server } from 'http';
4
+ import type { Server, IncomingMessage, ServerResponse } from 'http';
5
5
  import { FeedbackStore } from '../storage/store.js';
6
6
  import { createFeedbackRouter } from './feedback-api.js';
7
7
  import { createSdkRouter } from './sdk-serve.js';
8
- import { createHtmlInjector } from './html-injector.js';
9
8
 
10
9
  const PROXY_PORT = 3695;
10
+ const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
11
11
 
12
12
  let server: Server | null = null;
13
13
  let proxy: httpProxy | null = null;
@@ -24,52 +24,81 @@ export async function startProxyServer(
24
24
  const proxyUrl = `http://localhost:${PROXY_PORT}`;
25
25
 
26
26
  const app = express();
27
-
28
- // CORS for SDK to submit feedback
29
27
  app.use(cors());
30
-
31
- // Parse JSON for feedback API
32
28
  app.use(express.json({ limit: '10mb' }));
33
29
 
34
- // Serve SDK JS file
30
+ // Our own routes — served directly, not proxied
35
31
  app.use(createSdkRouter());
36
-
37
- // Feedback API routes
38
32
  app.use(createFeedbackRouter(store));
39
33
 
40
- // Create proxy
34
+ // Create proxy — let it handle responses by default (no selfHandleResponse)
41
35
  proxy = httpProxy.createProxyServer({
42
36
  target: targetUrl,
43
- ws: true, // WebSocket support for HMR
37
+ ws: true,
44
38
  changeOrigin: true,
45
39
  });
46
40
 
47
41
  proxy.on('error', (_err, _req, res) => {
48
42
  if (res && 'writeHead' in res) {
49
- (res as any).writeHead?.(502, { 'Content-Type': 'text/plain' });
50
- (res as any).end?.('Dev server not responding');
43
+ try {
44
+ (res as any).writeHead?.(502, { 'Content-Type': 'text/plain' });
45
+ (res as any).end?.('Dev server not responding');
46
+ } catch {}
51
47
  }
52
48
  });
53
49
 
54
- // Intercept proxy responses to inject SDK into HTML
55
- proxy.on('proxyRes', (proxyRes, _req, res) => {
56
- const injector = createHtmlInjector(proxyRes, res as any);
57
- if (injector) {
58
- // HTML response — buffer and inject
59
- proxyRes.on('data', (chunk) => injector.onData(chunk));
60
- proxyRes.on('end', () => injector.onEnd());
50
+ // Inject SDK into HTML responses using the modifyResponse approach
51
+ // We check Accept header on REQUEST to decide if we need to intercept
52
+ app.all('*', (req, res) => {
53
+ const accept = req.headers['accept'] || '';
54
+ const isHtmlRequest = accept.includes('text/html');
55
+
56
+ if (isHtmlRequest) {
57
+ // Potentially an HTML page — intercept to inject SDK
58
+ proxy!.web(req, res, { selfHandleResponse: true });
61
59
  } else {
62
- // Non-HTML or redirectpipe through with original status + headers
63
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
64
- proxyRes.pipe(res as any);
60
+ // Static asset (JS/CSS/images/fonts/etc)let proxy handle directly
61
+ proxy!.web(req, res);
65
62
  }
66
63
  });
67
64
 
68
- // All other requests proxy to dev server
69
- app.all('*', (req, res) => {
70
- proxy!.web(req, res, {
71
- selfHandleResponse: true, // We handle response in proxyRes event
72
- });
65
+ // Handle HTML injection in proxyRes (only fires for selfHandleResponse requests)
66
+ proxy.on('proxyRes', (proxyRes, _req, res) => {
67
+ const statusCode = proxyRes.statusCode || 200;
68
+ const contentType = proxyRes.headers['content-type'] || '';
69
+
70
+ // Only inject into 2xx HTML responses
71
+ if (statusCode >= 200 && statusCode < 300 && contentType.includes('text/html')) {
72
+ // Buffer response, inject SDK, send
73
+ const chunks: Buffer[] = [];
74
+ proxyRes.on('data', (chunk) => chunks.push(chunk));
75
+ proxyRes.on('end', () => {
76
+ let html = Buffer.concat(chunks).toString('utf-8');
77
+
78
+ if (html.includes('</body>')) {
79
+ html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
80
+ } else if (html.includes('</html>')) {
81
+ html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
82
+ } else {
83
+ html += `\n${INJECT_SCRIPT}`;
84
+ }
85
+
86
+ // Copy headers except content-length (body size changed)
87
+ const headers = { ...proxyRes.headers };
88
+ delete headers['content-length'];
89
+ // Remove content-encoding since we decoded the buffer as utf-8
90
+ delete headers['content-encoding'];
91
+ delete headers['transfer-encoding'];
92
+ headers['content-length'] = String(Buffer.byteLength(html));
93
+
94
+ res.writeHead(statusCode, headers);
95
+ res.end(html);
96
+ });
97
+ } else {
98
+ // Redirect or non-HTML — pass through as-is
99
+ res.writeHead(statusCode, proxyRes.headers);
100
+ proxyRes.pipe(res);
101
+ }
73
102
  });
74
103
 
75
104
  // Start server
@@ -1,46 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'http';
2
-
3
- const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
4
-
5
- /**
6
- * Middleware that intercepts HTML responses from the proxy
7
- * and injects the vibe-feedback SDK script before </body>.
8
- * Only intercepts 2xx HTML responses — redirects and errors pass through.
9
- */
10
- export function createHtmlInjector(proxyRes: IncomingMessage, res: ServerResponse) {
11
- const statusCode = proxyRes.statusCode || 200;
12
- const contentType = proxyRes.headers['content-type'] || '';
13
-
14
- // Only inject into 2xx HTML responses
15
- // Don't touch redirects (3xx), errors (4xx/5xx), or non-HTML
16
- if (statusCode < 200 || statusCode >= 300 || !contentType.includes('text/html')) {
17
- return null;
18
- }
19
-
20
- // Copy all response headers except content-length (we'll modify the body)
21
- const headers = Object.fromEntries(
22
- Object.entries(proxyRes.headers).filter(([key]) => key !== 'content-length')
23
- );
24
- res.writeHead(statusCode, headers);
25
-
26
- const chunks: Buffer[] = [];
27
-
28
- return {
29
- onData(chunk: Buffer) {
30
- chunks.push(chunk);
31
- },
32
- onEnd() {
33
- let html = Buffer.concat(chunks).toString('utf-8');
34
-
35
- if (html.includes('</body>')) {
36
- html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
37
- } else if (html.includes('</html>')) {
38
- html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
39
- } else {
40
- html += `\n${INJECT_SCRIPT}`;
41
- }
42
-
43
- res.end(html);
44
- },
45
- };
46
- }