@everydaydevopsio/pilot 0.5.2 → 0.6.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/CHANGELOG.md +64 -0
- package/CLAUDE.md +7 -7
- package/README.md +108 -53
- package/dist/browser/browser-manager.d.ts +60 -0
- package/dist/browser/browser-manager.d.ts.map +1 -0
- package/dist/browser/browser-manager.js +348 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/chrome-launcher.d.ts +30 -0
- package/dist/browser/chrome-launcher.d.ts.map +1 -0
- package/dist/browser/chrome-launcher.js +217 -0
- package/dist/browser/chrome-launcher.js.map +1 -0
- package/dist/browser/connection.d.ts +23 -0
- package/dist/browser/connection.d.ts.map +1 -0
- package/dist/browser/connection.js +53 -0
- package/dist/browser/connection.js.map +1 -0
- package/dist/browser/events.d.ts +9 -0
- package/dist/browser/events.d.ts.map +1 -0
- package/dist/browser/events.js +99 -0
- package/dist/browser/events.js.map +1 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/inspect/element-ref.d.ts +23 -0
- package/dist/browser/inspect/element-ref.d.ts.map +1 -0
- package/dist/browser/inspect/element-ref.js +52 -0
- package/dist/browser/inspect/element-ref.js.map +1 -0
- package/dist/browser/inspect/snapshot.d.ts +11 -0
- package/dist/browser/inspect/snapshot.d.ts.map +1 -0
- package/dist/browser/inspect/snapshot.js +282 -0
- package/dist/browser/inspect/snapshot.js.map +1 -0
- package/dist/browser/inspect/styles.d.ts +45 -0
- package/dist/browser/inspect/styles.d.ts.map +1 -0
- package/dist/browser/inspect/styles.js +190 -0
- package/dist/browser/inspect/styles.js.map +1 -0
- package/dist/browser/inspect/types.d.ts +35 -0
- package/dist/browser/inspect/types.d.ts.map +1 -0
- package/dist/browser/inspect/types.js +2 -0
- package/dist/browser/inspect/types.js.map +1 -0
- package/dist/browser/interaction/dialogs.d.ts +18 -0
- package/dist/browser/interaction/dialogs.d.ts.map +1 -0
- package/dist/browser/interaction/dialogs.js +35 -0
- package/dist/browser/interaction/dialogs.js.map +1 -0
- package/dist/browser/interaction/downloads.d.ts +23 -0
- package/dist/browser/interaction/downloads.d.ts.map +1 -0
- package/dist/browser/interaction/downloads.js +87 -0
- package/dist/browser/interaction/downloads.js.map +1 -0
- package/dist/browser/interaction/events.d.ts +17 -0
- package/dist/browser/interaction/events.d.ts.map +1 -0
- package/dist/browser/interaction/events.js +66 -0
- package/dist/browser/interaction/events.js.map +1 -0
- package/dist/browser/interaction/ref-resolver.d.ts +18 -0
- package/dist/browser/interaction/ref-resolver.d.ts.map +1 -0
- package/dist/browser/interaction/ref-resolver.js +32 -0
- package/dist/browser/interaction/ref-resolver.js.map +1 -0
- package/dist/browser/interaction/uploads.d.ts +4 -0
- package/dist/browser/interaction/uploads.d.ts.map +1 -0
- package/dist/browser/interaction/uploads.js +43 -0
- package/dist/browser/interaction/uploads.js.map +1 -0
- package/dist/browser/network/network-buffer.d.ts +19 -0
- package/dist/browser/network/network-buffer.d.ts.map +1 -0
- package/dist/browser/network/network-buffer.js +118 -0
- package/dist/browser/network/network-buffer.js.map +1 -0
- package/dist/browser/network/network-monitor.d.ts +8 -0
- package/dist/browser/network/network-monitor.d.ts.map +1 -0
- package/dist/browser/network/network-monitor.js +85 -0
- package/dist/browser/network/network-monitor.js.map +1 -0
- package/dist/browser/network/redaction.d.ts +3 -0
- package/dist/browser/network/redaction.d.ts.map +1 -0
- package/dist/browser/network/redaction.js +36 -0
- package/dist/browser/network/redaction.js.map +1 -0
- package/dist/browser/network/types.d.ts +45 -0
- package/dist/browser/network/types.d.ts.map +1 -0
- package/dist/browser/network/types.js +2 -0
- package/dist/browser/network/types.js.map +1 -0
- package/dist/browser/performance/analyze.d.ts +5 -0
- package/dist/browser/performance/analyze.d.ts.map +1 -0
- package/dist/browser/performance/analyze.js +230 -0
- package/dist/browser/performance/analyze.js.map +1 -0
- package/dist/browser/performance/trace.d.ts +10 -0
- package/dist/browser/performance/trace.d.ts.map +1 -0
- package/dist/browser/performance/trace.js +38 -0
- package/dist/browser/performance/trace.js.map +1 -0
- package/dist/browser/performance/types.d.ts +31 -0
- package/dist/browser/performance/types.d.ts.map +1 -0
- package/dist/browser/performance/types.js +2 -0
- package/dist/browser/performance/types.js.map +1 -0
- package/dist/browser/security/origins.d.ts +18 -0
- package/dist/browser/security/origins.d.ts.map +1 -0
- package/dist/browser/security/origins.js +84 -0
- package/dist/browser/security/origins.js.map +1 -0
- package/dist/browser/tabs.d.ts +15 -0
- package/dist/browser/tabs.d.ts.map +1 -0
- package/dist/browser/tabs.js +71 -0
- package/dist/browser/tabs.js.map +1 -0
- package/dist/browser/types.d.ts +31 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +2 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/browser.d.ts +2 -99
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -653
- package/dist/browser.js.map +1 -1
- package/dist/commands/check.d.ts +17 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +38 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/click.d.ts +3 -1
- package/dist/commands/click.d.ts.map +1 -1
- package/dist/commands/click.js +16 -4
- package/dist/commands/click.js.map +1 -1
- package/dist/commands/connect.d.ts +14 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +19 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/dialog.d.ts +19 -0
- package/dist/commands/dialog.d.ts.map +1 -0
- package/dist/commands/dialog.js +51 -0
- package/dist/commands/dialog.js.map +1 -0
- package/dist/commands/downloads.d.ts +30 -0
- package/dist/commands/downloads.d.ts.map +1 -0
- package/dist/commands/downloads.js +66 -0
- package/dist/commands/downloads.js.map +1 -0
- package/dist/commands/fill.d.ts +16 -0
- package/dist/commands/fill.d.ts.map +1 -0
- package/dist/commands/fill.js +15 -0
- package/dist/commands/fill.js.map +1 -0
- package/dist/commands/find.d.ts +14 -0
- package/dist/commands/find.d.ts.map +1 -0
- package/dist/commands/find.js +26 -0
- package/dist/commands/find.js.map +1 -0
- package/dist/commands/hover.d.ts +13 -0
- package/dist/commands/hover.d.ts.map +1 -0
- package/dist/commands/hover.js +24 -0
- package/dist/commands/hover.js.map +1 -0
- package/dist/commands/navigate.d.ts.map +1 -1
- package/dist/commands/navigate.js +13 -0
- package/dist/commands/navigate.js.map +1 -1
- package/dist/commands/network.d.ts +33 -0
- package/dist/commands/network.d.ts.map +1 -0
- package/dist/commands/network.js +84 -0
- package/dist/commands/network.js.map +1 -0
- package/dist/commands/performance.d.ts +12 -0
- package/dist/commands/performance.d.ts.map +1 -0
- package/dist/commands/performance.js +26 -0
- package/dist/commands/performance.js.map +1 -0
- package/dist/commands/press_key.d.ts +10 -0
- package/dist/commands/press_key.d.ts.map +1 -0
- package/dist/commands/press_key.js +103 -0
- package/dist/commands/press_key.js.map +1 -0
- package/dist/commands/scroll.d.ts +21 -0
- package/dist/commands/scroll.d.ts.map +1 -0
- package/dist/commands/scroll.js +54 -0
- package/dist/commands/scroll.js.map +1 -0
- package/dist/commands/select.d.ts +18 -0
- package/dist/commands/select.d.ts.map +1 -0
- package/dist/commands/select.js +53 -0
- package/dist/commands/select.js.map +1 -0
- package/dist/commands/snapshot.d.ts +9 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +7 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/styles.d.ts +13 -0
- package/dist/commands/styles.d.ts.map +1 -0
- package/dist/commands/styles.js +7 -0
- package/dist/commands/styles.js.map +1 -0
- package/dist/commands/type.d.ts +3 -1
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +11 -2
- package/dist/commands/type.js.map +1 -1
- package/dist/commands/upload.d.ts +12 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +13 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/mcp/console-buffer.d.ts +9 -0
- package/dist/mcp/console-buffer.d.ts.map +1 -1
- package/dist/mcp/console-buffer.js.map +1 -1
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +63 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/browser.d.ts.map +1 -1
- package/dist/mcp/tools/browser.js +99 -8
- package/dist/mcp/tools/browser.js.map +1 -1
- package/dist/mcp/tools/connect.d.ts +5 -0
- package/dist/mcp/tools/connect.d.ts.map +1 -0
- package/dist/mcp/tools/connect.js +48 -0
- package/dist/mcp/tools/connect.js.map +1 -0
- package/dist/mcp/tools/files.d.ts +4 -0
- package/dist/mcp/tools/files.d.ts.map +1 -0
- package/dist/mcp/tools/files.js +85 -0
- package/dist/mcp/tools/files.js.map +1 -0
- package/dist/mcp/tools/interaction.d.ts +4 -0
- package/dist/mcp/tools/interaction.d.ts.map +1 -0
- package/dist/mcp/tools/interaction.js +176 -0
- package/dist/mcp/tools/interaction.js.map +1 -0
- package/dist/mcp/tools/names.d.ts +1 -1
- package/dist/mcp/tools/names.d.ts.map +1 -1
- package/dist/mcp/tools/names.js +19 -1
- package/dist/mcp/tools/names.js.map +1 -1
- package/dist/mcp/tools/network.d.ts +4 -0
- package/dist/mcp/tools/network.d.ts.map +1 -0
- package/dist/mcp/tools/network.js +68 -0
- package/dist/mcp/tools/network.js.map +1 -0
- package/dist/mcp/tools/performance.d.ts +4 -0
- package/dist/mcp/tools/performance.d.ts.map +1 -0
- package/dist/mcp/tools/performance.js +26 -0
- package/dist/mcp/tools/performance.js.map +1 -0
- package/dist/mcp/tools/runtime.d.ts +4 -0
- package/dist/mcp/tools/runtime.d.ts.map +1 -0
- package/dist/mcp/tools/runtime.js +128 -0
- package/dist/mcp/tools/runtime.js.map +1 -0
- package/dist/mcp/tools/snapshot.d.ts +4 -0
- package/dist/mcp/tools/snapshot.d.ts.map +1 -0
- package/dist/mcp/tools/snapshot.js +42 -0
- package/dist/mcp/tools/snapshot.js.map +1 -0
- package/dist/mcp/tools/styles.d.ts +4 -0
- package/dist/mcp/tools/styles.d.ts.map +1 -0
- package/dist/mcp/tools/styles.js +27 -0
- package/dist/mcp/tools/styles.js.map +1 -0
- package/dist/util/config.d.ts.map +1 -1
- package/dist/util/config.js +3 -2
- package/dist/util/config.js.map +1 -1
- package/package.json +3 -3
- package/src/browser/browser-manager.ts +457 -0
- package/src/browser/chrome-launcher.ts +271 -0
- package/src/browser/connection.ts +84 -0
- package/src/browser/events.ts +121 -0
- package/src/browser/index.ts +20 -0
- package/src/browser/inspect/element-ref.ts +68 -0
- package/src/browser/inspect/snapshot.ts +348 -0
- package/src/browser/inspect/styles.ts +258 -0
- package/src/browser/inspect/types.ts +37 -0
- package/src/browser/interaction/dialogs.ts +54 -0
- package/src/browser/interaction/downloads.ts +116 -0
- package/src/browser/interaction/events.ts +79 -0
- package/src/browser/interaction/ref-resolver.ts +53 -0
- package/src/browser/interaction/uploads.ts +60 -0
- package/src/browser/network/network-buffer.ts +133 -0
- package/src/browser/network/network-monitor.ts +107 -0
- package/src/browser/network/redaction.ts +42 -0
- package/src/browser/network/types.ts +47 -0
- package/src/browser/performance/analyze.ts +298 -0
- package/src/browser/performance/trace.ts +60 -0
- package/src/browser/performance/types.ts +34 -0
- package/src/browser/security/origins.ts +102 -0
- package/src/browser/tabs.ts +110 -0
- package/src/browser/types.ts +31 -0
- package/src/browser.ts +22 -798
- package/src/commands/check.ts +59 -0
- package/src/commands/click.ts +18 -4
- package/src/commands/connect.ts +40 -0
- package/src/commands/dialog.ts +79 -0
- package/src/commands/downloads.ts +111 -0
- package/src/commands/fill.ts +34 -0
- package/src/commands/find.ts +52 -0
- package/src/commands/hover.ts +42 -0
- package/src/commands/navigate.ts +20 -0
- package/src/commands/network.ts +141 -0
- package/src/commands/performance.ts +51 -0
- package/src/commands/press_key.ts +134 -0
- package/src/commands/scroll.ts +79 -0
- package/src/commands/select.ts +78 -0
- package/src/commands/snapshot.ts +18 -0
- package/src/commands/styles.ts +24 -0
- package/src/commands/type.ts +12 -2
- package/src/commands/upload.ts +36 -0
- package/src/mcp/console-buffer.ts +10 -0
- package/src/mcp/server.ts +74 -2
- package/src/mcp/tools/browser.ts +113 -7
- package/src/mcp/tools/connect.ts +62 -0
- package/src/mcp/tools/files.ts +120 -0
- package/src/mcp/tools/interaction.ts +232 -0
- package/src/mcp/tools/names.ts +19 -1
- package/src/mcp/tools/network.ts +86 -0
- package/src/mcp/tools/performance.ts +40 -0
- package/src/mcp/tools/runtime.ts +164 -0
- package/src/mcp/tools/snapshot.ts +64 -0
- package/src/mcp/tools/styles.ts +41 -0
- package/src/util/config.ts +3 -2
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import type { ElementRefMap } from '../browser/inspect/element-ref.js';
|
|
3
|
+
import { resolveRef } from '../browser/interaction/ref-resolver.js';
|
|
4
|
+
|
|
5
|
+
export interface CheckParams {
|
|
6
|
+
ref: string;
|
|
7
|
+
checked?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CheckResult {
|
|
11
|
+
ref: string;
|
|
12
|
+
checked: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Toggle a checkbox or radio button by ref.
|
|
17
|
+
* If `checked` is specified, sets it to that state (no-op if already matching).
|
|
18
|
+
* If omitted, toggles the current state.
|
|
19
|
+
*/
|
|
20
|
+
export async function executeCheck(
|
|
21
|
+
client: Client,
|
|
22
|
+
refMap: ElementRefMap,
|
|
23
|
+
params: CheckParams
|
|
24
|
+
): Promise<CheckResult> {
|
|
25
|
+
const resolved = await resolveRef(client, refMap, params.ref);
|
|
26
|
+
|
|
27
|
+
const { result, exceptionDetails } = await client.Runtime.callFunctionOn({
|
|
28
|
+
objectId: resolved.objectId,
|
|
29
|
+
functionDeclaration: `function(desiredState) {
|
|
30
|
+
var tag = this.tagName.toLowerCase();
|
|
31
|
+
var type = (this.type || '').toLowerCase();
|
|
32
|
+
if (tag !== 'input' || (type !== 'checkbox' && type !== 'radio')) {
|
|
33
|
+
throw new Error('Element is not a checkbox or radio button');
|
|
34
|
+
}
|
|
35
|
+
var current = this.checked;
|
|
36
|
+
var target = desiredState !== null ? desiredState : !current;
|
|
37
|
+
if (current !== target) {
|
|
38
|
+
this.click();
|
|
39
|
+
}
|
|
40
|
+
return this.checked;
|
|
41
|
+
}`,
|
|
42
|
+
arguments: [{ value: params.checked ?? null }],
|
|
43
|
+
returnByValue: true,
|
|
44
|
+
awaitPromise: false
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (exceptionDetails) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
exceptionDetails.exception?.description ??
|
|
50
|
+
exceptionDetails.text ??
|
|
51
|
+
'Failed to toggle checkbox'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ref: params.ref,
|
|
57
|
+
checked: result.value as boolean
|
|
58
|
+
};
|
|
59
|
+
}
|
package/src/commands/click.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { Client } from 'chrome-remote-interface';
|
|
3
|
+
import type { ElementRefMap } from '../browser/inspect/element-ref.js';
|
|
4
|
+
import { resolveRef } from '../browser/interaction/ref-resolver.js';
|
|
3
5
|
|
|
4
6
|
export const ClickParamsSchema = z
|
|
5
7
|
.object({
|
|
8
|
+
ref: z.string().optional(),
|
|
6
9
|
selector: z.string().optional(),
|
|
7
10
|
x: z.number().optional(),
|
|
8
11
|
y: z.number().optional(),
|
|
@@ -11,9 +14,12 @@ export const ClickParamsSchema = z
|
|
|
11
14
|
timeoutMs: z.number().int().positive().default(5000)
|
|
12
15
|
})
|
|
13
16
|
.refine(
|
|
14
|
-
(v) =>
|
|
17
|
+
(v) =>
|
|
18
|
+
v.ref !== undefined ||
|
|
19
|
+
v.selector !== undefined ||
|
|
20
|
+
(v.x !== undefined && v.y !== undefined),
|
|
15
21
|
{
|
|
16
|
-
message: 'Either selector or x/y coordinates are required'
|
|
22
|
+
message: 'Either ref, selector, or x/y coordinates are required'
|
|
17
23
|
}
|
|
18
24
|
);
|
|
19
25
|
|
|
@@ -52,12 +58,20 @@ async function waitForSelector(
|
|
|
52
58
|
|
|
53
59
|
export async function executeClick(
|
|
54
60
|
client: Client,
|
|
55
|
-
params: ClickParams
|
|
61
|
+
params: ClickParams,
|
|
62
|
+
refMap?: ElementRefMap
|
|
56
63
|
): Promise<ClickResult> {
|
|
57
64
|
let x: number;
|
|
58
65
|
let y: number;
|
|
59
66
|
|
|
60
|
-
if (params.
|
|
67
|
+
if (params.ref !== undefined) {
|
|
68
|
+
if (!refMap) {
|
|
69
|
+
throw new Error('Element ref map is required when using ref parameter');
|
|
70
|
+
}
|
|
71
|
+
const resolved = await resolveRef(client, refMap, params.ref);
|
|
72
|
+
x = resolved.x;
|
|
73
|
+
y = resolved.y;
|
|
74
|
+
} else if (params.selector !== undefined) {
|
|
61
75
|
const pos = await waitForSelector(
|
|
62
76
|
client,
|
|
63
77
|
params.selector,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BrowserManager } from '../browser/browser-manager.js';
|
|
2
|
+
import type { TabInfo } from '../browser/types.js';
|
|
3
|
+
|
|
4
|
+
export interface ConnectParams {
|
|
5
|
+
host?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ConnectResult {
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
tabs: TabInfo[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function executeConnect(
|
|
16
|
+
manager: BrowserManager,
|
|
17
|
+
params: ConnectParams
|
|
18
|
+
): Promise<ConnectResult> {
|
|
19
|
+
const host = params.host ?? '127.0.0.1';
|
|
20
|
+
const port = params.port ?? 9222;
|
|
21
|
+
|
|
22
|
+
const { tabs } = await manager.connectExisting({ host, port });
|
|
23
|
+
|
|
24
|
+
return { host, port, tabs };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function formatConnectResult(result: ConnectResult): string {
|
|
28
|
+
const lines = [
|
|
29
|
+
`Connected to Chrome at ${result.host}:${result.port}`,
|
|
30
|
+
'',
|
|
31
|
+
`Tabs (${result.tabs.length}):`
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const tab of result.tabs) {
|
|
35
|
+
const active = tab.active ? ' (active)' : '';
|
|
36
|
+
lines.push(` [${tab.targetId}] ${tab.title || tab.url}${active}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import type { DialogQueue } from '../browser/interaction/dialogs.js';
|
|
3
|
+
import { handleDialog } from '../browser/interaction/dialogs.js';
|
|
4
|
+
|
|
5
|
+
export type DialogAction = 'list' | 'accept' | 'dismiss';
|
|
6
|
+
|
|
7
|
+
export interface DialogParams {
|
|
8
|
+
action: DialogAction;
|
|
9
|
+
promptText?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DialogResult {
|
|
13
|
+
action: DialogAction;
|
|
14
|
+
dialogs?: Array<{
|
|
15
|
+
type: string;
|
|
16
|
+
message: string;
|
|
17
|
+
defaultPrompt?: string;
|
|
18
|
+
}>;
|
|
19
|
+
handled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function executeDialog(
|
|
23
|
+
client: Client,
|
|
24
|
+
queue: DialogQueue,
|
|
25
|
+
params: DialogParams
|
|
26
|
+
): Promise<DialogResult> {
|
|
27
|
+
switch (params.action) {
|
|
28
|
+
case 'list': {
|
|
29
|
+
const pending = queue.list();
|
|
30
|
+
return {
|
|
31
|
+
action: 'list',
|
|
32
|
+
dialogs: pending.map((d) => ({
|
|
33
|
+
type: d.type,
|
|
34
|
+
message: d.message,
|
|
35
|
+
defaultPrompt: d.defaultPrompt
|
|
36
|
+
}))
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
case 'accept': {
|
|
41
|
+
const dialog = queue.shift();
|
|
42
|
+
if (!dialog) {
|
|
43
|
+
return { action: 'accept', handled: false };
|
|
44
|
+
}
|
|
45
|
+
await handleDialog(client, true, params.promptText);
|
|
46
|
+
return { action: 'accept', handled: true };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
case 'dismiss': {
|
|
50
|
+
const dialog = queue.shift();
|
|
51
|
+
if (!dialog) {
|
|
52
|
+
return { action: 'dismiss', handled: false };
|
|
53
|
+
}
|
|
54
|
+
await handleDialog(client, false);
|
|
55
|
+
return { action: 'dismiss', handled: true };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatDialogResult(result: DialogResult): string {
|
|
61
|
+
if (result.action === 'list') {
|
|
62
|
+
const dialogs = result.dialogs ?? [];
|
|
63
|
+
if (dialogs.length === 0) {
|
|
64
|
+
return 'No pending dialogs.';
|
|
65
|
+
}
|
|
66
|
+
const lines = [`Pending dialogs (${dialogs.length}):`];
|
|
67
|
+
for (const d of dialogs) {
|
|
68
|
+
const prompt =
|
|
69
|
+
d.defaultPrompt !== undefined ? ` (default: "${d.defaultPrompt}")` : '';
|
|
70
|
+
lines.push(` [${d.type}] ${d.message}${prompt}`);
|
|
71
|
+
}
|
|
72
|
+
return lines.join('\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (result.handled) {
|
|
76
|
+
return `Dialog ${result.action}ed.`;
|
|
77
|
+
}
|
|
78
|
+
return 'No pending dialog to handle.';
|
|
79
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { DownloadTracker } from '../browser/interaction/downloads.js';
|
|
2
|
+
import { waitForDownload } from '../browser/interaction/downloads.js';
|
|
3
|
+
|
|
4
|
+
export type DownloadsAction = 'list' | 'wait' | 'clear';
|
|
5
|
+
|
|
6
|
+
export interface DownloadsParams {
|
|
7
|
+
action: DownloadsAction;
|
|
8
|
+
guid?: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DownloadsResult {
|
|
13
|
+
action: DownloadsAction;
|
|
14
|
+
downloads?: Array<{
|
|
15
|
+
guid: string;
|
|
16
|
+
url: string;
|
|
17
|
+
filename: string;
|
|
18
|
+
state: string;
|
|
19
|
+
receivedBytes: number;
|
|
20
|
+
totalBytes: number;
|
|
21
|
+
}>;
|
|
22
|
+
download?: {
|
|
23
|
+
guid: string;
|
|
24
|
+
url: string;
|
|
25
|
+
filename: string;
|
|
26
|
+
state: string;
|
|
27
|
+
receivedBytes: number;
|
|
28
|
+
totalBytes: number;
|
|
29
|
+
};
|
|
30
|
+
cleared?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function executeDownloads(
|
|
34
|
+
tracker: DownloadTracker,
|
|
35
|
+
params: DownloadsParams
|
|
36
|
+
): Promise<DownloadsResult> {
|
|
37
|
+
switch (params.action) {
|
|
38
|
+
case 'list': {
|
|
39
|
+
const records = tracker.list();
|
|
40
|
+
return {
|
|
41
|
+
action: 'list',
|
|
42
|
+
downloads: records.map((r) => ({
|
|
43
|
+
guid: r.guid,
|
|
44
|
+
url: r.url,
|
|
45
|
+
filename: r.suggestedFilename,
|
|
46
|
+
state: r.state,
|
|
47
|
+
receivedBytes: r.receivedBytes,
|
|
48
|
+
totalBytes: r.totalBytes
|
|
49
|
+
}))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case 'wait': {
|
|
54
|
+
if (!params.guid) {
|
|
55
|
+
throw new Error('guid is required for action "wait"');
|
|
56
|
+
}
|
|
57
|
+
const record = await waitForDownload(
|
|
58
|
+
tracker,
|
|
59
|
+
params.guid,
|
|
60
|
+
params.timeoutMs ?? 30000
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
action: 'wait',
|
|
64
|
+
download: {
|
|
65
|
+
guid: record.guid,
|
|
66
|
+
url: record.url,
|
|
67
|
+
filename: record.suggestedFilename,
|
|
68
|
+
state: record.state,
|
|
69
|
+
receivedBytes: record.receivedBytes,
|
|
70
|
+
totalBytes: record.totalBytes
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case 'clear': {
|
|
76
|
+
const cleared = tracker.clear();
|
|
77
|
+
return { action: 'clear', cleared };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function formatDownloadsResult(result: DownloadsResult): string {
|
|
83
|
+
if (result.action === 'list') {
|
|
84
|
+
const downloads = result.downloads ?? [];
|
|
85
|
+
if (downloads.length === 0) {
|
|
86
|
+
return 'No downloads tracked.';
|
|
87
|
+
}
|
|
88
|
+
const lines = [`Downloads (${downloads.length}):`];
|
|
89
|
+
for (const d of downloads) {
|
|
90
|
+
const progress =
|
|
91
|
+
d.totalBytes > 0
|
|
92
|
+
? ` ${Math.round((d.receivedBytes / d.totalBytes) * 100)}%`
|
|
93
|
+
: '';
|
|
94
|
+
lines.push(` [${d.guid}] ${d.filename} (${d.state}${progress})`);
|
|
95
|
+
}
|
|
96
|
+
return lines.join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (result.action === 'wait' && result.download) {
|
|
100
|
+
const d = result.download;
|
|
101
|
+
const label =
|
|
102
|
+
d.state === 'completed' ? 'Download complete' : `Download ${d.state}`;
|
|
103
|
+
return `${label}: ${d.filename} (${d.receivedBytes} bytes)`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (result.action === 'clear') {
|
|
107
|
+
return `Cleared ${result.cleared} download records.`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return 'Done.';
|
|
111
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import type { ElementRefMap } from '../browser/inspect/element-ref.js';
|
|
3
|
+
import { resolveRef, focusRef } from '../browser/interaction/ref-resolver.js';
|
|
4
|
+
import { setFieldValue } from '../browser/interaction/events.js';
|
|
5
|
+
|
|
6
|
+
export interface FillParams {
|
|
7
|
+
ref: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FillResult {
|
|
12
|
+
ref: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fill a form field by ref — clears the existing value and sets a new one.
|
|
18
|
+
* Fires framework-compatible input/change events.
|
|
19
|
+
*/
|
|
20
|
+
export async function executeFill(
|
|
21
|
+
client: Client,
|
|
22
|
+
refMap: ElementRefMap,
|
|
23
|
+
params: FillParams
|
|
24
|
+
): Promise<FillResult> {
|
|
25
|
+
const resolved = await resolveRef(client, refMap, params.ref);
|
|
26
|
+
|
|
27
|
+
// Focus the element
|
|
28
|
+
await focusRef(client, resolved.backendNodeId);
|
|
29
|
+
|
|
30
|
+
// Set value and fire events
|
|
31
|
+
await setFieldValue(client, resolved.objectId, params.value);
|
|
32
|
+
|
|
33
|
+
return { ref: params.ref, value: params.value };
|
|
34
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import type { ElementRefMap } from '../browser/inspect/element-ref.js';
|
|
3
|
+
import {
|
|
4
|
+
buildSnapshot,
|
|
5
|
+
findElements,
|
|
6
|
+
formatSnapshot
|
|
7
|
+
} from '../browser/inspect/snapshot.js';
|
|
8
|
+
import type { ElementInfo } from '../browser/inspect/types.js';
|
|
9
|
+
|
|
10
|
+
export interface FindQuery {
|
|
11
|
+
role?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FindCommandResult {
|
|
17
|
+
text: string;
|
|
18
|
+
elements: ElementInfo[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function executeFind(
|
|
22
|
+
client: Client,
|
|
23
|
+
refMap: ElementRefMap,
|
|
24
|
+
query: FindQuery
|
|
25
|
+
): Promise<FindCommandResult> {
|
|
26
|
+
// Always builds a fresh snapshot — no caching across calls
|
|
27
|
+
const snapshot = await buildSnapshot(client, refMap);
|
|
28
|
+
const matched = findElements(snapshot, query);
|
|
29
|
+
|
|
30
|
+
if (matched.length === 0) {
|
|
31
|
+
const queryParts = Object.entries(query)
|
|
32
|
+
.filter(([, v]) => v !== undefined)
|
|
33
|
+
.map(([k, v]) => `${k}="${v}"`);
|
|
34
|
+
const queryDesc =
|
|
35
|
+
queryParts.length > 0 ? queryParts.join(', ') : 'the given query';
|
|
36
|
+
return {
|
|
37
|
+
text: `No elements found matching ${queryDesc}.`,
|
|
38
|
+
elements: []
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Format just the matched elements
|
|
43
|
+
const result = formatSnapshot({
|
|
44
|
+
...snapshot,
|
|
45
|
+
elements: matched
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
text: result,
|
|
50
|
+
elements: matched
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import type { ElementRefMap } from '../browser/inspect/element-ref.js';
|
|
3
|
+
import { resolveRef } from '../browser/interaction/ref-resolver.js';
|
|
4
|
+
|
|
5
|
+
export interface HoverParams {
|
|
6
|
+
ref?: string;
|
|
7
|
+
x?: number;
|
|
8
|
+
y?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HoverResult {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function executeHover(
|
|
17
|
+
client: Client,
|
|
18
|
+
refMap: ElementRefMap,
|
|
19
|
+
params: HoverParams
|
|
20
|
+
): Promise<HoverResult> {
|
|
21
|
+
let x: number;
|
|
22
|
+
let y: number;
|
|
23
|
+
|
|
24
|
+
if (params.ref) {
|
|
25
|
+
const resolved = await resolveRef(client, refMap, params.ref);
|
|
26
|
+
x = resolved.x;
|
|
27
|
+
y = resolved.y;
|
|
28
|
+
} else if (params.x !== undefined && params.y !== undefined) {
|
|
29
|
+
x = params.x;
|
|
30
|
+
y = params.y;
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error('Either ref or x/y coordinates are required');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await client.Input.dispatchMouseEvent({
|
|
36
|
+
type: 'mouseMoved',
|
|
37
|
+
x,
|
|
38
|
+
y
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { x, y };
|
|
42
|
+
}
|
package/src/commands/navigate.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { Client } from 'chrome-remote-interface';
|
|
3
3
|
import type { BrowserManager } from '../browser.js';
|
|
4
|
+
import { checkOrigin } from '../browser/security/origins.js';
|
|
4
5
|
|
|
5
6
|
export const NavigateParamsSchema = z.object({
|
|
6
7
|
url: z.string().url(),
|
|
@@ -22,6 +23,14 @@ export async function executeNavigate(
|
|
|
22
23
|
manager: BrowserManager,
|
|
23
24
|
params: NavigateParams
|
|
24
25
|
): Promise<NavigateResult> {
|
|
26
|
+
// Check origin policy before navigation
|
|
27
|
+
const originCheck = checkOrigin(params.url);
|
|
28
|
+
if (!originCheck.allowed) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
originCheck.reason ?? 'Navigation blocked by origin policy'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
let finalStatus = 200;
|
|
26
35
|
|
|
27
36
|
const navigationPromise = new Promise<{ url: string; status: number }>(
|
|
@@ -97,6 +106,17 @@ export async function executeNavigate(
|
|
|
97
106
|
|
|
98
107
|
try {
|
|
99
108
|
const result = await navigationPromise;
|
|
109
|
+
|
|
110
|
+
// Check final URL after redirects — block if redirected to a blocked origin
|
|
111
|
+
const finalCheck = checkOrigin(result.url);
|
|
112
|
+
if (!finalCheck.allowed) {
|
|
113
|
+
// Navigate away from the blocked origin
|
|
114
|
+
await client.Page.navigate({ url: 'about:blank' });
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Redirect landed on blocked origin: ${finalCheck.reason}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
return result;
|
|
101
121
|
} finally {
|
|
102
122
|
// Event listeners are auto-removed when client disconnects;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import type { NetworkBuffer } from '../browser/network/network-buffer.js';
|
|
3
|
+
import type {
|
|
4
|
+
NetworkSummary,
|
|
5
|
+
NetworkDetail,
|
|
6
|
+
NetworkFilter
|
|
7
|
+
} from '../browser/network/types.js';
|
|
8
|
+
import { getResponseBody } from '../browser/network/network-monitor.js';
|
|
9
|
+
|
|
10
|
+
export type NetworkAction = 'list' | 'get' | 'clear';
|
|
11
|
+
|
|
12
|
+
export interface NetworkParams {
|
|
13
|
+
action: NetworkAction;
|
|
14
|
+
requestId?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
method?: string;
|
|
17
|
+
statusMin?: number;
|
|
18
|
+
statusMax?: number;
|
|
19
|
+
resourceType?: string;
|
|
20
|
+
failed?: boolean;
|
|
21
|
+
limit?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface NetworkListResult {
|
|
25
|
+
action: 'list';
|
|
26
|
+
count: number;
|
|
27
|
+
requests: NetworkSummary[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface NetworkGetResult {
|
|
31
|
+
action: 'get';
|
|
32
|
+
request: NetworkDetail;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface NetworkClearResult {
|
|
36
|
+
action: 'clear';
|
|
37
|
+
cleared: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type NetworkResult =
|
|
41
|
+
NetworkListResult | NetworkGetResult | NetworkClearResult;
|
|
42
|
+
|
|
43
|
+
export async function executeNetwork(
|
|
44
|
+
client: Client,
|
|
45
|
+
buffer: NetworkBuffer,
|
|
46
|
+
params: NetworkParams
|
|
47
|
+
): Promise<NetworkResult> {
|
|
48
|
+
switch (params.action) {
|
|
49
|
+
case 'list': {
|
|
50
|
+
const filter: NetworkFilter = {
|
|
51
|
+
url: params.url,
|
|
52
|
+
method: params.method,
|
|
53
|
+
statusMin: params.statusMin,
|
|
54
|
+
statusMax: params.statusMax,
|
|
55
|
+
resourceType: params.resourceType,
|
|
56
|
+
failed: params.failed,
|
|
57
|
+
limit: params.limit
|
|
58
|
+
};
|
|
59
|
+
const requests = buffer.list(filter);
|
|
60
|
+
return { action: 'list', count: requests.length, requests };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'get': {
|
|
64
|
+
if (!params.requestId) {
|
|
65
|
+
throw new Error('requestId is required for action "get"');
|
|
66
|
+
}
|
|
67
|
+
const record = buffer.getDetail(params.requestId);
|
|
68
|
+
if (!record) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`No network request found with id: ${params.requestId}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
// Lazily fetch response body
|
|
74
|
+
const bodyResult = await getResponseBody(client, params.requestId);
|
|
75
|
+
// record headers are already redacted by getDetail()
|
|
76
|
+
const detail: NetworkDetail = {
|
|
77
|
+
...record,
|
|
78
|
+
...(bodyResult && {
|
|
79
|
+
responseBody: bodyResult.body,
|
|
80
|
+
bodyTruncated: bodyResult.truncated
|
|
81
|
+
})
|
|
82
|
+
};
|
|
83
|
+
return { action: 'get', request: detail };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'clear': {
|
|
87
|
+
const cleared = buffer.clear();
|
|
88
|
+
return { action: 'clear', cleared };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function formatNetworkList(result: NetworkListResult): string {
|
|
94
|
+
if (result.requests.length === 0) {
|
|
95
|
+
return 'No network requests captured.';
|
|
96
|
+
}
|
|
97
|
+
const lines = [`Network requests (${result.count}):\n`];
|
|
98
|
+
for (const req of result.requests) {
|
|
99
|
+
const status = req.failed ? 'FAILED' : String(req.status);
|
|
100
|
+
const dur = req.duration !== undefined ? `${req.duration}ms` : '...';
|
|
101
|
+
const size =
|
|
102
|
+
req.size > 1024 ? `${(req.size / 1024).toFixed(1)}KB` : `${req.size}B`;
|
|
103
|
+
lines.push(
|
|
104
|
+
` [${req.id}] ${req.method} ${status} ${req.url} (${dur}, ${size})`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function formatNetworkDetail(result: NetworkGetResult): string {
|
|
111
|
+
const r = result.request;
|
|
112
|
+
const lines = [
|
|
113
|
+
`${r.method} ${r.url}`,
|
|
114
|
+
`Status: ${r.failed ? `FAILED — ${r.errorText}` : r.status}`,
|
|
115
|
+
`Type: ${r.resourceType} (${r.mimeType})`,
|
|
116
|
+
`Duration: ${r.duration !== undefined ? `${r.duration}ms` : 'unknown'}`,
|
|
117
|
+
`Size: ${r.encodedDataLength}B`,
|
|
118
|
+
`Cache: ${r.fromCache ? 'yes' : 'no'}`
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
if (Object.keys(r.requestHeaders).length > 0) {
|
|
122
|
+
lines.push('', 'Request Headers:');
|
|
123
|
+
for (const [k, v] of Object.entries(r.requestHeaders)) {
|
|
124
|
+
lines.push(` ${k}: ${v}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (Object.keys(r.responseHeaders).length > 0) {
|
|
129
|
+
lines.push('', 'Response Headers:');
|
|
130
|
+
for (const [k, v] of Object.entries(r.responseHeaders)) {
|
|
131
|
+
lines.push(` ${k}: ${v}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (r.responseBody !== undefined) {
|
|
136
|
+
const truncNote = r.bodyTruncated ? ' (truncated to 1MB)' : '';
|
|
137
|
+
lines.push('', `Response Body${truncNote}:`, r.responseBody);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
import {
|
|
3
|
+
type TraceState,
|
|
4
|
+
startTrace,
|
|
5
|
+
stopTrace
|
|
6
|
+
} from '../browser/performance/trace.js';
|
|
7
|
+
import {
|
|
8
|
+
analyzePerformance,
|
|
9
|
+
formatPerformanceSummary
|
|
10
|
+
} from '../browser/performance/analyze.js';
|
|
11
|
+
|
|
12
|
+
export type PerformanceAction = 'start' | 'stop' | 'analyze';
|
|
13
|
+
|
|
14
|
+
export interface PerformanceParams {
|
|
15
|
+
action: PerformanceAction;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PerformanceResult {
|
|
19
|
+
action: PerformanceAction;
|
|
20
|
+
text: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function executePerformance(
|
|
24
|
+
client: Client,
|
|
25
|
+
traceState: TraceState,
|
|
26
|
+
params: PerformanceParams
|
|
27
|
+
): Promise<PerformanceResult> {
|
|
28
|
+
switch (params.action) {
|
|
29
|
+
case 'start': {
|
|
30
|
+
await client.Performance.enable();
|
|
31
|
+
await startTrace(client, traceState);
|
|
32
|
+
return { action: 'start', text: 'Performance tracing started.' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case 'stop': {
|
|
36
|
+
await stopTrace(client, traceState);
|
|
37
|
+
return {
|
|
38
|
+
action: 'stop',
|
|
39
|
+
text: `Tracing stopped. ${traceState.events.length} events captured. Use action="analyze" to view summary.`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'analyze': {
|
|
44
|
+
const summary = await analyzePerformance(client, traceState.events);
|
|
45
|
+
return {
|
|
46
|
+
action: 'analyze',
|
|
47
|
+
text: formatPerformanceSummary(summary)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|