@wordbricks/playwright-mcp 0.1.20 → 0.1.22

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.
Files changed (87) hide show
  1. package/cli-wrapper.js +15 -14
  2. package/cli.js +1 -1
  3. package/config.d.ts +11 -6
  4. package/index.d.ts +7 -5
  5. package/index.js +1 -1
  6. package/lib/browserContextFactory.js +131 -58
  7. package/lib/browserServerBackend.js +14 -12
  8. package/lib/config.js +60 -46
  9. package/lib/context.js +41 -39
  10. package/lib/extension/cdpRelay.js +67 -61
  11. package/lib/extension/extensionContextFactory.js +10 -10
  12. package/lib/frameworkPatterns.js +21 -21
  13. package/lib/hooks/antiBotDetectionHook.js +59 -52
  14. package/lib/hooks/core.js +11 -10
  15. package/lib/hooks/eventConsumer.js +21 -21
  16. package/lib/hooks/events.js +3 -3
  17. package/lib/hooks/formatToolCallEvent.js +3 -7
  18. package/lib/hooks/frameworkStateHook.js +40 -40
  19. package/lib/hooks/grouping.js +3 -3
  20. package/lib/hooks/jsonLdDetectionHook.js +44 -37
  21. package/lib/hooks/networkFilters.js +17 -17
  22. package/lib/hooks/networkSetup.js +9 -7
  23. package/lib/hooks/networkTrackingHook.js +21 -21
  24. package/lib/hooks/pageHeightHook.js +9 -9
  25. package/lib/hooks/registry.js +15 -16
  26. package/lib/hooks/requireTabHook.js +3 -3
  27. package/lib/hooks/schema.js +38 -38
  28. package/lib/hooks/waitHook.js +7 -7
  29. package/lib/index.js +12 -10
  30. package/lib/mcp/inProcessTransport.js +3 -4
  31. package/lib/mcp/proxyBackend.js +43 -28
  32. package/lib/mcp/server.js +24 -19
  33. package/lib/mcp/tool.js +14 -8
  34. package/lib/mcp/transport.js +60 -53
  35. package/lib/playwrightTransformer.js +129 -106
  36. package/lib/program.js +54 -52
  37. package/lib/response.js +36 -30
  38. package/lib/sessionLog.js +19 -17
  39. package/lib/tab.js +41 -39
  40. package/lib/tools/common.js +19 -19
  41. package/lib/tools/console.js +11 -11
  42. package/lib/tools/dialogs.js +18 -15
  43. package/lib/tools/evaluate.js +26 -17
  44. package/lib/tools/extractFrameworkState.js +48 -37
  45. package/lib/tools/files.js +17 -14
  46. package/lib/tools/form.js +32 -23
  47. package/lib/tools/getSnapshot.js +14 -15
  48. package/lib/tools/getVisibleHtml.js +33 -17
  49. package/lib/tools/install.js +20 -20
  50. package/lib/tools/keyboard.js +29 -24
  51. package/lib/tools/mouse.js +29 -31
  52. package/lib/tools/navigate.js +19 -23
  53. package/lib/tools/network.js +12 -14
  54. package/lib/tools/networkDetail.js +58 -49
  55. package/lib/tools/networkSearch/bodySearch.js +46 -32
  56. package/lib/tools/networkSearch/grouping.js +15 -6
  57. package/lib/tools/networkSearch/helpers.js +4 -4
  58. package/lib/tools/networkSearch/searchHtml.js +25 -16
  59. package/lib/tools/networkSearch/urlSearch.js +56 -14
  60. package/lib/tools/networkSearch.js +46 -36
  61. package/lib/tools/pdf.js +13 -12
  62. package/lib/tools/repl.js +66 -54
  63. package/lib/tools/screenshot.js +57 -33
  64. package/lib/tools/scroll.js +29 -24
  65. package/lib/tools/snapshot.js +66 -49
  66. package/lib/tools/tabs.js +22 -19
  67. package/lib/tools/tool.js +5 -3
  68. package/lib/tools/utils.js +17 -13
  69. package/lib/tools/wait.js +24 -19
  70. package/lib/tools.js +21 -20
  71. package/lib/utils/adBlockFilter.js +29 -26
  72. package/lib/utils/codegen.js +20 -16
  73. package/lib/utils/extensionPath.js +4 -4
  74. package/lib/utils/fileUtils.js +17 -13
  75. package/lib/utils/graphql.js +69 -58
  76. package/lib/utils/guid.js +3 -3
  77. package/lib/utils/httpServer.js +9 -9
  78. package/lib/utils/log.js +3 -3
  79. package/lib/utils/manualPromise.js +7 -7
  80. package/lib/utils/networkFormat.js +7 -5
  81. package/lib/utils/package.js +4 -4
  82. package/lib/utils/sanitizeHtml.js +66 -34
  83. package/lib/utils/truncate.js +25 -25
  84. package/lib/utils/withTimeout.js +1 -1
  85. package/package.json +34 -57
  86. package/src/index.ts +27 -17
  87. package/LICENSE +0 -202
@@ -13,21 +13,23 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTabTool } from './tool.js';
18
- import { elementSchema } from './snapshot.js';
19
- import { generateLocator } from './utils.js';
20
- import * as javascript from '../utils/codegen.js';
16
+ import { z } from "zod";
17
+ import * as javascript from "../utils/codegen.js";
18
+ import { elementSchema } from "./snapshot.js";
19
+ import { defineTabTool } from "./tool.js";
20
+ import { generateLocator } from "./utils.js";
21
21
  const pressKey = defineTabTool({
22
- capability: 'core',
22
+ capability: "core",
23
23
  schema: {
24
- name: 'browser_press_key',
25
- title: 'Press a key',
26
- description: 'Press a key on the keyboard',
24
+ name: "browser_press_key",
25
+ title: "Press a key",
26
+ description: "Press a key on the keyboard",
27
27
  inputSchema: z.object({
28
- key: z.string().describe('Name of the key to press or a character to generate, such as `ArrowLeft` or `a`'),
28
+ key: z
29
+ .string()
30
+ .describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`"),
29
31
  }),
30
- type: 'input',
32
+ type: "input",
31
33
  },
32
34
  handle: async (tab, params, response) => {
33
35
  response.setIncludeSnapshot();
@@ -39,18 +41,24 @@ const pressKey = defineTabTool({
39
41
  },
40
42
  });
41
43
  const typeSchema = elementSchema.extend({
42
- text: z.string().describe('Text to type into the element'),
43
- submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'),
44
- slowly: z.boolean().optional().describe('Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.'),
44
+ text: z.string().describe("Text to type into the element"),
45
+ submit: z
46
+ .boolean()
47
+ .optional()
48
+ .describe("Whether to submit entered text (press Enter after)"),
49
+ slowly: z
50
+ .boolean()
51
+ .optional()
52
+ .describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once."),
45
53
  });
46
54
  const type = defineTabTool({
47
- capability: 'core',
55
+ capability: "core",
48
56
  schema: {
49
- name: 'browser_type',
50
- title: 'Type text',
51
- description: 'Type text into editable element',
57
+ name: "browser_type",
58
+ title: "Type text",
59
+ description: "Type text into editable element",
52
60
  inputSchema: typeSchema,
53
- type: 'input',
61
+ type: "input",
54
62
  },
55
63
  handle: async (tab, params, response) => {
56
64
  const locator = await tab.refLocator(params);
@@ -67,12 +75,9 @@ const type = defineTabTool({
67
75
  if (params.submit) {
68
76
  response.setIncludeSnapshot();
69
77
  response.addCode(`await page.${await generateLocator(locator)}.press('Enter');`);
70
- await locator.press('Enter');
78
+ await locator.press("Enter");
71
79
  }
72
80
  });
73
81
  },
74
82
  });
75
- export default [
76
- pressKey,
77
- type,
78
- ];
83
+ export default [pressKey, type];
@@ -13,22 +13,24 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTabTool } from './tool.js';
16
+ import { z } from "zod";
17
+ import { defineTabTool } from "./tool.js";
18
18
  const elementSchema = z.object({
19
- element: z.string().describe('Human-readable element description used to obtain permission to interact with the element'),
19
+ element: z
20
+ .string()
21
+ .describe("Human-readable element description used to obtain permission to interact with the element"),
20
22
  });
21
23
  const mouseMove = defineTabTool({
22
- capability: 'vision',
24
+ capability: "vision",
23
25
  schema: {
24
- name: 'browser_mouse_move_xy',
25
- title: 'Move mouse',
26
- description: 'Move mouse to a given position',
26
+ name: "browser_mouse_move_xy",
27
+ title: "Move mouse",
28
+ description: "Move mouse to a given position",
27
29
  inputSchema: elementSchema.extend({
28
- x: z.number().describe('X coordinate'),
29
- y: z.number().describe('Y coordinate'),
30
+ x: z.number().describe("X coordinate"),
31
+ y: z.number().describe("Y coordinate"),
30
32
  }),
31
- type: 'readOnly',
33
+ type: "readOnly",
32
34
  },
33
35
  handle: async (tab, params, response) => {
34
36
  response.addCode(`// Move mouse to (${params.x}, ${params.y})`);
@@ -39,16 +41,16 @@ const mouseMove = defineTabTool({
39
41
  },
40
42
  });
41
43
  const mouseClick = defineTabTool({
42
- capability: 'vision',
44
+ capability: "vision",
43
45
  schema: {
44
- name: 'browser_mouse_click_xy',
45
- title: 'Click',
46
- description: 'Click left mouse button at a given position',
46
+ name: "browser_mouse_click_xy",
47
+ title: "Click",
48
+ description: "Click left mouse button at a given position",
47
49
  inputSchema: elementSchema.extend({
48
- x: z.number().describe('X coordinate'),
49
- y: z.number().describe('Y coordinate'),
50
+ x: z.number().describe("X coordinate"),
51
+ y: z.number().describe("Y coordinate"),
50
52
  }),
51
- type: 'input',
53
+ type: "input",
52
54
  },
53
55
  handle: async (tab, params, response) => {
54
56
  response.setIncludeSnapshot();
@@ -64,18 +66,18 @@ const mouseClick = defineTabTool({
64
66
  },
65
67
  });
66
68
  const mouseDrag = defineTabTool({
67
- capability: 'vision',
69
+ capability: "vision",
68
70
  schema: {
69
- name: 'browser_mouse_drag_xy',
70
- title: 'Drag mouse',
71
- description: 'Drag left mouse button to a given position',
71
+ name: "browser_mouse_drag_xy",
72
+ title: "Drag mouse",
73
+ description: "Drag left mouse button to a given position",
72
74
  inputSchema: elementSchema.extend({
73
- startX: z.number().describe('Start X coordinate'),
74
- startY: z.number().describe('Start Y coordinate'),
75
- endX: z.number().describe('End X coordinate'),
76
- endY: z.number().describe('End Y coordinate'),
75
+ startX: z.number().describe("Start X coordinate"),
76
+ startY: z.number().describe("Start Y coordinate"),
77
+ endX: z.number().describe("End X coordinate"),
78
+ endY: z.number().describe("End Y coordinate"),
77
79
  }),
78
- type: 'input',
80
+ type: "input",
79
81
  },
80
82
  handle: async (tab, params, response) => {
81
83
  response.setIncludeSnapshot();
@@ -92,8 +94,4 @@ const mouseDrag = defineTabTool({
92
94
  });
93
95
  },
94
96
  });
95
- export default [
96
- mouseMove,
97
- mouseClick,
98
- mouseDrag,
99
- ];
97
+ export default [mouseMove, mouseClick, mouseDrag];
@@ -13,18 +13,18 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTool, defineTabTool } from './tool.js';
16
+ import { z } from "zod";
17
+ import { defineTabTool, defineTool } from "./tool.js";
18
18
  const navigate = defineTool({
19
- capability: 'core',
19
+ capability: "core",
20
20
  schema: {
21
- name: 'browser_navigate',
22
- title: 'Navigate to a URL',
23
- description: 'Navigate to a URL',
21
+ name: "browser_navigate",
22
+ title: "Navigate to a URL",
23
+ description: "Navigate to a URL",
24
24
  inputSchema: z.object({
25
- url: z.string().describe('The URL to navigate to'),
25
+ url: z.string().describe("The URL to navigate to"),
26
26
  }),
27
- type: 'action',
27
+ type: "action",
28
28
  },
29
29
  handle: async (context, params, response) => {
30
30
  const tab = await context.ensureTab();
@@ -34,13 +34,13 @@ const navigate = defineTool({
34
34
  },
35
35
  });
36
36
  const goBack = defineTabTool({
37
- capability: 'core',
37
+ capability: "core",
38
38
  schema: {
39
- name: 'browser_navigate_back',
40
- title: 'Go back',
41
- description: 'Go back to the previous page',
39
+ name: "browser_navigate_back",
40
+ title: "Go back",
41
+ description: "Go back to the previous page",
42
42
  inputSchema: z.object({}),
43
- type: 'readOnly',
43
+ type: "readOnly",
44
44
  },
45
45
  handle: async (tab, params, response) => {
46
46
  await tab.page.goBack();
@@ -49,13 +49,13 @@ const goBack = defineTabTool({
49
49
  },
50
50
  });
51
51
  const goForward = defineTabTool({
52
- capability: 'core',
52
+ capability: "core",
53
53
  schema: {
54
- name: 'browser_navigate_forward',
55
- title: 'Go forward',
56
- description: 'Go forward to the next page',
54
+ name: "browser_navigate_forward",
55
+ title: "Go forward",
56
+ description: "Go forward to the next page",
57
57
  inputSchema: z.object({}),
58
- type: 'readOnly',
58
+ type: "readOnly",
59
59
  },
60
60
  handle: async (tab, params, response) => {
61
61
  await tab.page.goForward();
@@ -63,8 +63,4 @@ const goForward = defineTabTool({
63
63
  response.addCode(`await page.goForward();`);
64
64
  },
65
65
  });
66
- export default [
67
- navigate,
68
- goBack,
69
- goForward,
70
- ];
66
+ export default [navigate, goBack, goForward];
@@ -13,9 +13,9 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTabTool } from './tool.js';
18
- import { formatUrlWithTrimmedParams } from '../hooks/networkFilters.js';
16
+ import { z } from "zod";
17
+ import { formatUrlWithTrimmedParams } from "../hooks/networkFilters.js";
18
+ import { defineTabTool } from "./tool.js";
19
19
  // const requests = defineTool({
20
20
  // capability: 'core',
21
21
  // schema: {
@@ -61,13 +61,13 @@ import { formatUrlWithTrimmedParams } from '../hooks/networkFilters.js';
61
61
  // },
62
62
  // });
63
63
  const requests = defineTabTool({
64
- capability: 'core',
64
+ capability: "core",
65
65
  schema: {
66
- name: 'browser_network_requests',
67
- title: 'List network requests',
68
- description: 'Returns all network requests since loading the page',
66
+ name: "browser_network_requests",
67
+ title: "List network requests",
68
+ description: "Returns all network requests since loading the page",
69
69
  inputSchema: z.object({}),
70
- type: 'readOnly',
70
+ type: "readOnly",
71
71
  },
72
72
  handle: async (tab, params, response) => {
73
73
  const requests = tab.requests();
@@ -82,8 +82,8 @@ export async function renderRequest(request, response) {
82
82
  result.push(`[${request.method().toUpperCase()}] ${trimmedUrl}`);
83
83
  // Append response status if available with compact content-type
84
84
  if (response) {
85
- const ct = response.headers()?.['content-type'];
86
- result.push(`=> [${response.status()}] ${response.statusText()}${ct ? ` ct=${ct}` : ''}`);
85
+ const ct = response.headers()?.["content-type"];
86
+ result.push(`=> [${response.status()}] ${response.statusText()}${ct ? ` ct=${ct}` : ""}`);
87
87
  }
88
88
  // Always include request headers (excluding HTTP/2 pseudo-headers and common headers)
89
89
  // const allHeaders = await request.allHeaders();
@@ -116,8 +116,6 @@ export async function renderRequest(request, response) {
116
116
  // if (body)
117
117
  // result.push(`body=${body}`);
118
118
  // }
119
- return result.join('\n');
119
+ return result.join("\n");
120
120
  }
121
- export default [
122
- requests,
123
- ];
121
+ export default [requests];
@@ -1,25 +1,25 @@
1
- import { z } from 'zod';
2
- import ms from 'ms';
3
- import { defineTabTool } from './tool.js';
4
- import { sanitizeHtml } from '../utils/sanitizeHtml.js';
5
- import { formatTruncationLine } from '../utils/truncate.js';
6
- import { withTimeout } from '../utils/withTimeout.js';
7
- import { getNetworkEventEntry } from '../hooks/networkSetup.js';
8
- import { parseGraphQLRequestFromHttp, summarizeGraphQL, extractGraphQLResponseInfo, minifyGraphQLQuery, minifyGraphQLRequestBody } from '../utils/graphql.js';
9
- import { formatNetworkSummaryLine } from '../utils/networkFormat.js';
10
- const TIMEOUT_MS = ms('5s');
1
+ import ms from "ms";
2
+ import { z } from "zod";
3
+ import { getNetworkEventEntry } from "../hooks/networkSetup.js";
4
+ import { extractGraphQLResponseInfo, minifyGraphQLQuery, minifyGraphQLRequestBody, parseGraphQLRequestFromHttp, summarizeGraphQL, } from "../utils/graphql.js";
5
+ import { formatNetworkSummaryLine } from "../utils/networkFormat.js";
6
+ import { sanitizeHtml } from "../utils/sanitizeHtml.js";
7
+ import { formatTruncationLine } from "../utils/truncate.js";
8
+ import { withTimeout } from "../utils/withTimeout.js";
9
+ import { defineTabTool } from "./tool.js";
10
+ const TIMEOUT_MS = ms("5s");
11
11
  const CHUNK_BYTES = 8 * 1024; // 8KB per page
12
12
  const contextBodyMap = new WeakMap();
13
13
  // Skip HTTP/2 pseudo-headers and their non-colon variants in displayed request headers
14
14
  const SKIP_REQUEST_HEADER_KEYS = new Set([
15
- ':authority',
16
- ':method',
17
- ':path',
18
- ':scheme',
19
- 'authority',
20
- 'method',
21
- 'path',
22
- 'scheme',
15
+ ":authority",
16
+ ":method",
17
+ ":path",
18
+ ":scheme",
19
+ "authority",
20
+ "method",
21
+ "path",
22
+ "scheme",
23
23
  ]);
24
24
  const getStateMap = (ctx) => {
25
25
  let m = contextBodyMap.get(ctx);
@@ -30,7 +30,7 @@ const getStateMap = (ctx) => {
30
30
  return m;
31
31
  };
32
32
  const networkDetailSchema = z.object({
33
- id: z.number().describe('The network event id from events log'),
33
+ id: z.number().describe("The network event id from events log"),
34
34
  });
35
35
  const normalizeHeaderKeys = (headers) => {
36
36
  const out = {};
@@ -39,7 +39,7 @@ const normalizeHeaderKeys = (headers) => {
39
39
  return out;
40
40
  };
41
41
  const formatBytes = (n) => {
42
- const units = ['B', 'KB', 'MB', 'GB'];
42
+ const units = ["B", "KB", "MB", "GB"];
43
43
  let i = 0;
44
44
  let v = n;
45
45
  while (v >= 1024 && i < units.length - 1) {
@@ -67,16 +67,16 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
67
67
  if (!Number.isNaN(duration))
68
68
  lines.push(`Duration: ${duration}ms`);
69
69
  }
70
- lines.push(`Failure: ${request.failure()?.errorText || 'None'}`);
70
+ lines.push(`Failure: ${request.failure()?.errorText || "None"}`);
71
71
  if (!response) {
72
72
  lines.push(`\nRequest Headers:`);
73
73
  const headerKeysNoResp = Object.keys(reqHeaders)
74
- .filter(k => !SKIP_REQUEST_HEADER_KEYS.has(k))
74
+ .filter((k) => !SKIP_REQUEST_HEADER_KEYS.has(k))
75
75
  .sort();
76
76
  for (const key of headerKeysNoResp)
77
77
  lines.push(`${key}: ${reqHeaders[key]}`);
78
78
  lines.push(`\nNo response received yet`);
79
- return lines.join('\n');
79
+ return lines.join("\n");
80
80
  }
81
81
  // Determine if this request is GraphQL (used for both request and response sections)
82
82
  const gqlReq = parseGraphQLRequestFromHttp(request.method(), request.url(), reqHeaders, requestBody);
@@ -96,25 +96,27 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
96
96
  // Print full request headers on the first call (excluding authority/method/path/scheme)
97
97
  lines.push(`\nRequest Headers:`);
98
98
  const headerKeys = Object.keys(reqHeaders)
99
- .filter(k => !SKIP_REQUEST_HEADER_KEYS.has(k))
99
+ .filter((k) => !SKIP_REQUEST_HEADER_KEYS.has(k))
100
100
  .sort();
101
101
  for (const key of headerKeys)
102
102
  lines.push(`${key}: ${reqHeaders[key]}`);
103
- const reqCt = reqHeaders['content-type'];
103
+ const reqCt = reqHeaders["content-type"];
104
104
  // GraphQL request summary (if applicable) and compact body handling
105
105
  if (gqlReq) {
106
106
  lines.push(`\nGraphQL Request: ${summarizeGraphQL(gqlReq)}`);
107
- if (gqlReq.variables && typeof gqlReq.variables === 'object' && !Array.isArray(gqlReq.variables)) {
107
+ if (gqlReq.variables &&
108
+ typeof gqlReq.variables === "object" &&
109
+ !Array.isArray(gqlReq.variables)) {
108
110
  const varKeys = Object.keys(gqlReq.variables);
109
111
  if (varKeys.length)
110
- lines.push(`Variables: { ${varKeys.join(', ')} }`);
112
+ lines.push(`Variables: { ${varKeys.join(", ")} }`);
111
113
  }
112
114
  }
113
115
  if (requestBody) {
114
116
  if (gqlReq) {
115
117
  lines.push(`\nRequest Body (GraphQL):`);
116
118
  try {
117
- if (reqCt && reqCt.includes('application/graphql')) {
119
+ if (reqCt && reqCt.includes("application/graphql")) {
118
120
  lines.push(minifyGraphQLQuery(requestBody));
119
121
  }
120
122
  else {
@@ -138,16 +140,18 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
138
140
  }
139
141
  }
140
142
  const resHeaders = normalizeHeaderKeys(await response.allHeaders());
141
- const resCt = resHeaders['content-type'];
142
- const resCl = resHeaders['content-length'];
143
+ const resCt = resHeaders["content-type"];
144
+ const resCl = resHeaders["content-length"];
143
145
  const headerParts = [];
144
146
  if (resCt)
145
147
  headerParts.push(`content-type=${resCt}`);
146
148
  if (resCl)
147
149
  headerParts.push(`content-length=${resCl}`);
148
150
  if (headerParts.length)
149
- lines.push(`\nResponse Headers: { ${headerParts.join(', ')} }`);
150
- const setCookieHeaders = await response.headerValues('set-cookie').catch(() => []);
151
+ lines.push(`\nResponse Headers: { ${headerParts.join(", ")} }`);
152
+ const setCookieHeaders = await response
153
+ .headerValues("set-cookie")
154
+ .catch(() => []);
151
155
  if (setCookieHeaders.length) {
152
156
  lines.push(`\nSet-Cookie Headers (${setCookieHeaders.length}):`);
153
157
  for (const cookie of setCookieHeaders)
@@ -162,29 +166,32 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
162
166
  catch (e) {
163
167
  const msg = e instanceof Error ? e.message : String(e);
164
168
  lines.push(`\nFailed to read response body: ${msg}`);
165
- return lines.join('\n');
169
+ return lines.join("\n");
166
170
  }
167
171
  // On first page, summarize GraphQL response if applicable
168
172
  if (!state.headersShown && state.isGraphQLRequest) {
169
173
  try {
170
- const fullText = buf.toString('utf8');
174
+ const fullText = buf.toString("utf8");
171
175
  const gqlInfo = extractGraphQLResponseInfo(fullText);
172
176
  if (gqlInfo) {
173
- lines.push(`\nGraphQL Response: ${gqlInfo.hasErrors ? `${gqlInfo.errorCount} error(s)` : 'ok'}`);
177
+ lines.push(`\nGraphQL Response: ${gqlInfo.hasErrors ? `${gqlInfo.errorCount} error(s)` : "ok"}`);
174
178
  if (gqlInfo.topMessages.length)
175
- lines.push(`Errors: ${gqlInfo.topMessages.join(' | ')}`);
179
+ lines.push(`Errors: ${gqlInfo.topMessages.join(" | ")}`);
176
180
  if (gqlInfo.dataKeys.length)
177
- lines.push(`Data keys: ${gqlInfo.dataKeys.join(', ')}`);
181
+ lines.push(`Data keys: ${gqlInfo.dataKeys.join(", ")}`);
178
182
  }
179
183
  }
180
184
  catch { }
181
185
  }
182
- let bodyText = buf.toString('utf8');
186
+ let bodyText = buf.toString("utf8");
183
187
  const resHeadersForBody = normalizeHeaderKeys(await response.allHeaders());
184
- const resCtForBody = resHeadersForBody['content-type'] || '';
185
- const isHtml = resCtForBody.includes('html');
188
+ const resCtForBody = resHeadersForBody["content-type"] || "";
189
+ const isHtml = resCtForBody.includes("html");
186
190
  if (isHtml)
187
- bodyText = sanitizeHtml(bodyText, { shouldRemoveScripts: false, shouldRemoveStyles: true });
191
+ bodyText = sanitizeHtml(bodyText, {
192
+ shouldRemoveScripts: false,
193
+ shouldRemoveStyles: true,
194
+ });
188
195
  const totalLength = bodyText.length;
189
196
  if (state.cursor >= totalLength) {
190
197
  state.cursor = 0;
@@ -195,23 +202,25 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
195
202
  const chunk = bodyText.slice(start, end);
196
203
  const nextCursor = end;
197
204
  const more = nextCursor < totalLength;
198
- const percent = totalLength ? Math.round((nextCursor / totalLength) * 100) : 100;
199
- lines.push(`\nBody Chunk (text): ${percent}%${more ? ' (more)' : ' (done)'}:`);
205
+ const percent = totalLength
206
+ ? Math.round((nextCursor / totalLength) * 100)
207
+ : 100;
208
+ lines.push(`\nBody Chunk (text): ${percent}%${more ? " (more)" : " (done)"}:`);
200
209
  lines.push(chunk);
201
210
  if (more)
202
211
  lines.push(formatTruncationLine(nextCursor, totalLength, { formatter: formatBytes }));
203
212
  state.cursor = nextCursor;
204
213
  state.headersShown = true;
205
- return lines.join('\n');
214
+ return lines.join("\n");
206
215
  };
207
216
  const networkDetail = defineTabTool({
208
- capability: 'core',
217
+ capability: "core",
209
218
  schema: {
210
- name: 'browser_network_detail',
211
- title: 'Get network request detail',
212
- description: 'Show detailed info for a specific network request by event id. Call repeatedly to page through the body.',
219
+ name: "browser_network_detail",
220
+ title: "Get network request detail",
221
+ description: "Show detailed info for a specific network request by event id. Call repeatedly to page through the body.",
213
222
  inputSchema: networkDetailSchema,
214
- type: 'readOnly',
223
+ type: "readOnly",
215
224
  },
216
225
  handle: async (tab, params, r) => {
217
226
  const entry = getNetworkEventEntry(tab.context, params.id);