@narumitw/pi-chrome-devtools 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/chrome-devtools.ts +85 -61
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ This package is inspired by [`chrome-devtools-mcp`](https://github.com/ChromeDev
|
|
|
16
16
|
- Evaluates JavaScript in the selected page.
|
|
17
17
|
- Captures PNG screenshots, including optional full-page screenshots.
|
|
18
18
|
- Uses a local Chrome DevTools Protocol endpoint.
|
|
19
|
+
- Shows statusline activity only while Chrome DevTools tools are running.
|
|
19
20
|
|
|
20
21
|
## 📦 Install
|
|
21
22
|
|
package/package.json
CHANGED
package/src/chrome-devtools.ts
CHANGED
|
@@ -4,6 +4,11 @@ import { Type } from "typebox";
|
|
|
4
4
|
const DEFAULT_HOST = "127.0.0.1";
|
|
5
5
|
const DEFAULT_PORT = 9222;
|
|
6
6
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
7
|
+
const STATUS_KEY = "chrome-devtools";
|
|
8
|
+
|
|
9
|
+
interface StatusContext {
|
|
10
|
+
ui: { setStatus: (key: string, value: string | undefined) => void };
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
interface DevToolsPage {
|
|
9
14
|
id: string;
|
|
@@ -40,9 +45,11 @@ const listPagesTool = defineTool({
|
|
|
40
45
|
description: "List Chrome tabs/pages from a running Chrome DevTools Protocol endpoint.",
|
|
41
46
|
promptSnippet: "List Chrome tabs/pages available over Chrome DevTools Protocol",
|
|
42
47
|
parameters: Type.Object({}),
|
|
43
|
-
async execute() {
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
49
|
+
return withStatus(ctx, "cdp: list pages", async () => {
|
|
50
|
+
const pages = await listPages();
|
|
51
|
+
return textResult(JSON.stringify(pages.map(formatPage), null, 2), { pages });
|
|
52
|
+
});
|
|
46
53
|
},
|
|
47
54
|
});
|
|
48
55
|
|
|
@@ -54,11 +61,13 @@ const selectPageTool = defineTool({
|
|
|
54
61
|
parameters: Type.Object({
|
|
55
62
|
pageId: Type.String({ description: "Page id from chrome_devtools_list_pages." }),
|
|
56
63
|
}),
|
|
57
|
-
async execute(_toolCallId, params) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
page:
|
|
64
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
65
|
+
return withStatus(ctx, "cdp: select page", async () => {
|
|
66
|
+
const page = await getPage(params.pageId);
|
|
67
|
+
state.activePageId = page.id;
|
|
68
|
+
return textResult(`Selected page ${page.id}: ${page.title}\n${page.url}`, {
|
|
69
|
+
page: formatPage(page),
|
|
70
|
+
});
|
|
62
71
|
});
|
|
63
72
|
},
|
|
64
73
|
});
|
|
@@ -74,15 +83,17 @@ const navigateTool = defineTool({
|
|
|
74
83
|
Type.String({ description: "Optional page id. Defaults to selected or first page." }),
|
|
75
84
|
),
|
|
76
85
|
}),
|
|
77
|
-
async execute(_toolCallId, params) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
await client
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
87
|
+
return withStatus(ctx, "cdp: navigate", async () => {
|
|
88
|
+
const page = await resolvePage(params.pageId);
|
|
89
|
+
const result = await withCdp(page, async (client) => {
|
|
90
|
+
await client.send("Page.enable");
|
|
91
|
+
return client.send("Page.navigate", { url: params.url });
|
|
92
|
+
});
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
state.activePageId = page.id;
|
|
95
|
+
return textResult(`Navigated ${page.id} to ${params.url}`, { page: formatPage(page), result });
|
|
96
|
+
});
|
|
86
97
|
},
|
|
87
98
|
});
|
|
88
99
|
|
|
@@ -100,18 +111,20 @@ const evaluateTool = defineTool({
|
|
|
100
111
|
Type.Boolean({ description: "Whether to await a returned Promise. Defaults to true." }),
|
|
101
112
|
),
|
|
102
113
|
}),
|
|
103
|
-
async execute(_toolCallId, params) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
115
|
+
return withStatus(ctx, "cdp: evaluate", async () => {
|
|
116
|
+
const page = await resolvePage(params.pageId);
|
|
117
|
+
const result = await withCdp(page, (client) =>
|
|
118
|
+
client.send("Runtime.evaluate", {
|
|
119
|
+
expression: params.expression,
|
|
120
|
+
awaitPromise: params.awaitPromise ?? true,
|
|
121
|
+
returnByValue: true,
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
state.activePageId = page.id;
|
|
126
|
+
return textResult(JSON.stringify(result, null, 2), { page: formatPage(page), result });
|
|
127
|
+
});
|
|
115
128
|
},
|
|
116
129
|
});
|
|
117
130
|
|
|
@@ -128,40 +141,42 @@ const screenshotTool = defineTool({
|
|
|
128
141
|
Type.Boolean({ description: "Capture the full document, not just the viewport." }),
|
|
129
142
|
),
|
|
130
143
|
}),
|
|
131
|
-
async execute(_toolCallId, params) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
await client
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
145
|
+
return withStatus(ctx, "cdp: screenshot", async () => {
|
|
146
|
+
const page = await resolvePage(params.pageId);
|
|
147
|
+
const result = await withCdp(page, async (client) => {
|
|
148
|
+
await client.send("Page.enable");
|
|
149
|
+
|
|
150
|
+
if (!params.fullPage) {
|
|
151
|
+
return client.send<{ data: string }>("Page.captureScreenshot", { format: "png" });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const metrics = await client.send<{
|
|
155
|
+
contentSize: { x: number; y: number; width: number; height: number };
|
|
156
|
+
}>("Page.getLayoutMetrics");
|
|
157
|
+
|
|
158
|
+
return client.send<{ data: string }>("Page.captureScreenshot", {
|
|
159
|
+
captureBeyondViewport: true,
|
|
160
|
+
format: "png",
|
|
161
|
+
clip: {
|
|
162
|
+
x: metrics.contentSize.x,
|
|
163
|
+
y: metrics.contentSize.y,
|
|
164
|
+
width: metrics.contentSize.width,
|
|
165
|
+
height: metrics.contentSize.height,
|
|
166
|
+
scale: 1,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
154
169
|
});
|
|
155
|
-
});
|
|
156
170
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
state.activePageId = page.id;
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{ type: "text", text: `Captured PNG screenshot from ${page.title || page.url}` },
|
|
175
|
+
{ type: "image", data: result.data, mimeType: "image/png" },
|
|
176
|
+
],
|
|
177
|
+
details: { page: formatPage(page), bytes: Buffer.byteLength(result.data, "base64") },
|
|
178
|
+
};
|
|
179
|
+
});
|
|
165
180
|
},
|
|
166
181
|
});
|
|
167
182
|
|
|
@@ -183,11 +198,11 @@ export default function chromeDevtools(pi: ExtensionAPI) {
|
|
|
183
198
|
});
|
|
184
199
|
|
|
185
200
|
pi.on("session_start", (_event, ctx) => {
|
|
186
|
-
ctx.ui.setStatus(
|
|
201
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
187
202
|
});
|
|
188
203
|
|
|
189
204
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
190
|
-
ctx.ui.setStatus(
|
|
205
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
191
206
|
});
|
|
192
207
|
}
|
|
193
208
|
|
|
@@ -239,6 +254,15 @@ function textResult(text: string, details: unknown) {
|
|
|
239
254
|
};
|
|
240
255
|
}
|
|
241
256
|
|
|
257
|
+
async function withStatus<T>(ctx: StatusContext, status: string, callback: () => Promise<T>) {
|
|
258
|
+
ctx.ui.setStatus(STATUS_KEY, status);
|
|
259
|
+
try {
|
|
260
|
+
return await callback();
|
|
261
|
+
} finally {
|
|
262
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
242
266
|
async function withCdp<T>(page: DevToolsPage, callback: (client: CdpClient) => Promise<T>) {
|
|
243
267
|
if (!page.webSocketDebuggerUrl) throw new Error(`Page has no webSocketDebuggerUrl: ${page.id}`);
|
|
244
268
|
|