@narumitw/pi-chrome-devtools 0.20.0 → 0.31.0
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 +4 -3
- package/package.json +5 -5
- package/src/browser-manager.ts +26 -13
- package/src/cdp-client.ts +2 -2
- package/src/chrome-devtools.ts +11 -6
- package/src/index.ts +1 -0
- package/src/render.ts +7 -8
- package/src/screenshot.ts +8 -3
- package/src/settings.ts +5 -13
- package/src/tool-selector.ts +7 -8
package/README.md
CHANGED
|
@@ -189,7 +189,8 @@ filename is deprecated and will be removed in a future major release.
|
|
|
189
189
|
```txt
|
|
190
190
|
extensions/pi-chrome-devtools/
|
|
191
191
|
├── src/
|
|
192
|
-
│ ├──
|
|
192
|
+
│ ├── index.ts # Pi package entrypoint
|
|
193
|
+
│ ├── chrome-devtools.ts # Extension registration and command orchestration
|
|
193
194
|
│ └── *.ts # Package-local browser, CDP, tool, and storage modules
|
|
194
195
|
├── README.md
|
|
195
196
|
├── LICENSE
|
|
@@ -197,12 +198,12 @@ extensions/pi-chrome-devtools/
|
|
|
197
198
|
└── package.json
|
|
198
199
|
```
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
`index.ts` is the Pi entrypoint and forwards to `chrome-devtools.ts`; the other source modules are internal. The package exposes its Pi extension through `package.json`:
|
|
201
202
|
|
|
202
203
|
```json
|
|
203
204
|
{
|
|
204
205
|
"pi": {
|
|
205
|
-
"extensions": ["./src/
|
|
206
|
+
"extensions": ["./src/index.ts"]
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-chrome-devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Pi extension that exposes Chrome DevTools Protocol tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"pi": {
|
|
22
22
|
"extensions": [
|
|
23
|
-
"./src/
|
|
23
|
+
"./src/index.ts"
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"typecheck": "tsc --noEmit"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"typebox": "^1.3.
|
|
32
|
+
"typebox": "^1.3.8"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@biomejs/biome": "2.5.
|
|
36
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
35
|
+
"@biomejs/biome": "2.5.5",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
37
37
|
"typescript": "7.0.2"
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
package/src/browser-manager.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { access, mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import {
|
|
5
|
+
import { delimiter, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import {
|
|
7
7
|
BROWSER_SHUTDOWN_WAIT_MS,
|
|
8
|
+
type BrowserCandidate,
|
|
9
|
+
type BrowserCandidateDefinition,
|
|
8
10
|
DEFAULT_ENDPOINT_RETRY_MS,
|
|
9
11
|
DEFAULT_ENDPOINT_WAIT_MS,
|
|
10
|
-
DEFAULT_HOST,
|
|
11
12
|
DEFAULT_HTTP_TIMEOUT_MS,
|
|
12
13
|
DEVTOOLS_ACTIVE_PORT_FILE,
|
|
13
|
-
MANAGED_BROWSER_PROFILE_PREFIX,
|
|
14
|
-
state,
|
|
15
|
-
type BrowserCandidate,
|
|
16
|
-
type BrowserCandidateDefinition,
|
|
17
14
|
type DevToolsPage,
|
|
15
|
+
MANAGED_BROWSER_PROFILE_PREFIX,
|
|
18
16
|
type ManagedBrowser,
|
|
17
|
+
state,
|
|
19
18
|
} from "./runtime.js";
|
|
20
19
|
|
|
21
20
|
function formatError(error: unknown) {
|
|
@@ -128,7 +127,12 @@ async function launchBrowserCandidate(candidate: BrowserCandidate, waitMs: numbe
|
|
|
128
127
|
];
|
|
129
128
|
throwIfBrowserLaunchCancelled();
|
|
130
129
|
const child = spawn(candidate.resolvedExecutable, args, { shell: false, stdio: "ignore" });
|
|
131
|
-
const launchedBrowser: ManagedBrowser = {
|
|
130
|
+
const launchedBrowser: ManagedBrowser = {
|
|
131
|
+
process: child,
|
|
132
|
+
userDataDir,
|
|
133
|
+
exited: false,
|
|
134
|
+
ready: false,
|
|
135
|
+
};
|
|
132
136
|
managedBrowser = launchedBrowser;
|
|
133
137
|
state.managedBrowser = launchedBrowser;
|
|
134
138
|
|
|
@@ -259,7 +263,8 @@ export async function shutdownManagedBrowser(
|
|
|
259
263
|
});
|
|
260
264
|
}
|
|
261
265
|
await rm(managedBrowser.userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
262
|
-
if (!state.portConfigured && managedBrowser.port === state.port)
|
|
266
|
+
if (!state.portConfigured && managedBrowser.port === state.port)
|
|
267
|
+
state.port = state.configuredPort;
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
function killManagedBrowserProcess(managedBrowser: ManagedBrowser, signal?: NodeJS.Signals) {
|
|
@@ -400,9 +405,13 @@ export function launchModeLabel() {
|
|
|
400
405
|
if (!isLocalDevToolsHost(state.host)) return "manual remote endpoint";
|
|
401
406
|
if (!state.autoLaunchEnabled) return "manual; auto-launch disabled";
|
|
402
407
|
if (state.managedBrowser && !state.managedBrowser.exited) {
|
|
403
|
-
return state.portConfigured
|
|
408
|
+
return state.portConfigured
|
|
409
|
+
? "auto-launched on explicit port"
|
|
410
|
+
: "auto-launched on dynamic port";
|
|
404
411
|
}
|
|
405
|
-
return state.portConfigured
|
|
412
|
+
return state.portConfigured
|
|
413
|
+
? "attach first; auto-launch explicit port"
|
|
414
|
+
: "attach first; auto-launch dynamic port";
|
|
406
415
|
}
|
|
407
416
|
|
|
408
417
|
export function launchAttemptLines() {
|
|
@@ -435,7 +444,9 @@ export function launchHint() {
|
|
|
435
444
|
}
|
|
436
445
|
|
|
437
446
|
export function browserCandidateHint() {
|
|
438
|
-
return `Browser candidates: ${browserCandidateDefinitions()
|
|
447
|
+
return `Browser candidates: ${browserCandidateDefinitions()
|
|
448
|
+
.map((candidate) => candidate.label)
|
|
449
|
+
.join(", ")}`;
|
|
439
450
|
}
|
|
440
451
|
|
|
441
452
|
export function chromeLaunchCommand() {
|
|
@@ -475,7 +486,9 @@ function browserCandidateDefinitions(): BrowserCandidateDefinition[] {
|
|
|
475
486
|
|
|
476
487
|
function explicitBrowserCandidateDefinition(): BrowserCandidateDefinition[] {
|
|
477
488
|
if (!state.browserExecutable) return [];
|
|
478
|
-
return [
|
|
489
|
+
return [
|
|
490
|
+
{ label: "PI_CHROME_DEVTOOLS_BROWSER", executable: state.browserExecutable, source: "env" },
|
|
491
|
+
];
|
|
479
492
|
}
|
|
480
493
|
|
|
481
494
|
function platformBrowserCandidateDefinitions(): BrowserCandidateDefinition[] {
|
package/src/cdp-client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ensureDevToolsEndpoint,
|
|
3
2
|
devToolsEndpoint,
|
|
3
|
+
ensureDevToolsEndpoint,
|
|
4
4
|
fetchDevToolsJson,
|
|
5
5
|
formatPageListItem,
|
|
6
6
|
launchHint,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_ENDPOINT_WAIT_MS,
|
|
11
11
|
DEFAULT_TIMEOUT_MS,
|
|
12
|
-
state,
|
|
13
12
|
type DevToolsPage,
|
|
13
|
+
state,
|
|
14
14
|
} from "./runtime.js";
|
|
15
15
|
|
|
16
16
|
interface CdpResponse<T = unknown> {
|
package/src/chrome-devtools.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { shutdownManagedBrowser } from "./browser-manager.js";
|
|
3
|
-
import { loadSettings } from "./settings.js";
|
|
4
3
|
import { state } from "./runtime.js";
|
|
4
|
+
import { loadSettings } from "./settings.js";
|
|
5
5
|
import {
|
|
6
6
|
allChromeDevtoolsTools,
|
|
7
7
|
applyChromeDevtoolsTools,
|
|
8
8
|
buildCommandGuide,
|
|
9
9
|
buildQuickstartMessage,
|
|
10
10
|
buildToolStatusMessage,
|
|
11
|
-
setSelectedChromeDevtoolsTools,
|
|
12
11
|
showToolSelector,
|
|
13
12
|
updateChromeDevtoolsTools,
|
|
14
13
|
} from "./tool-selector.js";
|
|
@@ -104,16 +103,22 @@ async function handleChromeDevtoolsCommand(pi: ExtensionAPI, args: string, ctx:
|
|
|
104
103
|
return;
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
ctx.ui.notify(
|
|
106
|
+
ctx.ui.notify(
|
|
107
|
+
`Unknown /chrome-devtools command: ${args.trim()}
|
|
108
108
|
|
|
109
|
-
${buildCommandGuide()}`,
|
|
109
|
+
${buildCommandGuide()}`,
|
|
110
|
+
"warning",
|
|
111
|
+
);
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
async function showMenu(pi: ExtensionAPI, ctx: CommandContext) {
|
|
113
115
|
if (!ctx.hasUI) {
|
|
114
|
-
ctx.ui.notify(
|
|
116
|
+
ctx.ui.notify(
|
|
117
|
+
`${buildCommandGuide()}
|
|
115
118
|
|
|
116
|
-
${await buildToolStatusMessage(pi)}`,
|
|
119
|
+
${await buildToolStatusMessage(pi)}`,
|
|
120
|
+
"info",
|
|
121
|
+
);
|
|
117
122
|
return;
|
|
118
123
|
}
|
|
119
124
|
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./chrome-devtools.js";
|
package/src/render.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentToolResult,
|
|
3
|
-
ToolRenderResultOptions,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { AgentToolResult, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
5
2
|
|
|
6
3
|
const STATUS_KEY = "chrome-devtools";
|
|
7
4
|
interface StatusContext {
|
|
@@ -90,9 +87,7 @@ class PiTextComponent implements RenderComponent {
|
|
|
90
87
|
.split(/\r?\n/)
|
|
91
88
|
.map((line) => {
|
|
92
89
|
const truncatedLine = truncateLine(line, Math.max(1, width));
|
|
93
|
-
return this.theme && this.color
|
|
94
|
-
? this.theme.fg(this.color, truncatedLine)
|
|
95
|
-
: truncatedLine;
|
|
90
|
+
return this.theme && this.color ? this.theme.fg(this.color, truncatedLine) : truncatedLine;
|
|
96
91
|
});
|
|
97
92
|
}
|
|
98
93
|
}
|
|
@@ -101,7 +96,11 @@ function truncateLine(line: string, maxWidth: number) {
|
|
|
101
96
|
return Array.from(line).slice(0, maxWidth).join("");
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
export async function withStatus<T>(
|
|
99
|
+
export async function withStatus<T>(
|
|
100
|
+
ctx: StatusContext,
|
|
101
|
+
status: string,
|
|
102
|
+
callback: () => Promise<T>,
|
|
103
|
+
) {
|
|
105
104
|
ctx.ui.setStatus(STATUS_KEY, status);
|
|
106
105
|
try {
|
|
107
106
|
return await callback();
|
package/src/screenshot.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { lstat, mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
@@ -47,7 +47,10 @@ export async function saveScreenshot(
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export function resolveScreenshotPath(
|
|
50
|
+
export function resolveScreenshotPath(
|
|
51
|
+
savePath: string | undefined,
|
|
52
|
+
cwd: string,
|
|
53
|
+
): ResolvedScreenshotPath {
|
|
51
54
|
const cwdRoot = resolve(cwd);
|
|
52
55
|
const tempRoot = resolve(tmpdir());
|
|
53
56
|
|
|
@@ -228,7 +231,9 @@ export function isPathInsideRoot(path: string, root: string) {
|
|
|
228
231
|
const normalizedPath = normalizePathForComparison(resolve(path));
|
|
229
232
|
const normalizedRoot = normalizePathForComparison(resolve(root));
|
|
230
233
|
if (normalizedPath === normalizedRoot) return true;
|
|
231
|
-
const rootWithSeparator = normalizedRoot.endsWith(sep)
|
|
234
|
+
const rootWithSeparator = normalizedRoot.endsWith(sep)
|
|
235
|
+
? normalizedRoot
|
|
236
|
+
: `${normalizedRoot}${sep}`;
|
|
232
237
|
return normalizedPath.startsWith(rootWithSeparator);
|
|
233
238
|
}
|
|
234
239
|
|
package/src/settings.ts
CHANGED
|
@@ -3,10 +3,7 @@ import { constants } from "node:fs";
|
|
|
3
3
|
import { access, link, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
CHROME_DEVTOOLS_TOOL_NAMES,
|
|
8
|
-
type ChromeDevToolsToolName,
|
|
9
|
-
} from "./tool-names.js";
|
|
6
|
+
import { CHROME_DEVTOOLS_TOOL_NAMES, type ChromeDevToolsToolName } from "./tool-names.js";
|
|
10
7
|
|
|
11
8
|
const NEW_SETTINGS_FILE_NAME = "pi-chrome-devtools.json";
|
|
12
9
|
const LEGACY_SETTINGS_FILE_NAME = "pi-chrome-devtools-settings.json";
|
|
@@ -99,10 +96,7 @@ async function migrateLegacySettings(
|
|
|
99
96
|
): Promise<SettingsMigrationResult> {
|
|
100
97
|
const newPath = settingsFilePath();
|
|
101
98
|
try {
|
|
102
|
-
await installSettingsFileExclusively(
|
|
103
|
-
newPath,
|
|
104
|
-
`${JSON.stringify(settings, null, 2)}\n`,
|
|
105
|
-
);
|
|
99
|
+
await installSettingsFileExclusively(newPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
106
100
|
} catch (error) {
|
|
107
101
|
return {
|
|
108
102
|
kind: "failed",
|
|
@@ -134,7 +128,9 @@ async function fileExists(filePath: string) {
|
|
|
134
128
|
}
|
|
135
129
|
}
|
|
136
130
|
|
|
137
|
-
export function normalizeChromeDevtoolsSettings(
|
|
131
|
+
export function normalizeChromeDevtoolsSettings(
|
|
132
|
+
value: unknown,
|
|
133
|
+
): ChromeDevToolsSettings | undefined {
|
|
138
134
|
if (!value || typeof value !== "object") return undefined;
|
|
139
135
|
const settings = value as { tools?: unknown; updatedAt?: unknown };
|
|
140
136
|
if (typeof settings.updatedAt !== "number") return undefined;
|
|
@@ -190,7 +186,3 @@ function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
|
190
186
|
function formatError(error: unknown) {
|
|
191
187
|
return error instanceof Error ? error.message : String(error);
|
|
192
188
|
}
|
|
193
|
-
|
|
194
|
-
function unique<T>(values: T[]) {
|
|
195
|
-
return Array.from(new Set(values));
|
|
196
|
-
}
|
package/src/tool-selector.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
3
|
browserCandidateHint,
|
|
4
|
-
chromeLaunchCommand,
|
|
5
4
|
devToolsEndpoint,
|
|
6
5
|
endpointConfigHint,
|
|
7
6
|
endpointSourceLabel,
|
|
@@ -9,12 +8,9 @@ import {
|
|
|
9
8
|
launchHint,
|
|
10
9
|
launchModeLabel,
|
|
11
10
|
} from "./browser-manager.js";
|
|
12
|
-
import { loadSettings, saveSettings, settingsFilePath } from "./settings.js";
|
|
13
11
|
import { state } from "./runtime.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
type ChromeDevToolsToolName,
|
|
17
|
-
} from "./tool-names.js";
|
|
12
|
+
import { loadSettings, saveSettings, settingsFilePath } from "./settings.js";
|
|
13
|
+
import { CHROME_DEVTOOLS_TOOL_NAMES, type ChromeDevToolsToolName } from "./tool-names.js";
|
|
18
14
|
|
|
19
15
|
type CommandContext = ExtensionCommandContext;
|
|
20
16
|
const TOOL_SELECTOR_DONE = "Done";
|
|
@@ -187,9 +183,12 @@ export async function updateChromeDevtoolsTools(
|
|
|
187
183
|
action: string,
|
|
188
184
|
) {
|
|
189
185
|
await setSelectedChromeDevtoolsTools(pi, ctx, selectedTools);
|
|
190
|
-
ctx.ui.notify(
|
|
186
|
+
ctx.ui.notify(
|
|
187
|
+
`Chrome DevTools tools ${action}.
|
|
191
188
|
|
|
192
|
-
${await buildToolStatusMessage(pi)}`,
|
|
189
|
+
${await buildToolStatusMessage(pi)}`,
|
|
190
|
+
"info",
|
|
191
|
+
);
|
|
193
192
|
}
|
|
194
193
|
|
|
195
194
|
export async function setSelectedChromeDevtoolsTools(
|