@everydaydevopsio/pilot 0.5.2 → 0.6.1

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 (306) hide show
  1. package/.mcp.json +9 -0
  2. package/AGENTS.md +9 -0
  3. package/CHANGELOG.md +64 -0
  4. package/CLAUDE.md +7 -7
  5. package/README.md +282 -69
  6. package/dist/browser/browser-manager.d.ts +60 -0
  7. package/dist/browser/browser-manager.d.ts.map +1 -0
  8. package/dist/browser/browser-manager.js +355 -0
  9. package/dist/browser/browser-manager.js.map +1 -0
  10. package/dist/browser/chrome-launcher.d.ts +31 -0
  11. package/dist/browser/chrome-launcher.d.ts.map +1 -0
  12. package/dist/browser/chrome-launcher.js +225 -0
  13. package/dist/browser/chrome-launcher.js.map +1 -0
  14. package/dist/browser/connection.d.ts +23 -0
  15. package/dist/browser/connection.d.ts.map +1 -0
  16. package/dist/browser/connection.js +53 -0
  17. package/dist/browser/connection.js.map +1 -0
  18. package/dist/browser/events.d.ts +9 -0
  19. package/dist/browser/events.d.ts.map +1 -0
  20. package/dist/browser/events.js +99 -0
  21. package/dist/browser/events.js.map +1 -0
  22. package/dist/browser/index.d.ts +4 -0
  23. package/dist/browser/index.d.ts.map +1 -0
  24. package/dist/browser/index.js +4 -0
  25. package/dist/browser/index.js.map +1 -0
  26. package/dist/browser/inspect/element-ref.d.ts +23 -0
  27. package/dist/browser/inspect/element-ref.d.ts.map +1 -0
  28. package/dist/browser/inspect/element-ref.js +52 -0
  29. package/dist/browser/inspect/element-ref.js.map +1 -0
  30. package/dist/browser/inspect/snapshot.d.ts +11 -0
  31. package/dist/browser/inspect/snapshot.d.ts.map +1 -0
  32. package/dist/browser/inspect/snapshot.js +282 -0
  33. package/dist/browser/inspect/snapshot.js.map +1 -0
  34. package/dist/browser/inspect/styles.d.ts +45 -0
  35. package/dist/browser/inspect/styles.d.ts.map +1 -0
  36. package/dist/browser/inspect/styles.js +190 -0
  37. package/dist/browser/inspect/styles.js.map +1 -0
  38. package/dist/browser/inspect/types.d.ts +35 -0
  39. package/dist/browser/inspect/types.d.ts.map +1 -0
  40. package/dist/browser/inspect/types.js +2 -0
  41. package/dist/browser/inspect/types.js.map +1 -0
  42. package/dist/browser/interaction/dialogs.d.ts +18 -0
  43. package/dist/browser/interaction/dialogs.d.ts.map +1 -0
  44. package/dist/browser/interaction/dialogs.js +35 -0
  45. package/dist/browser/interaction/dialogs.js.map +1 -0
  46. package/dist/browser/interaction/downloads.d.ts +23 -0
  47. package/dist/browser/interaction/downloads.d.ts.map +1 -0
  48. package/dist/browser/interaction/downloads.js +87 -0
  49. package/dist/browser/interaction/downloads.js.map +1 -0
  50. package/dist/browser/interaction/events.d.ts +17 -0
  51. package/dist/browser/interaction/events.d.ts.map +1 -0
  52. package/dist/browser/interaction/events.js +66 -0
  53. package/dist/browser/interaction/events.js.map +1 -0
  54. package/dist/browser/interaction/ref-resolver.d.ts +18 -0
  55. package/dist/browser/interaction/ref-resolver.d.ts.map +1 -0
  56. package/dist/browser/interaction/ref-resolver.js +32 -0
  57. package/dist/browser/interaction/ref-resolver.js.map +1 -0
  58. package/dist/browser/interaction/uploads.d.ts +4 -0
  59. package/dist/browser/interaction/uploads.d.ts.map +1 -0
  60. package/dist/browser/interaction/uploads.js +43 -0
  61. package/dist/browser/interaction/uploads.js.map +1 -0
  62. package/dist/browser/network/network-buffer.d.ts +19 -0
  63. package/dist/browser/network/network-buffer.d.ts.map +1 -0
  64. package/dist/browser/network/network-buffer.js +118 -0
  65. package/dist/browser/network/network-buffer.js.map +1 -0
  66. package/dist/browser/network/network-monitor.d.ts +8 -0
  67. package/dist/browser/network/network-monitor.d.ts.map +1 -0
  68. package/dist/browser/network/network-monitor.js +85 -0
  69. package/dist/browser/network/network-monitor.js.map +1 -0
  70. package/dist/browser/network/redaction.d.ts +3 -0
  71. package/dist/browser/network/redaction.d.ts.map +1 -0
  72. package/dist/browser/network/redaction.js +36 -0
  73. package/dist/browser/network/redaction.js.map +1 -0
  74. package/dist/browser/network/types.d.ts +45 -0
  75. package/dist/browser/network/types.d.ts.map +1 -0
  76. package/dist/browser/network/types.js +2 -0
  77. package/dist/browser/network/types.js.map +1 -0
  78. package/dist/browser/performance/analyze.d.ts +5 -0
  79. package/dist/browser/performance/analyze.d.ts.map +1 -0
  80. package/dist/browser/performance/analyze.js +230 -0
  81. package/dist/browser/performance/analyze.js.map +1 -0
  82. package/dist/browser/performance/trace.d.ts +10 -0
  83. package/dist/browser/performance/trace.d.ts.map +1 -0
  84. package/dist/browser/performance/trace.js +38 -0
  85. package/dist/browser/performance/trace.js.map +1 -0
  86. package/dist/browser/performance/types.d.ts +31 -0
  87. package/dist/browser/performance/types.d.ts.map +1 -0
  88. package/dist/browser/performance/types.js +2 -0
  89. package/dist/browser/performance/types.js.map +1 -0
  90. package/dist/browser/preflight.d.ts +20 -0
  91. package/dist/browser/preflight.d.ts.map +1 -0
  92. package/dist/browser/preflight.js +105 -0
  93. package/dist/browser/preflight.js.map +1 -0
  94. package/dist/browser/security/origins.d.ts +18 -0
  95. package/dist/browser/security/origins.d.ts.map +1 -0
  96. package/dist/browser/security/origins.js +84 -0
  97. package/dist/browser/security/origins.js.map +1 -0
  98. package/dist/browser/tabs.d.ts +15 -0
  99. package/dist/browser/tabs.d.ts.map +1 -0
  100. package/dist/browser/tabs.js +71 -0
  101. package/dist/browser/tabs.js.map +1 -0
  102. package/dist/browser/types.d.ts +32 -0
  103. package/dist/browser/types.d.ts.map +1 -0
  104. package/dist/browser/types.js +2 -0
  105. package/dist/browser/types.js.map +1 -0
  106. package/dist/browser.d.ts +2 -99
  107. package/dist/browser.d.ts.map +1 -1
  108. package/dist/browser.js +3 -653
  109. package/dist/browser.js.map +1 -1
  110. package/dist/cli/init.d.ts +3 -0
  111. package/dist/cli/init.d.ts.map +1 -1
  112. package/dist/cli/init.js +33 -48
  113. package/dist/cli/init.js.map +1 -1
  114. package/dist/cli/linux-check.d.ts +2 -0
  115. package/dist/cli/linux-check.d.ts.map +1 -0
  116. package/dist/cli/linux-check.js +20 -0
  117. package/dist/cli/linux-check.js.map +1 -0
  118. package/dist/cli/skill-template.d.ts +1 -1
  119. package/dist/cli/skill-template.d.ts.map +1 -1
  120. package/dist/cli/skill-template.js +3 -113
  121. package/dist/cli/skill-template.js.map +1 -1
  122. package/dist/commands/check.d.ts +17 -0
  123. package/dist/commands/check.d.ts.map +1 -0
  124. package/dist/commands/check.js +38 -0
  125. package/dist/commands/check.js.map +1 -0
  126. package/dist/commands/click.d.ts +3 -1
  127. package/dist/commands/click.d.ts.map +1 -1
  128. package/dist/commands/click.js +16 -4
  129. package/dist/commands/click.js.map +1 -1
  130. package/dist/commands/connect.d.ts +14 -0
  131. package/dist/commands/connect.d.ts.map +1 -0
  132. package/dist/commands/connect.js +19 -0
  133. package/dist/commands/connect.js.map +1 -0
  134. package/dist/commands/dialog.d.ts +19 -0
  135. package/dist/commands/dialog.d.ts.map +1 -0
  136. package/dist/commands/dialog.js +51 -0
  137. package/dist/commands/dialog.js.map +1 -0
  138. package/dist/commands/downloads.d.ts +30 -0
  139. package/dist/commands/downloads.d.ts.map +1 -0
  140. package/dist/commands/downloads.js +66 -0
  141. package/dist/commands/downloads.js.map +1 -0
  142. package/dist/commands/fill.d.ts +16 -0
  143. package/dist/commands/fill.d.ts.map +1 -0
  144. package/dist/commands/fill.js +15 -0
  145. package/dist/commands/fill.js.map +1 -0
  146. package/dist/commands/find.d.ts +14 -0
  147. package/dist/commands/find.d.ts.map +1 -0
  148. package/dist/commands/find.js +26 -0
  149. package/dist/commands/find.js.map +1 -0
  150. package/dist/commands/hover.d.ts +13 -0
  151. package/dist/commands/hover.d.ts.map +1 -0
  152. package/dist/commands/hover.js +24 -0
  153. package/dist/commands/hover.js.map +1 -0
  154. package/dist/commands/navigate.d.ts.map +1 -1
  155. package/dist/commands/navigate.js +13 -0
  156. package/dist/commands/navigate.js.map +1 -1
  157. package/dist/commands/network.d.ts +33 -0
  158. package/dist/commands/network.d.ts.map +1 -0
  159. package/dist/commands/network.js +84 -0
  160. package/dist/commands/network.js.map +1 -0
  161. package/dist/commands/performance.d.ts +12 -0
  162. package/dist/commands/performance.d.ts.map +1 -0
  163. package/dist/commands/performance.js +26 -0
  164. package/dist/commands/performance.js.map +1 -0
  165. package/dist/commands/press_key.d.ts +10 -0
  166. package/dist/commands/press_key.d.ts.map +1 -0
  167. package/dist/commands/press_key.js +103 -0
  168. package/dist/commands/press_key.js.map +1 -0
  169. package/dist/commands/scroll.d.ts +21 -0
  170. package/dist/commands/scroll.d.ts.map +1 -0
  171. package/dist/commands/scroll.js +54 -0
  172. package/dist/commands/scroll.js.map +1 -0
  173. package/dist/commands/select.d.ts +18 -0
  174. package/dist/commands/select.d.ts.map +1 -0
  175. package/dist/commands/select.js +53 -0
  176. package/dist/commands/select.js.map +1 -0
  177. package/dist/commands/snapshot.d.ts +9 -0
  178. package/dist/commands/snapshot.d.ts.map +1 -0
  179. package/dist/commands/snapshot.js +7 -0
  180. package/dist/commands/snapshot.js.map +1 -0
  181. package/dist/commands/styles.d.ts +13 -0
  182. package/dist/commands/styles.d.ts.map +1 -0
  183. package/dist/commands/styles.js +7 -0
  184. package/dist/commands/styles.js.map +1 -0
  185. package/dist/commands/type.d.ts +3 -1
  186. package/dist/commands/type.d.ts.map +1 -1
  187. package/dist/commands/type.js +11 -2
  188. package/dist/commands/type.js.map +1 -1
  189. package/dist/commands/upload.d.ts +12 -0
  190. package/dist/commands/upload.d.ts.map +1 -0
  191. package/dist/commands/upload.js +13 -0
  192. package/dist/commands/upload.js.map +1 -0
  193. package/dist/mcp/console-buffer.d.ts +9 -0
  194. package/dist/mcp/console-buffer.d.ts.map +1 -1
  195. package/dist/mcp/console-buffer.js.map +1 -1
  196. package/dist/mcp/index.js +20 -2
  197. package/dist/mcp/index.js.map +1 -1
  198. package/dist/mcp/server.d.ts +10 -0
  199. package/dist/mcp/server.d.ts.map +1 -1
  200. package/dist/mcp/server.js +63 -2
  201. package/dist/mcp/server.js.map +1 -1
  202. package/dist/mcp/tools/browser.d.ts.map +1 -1
  203. package/dist/mcp/tools/browser.js +109 -11
  204. package/dist/mcp/tools/browser.js.map +1 -1
  205. package/dist/mcp/tools/connect.d.ts +5 -0
  206. package/dist/mcp/tools/connect.d.ts.map +1 -0
  207. package/dist/mcp/tools/connect.js +48 -0
  208. package/dist/mcp/tools/connect.js.map +1 -0
  209. package/dist/mcp/tools/files.d.ts +4 -0
  210. package/dist/mcp/tools/files.d.ts.map +1 -0
  211. package/dist/mcp/tools/files.js +85 -0
  212. package/dist/mcp/tools/files.js.map +1 -0
  213. package/dist/mcp/tools/interaction.d.ts +4 -0
  214. package/dist/mcp/tools/interaction.d.ts.map +1 -0
  215. package/dist/mcp/tools/interaction.js +176 -0
  216. package/dist/mcp/tools/interaction.js.map +1 -0
  217. package/dist/mcp/tools/names.d.ts +1 -1
  218. package/dist/mcp/tools/names.d.ts.map +1 -1
  219. package/dist/mcp/tools/names.js +19 -1
  220. package/dist/mcp/tools/names.js.map +1 -1
  221. package/dist/mcp/tools/network.d.ts +4 -0
  222. package/dist/mcp/tools/network.d.ts.map +1 -0
  223. package/dist/mcp/tools/network.js +68 -0
  224. package/dist/mcp/tools/network.js.map +1 -0
  225. package/dist/mcp/tools/performance.d.ts +4 -0
  226. package/dist/mcp/tools/performance.d.ts.map +1 -0
  227. package/dist/mcp/tools/performance.js +26 -0
  228. package/dist/mcp/tools/performance.js.map +1 -0
  229. package/dist/mcp/tools/runtime.d.ts +4 -0
  230. package/dist/mcp/tools/runtime.d.ts.map +1 -0
  231. package/dist/mcp/tools/runtime.js +128 -0
  232. package/dist/mcp/tools/runtime.js.map +1 -0
  233. package/dist/mcp/tools/snapshot.d.ts +4 -0
  234. package/dist/mcp/tools/snapshot.d.ts.map +1 -0
  235. package/dist/mcp/tools/snapshot.js +42 -0
  236. package/dist/mcp/tools/snapshot.js.map +1 -0
  237. package/dist/mcp/tools/styles.d.ts +4 -0
  238. package/dist/mcp/tools/styles.d.ts.map +1 -0
  239. package/dist/mcp/tools/styles.js +27 -0
  240. package/dist/mcp/tools/styles.js.map +1 -0
  241. package/dist/util/config.d.ts.map +1 -1
  242. package/dist/util/config.js +3 -2
  243. package/dist/util/config.js.map +1 -1
  244. package/package.json +5 -4
  245. package/skills/pilot/SKILL.md +48 -0
  246. package/src/browser/browser-manager.ts +465 -0
  247. package/src/browser/chrome-launcher.ts +282 -0
  248. package/src/browser/connection.ts +84 -0
  249. package/src/browser/events.ts +121 -0
  250. package/src/browser/index.ts +20 -0
  251. package/src/browser/inspect/element-ref.ts +68 -0
  252. package/src/browser/inspect/snapshot.ts +348 -0
  253. package/src/browser/inspect/styles.ts +258 -0
  254. package/src/browser/inspect/types.ts +37 -0
  255. package/src/browser/interaction/dialogs.ts +54 -0
  256. package/src/browser/interaction/downloads.ts +116 -0
  257. package/src/browser/interaction/events.ts +79 -0
  258. package/src/browser/interaction/ref-resolver.ts +53 -0
  259. package/src/browser/interaction/uploads.ts +60 -0
  260. package/src/browser/network/network-buffer.ts +133 -0
  261. package/src/browser/network/network-monitor.ts +107 -0
  262. package/src/browser/network/redaction.ts +42 -0
  263. package/src/browser/network/types.ts +47 -0
  264. package/src/browser/performance/analyze.ts +298 -0
  265. package/src/browser/performance/trace.ts +60 -0
  266. package/src/browser/performance/types.ts +34 -0
  267. package/src/browser/preflight.ts +144 -0
  268. package/src/browser/security/origins.ts +102 -0
  269. package/src/browser/tabs.ts +110 -0
  270. package/src/browser/types.ts +32 -0
  271. package/src/browser.ts +22 -798
  272. package/src/cli/init.ts +40 -49
  273. package/src/cli/linux-check.ts +23 -0
  274. package/src/cli/skill-template.ts +4 -113
  275. package/src/commands/check.ts +59 -0
  276. package/src/commands/click.ts +18 -4
  277. package/src/commands/connect.ts +40 -0
  278. package/src/commands/dialog.ts +79 -0
  279. package/src/commands/downloads.ts +111 -0
  280. package/src/commands/fill.ts +34 -0
  281. package/src/commands/find.ts +52 -0
  282. package/src/commands/hover.ts +42 -0
  283. package/src/commands/navigate.ts +20 -0
  284. package/src/commands/network.ts +141 -0
  285. package/src/commands/performance.ts +51 -0
  286. package/src/commands/press_key.ts +134 -0
  287. package/src/commands/scroll.ts +79 -0
  288. package/src/commands/select.ts +78 -0
  289. package/src/commands/snapshot.ts +18 -0
  290. package/src/commands/styles.ts +24 -0
  291. package/src/commands/type.ts +12 -2
  292. package/src/commands/upload.ts +36 -0
  293. package/src/mcp/console-buffer.ts +10 -0
  294. package/src/mcp/index.ts +24 -2
  295. package/src/mcp/server.ts +74 -2
  296. package/src/mcp/tools/browser.ts +125 -9
  297. package/src/mcp/tools/connect.ts +62 -0
  298. package/src/mcp/tools/files.ts +120 -0
  299. package/src/mcp/tools/interaction.ts +232 -0
  300. package/src/mcp/tools/names.ts +19 -1
  301. package/src/mcp/tools/network.ts +86 -0
  302. package/src/mcp/tools/performance.ts +40 -0
  303. package/src/mcp/tools/runtime.ts +164 -0
  304. package/src/mcp/tools/snapshot.ts +64 -0
  305. package/src/mcp/tools/styles.ts +41 -0
  306. package/src/util/config.ts +3 -2
@@ -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
+ }
@@ -0,0 +1,134 @@
1
+ import type { Client } from 'chrome-remote-interface';
2
+
3
+ /**
4
+ * Map of named keys to CDP key event properties.
5
+ */
6
+ const KEY_MAP: Record<
7
+ string,
8
+ { key: string; code: string; keyCode?: number; text?: string }
9
+ > = {
10
+ enter: { key: 'Enter', code: 'Enter', keyCode: 13, text: '\r' },
11
+ tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
12
+ escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
13
+ backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
14
+ delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
15
+ space: { key: ' ', code: 'Space', keyCode: 32, text: ' ' },
16
+ arrowup: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
17
+ arrowdown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
18
+ arrowleft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
19
+ arrowright: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
20
+ home: { key: 'Home', code: 'Home', keyCode: 36 },
21
+ end: { key: 'End', code: 'End', keyCode: 35 },
22
+ pageup: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
23
+ pagedown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
24
+ f1: { key: 'F1', code: 'F1', keyCode: 112 },
25
+ f2: { key: 'F2', code: 'F2', keyCode: 113 },
26
+ f3: { key: 'F3', code: 'F3', keyCode: 114 },
27
+ f4: { key: 'F4', code: 'F4', keyCode: 115 },
28
+ f5: { key: 'F5', code: 'F5', keyCode: 116 },
29
+ f6: { key: 'F6', code: 'F6', keyCode: 117 },
30
+ f7: { key: 'F7', code: 'F7', keyCode: 118 },
31
+ f8: { key: 'F8', code: 'F8', keyCode: 119 },
32
+ f9: { key: 'F9', code: 'F9', keyCode: 120 },
33
+ f10: { key: 'F10', code: 'F10', keyCode: 121 },
34
+ f11: { key: 'F11', code: 'F11', keyCode: 122 },
35
+ f12: { key: 'F12', code: 'F12', keyCode: 123 }
36
+ };
37
+
38
+ // CDP modifier bit flags
39
+ const MODIFIER_FLAGS: Record<string, number> = {
40
+ alt: 1,
41
+ control: 2,
42
+ ctrl: 2,
43
+ meta: 4,
44
+ cmd: 4,
45
+ command: 4,
46
+ shift: 8
47
+ };
48
+
49
+ export interface PressKeyParams {
50
+ key: string;
51
+ }
52
+
53
+ export interface PressKeyResult {
54
+ key: string;
55
+ modifiers: string[];
56
+ }
57
+
58
+ /**
59
+ * Parse a key string like "Control+a", "Meta+Shift+Enter", or "Tab".
60
+ */
61
+ function parseKeyCombo(input: string): {
62
+ modifiers: number;
63
+ modifierNames: string[];
64
+ keyInfo: { key: string; code: string; keyCode?: number; text?: string };
65
+ } {
66
+ const parts = input.split('+');
67
+ let modifiers = 0;
68
+ const modifierNames: string[] = [];
69
+
70
+ // Last part is the actual key, everything before is a modifier
71
+ const keyPart = parts.pop()!;
72
+
73
+ for (const mod of parts) {
74
+ const flag = MODIFIER_FLAGS[mod.toLowerCase()];
75
+ if (flag) {
76
+ modifiers |= flag;
77
+ modifierNames.push(mod);
78
+ }
79
+ }
80
+
81
+ // Look up the key in our map
82
+ const mapped = KEY_MAP[keyPart.toLowerCase()];
83
+ if (mapped) {
84
+ return { modifiers, modifierNames, keyInfo: mapped };
85
+ }
86
+
87
+ // Single character key
88
+ if (keyPart.length === 1) {
89
+ const charCode = keyPart.charCodeAt(0);
90
+ return {
91
+ modifiers,
92
+ modifierNames,
93
+ keyInfo: {
94
+ key: keyPart,
95
+ code: `Key${keyPart.toUpperCase()}`,
96
+ keyCode: charCode,
97
+ text: modifiers === 0 ? keyPart : undefined
98
+ }
99
+ };
100
+ }
101
+
102
+ // Unknown named key — pass through
103
+ return {
104
+ modifiers,
105
+ modifierNames,
106
+ keyInfo: { key: keyPart, code: keyPart }
107
+ };
108
+ }
109
+
110
+ export async function executePressKey(
111
+ client: Client,
112
+ params: PressKeyParams
113
+ ): Promise<PressKeyResult> {
114
+ const { modifiers, modifierNames, keyInfo } = parseKeyCombo(params.key);
115
+
116
+ await client.Input.dispatchKeyEvent({
117
+ type: 'keyDown',
118
+ key: keyInfo.key,
119
+ code: keyInfo.code,
120
+ windowsVirtualKeyCode: keyInfo.keyCode,
121
+ modifiers,
122
+ ...(keyInfo.text && { text: keyInfo.text })
123
+ });
124
+
125
+ await client.Input.dispatchKeyEvent({
126
+ type: 'keyUp',
127
+ key: keyInfo.key,
128
+ code: keyInfo.code,
129
+ windowsVirtualKeyCode: keyInfo.keyCode,
130
+ modifiers
131
+ });
132
+
133
+ return { key: keyInfo.key, modifiers: modifierNames };
134
+ }
@@ -0,0 +1,79 @@
1
+ import type { Client } from 'chrome-remote-interface';
2
+ import type { ElementRefMap } from '../browser/inspect/element-ref.js';
3
+
4
+ export interface ScrollParams {
5
+ ref?: string;
6
+ x?: number;
7
+ y?: number;
8
+ direction?: 'up' | 'down' | 'left' | 'right';
9
+ amount?: number;
10
+ }
11
+
12
+ export interface ScrollResult {
13
+ scrollX: number;
14
+ scrollY: number;
15
+ }
16
+
17
+ /**
18
+ * Scroll the page or an element.
19
+ * - If ref is given, scrolls the element into view.
20
+ * - If direction/amount is given, scrolls the page.
21
+ * - If x/y is given, scrolls the page to absolute coordinates.
22
+ */
23
+ export async function executeScroll(
24
+ client: Client,
25
+ refMap: ElementRefMap,
26
+ params: ScrollParams
27
+ ): Promise<ScrollResult> {
28
+ if (params.ref) {
29
+ // Scroll element into view
30
+ const backendNodeId = refMap.resolve(params.ref);
31
+ await client.DOM.scrollIntoViewIfNeeded({ backendNodeId });
32
+
33
+ const { result } = await client.Runtime.evaluate({
34
+ expression: `JSON.stringify({ scrollX: window.scrollX, scrollY: window.scrollY })`,
35
+ returnByValue: true
36
+ });
37
+ const pos = JSON.parse(String(result.value));
38
+ return { scrollX: pos.scrollX, scrollY: pos.scrollY };
39
+ }
40
+
41
+ if (params.direction) {
42
+ const amount = params.amount ?? 300;
43
+ const deltaX =
44
+ params.direction === 'left'
45
+ ? -amount
46
+ : params.direction === 'right'
47
+ ? amount
48
+ : 0;
49
+ const deltaY =
50
+ params.direction === 'up'
51
+ ? -amount
52
+ : params.direction === 'down'
53
+ ? amount
54
+ : 0;
55
+
56
+ await client.Input.dispatchMouseEvent({
57
+ type: 'mouseWheel',
58
+ x: 0,
59
+ y: 0,
60
+ deltaX,
61
+ deltaY
62
+ });
63
+
64
+ // Small delay for scroll to take effect
65
+ await new Promise((r) => setTimeout(r, 100));
66
+ } else if (params.x !== undefined || params.y !== undefined) {
67
+ await client.Runtime.evaluate({
68
+ expression: `window.scrollTo(${params.x ?? 0}, ${params.y ?? 0})`,
69
+ awaitPromise: false
70
+ });
71
+ }
72
+
73
+ const { result } = await client.Runtime.evaluate({
74
+ expression: `JSON.stringify({ scrollX: window.scrollX, scrollY: window.scrollY })`,
75
+ returnByValue: true
76
+ });
77
+ const pos = JSON.parse(String(result.value));
78
+ return { scrollX: pos.scrollX, scrollY: pos.scrollY };
79
+ }
@@ -0,0 +1,78 @@
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
+
5
+ export interface SelectParams {
6
+ ref: string;
7
+ value?: string;
8
+ label?: string;
9
+ index?: number;
10
+ }
11
+
12
+ export interface SelectResult {
13
+ ref: string;
14
+ selectedValue: string;
15
+ selectedLabel: string;
16
+ }
17
+
18
+ /**
19
+ * Select an option in a <select> element by value, label, or index.
20
+ */
21
+ export async function executeSelect(
22
+ client: Client,
23
+ refMap: ElementRefMap,
24
+ params: SelectParams
25
+ ): Promise<SelectResult> {
26
+ const resolved = await resolveRef(client, refMap, params.ref);
27
+
28
+ await focusRef(client, resolved.backendNodeId);
29
+
30
+ const { result, exceptionDetails } = await client.Runtime.callFunctionOn({
31
+ objectId: resolved.objectId,
32
+ functionDeclaration: `function(byValue, byLabel, byIndex) {
33
+ if (this.tagName.toLowerCase() !== 'select') {
34
+ throw new Error('Element is not a <select>');
35
+ }
36
+ var found = false;
37
+ for (var i = 0; i < this.options.length; i++) {
38
+ var opt = this.options[i];
39
+ if (byValue !== null && opt.value === byValue) {
40
+ this.selectedIndex = i; found = true; break;
41
+ }
42
+ if (byLabel !== null && opt.textContent.trim() === byLabel) {
43
+ this.selectedIndex = i; found = true; break;
44
+ }
45
+ if (byIndex !== null && i === byIndex) {
46
+ this.selectedIndex = i; found = true; break;
47
+ }
48
+ }
49
+ if (!found) throw new Error('Option not found');
50
+ this.dispatchEvent(new Event('input', { bubbles: true }));
51
+ this.dispatchEvent(new Event('change', { bubbles: true }));
52
+ var sel = this.options[this.selectedIndex];
53
+ return { value: sel.value, label: sel.textContent.trim() };
54
+ }`,
55
+ arguments: [
56
+ { value: params.value ?? null },
57
+ { value: params.label ?? null },
58
+ { value: params.index ?? null }
59
+ ],
60
+ returnByValue: true,
61
+ awaitPromise: false
62
+ });
63
+
64
+ if (exceptionDetails) {
65
+ throw new Error(
66
+ exceptionDetails.exception?.description ??
67
+ exceptionDetails.text ??
68
+ 'Failed to select option'
69
+ );
70
+ }
71
+
72
+ const selected = result.value as { value: string; label: string };
73
+ return {
74
+ ref: params.ref,
75
+ selectedValue: selected.value,
76
+ selectedLabel: selected.label
77
+ };
78
+ }
@@ -0,0 +1,18 @@
1
+ import type { Client } from 'chrome-remote-interface';
2
+ import type { ElementRefMap } from '../browser/inspect/element-ref.js';
3
+ import { buildSnapshot, formatSnapshot } from '../browser/inspect/snapshot.js';
4
+ import type { SnapshotResult } from '../browser/inspect/types.js';
5
+
6
+ export interface SnapshotCommandResult {
7
+ text: string;
8
+ snapshot: SnapshotResult;
9
+ }
10
+
11
+ export async function executeSnapshot(
12
+ client: Client,
13
+ refMap: ElementRefMap
14
+ ): Promise<SnapshotCommandResult> {
15
+ const snapshot = await buildSnapshot(client, refMap);
16
+ const text = formatSnapshot(snapshot);
17
+ return { text, snapshot };
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { Client } from 'chrome-remote-interface';
2
+ import type { ElementRefMap } from '../browser/inspect/element-ref.js';
3
+ import { getStyles, formatStyles } from '../browser/inspect/styles.js';
4
+ import type { StylesResult } from '../browser/inspect/styles.js';
5
+
6
+ export interface StylesParams {
7
+ ref: string;
8
+ properties?: string[];
9
+ }
10
+
11
+ export interface StylesCommandResult {
12
+ text: string;
13
+ styles: StylesResult;
14
+ }
15
+
16
+ export async function executeStyles(
17
+ client: Client,
18
+ refMap: ElementRefMap,
19
+ params: StylesParams
20
+ ): Promise<StylesCommandResult> {
21
+ const styles = await getStyles(client, refMap, params.ref, params.properties);
22
+ const text = formatStyles(styles);
23
+ return { text, styles };
24
+ }
@@ -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, focusRef } from '../browser/interaction/ref-resolver.js';
3
5
 
4
6
  export const TypeParamsSchema = z.object({
5
7
  text: z.string(),
8
+ ref: z.string().optional(),
6
9
  selector: z.string().optional(),
7
10
  clearFirst: z.boolean().default(false),
8
11
  delayMs: z.number().int().min(0).default(0)
@@ -30,9 +33,16 @@ async function focusSelector(client: Client, selector: string): Promise<void> {
30
33
 
31
34
  export async function executeType(
32
35
  client: Client,
33
- params: TypeParams
36
+ params: TypeParams,
37
+ refMap?: ElementRefMap
34
38
  ): Promise<TypeResult> {
35
- if (params.selector) {
39
+ if (params.ref) {
40
+ if (!refMap) {
41
+ throw new Error('Element ref map is required when using ref parameter');
42
+ }
43
+ const resolved = await resolveRef(client, refMap, params.ref);
44
+ await focusRef(client, resolved.backendNodeId);
45
+ } else if (params.selector) {
36
46
  await focusSelector(client, params.selector);
37
47
  }
38
48
 
@@ -0,0 +1,36 @@
1
+ import type { Client } from 'chrome-remote-interface';
2
+ import type { ElementRefMap } from '../browser/inspect/element-ref.js';
3
+ import {
4
+ validateUploadFiles,
5
+ uploadFiles
6
+ } from '../browser/interaction/uploads.js';
7
+
8
+ export interface UploadParams {
9
+ ref: string;
10
+ files: string[];
11
+ }
12
+
13
+ export interface UploadResult {
14
+ ref: string;
15
+ files: string[];
16
+ }
17
+
18
+ export async function executeUpload(
19
+ client: Client,
20
+ refMap: ElementRefMap,
21
+ params: UploadParams
22
+ ): Promise<UploadResult> {
23
+ if (params.files.length === 0) {
24
+ throw new Error('At least one file is required');
25
+ }
26
+
27
+ const backendNodeId = refMap.resolve(params.ref);
28
+
29
+ // Validate files before uploading
30
+ validateUploadFiles(params.files);
31
+
32
+ // Upload to the file input element
33
+ await uploadFiles(client, backendNodeId, params.files);
34
+
35
+ return { ref: params.ref, files: params.files };
36
+ }