@narumitw/pi-chrome-devtools 0.1.11 → 0.1.13

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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-chrome-devtools",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Pi extension that exposes Chrome DevTools Protocol tools.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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
- const pages = await listPages();
45
- return textResult(JSON.stringify(pages.map(formatPage), null, 2), { pages });
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
- const page = await getPage(params.pageId);
59
- state.activePageId = page.id;
60
- return textResult(`Selected page ${page.id}: ${page.title}\n${page.url}`, {
61
- page: formatPage(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
- const page = await resolvePage(params.pageId);
79
- const result = await withCdp(page, async (client) => {
80
- await client.send("Page.enable");
81
- return client.send("Page.navigate", { url: params.url });
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
- state.activePageId = page.id;
85
- return textResult(`Navigated ${page.id} to ${params.url}`, { page: formatPage(page), result });
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
- const page = await resolvePage(params.pageId);
105
- const result = await withCdp(page, (client) =>
106
- client.send("Runtime.evaluate", {
107
- expression: params.expression,
108
- awaitPromise: params.awaitPromise ?? true,
109
- returnByValue: true,
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
- state.activePageId = page.id;
114
- return textResult(JSON.stringify(result, null, 2), { page: formatPage(page), result });
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
- const page = await resolvePage(params.pageId);
133
- const result = await withCdp(page, async (client) => {
134
- await client.send("Page.enable");
135
-
136
- if (!params.fullPage) {
137
- return client.send<{ data: string }>("Page.captureScreenshot", { format: "png" });
138
- }
139
-
140
- const metrics = await client.send<{
141
- contentSize: { x: number; y: number; width: number; height: number };
142
- }>("Page.getLayoutMetrics");
143
-
144
- return client.send<{ data: string }>("Page.captureScreenshot", {
145
- captureBeyondViewport: true,
146
- format: "png",
147
- clip: {
148
- x: metrics.contentSize.x,
149
- y: metrics.contentSize.y,
150
- width: metrics.contentSize.width,
151
- height: metrics.contentSize.height,
152
- scale: 1,
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
- state.activePageId = page.id;
158
- return {
159
- content: [
160
- { type: "text", text: `Captured PNG screenshot from ${page.title || page.url}` },
161
- { type: "image", data: result.data, mimeType: "image/png" },
162
- ],
163
- details: { page: formatPage(page), bytes: Buffer.byteLength(result.data, "base64") },
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("chrome-devtools", `cdp: ${state.host}:${state.port}`);
201
+ ctx.ui.setStatus(STATUS_KEY, undefined);
187
202
  });
188
203
 
189
204
  pi.on("session_shutdown", (_event, ctx) => {
190
- ctx.ui.setStatus("chrome-devtools", undefined);
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