@narumitw/pi-chrome-devtools 0.53.2 → 0.53.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.
Files changed (40) hide show
  1. package/README.md +1 -0
  2. package/dist/chunks/{browser-settings-menu-IMB6HXSU.js → browser-settings-menu-FVMCCUPM.ts} +16 -42
  3. package/dist/chunks/browser-settings-menu-FVMCCUPM.ts.map +7 -0
  4. package/dist/chunks/{chunk-GTTYS5GT.js → chunk-4B3EQHOH.ts} +2 -2
  5. package/dist/chunks/chunk-4B3EQHOH.ts.map +7 -0
  6. package/dist/chunks/{chunk-SRT3UUIP.js → chunk-LSJSDY2M.ts} +23 -74
  7. package/dist/chunks/chunk-LSJSDY2M.ts.map +7 -0
  8. package/dist/chunks/{chunk-LLXOCRNF.js → chunk-V7N3WI5B.ts} +12 -33
  9. package/dist/chunks/chunk-V7N3WI5B.ts.map +7 -0
  10. package/dist/chunks/{menu-UJETYMSB.js → menu-MTPSLDHZ.ts} +10 -23
  11. package/dist/chunks/menu-MTPSLDHZ.ts.map +7 -0
  12. package/dist/chunks/{tools-6P7EUX37.js → tools-OVNH45IJ.ts} +17 -59
  13. package/dist/chunks/tools-OVNH45IJ.ts.map +7 -0
  14. package/dist/index.ts +18 -42
  15. package/dist/index.ts.map +2 -2
  16. package/package.json +54 -55
  17. package/src/browser-manager.ts +872 -942
  18. package/src/browser-settings-menu.ts +315 -350
  19. package/src/cdp-client.ts +381 -398
  20. package/src/chrome-devtools.ts +205 -232
  21. package/src/display.ts +26 -29
  22. package/src/lazy-tools.ts +158 -181
  23. package/src/menu.ts +409 -446
  24. package/src/render.ts +72 -81
  25. package/src/runtime.ts +137 -140
  26. package/src/screenshot.ts +170 -186
  27. package/src/settings.ts +472 -523
  28. package/src/tool-names.ts +8 -14
  29. package/src/tool-selector.ts +242 -271
  30. package/src/tools.ts +218 -226
  31. package/src/webmcp/discovery.ts +184 -188
  32. package/src/webmcp/policy.ts +296 -329
  33. package/src/webmcp/protocol.ts +324 -337
  34. package/src/webmcp/tools.ts +136 -147
  35. package/dist/chunks/browser-settings-menu-IMB6HXSU.js.map +0 -7
  36. package/dist/chunks/chunk-GTTYS5GT.js.map +0 -7
  37. package/dist/chunks/chunk-LLXOCRNF.js.map +0 -7
  38. package/dist/chunks/chunk-SRT3UUIP.js.map +0 -7
  39. package/dist/chunks/menu-UJETYMSB.js.map +0 -7
  40. package/dist/chunks/tools-6P7EUX37.js.map +0 -7
@@ -1,285 +1,258 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
- import {
3
- shutdownManagedBrowser,
4
- startManagedBrowserSession,
5
- syncManagedBrowserSettings,
6
- } from "./browser-manager.js";
2
+ import { shutdownManagedBrowser, startManagedBrowserSession, syncManagedBrowserSettings } from "./browser-manager.js";
7
3
  import { setActivePageId } from "./cdp-client.js";
8
4
  import {
9
- availableChromeDevtoolsTools,
10
- configureChromeDevtoolsToolExposure,
11
- createChromeDevtoolsLoadTool,
12
- initializeAvailableChromeDevtoolsTools,
13
- requireEagerChromeDevtoolsToolExposure,
14
- setChromeDevtoolsSessionOwner,
15
- supportsNativeDeferredToolLoading,
5
+ availableChromeDevtoolsTools,
6
+ configureChromeDevtoolsToolExposure,
7
+ createChromeDevtoolsLoadTool,
8
+ initializeAvailableChromeDevtoolsTools,
9
+ requireEagerChromeDevtoolsToolExposure,
10
+ setChromeDevtoolsSessionOwner,
11
+ supportsNativeDeferredToolLoading,
16
12
  } from "./lazy-tools.js";
17
13
  import {
18
- applyRuntimeBrowserSettings,
19
- applyRuntimeWebMcpSetting,
20
- invalidateWebMcpOperations,
21
- setWebMcpSessionOwner,
22
- state,
23
- webMcpEnabled,
14
+ applyRuntimeBrowserSettings,
15
+ applyRuntimeWebMcpSetting,
16
+ invalidateWebMcpOperations,
17
+ setWebMcpSessionOwner,
18
+ state,
19
+ webMcpEnabled,
24
20
  } from "./runtime.js";
25
21
  import { loadSettings, waitForSettingsWrites } from "./settings.js";
26
22
  import {
27
- allChromeDevtoolsTools,
28
- buildCommandGuide,
29
- buildQuickstartMessage,
30
- buildToolStatusMessage,
31
- sanitizeChromeDevtoolsDisplay,
32
- updateChromeDevtoolsTools,
33
- waitForChromeDevtoolsSettings,
23
+ allChromeDevtoolsTools,
24
+ buildCommandGuide,
25
+ buildQuickstartMessage,
26
+ buildToolStatusMessage,
27
+ sanitizeChromeDevtoolsDisplay,
28
+ updateChromeDevtoolsTools,
29
+ waitForChromeDevtoolsSettings,
34
30
  } from "./tool-selector.js";
35
31
  import {
36
- evaluateTool,
37
- listPagesTool,
38
- navigateTool,
39
- screenshotTool,
40
- selectPageTool,
41
- webMcpCallTool,
42
- webMcpListToolsTool,
32
+ evaluateTool,
33
+ listPagesTool,
34
+ navigateTool,
35
+ screenshotTool,
36
+ selectPageTool,
37
+ webMcpCallTool,
38
+ webMcpListToolsTool,
43
39
  } from "./tools.js";
44
40
 
45
- type CommandAction =
46
- | "menu"
47
- | "help"
48
- | "quickstart"
49
- | "status"
50
- | "settings"
51
- | "tools"
52
- | "enable"
53
- | "disable";
41
+ type CommandAction = "menu" | "help" | "quickstart" | "status" | "settings" | "tools" | "enable" | "disable";
54
42
  type CommandContext = ExtensionCommandContext;
55
43
  const STATUS_KEY = "chrome-devtools";
56
44
  const COMMAND_COMPLETIONS = [
57
- { value: "help", label: "help", description: "Show command usage" },
58
- { value: "quickstart", label: "quickstart", description: "Show endpoint and launch help" },
59
- { value: "status", label: "status", description: "Show tool and settings status" },
60
- { value: "settings", label: "settings", description: "Edit browser connection settings" },
61
- { value: "tools", label: "tools", description: "Choose available Chrome DevTools tools" },
62
- { value: "toggle", label: "toggle", description: "Alias for tools" },
63
- { value: "select", label: "select", description: "Compatibility alias for tools" },
64
- { value: "enable", label: "enable", description: "Make all Chrome DevTools tools available" },
65
- { value: "on", label: "on", description: "Compatibility alias for enable" },
66
- { value: "disable", label: "disable", description: "Make all Chrome DevTools tools unavailable" },
67
- { value: "off", label: "off", description: "Compatibility alias for disable" },
45
+ { value: "help", label: "help", description: "Show command usage" },
46
+ { value: "quickstart", label: "quickstart", description: "Show endpoint and launch help" },
47
+ { value: "status", label: "status", description: "Show tool and settings status" },
48
+ { value: "settings", label: "settings", description: "Edit browser connection settings" },
49
+ { value: "tools", label: "tools", description: "Choose available Chrome DevTools tools" },
50
+ { value: "toggle", label: "toggle", description: "Alias for tools" },
51
+ { value: "select", label: "select", description: "Compatibility alias for tools" },
52
+ { value: "enable", label: "enable", description: "Make all Chrome DevTools tools available" },
53
+ { value: "on", label: "on", description: "Compatibility alias for enable" },
54
+ { value: "disable", label: "disable", description: "Make all Chrome DevTools tools unavailable" },
55
+ { value: "off", label: "off", description: "Compatibility alias for disable" },
68
56
  ];
69
57
  export default function chromeDevtools(pi: ExtensionAPI) {
70
- pi.registerTool(listPagesTool);
71
- pi.registerTool(selectPageTool);
72
- pi.registerTool(navigateTool);
73
- pi.registerTool(evaluateTool);
74
- pi.registerTool(screenshotTool);
75
- pi.registerTool(webMcpListToolsTool);
76
- pi.registerTool(webMcpCallTool);
77
- pi.registerTool(createChromeDevtoolsLoadTool(pi));
58
+ pi.registerTool(listPagesTool);
59
+ pi.registerTool(selectPageTool);
60
+ pi.registerTool(navigateTool);
61
+ pi.registerTool(evaluateTool);
62
+ pi.registerTool(screenshotTool);
63
+ pi.registerTool(webMcpListToolsTool);
64
+ pi.registerTool(webMcpCallTool);
65
+ pi.registerTool(createChromeDevtoolsLoadTool(pi));
78
66
 
79
- pi.registerCommand("chrome-devtools", {
80
- description: "Open Chrome DevTools help and tool controls",
81
- getArgumentCompletions: (prefix) => commandCompletions(prefix),
82
- handler: async (args, ctx) => {
83
- setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
84
- initializeAvailableChromeDevtoolsTools(pi);
85
- const generation = state.sessionGeneration;
86
- await handleChromeDevtoolsCommand(pi, args, ctx, generation);
87
- },
88
- });
67
+ pi.registerCommand("chrome-devtools", {
68
+ description: "Open Chrome DevTools help and tool controls",
69
+ getArgumentCompletions: (prefix) => commandCompletions(prefix),
70
+ handler: async (args, ctx) => {
71
+ setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
72
+ initializeAvailableChromeDevtoolsTools(pi);
73
+ const generation = state.sessionGeneration;
74
+ await handleChromeDevtoolsCommand(pi, args, ctx, generation);
75
+ },
76
+ });
89
77
 
90
- pi.on("session_start", async (_event, ctx) => {
91
- const generation = ++state.sessionGeneration;
92
- setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
93
- initializeAvailableChromeDevtoolsTools(pi);
94
- setWebMcpSessionOwner(ctx.sessionManager);
95
- replaceSessionController("Chrome DevTools session replaced");
96
- invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools session replaced");
97
- state.shuttingDown = false;
98
- state.settingsNotice = undefined;
99
- ctx.ui.setStatus(STATUS_KEY, undefined);
100
- await shutdownManagedBrowser(undefined, {
101
- cancelLaunch: true,
102
- owner: ctx.sessionManager,
103
- });
104
- startManagedBrowserSession(ctx.sessionManager);
105
- if (generation !== state.sessionGeneration) return;
106
- setActivePageId(ctx.sessionManager, undefined);
107
- state.activePageId = undefined;
108
- state.lastLaunchAttempt = undefined;
109
- const projectTrusted = ctx.isProjectTrusted();
110
- const settings = await loadSettings({ cwd: ctx.cwd, projectTrusted });
111
- if (generation !== state.sessionGeneration) return;
112
- applyRuntimeBrowserSettings(settings.effectiveBrowser, settings.paths, projectTrusted);
113
- syncManagedBrowserSettings(ctx.sessionManager, settings.effectiveBrowser);
114
- applyRuntimeWebMcpSetting(settings.effectiveWebMcpEnabled, ctx.sessionManager);
115
- state.settingsNotice = settings.notice;
116
- for (const warning of settings.warnings) {
117
- ctx.ui.notify(sanitizeChromeDevtoolsDisplay(warning), "warning");
118
- }
119
- if (webMcpEnabled(ctx.sessionManager)) {
120
- ctx.ui.notify(
121
- "Experimental WebMCP is enabled. Page-provided tools use the visible browser session and require confirmation for every call.",
122
- "warning",
123
- );
124
- }
125
- const availableTools =
126
- settings.kind === "loaded" && settings.settings.tools
127
- ? settings.settings.tools
128
- : availableChromeDevtoolsTools(pi);
129
- configureChromeDevtoolsToolExposure(pi, availableTools, ctx.model);
130
- });
78
+ pi.on("session_start", async (_event, ctx) => {
79
+ const generation = ++state.sessionGeneration;
80
+ setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
81
+ initializeAvailableChromeDevtoolsTools(pi);
82
+ setWebMcpSessionOwner(ctx.sessionManager);
83
+ replaceSessionController("Chrome DevTools session replaced");
84
+ invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools session replaced");
85
+ state.shuttingDown = false;
86
+ state.settingsNotice = undefined;
87
+ ctx.ui.setStatus(STATUS_KEY, undefined);
88
+ await shutdownManagedBrowser(undefined, {
89
+ cancelLaunch: true,
90
+ owner: ctx.sessionManager,
91
+ });
92
+ startManagedBrowserSession(ctx.sessionManager);
93
+ if (generation !== state.sessionGeneration) return;
94
+ setActivePageId(ctx.sessionManager, undefined);
95
+ state.activePageId = undefined;
96
+ state.lastLaunchAttempt = undefined;
97
+ const projectTrusted = ctx.isProjectTrusted();
98
+ const settings = await loadSettings({ cwd: ctx.cwd, projectTrusted });
99
+ if (generation !== state.sessionGeneration) return;
100
+ applyRuntimeBrowserSettings(settings.effectiveBrowser, settings.paths, projectTrusted);
101
+ syncManagedBrowserSettings(ctx.sessionManager, settings.effectiveBrowser);
102
+ applyRuntimeWebMcpSetting(settings.effectiveWebMcpEnabled, ctx.sessionManager);
103
+ state.settingsNotice = settings.notice;
104
+ for (const warning of settings.warnings) {
105
+ ctx.ui.notify(sanitizeChromeDevtoolsDisplay(warning), "warning");
106
+ }
107
+ if (webMcpEnabled(ctx.sessionManager)) {
108
+ ctx.ui.notify(
109
+ "Experimental WebMCP is enabled. Page-provided tools use the visible browser session and require confirmation for every call.",
110
+ "warning",
111
+ );
112
+ }
113
+ const availableTools =
114
+ settings.kind === "loaded" && settings.settings.tools
115
+ ? settings.settings.tools
116
+ : availableChromeDevtoolsTools(pi);
117
+ configureChromeDevtoolsToolExposure(pi, availableTools, ctx.model);
118
+ });
131
119
 
132
- pi.on("model_select", (event, ctx) => {
133
- invalidateWebMcpOperations(
134
- ctx.sessionManager,
135
- "Chrome DevTools model and tool exposure changed",
136
- );
137
- if (!supportsNativeDeferredToolLoading(event.model)) {
138
- requireEagerChromeDevtoolsToolExposure(pi);
139
- }
140
- });
120
+ pi.on("model_select", (event, ctx) => {
121
+ invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools model and tool exposure changed");
122
+ if (!supportsNativeDeferredToolLoading(event.model)) {
123
+ requireEagerChromeDevtoolsToolExposure(pi);
124
+ }
125
+ });
141
126
 
142
- pi.on("session_shutdown", async (_event, ctx) => {
143
- state.sessionGeneration += 1;
144
- replaceSessionController("Chrome DevTools session shut down");
145
- invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools session shut down");
146
- ctx.ui.setStatus(STATUS_KEY, undefined);
147
- const browserShutdown = shutdownManagedBrowser(undefined, {
148
- cancelLaunch: true,
149
- owner: ctx.sessionManager,
150
- });
151
- await waitForChromeDevtoolsSettings();
152
- await waitForSettingsWrites();
153
- await browserShutdown;
154
- });
127
+ pi.on("session_shutdown", async (_event, ctx) => {
128
+ state.sessionGeneration += 1;
129
+ replaceSessionController("Chrome DevTools session shut down");
130
+ invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools session shut down");
131
+ ctx.ui.setStatus(STATUS_KEY, undefined);
132
+ const browserShutdown = shutdownManagedBrowser(undefined, {
133
+ cancelLaunch: true,
134
+ owner: ctx.sessionManager,
135
+ });
136
+ await waitForChromeDevtoolsSettings();
137
+ await waitForSettingsWrites();
138
+ await browserShutdown;
139
+ });
155
140
  }
156
141
 
157
- async function handleChromeDevtoolsCommand(
158
- pi: ExtensionAPI,
159
- args: string,
160
- ctx: CommandContext,
161
- generation: number,
162
- ) {
163
- const command = parseCommand(args);
164
- switch (command) {
165
- case "menu":
166
- await showMenu(pi, ctx, generation);
167
- return;
168
- case "help":
169
- requireObservableUi(ctx, "help");
170
- ctx.ui.notify(buildCommandGuide(ctx.sessionManager), "info");
171
- return;
172
- case "quickstart":
173
- requireObservableUi(ctx, "quickstart");
174
- ctx.ui.notify(buildQuickstartMessage(ctx.sessionManager), "info");
175
- return;
176
- case "status": {
177
- requireObservableUi(ctx, "status");
178
- const status = await buildToolStatusMessage(pi, ctx.sessionManager);
179
- if (generation !== state.sessionGeneration) return;
180
- ctx.ui.notify(status, "info");
181
- return;
182
- }
183
- case "settings": {
184
- if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
185
- throw new Error("/chrome-devtools settings requires TUI or RPC mode");
186
- }
187
- const { showChromeDevtoolsBrowserSettings } = await import("./browser-settings-menu.js");
188
- if (generation !== state.sessionGeneration) return;
189
- await showChromeDevtoolsBrowserSettings(pi, ctx, generation);
190
- return;
191
- }
192
- case "tools": {
193
- if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
194
- throw new Error("/chrome-devtools tools requires TUI or RPC mode");
195
- }
196
- const { showChromeDevtoolsToolWorkflow } = await import("./menu.js");
197
- if (generation !== state.sessionGeneration) return;
198
- await showChromeDevtoolsToolWorkflow(pi, ctx, generation);
199
- return;
200
- }
201
- case "enable":
202
- await updateChromeDevtoolsTools(
203
- pi,
204
- ctx,
205
- allChromeDevtoolsTools(ctx.sessionManager),
206
- "made all available",
207
- );
208
- return;
209
- case "disable":
210
- await updateChromeDevtoolsTools(pi, ctx, [], "made all unavailable");
211
- return;
212
- }
142
+ async function handleChromeDevtoolsCommand(pi: ExtensionAPI, args: string, ctx: CommandContext, generation: number) {
143
+ const command = parseCommand(args);
144
+ switch (command) {
145
+ case "menu":
146
+ await showMenu(pi, ctx, generation);
147
+ return;
148
+ case "help":
149
+ requireObservableUi(ctx, "help");
150
+ ctx.ui.notify(buildCommandGuide(ctx.sessionManager), "info");
151
+ return;
152
+ case "quickstart":
153
+ requireObservableUi(ctx, "quickstart");
154
+ ctx.ui.notify(buildQuickstartMessage(ctx.sessionManager), "info");
155
+ return;
156
+ case "status": {
157
+ requireObservableUi(ctx, "status");
158
+ const status = await buildToolStatusMessage(pi, ctx.sessionManager);
159
+ if (generation !== state.sessionGeneration) return;
160
+ ctx.ui.notify(status, "info");
161
+ return;
162
+ }
163
+ case "settings": {
164
+ if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
165
+ throw new Error("/chrome-devtools settings requires TUI or RPC mode");
166
+ }
167
+ const { showChromeDevtoolsBrowserSettings } = await import("./browser-settings-menu.js");
168
+ if (generation !== state.sessionGeneration) return;
169
+ await showChromeDevtoolsBrowserSettings(pi, ctx, generation);
170
+ return;
171
+ }
172
+ case "tools": {
173
+ if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
174
+ throw new Error("/chrome-devtools tools requires TUI or RPC mode");
175
+ }
176
+ const { showChromeDevtoolsToolWorkflow } = await import("./menu.js");
177
+ if (generation !== state.sessionGeneration) return;
178
+ await showChromeDevtoolsToolWorkflow(pi, ctx, generation);
179
+ return;
180
+ }
181
+ case "enable":
182
+ await updateChromeDevtoolsTools(pi, ctx, allChromeDevtoolsTools(ctx.sessionManager), "made all available");
183
+ return;
184
+ case "disable":
185
+ await updateChromeDevtoolsTools(pi, ctx, [], "made all unavailable");
186
+ return;
187
+ }
213
188
 
214
- if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
215
- throw new Error(`Unknown /chrome-devtools command: ${args.trim()}`);
216
- }
217
- ctx.ui.notify(
218
- `Unknown /chrome-devtools command: ${args.trim()}
189
+ if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
190
+ throw new Error(`Unknown /chrome-devtools command: ${args.trim()}`);
191
+ }
192
+ ctx.ui.notify(
193
+ `Unknown /chrome-devtools command: ${args.trim()}
219
194
 
220
195
  ${buildCommandGuide(ctx.sessionManager)}`,
221
- "warning",
222
- );
196
+ "warning",
197
+ );
223
198
  }
224
199
 
225
200
  function requireObservableUi(ctx: CommandContext, route: string) {
226
- if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
227
- throw new Error(`/chrome-devtools ${route} requires TUI or RPC mode`);
228
- }
201
+ if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
202
+ throw new Error(`/chrome-devtools ${route} requires TUI or RPC mode`);
203
+ }
229
204
  }
230
205
 
231
206
  async function showMenu(pi: ExtensionAPI, ctx: CommandContext, generation: number) {
232
- if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
233
- throw new Error("/chrome-devtools menu requires TUI or RPC mode; use a direct subcommand");
234
- }
235
- const { showChromeDevtoolsMenu } = await import("./menu.js");
236
- if (generation !== state.sessionGeneration) return;
237
- await showChromeDevtoolsMenu(pi, ctx, generation);
207
+ if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
208
+ throw new Error("/chrome-devtools menu requires TUI or RPC mode; use a direct subcommand");
209
+ }
210
+ const { showChromeDevtoolsMenu } = await import("./menu.js");
211
+ if (generation !== state.sessionGeneration) return;
212
+ await showChromeDevtoolsMenu(pi, ctx, generation);
238
213
  }
239
214
 
240
215
  function replaceSessionController(reason: string) {
241
- state.sessionController.abort(new DOMException(reason, "AbortError"));
242
- state.sessionController = new AbortController();
216
+ state.sessionController.abort(new DOMException(reason, "AbortError"));
217
+ state.sessionController = new AbortController();
243
218
  }
244
219
 
245
220
  export function parseCommand(args: string): CommandAction | "unknown" {
246
- const command = args.trim().toLowerCase();
247
- if (!command) return "menu";
248
- if (command === "help") return "help";
249
- if (command === "quickstart") return "quickstart";
250
- if (command === "status") return "status";
251
- if (command === "settings") return "settings";
252
- if (command === "tools" || command === "select" || command === "toggle") return "tools";
253
- if (command === "enable" || command === "on") return "enable";
254
- if (command === "disable" || command === "off") return "disable";
255
- return "unknown";
221
+ const command = args.trim().toLowerCase();
222
+ if (!command) return "menu";
223
+ if (command === "help") return "help";
224
+ if (command === "quickstart") return "quickstart";
225
+ if (command === "status") return "status";
226
+ if (command === "settings") return "settings";
227
+ if (command === "tools" || command === "select" || command === "toggle") return "tools";
228
+ if (command === "enable" || command === "on") return "enable";
229
+ if (command === "disable" || command === "off") return "disable";
230
+ return "unknown";
256
231
  }
257
232
 
258
233
  export function commandCompletions(prefix: string) {
259
- const normalized = prefix.trimStart().toLowerCase();
260
- if (/\s/.test(normalized)) return null;
234
+ const normalized = prefix.trimStart().toLowerCase();
235
+ if (/\s/.test(normalized)) return null;
261
236
 
262
- const matches = COMMAND_COMPLETIONS.filter((completion) =>
263
- completion.value.startsWith(normalized),
264
- );
265
- return matches.length > 0 ? matches : null;
237
+ const matches = COMMAND_COMPLETIONS.filter((completion) => completion.value.startsWith(normalized));
238
+ return matches.length > 0 ? matches : null;
266
239
  }
267
240
 
268
241
  export {
269
- formatHostForUrl,
270
- isLocalDevToolsHost,
271
- quoteCommandPart,
242
+ formatHostForUrl,
243
+ isLocalDevToolsHost,
244
+ quoteCommandPart,
272
245
  } from "./browser-manager.js";
273
246
  export { CHROME_DEVTOOLS_LOAD_TOOL_NAME } from "./lazy-tools.js";
274
247
  export { parseConfiguredPort } from "./runtime.js";
275
248
  export {
276
- hasParentPathSegment,
277
- isPathInsideRoot,
278
- resolveScreenshotPath,
279
- selectAllowedRoot,
249
+ hasParentPathSegment,
250
+ isPathInsideRoot,
251
+ resolveScreenshotPath,
252
+ selectAllowedRoot,
280
253
  } from "./screenshot.js";
281
254
  export { normalizeChromeDevtoolsSettings } from "./settings.js";
282
255
  export {
283
- orderedChromeDevtoolsTools,
284
- sanitizeChromeDevtoolsDisplay,
256
+ orderedChromeDevtoolsTools,
257
+ sanitizeChromeDevtoolsDisplay,
285
258
  } from "./tool-selector.js";
package/src/display.ts CHANGED
@@ -2,37 +2,34 @@ const MAX_SANITIZER_INPUT_CODE_UNITS = 50_000;
2
2
  const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
3
3
 
4
4
  export function sanitizeChromeDevtoolsDisplay(value: string, maxCharacters = 50_000) {
5
- const inputWasTruncated = value.length > MAX_SANITIZER_INPUT_CODE_UNITS;
6
- const boundedInput = inputWasTruncated
7
- ? truncateAtGraphemeBoundary(value, MAX_SANITIZER_INPUT_CODE_UNITS)
8
- : value;
9
- const normalizedLineEndings = boundedInput.replace(/\r\n/g, "\n");
10
- // Neutralize controls without parsing untrusted terminal sequences; keep printable payload visible.
11
- const sanitized = Array.from(normalizedLineEndings, (character) => {
12
- const codePoint = character.codePointAt(0) ?? 0;
13
- const unsafeControl =
14
- (codePoint >= 0 && codePoint <= 8) ||
15
- (codePoint >= 11 && codePoint <= 31) ||
16
- (codePoint >= 127 && codePoint <= 159);
17
- const unsafeBidi =
18
- (codePoint >= 0x202a && codePoint <= 0x202e) || (codePoint >= 0x2066 && codePoint <= 0x2069);
19
- const loneSurrogate = character.length === 1 && codePoint >= 0xd800 && codePoint <= 0xdfff;
20
- return unsafeControl || unsafeBidi || loneSurrogate ? "�" : character;
21
- }).join("");
22
- const outputLimit = Math.min(maxCharacters, MAX_SANITIZER_INPUT_CODE_UNITS);
23
- if (!inputWasTruncated && sanitized.length <= outputLimit) return sanitized;
5
+ const inputWasTruncated = value.length > MAX_SANITIZER_INPUT_CODE_UNITS;
6
+ const boundedInput = inputWasTruncated ? truncateAtGraphemeBoundary(value, MAX_SANITIZER_INPUT_CODE_UNITS) : value;
7
+ const normalizedLineEndings = boundedInput.replace(/\r\n/g, "\n");
8
+ // Neutralize controls without parsing untrusted terminal sequences; keep printable payload visible.
9
+ const sanitized = Array.from(normalizedLineEndings, (character) => {
10
+ const codePoint = character.codePointAt(0) ?? 0;
11
+ const unsafeControl =
12
+ (codePoint >= 0 && codePoint <= 8) ||
13
+ (codePoint >= 11 && codePoint <= 31) ||
14
+ (codePoint >= 127 && codePoint <= 159);
15
+ const unsafeBidi = (codePoint >= 0x202a && codePoint <= 0x202e) || (codePoint >= 0x2066 && codePoint <= 0x2069);
16
+ const loneSurrogate = character.length === 1 && codePoint >= 0xd800 && codePoint <= 0xdfff;
17
+ return unsafeControl || unsafeBidi || loneSurrogate ? "�" : character;
18
+ }).join("");
19
+ const outputLimit = Math.min(maxCharacters, MAX_SANITIZER_INPUT_CODE_UNITS);
20
+ if (!inputWasTruncated && sanitized.length <= outputLimit) return sanitized;
24
21
 
25
- return `${truncateAtGraphemeBoundary(sanitized, Math.max(0, outputLimit - 1))}…`;
22
+ return `${truncateAtGraphemeBoundary(sanitized, Math.max(0, outputLimit - 1))}…`;
26
23
  }
27
24
 
28
25
  function truncateAtGraphemeBoundary(value: string, maxCodeUnits: number) {
29
- if (value.length <= maxCodeUnits) return value;
30
- const boundedLookahead = value.slice(0, Math.max(0, maxCodeUnits) + 2);
31
- let safeEnd = 0;
32
- for (const { index, segment } of graphemeSegmenter.segment(boundedLookahead)) {
33
- const segmentEnd = index + segment.length;
34
- if (segmentEnd > maxCodeUnits) break;
35
- safeEnd = segmentEnd;
36
- }
37
- return value.slice(0, safeEnd);
26
+ if (value.length <= maxCodeUnits) return value;
27
+ const boundedLookahead = value.slice(0, Math.max(0, maxCodeUnits) + 2);
28
+ let safeEnd = 0;
29
+ for (const { index, segment } of graphemeSegmenter.segment(boundedLookahead)) {
30
+ const segmentEnd = index + segment.length;
31
+ if (segmentEnd > maxCodeUnits) break;
32
+ safeEnd = segmentEnd;
33
+ }
34
+ return value.slice(0, safeEnd);
38
35
  }