@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.
Files changed (280) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/CLAUDE.md +7 -7
  3. package/README.md +108 -53
  4. package/dist/browser/browser-manager.d.ts +60 -0
  5. package/dist/browser/browser-manager.d.ts.map +1 -0
  6. package/dist/browser/browser-manager.js +348 -0
  7. package/dist/browser/browser-manager.js.map +1 -0
  8. package/dist/browser/chrome-launcher.d.ts +30 -0
  9. package/dist/browser/chrome-launcher.d.ts.map +1 -0
  10. package/dist/browser/chrome-launcher.js +217 -0
  11. package/dist/browser/chrome-launcher.js.map +1 -0
  12. package/dist/browser/connection.d.ts +23 -0
  13. package/dist/browser/connection.d.ts.map +1 -0
  14. package/dist/browser/connection.js +53 -0
  15. package/dist/browser/connection.js.map +1 -0
  16. package/dist/browser/events.d.ts +9 -0
  17. package/dist/browser/events.d.ts.map +1 -0
  18. package/dist/browser/events.js +99 -0
  19. package/dist/browser/events.js.map +1 -0
  20. package/dist/browser/index.d.ts +4 -0
  21. package/dist/browser/index.d.ts.map +1 -0
  22. package/dist/browser/index.js +4 -0
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/inspect/element-ref.d.ts +23 -0
  25. package/dist/browser/inspect/element-ref.d.ts.map +1 -0
  26. package/dist/browser/inspect/element-ref.js +52 -0
  27. package/dist/browser/inspect/element-ref.js.map +1 -0
  28. package/dist/browser/inspect/snapshot.d.ts +11 -0
  29. package/dist/browser/inspect/snapshot.d.ts.map +1 -0
  30. package/dist/browser/inspect/snapshot.js +282 -0
  31. package/dist/browser/inspect/snapshot.js.map +1 -0
  32. package/dist/browser/inspect/styles.d.ts +45 -0
  33. package/dist/browser/inspect/styles.d.ts.map +1 -0
  34. package/dist/browser/inspect/styles.js +190 -0
  35. package/dist/browser/inspect/styles.js.map +1 -0
  36. package/dist/browser/inspect/types.d.ts +35 -0
  37. package/dist/browser/inspect/types.d.ts.map +1 -0
  38. package/dist/browser/inspect/types.js +2 -0
  39. package/dist/browser/inspect/types.js.map +1 -0
  40. package/dist/browser/interaction/dialogs.d.ts +18 -0
  41. package/dist/browser/interaction/dialogs.d.ts.map +1 -0
  42. package/dist/browser/interaction/dialogs.js +35 -0
  43. package/dist/browser/interaction/dialogs.js.map +1 -0
  44. package/dist/browser/interaction/downloads.d.ts +23 -0
  45. package/dist/browser/interaction/downloads.d.ts.map +1 -0
  46. package/dist/browser/interaction/downloads.js +87 -0
  47. package/dist/browser/interaction/downloads.js.map +1 -0
  48. package/dist/browser/interaction/events.d.ts +17 -0
  49. package/dist/browser/interaction/events.d.ts.map +1 -0
  50. package/dist/browser/interaction/events.js +66 -0
  51. package/dist/browser/interaction/events.js.map +1 -0
  52. package/dist/browser/interaction/ref-resolver.d.ts +18 -0
  53. package/dist/browser/interaction/ref-resolver.d.ts.map +1 -0
  54. package/dist/browser/interaction/ref-resolver.js +32 -0
  55. package/dist/browser/interaction/ref-resolver.js.map +1 -0
  56. package/dist/browser/interaction/uploads.d.ts +4 -0
  57. package/dist/browser/interaction/uploads.d.ts.map +1 -0
  58. package/dist/browser/interaction/uploads.js +43 -0
  59. package/dist/browser/interaction/uploads.js.map +1 -0
  60. package/dist/browser/network/network-buffer.d.ts +19 -0
  61. package/dist/browser/network/network-buffer.d.ts.map +1 -0
  62. package/dist/browser/network/network-buffer.js +118 -0
  63. package/dist/browser/network/network-buffer.js.map +1 -0
  64. package/dist/browser/network/network-monitor.d.ts +8 -0
  65. package/dist/browser/network/network-monitor.d.ts.map +1 -0
  66. package/dist/browser/network/network-monitor.js +85 -0
  67. package/dist/browser/network/network-monitor.js.map +1 -0
  68. package/dist/browser/network/redaction.d.ts +3 -0
  69. package/dist/browser/network/redaction.d.ts.map +1 -0
  70. package/dist/browser/network/redaction.js +36 -0
  71. package/dist/browser/network/redaction.js.map +1 -0
  72. package/dist/browser/network/types.d.ts +45 -0
  73. package/dist/browser/network/types.d.ts.map +1 -0
  74. package/dist/browser/network/types.js +2 -0
  75. package/dist/browser/network/types.js.map +1 -0
  76. package/dist/browser/performance/analyze.d.ts +5 -0
  77. package/dist/browser/performance/analyze.d.ts.map +1 -0
  78. package/dist/browser/performance/analyze.js +230 -0
  79. package/dist/browser/performance/analyze.js.map +1 -0
  80. package/dist/browser/performance/trace.d.ts +10 -0
  81. package/dist/browser/performance/trace.d.ts.map +1 -0
  82. package/dist/browser/performance/trace.js +38 -0
  83. package/dist/browser/performance/trace.js.map +1 -0
  84. package/dist/browser/performance/types.d.ts +31 -0
  85. package/dist/browser/performance/types.d.ts.map +1 -0
  86. package/dist/browser/performance/types.js +2 -0
  87. package/dist/browser/performance/types.js.map +1 -0
  88. package/dist/browser/security/origins.d.ts +18 -0
  89. package/dist/browser/security/origins.d.ts.map +1 -0
  90. package/dist/browser/security/origins.js +84 -0
  91. package/dist/browser/security/origins.js.map +1 -0
  92. package/dist/browser/tabs.d.ts +15 -0
  93. package/dist/browser/tabs.d.ts.map +1 -0
  94. package/dist/browser/tabs.js +71 -0
  95. package/dist/browser/tabs.js.map +1 -0
  96. package/dist/browser/types.d.ts +31 -0
  97. package/dist/browser/types.d.ts.map +1 -0
  98. package/dist/browser/types.js +2 -0
  99. package/dist/browser/types.js.map +1 -0
  100. package/dist/browser.d.ts +2 -99
  101. package/dist/browser.d.ts.map +1 -1
  102. package/dist/browser.js +3 -653
  103. package/dist/browser.js.map +1 -1
  104. package/dist/commands/check.d.ts +17 -0
  105. package/dist/commands/check.d.ts.map +1 -0
  106. package/dist/commands/check.js +38 -0
  107. package/dist/commands/check.js.map +1 -0
  108. package/dist/commands/click.d.ts +3 -1
  109. package/dist/commands/click.d.ts.map +1 -1
  110. package/dist/commands/click.js +16 -4
  111. package/dist/commands/click.js.map +1 -1
  112. package/dist/commands/connect.d.ts +14 -0
  113. package/dist/commands/connect.d.ts.map +1 -0
  114. package/dist/commands/connect.js +19 -0
  115. package/dist/commands/connect.js.map +1 -0
  116. package/dist/commands/dialog.d.ts +19 -0
  117. package/dist/commands/dialog.d.ts.map +1 -0
  118. package/dist/commands/dialog.js +51 -0
  119. package/dist/commands/dialog.js.map +1 -0
  120. package/dist/commands/downloads.d.ts +30 -0
  121. package/dist/commands/downloads.d.ts.map +1 -0
  122. package/dist/commands/downloads.js +66 -0
  123. package/dist/commands/downloads.js.map +1 -0
  124. package/dist/commands/fill.d.ts +16 -0
  125. package/dist/commands/fill.d.ts.map +1 -0
  126. package/dist/commands/fill.js +15 -0
  127. package/dist/commands/fill.js.map +1 -0
  128. package/dist/commands/find.d.ts +14 -0
  129. package/dist/commands/find.d.ts.map +1 -0
  130. package/dist/commands/find.js +26 -0
  131. package/dist/commands/find.js.map +1 -0
  132. package/dist/commands/hover.d.ts +13 -0
  133. package/dist/commands/hover.d.ts.map +1 -0
  134. package/dist/commands/hover.js +24 -0
  135. package/dist/commands/hover.js.map +1 -0
  136. package/dist/commands/navigate.d.ts.map +1 -1
  137. package/dist/commands/navigate.js +13 -0
  138. package/dist/commands/navigate.js.map +1 -1
  139. package/dist/commands/network.d.ts +33 -0
  140. package/dist/commands/network.d.ts.map +1 -0
  141. package/dist/commands/network.js +84 -0
  142. package/dist/commands/network.js.map +1 -0
  143. package/dist/commands/performance.d.ts +12 -0
  144. package/dist/commands/performance.d.ts.map +1 -0
  145. package/dist/commands/performance.js +26 -0
  146. package/dist/commands/performance.js.map +1 -0
  147. package/dist/commands/press_key.d.ts +10 -0
  148. package/dist/commands/press_key.d.ts.map +1 -0
  149. package/dist/commands/press_key.js +103 -0
  150. package/dist/commands/press_key.js.map +1 -0
  151. package/dist/commands/scroll.d.ts +21 -0
  152. package/dist/commands/scroll.d.ts.map +1 -0
  153. package/dist/commands/scroll.js +54 -0
  154. package/dist/commands/scroll.js.map +1 -0
  155. package/dist/commands/select.d.ts +18 -0
  156. package/dist/commands/select.d.ts.map +1 -0
  157. package/dist/commands/select.js +53 -0
  158. package/dist/commands/select.js.map +1 -0
  159. package/dist/commands/snapshot.d.ts +9 -0
  160. package/dist/commands/snapshot.d.ts.map +1 -0
  161. package/dist/commands/snapshot.js +7 -0
  162. package/dist/commands/snapshot.js.map +1 -0
  163. package/dist/commands/styles.d.ts +13 -0
  164. package/dist/commands/styles.d.ts.map +1 -0
  165. package/dist/commands/styles.js +7 -0
  166. package/dist/commands/styles.js.map +1 -0
  167. package/dist/commands/type.d.ts +3 -1
  168. package/dist/commands/type.d.ts.map +1 -1
  169. package/dist/commands/type.js +11 -2
  170. package/dist/commands/type.js.map +1 -1
  171. package/dist/commands/upload.d.ts +12 -0
  172. package/dist/commands/upload.d.ts.map +1 -0
  173. package/dist/commands/upload.js +13 -0
  174. package/dist/commands/upload.js.map +1 -0
  175. package/dist/mcp/console-buffer.d.ts +9 -0
  176. package/dist/mcp/console-buffer.d.ts.map +1 -1
  177. package/dist/mcp/console-buffer.js.map +1 -1
  178. package/dist/mcp/server.d.ts +10 -0
  179. package/dist/mcp/server.d.ts.map +1 -1
  180. package/dist/mcp/server.js +63 -2
  181. package/dist/mcp/server.js.map +1 -1
  182. package/dist/mcp/tools/browser.d.ts.map +1 -1
  183. package/dist/mcp/tools/browser.js +99 -8
  184. package/dist/mcp/tools/browser.js.map +1 -1
  185. package/dist/mcp/tools/connect.d.ts +5 -0
  186. package/dist/mcp/tools/connect.d.ts.map +1 -0
  187. package/dist/mcp/tools/connect.js +48 -0
  188. package/dist/mcp/tools/connect.js.map +1 -0
  189. package/dist/mcp/tools/files.d.ts +4 -0
  190. package/dist/mcp/tools/files.d.ts.map +1 -0
  191. package/dist/mcp/tools/files.js +85 -0
  192. package/dist/mcp/tools/files.js.map +1 -0
  193. package/dist/mcp/tools/interaction.d.ts +4 -0
  194. package/dist/mcp/tools/interaction.d.ts.map +1 -0
  195. package/dist/mcp/tools/interaction.js +176 -0
  196. package/dist/mcp/tools/interaction.js.map +1 -0
  197. package/dist/mcp/tools/names.d.ts +1 -1
  198. package/dist/mcp/tools/names.d.ts.map +1 -1
  199. package/dist/mcp/tools/names.js +19 -1
  200. package/dist/mcp/tools/names.js.map +1 -1
  201. package/dist/mcp/tools/network.d.ts +4 -0
  202. package/dist/mcp/tools/network.d.ts.map +1 -0
  203. package/dist/mcp/tools/network.js +68 -0
  204. package/dist/mcp/tools/network.js.map +1 -0
  205. package/dist/mcp/tools/performance.d.ts +4 -0
  206. package/dist/mcp/tools/performance.d.ts.map +1 -0
  207. package/dist/mcp/tools/performance.js +26 -0
  208. package/dist/mcp/tools/performance.js.map +1 -0
  209. package/dist/mcp/tools/runtime.d.ts +4 -0
  210. package/dist/mcp/tools/runtime.d.ts.map +1 -0
  211. package/dist/mcp/tools/runtime.js +128 -0
  212. package/dist/mcp/tools/runtime.js.map +1 -0
  213. package/dist/mcp/tools/snapshot.d.ts +4 -0
  214. package/dist/mcp/tools/snapshot.d.ts.map +1 -0
  215. package/dist/mcp/tools/snapshot.js +42 -0
  216. package/dist/mcp/tools/snapshot.js.map +1 -0
  217. package/dist/mcp/tools/styles.d.ts +4 -0
  218. package/dist/mcp/tools/styles.d.ts.map +1 -0
  219. package/dist/mcp/tools/styles.js +27 -0
  220. package/dist/mcp/tools/styles.js.map +1 -0
  221. package/dist/util/config.d.ts.map +1 -1
  222. package/dist/util/config.js +3 -2
  223. package/dist/util/config.js.map +1 -1
  224. package/package.json +3 -3
  225. package/src/browser/browser-manager.ts +457 -0
  226. package/src/browser/chrome-launcher.ts +271 -0
  227. package/src/browser/connection.ts +84 -0
  228. package/src/browser/events.ts +121 -0
  229. package/src/browser/index.ts +20 -0
  230. package/src/browser/inspect/element-ref.ts +68 -0
  231. package/src/browser/inspect/snapshot.ts +348 -0
  232. package/src/browser/inspect/styles.ts +258 -0
  233. package/src/browser/inspect/types.ts +37 -0
  234. package/src/browser/interaction/dialogs.ts +54 -0
  235. package/src/browser/interaction/downloads.ts +116 -0
  236. package/src/browser/interaction/events.ts +79 -0
  237. package/src/browser/interaction/ref-resolver.ts +53 -0
  238. package/src/browser/interaction/uploads.ts +60 -0
  239. package/src/browser/network/network-buffer.ts +133 -0
  240. package/src/browser/network/network-monitor.ts +107 -0
  241. package/src/browser/network/redaction.ts +42 -0
  242. package/src/browser/network/types.ts +47 -0
  243. package/src/browser/performance/analyze.ts +298 -0
  244. package/src/browser/performance/trace.ts +60 -0
  245. package/src/browser/performance/types.ts +34 -0
  246. package/src/browser/security/origins.ts +102 -0
  247. package/src/browser/tabs.ts +110 -0
  248. package/src/browser/types.ts +31 -0
  249. package/src/browser.ts +22 -798
  250. package/src/commands/check.ts +59 -0
  251. package/src/commands/click.ts +18 -4
  252. package/src/commands/connect.ts +40 -0
  253. package/src/commands/dialog.ts +79 -0
  254. package/src/commands/downloads.ts +111 -0
  255. package/src/commands/fill.ts +34 -0
  256. package/src/commands/find.ts +52 -0
  257. package/src/commands/hover.ts +42 -0
  258. package/src/commands/navigate.ts +20 -0
  259. package/src/commands/network.ts +141 -0
  260. package/src/commands/performance.ts +51 -0
  261. package/src/commands/press_key.ts +134 -0
  262. package/src/commands/scroll.ts +79 -0
  263. package/src/commands/select.ts +78 -0
  264. package/src/commands/snapshot.ts +18 -0
  265. package/src/commands/styles.ts +24 -0
  266. package/src/commands/type.ts +12 -2
  267. package/src/commands/upload.ts +36 -0
  268. package/src/mcp/console-buffer.ts +10 -0
  269. package/src/mcp/server.ts +74 -2
  270. package/src/mcp/tools/browser.ts +113 -7
  271. package/src/mcp/tools/connect.ts +62 -0
  272. package/src/mcp/tools/files.ts +120 -0
  273. package/src/mcp/tools/interaction.ts +232 -0
  274. package/src/mcp/tools/names.ts +19 -1
  275. package/src/mcp/tools/network.ts +86 -0
  276. package/src/mcp/tools/performance.ts +40 -0
  277. package/src/mcp/tools/runtime.ts +164 -0
  278. package/src/mcp/tools/snapshot.ts +64 -0
  279. package/src/mcp/tools/styles.ts +41 -0
  280. 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
+ }
@@ -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) => v.selector !== undefined || (v.x !== undefined && v.y !== undefined),
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.selector !== undefined) {
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
+ }
@@ -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
+ }