e2e-pilot 0.0.69

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 (152) hide show
  1. package/bin.js +3 -0
  2. package/dist/aria-snapshot.d.ts +95 -0
  3. package/dist/aria-snapshot.d.ts.map +1 -0
  4. package/dist/aria-snapshot.js +490 -0
  5. package/dist/aria-snapshot.js.map +1 -0
  6. package/dist/bippy.js +971 -0
  7. package/dist/cdp-relay.d.ts +16 -0
  8. package/dist/cdp-relay.d.ts.map +1 -0
  9. package/dist/cdp-relay.js +715 -0
  10. package/dist/cdp-relay.js.map +1 -0
  11. package/dist/cdp-session.d.ts +42 -0
  12. package/dist/cdp-session.d.ts.map +1 -0
  13. package/dist/cdp-session.js +154 -0
  14. package/dist/cdp-session.js.map +1 -0
  15. package/dist/cdp-types.d.ts +63 -0
  16. package/dist/cdp-types.d.ts.map +1 -0
  17. package/dist/cdp-types.js +91 -0
  18. package/dist/cdp-types.js.map +1 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +213 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/create-logger.d.ts +9 -0
  24. package/dist/create-logger.d.ts.map +1 -0
  25. package/dist/create-logger.js +25 -0
  26. package/dist/create-logger.js.map +1 -0
  27. package/dist/debugger-api.md +458 -0
  28. package/dist/debugger-examples-types.d.ts +24 -0
  29. package/dist/debugger-examples-types.d.ts.map +1 -0
  30. package/dist/debugger-examples-types.js +2 -0
  31. package/dist/debugger-examples-types.js.map +1 -0
  32. package/dist/debugger-examples.d.ts +6 -0
  33. package/dist/debugger-examples.d.ts.map +1 -0
  34. package/dist/debugger-examples.js +53 -0
  35. package/dist/debugger-examples.js.map +1 -0
  36. package/dist/debugger.d.ts +381 -0
  37. package/dist/debugger.d.ts.map +1 -0
  38. package/dist/debugger.js +633 -0
  39. package/dist/debugger.js.map +1 -0
  40. package/dist/editor-api.md +364 -0
  41. package/dist/editor-examples.d.ts +11 -0
  42. package/dist/editor-examples.d.ts.map +1 -0
  43. package/dist/editor-examples.js +124 -0
  44. package/dist/editor-examples.js.map +1 -0
  45. package/dist/editor.d.ts +203 -0
  46. package/dist/editor.d.ts.map +1 -0
  47. package/dist/editor.js +336 -0
  48. package/dist/editor.js.map +1 -0
  49. package/dist/execute.d.ts +50 -0
  50. package/dist/execute.d.ts.map +1 -0
  51. package/dist/execute.js +576 -0
  52. package/dist/execute.js.map +1 -0
  53. package/dist/index.d.ts +11 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/mcp-client.d.ts +20 -0
  58. package/dist/mcp-client.d.ts.map +1 -0
  59. package/dist/mcp-client.js +56 -0
  60. package/dist/mcp-client.js.map +1 -0
  61. package/dist/mcp.d.ts +5 -0
  62. package/dist/mcp.d.ts.map +1 -0
  63. package/dist/mcp.js +720 -0
  64. package/dist/mcp.js.map +1 -0
  65. package/dist/mcp.test.d.ts +10 -0
  66. package/dist/mcp.test.d.ts.map +1 -0
  67. package/dist/mcp.test.js +2999 -0
  68. package/dist/mcp.test.js.map +1 -0
  69. package/dist/network-capture.d.ts +23 -0
  70. package/dist/network-capture.d.ts.map +1 -0
  71. package/dist/network-capture.js +98 -0
  72. package/dist/network-capture.js.map +1 -0
  73. package/dist/protocol.d.ts +54 -0
  74. package/dist/protocol.d.ts.map +1 -0
  75. package/dist/protocol.js +2 -0
  76. package/dist/protocol.js.map +1 -0
  77. package/dist/react-source.d.ts +13 -0
  78. package/dist/react-source.d.ts.map +1 -0
  79. package/dist/react-source.js +68 -0
  80. package/dist/react-source.js.map +1 -0
  81. package/dist/scoped-fs.d.ts +94 -0
  82. package/dist/scoped-fs.d.ts.map +1 -0
  83. package/dist/scoped-fs.js +356 -0
  84. package/dist/scoped-fs.js.map +1 -0
  85. package/dist/selector-generator.js +8126 -0
  86. package/dist/start-relay-server.d.ts +6 -0
  87. package/dist/start-relay-server.d.ts.map +1 -0
  88. package/dist/start-relay-server.js +33 -0
  89. package/dist/start-relay-server.js.map +1 -0
  90. package/dist/styles-api.md +117 -0
  91. package/dist/styles-examples.d.ts +8 -0
  92. package/dist/styles-examples.d.ts.map +1 -0
  93. package/dist/styles-examples.js +64 -0
  94. package/dist/styles-examples.js.map +1 -0
  95. package/dist/styles.d.ts +27 -0
  96. package/dist/styles.d.ts.map +1 -0
  97. package/dist/styles.js +234 -0
  98. package/dist/styles.js.map +1 -0
  99. package/dist/trace-utils.d.ts +14 -0
  100. package/dist/trace-utils.d.ts.map +1 -0
  101. package/dist/trace-utils.js +21 -0
  102. package/dist/trace-utils.js.map +1 -0
  103. package/dist/utils.d.ts +20 -0
  104. package/dist/utils.d.ts.map +1 -0
  105. package/dist/utils.js +75 -0
  106. package/dist/utils.js.map +1 -0
  107. package/dist/wait-for-page-load.d.ts +16 -0
  108. package/dist/wait-for-page-load.d.ts.map +1 -0
  109. package/dist/wait-for-page-load.js +127 -0
  110. package/dist/wait-for-page-load.js.map +1 -0
  111. package/package.json +67 -0
  112. package/src/aria-snapshot.ts +610 -0
  113. package/src/assets/aria-labels-github-snapshot.txt +605 -0
  114. package/src/assets/aria-labels-github.png +0 -0
  115. package/src/assets/aria-labels-google-snapshot.txt +49 -0
  116. package/src/assets/aria-labels-google.png +0 -0
  117. package/src/assets/aria-labels-hacker-news-snapshot.txt +1023 -0
  118. package/src/assets/aria-labels-hacker-news.png +0 -0
  119. package/src/cdp-relay.ts +925 -0
  120. package/src/cdp-session.ts +203 -0
  121. package/src/cdp-timing.md +128 -0
  122. package/src/cdp-types.ts +155 -0
  123. package/src/cli.ts +250 -0
  124. package/src/create-logger.ts +36 -0
  125. package/src/debugger-examples-types.ts +13 -0
  126. package/src/debugger-examples.ts +66 -0
  127. package/src/debugger.md +453 -0
  128. package/src/debugger.ts +713 -0
  129. package/src/editor-examples.ts +148 -0
  130. package/src/editor.ts +390 -0
  131. package/src/execute.ts +763 -0
  132. package/src/index.ts +10 -0
  133. package/src/mcp-client.ts +78 -0
  134. package/src/mcp.test.ts +3596 -0
  135. package/src/mcp.ts +876 -0
  136. package/src/network-capture.ts +140 -0
  137. package/src/prompt.bak.md +323 -0
  138. package/src/prompt.md +7 -0
  139. package/src/protocol.ts +63 -0
  140. package/src/react-source.ts +94 -0
  141. package/src/resource.md +436 -0
  142. package/src/scoped-fs.ts +411 -0
  143. package/src/snapshots/hacker-news-focused-accessibility.md +202 -0
  144. package/src/snapshots/hacker-news-initial-accessibility.md +11 -0
  145. package/src/snapshots/hacker-news-tabbed-accessibility.md +202 -0
  146. package/src/snapshots/shadcn-ui-accessibility.md +11 -0
  147. package/src/start-relay-server.ts +43 -0
  148. package/src/styles-examples.ts +77 -0
  149. package/src/styles.ts +345 -0
  150. package/src/trace-utils.ts +43 -0
  151. package/src/utils.ts +91 -0
  152. package/src/wait-for-page-load.ts +174 -0
@@ -0,0 +1,148 @@
1
+ import { page, getCDPSession, createEditor, console } from './debugger-examples-types.js'
2
+
3
+ // Example: List available scripts
4
+ async function listScripts() {
5
+ const cdp = await getCDPSession({ page })
6
+ const editor = createEditor({ cdp })
7
+ await editor.enable()
8
+
9
+ const scripts = editor.list({ pattern: /app/ })
10
+ console.log(scripts)
11
+ }
12
+
13
+ // Example: Read a script with line numbers
14
+ async function readScript() {
15
+ const cdp = await getCDPSession({ page })
16
+ const editor = createEditor({ cdp })
17
+ await editor.enable()
18
+
19
+ const { content, totalLines } = await editor.read({
20
+ url: 'https://example.com/app.js',
21
+ })
22
+ console.log('Total lines:', totalLines)
23
+ console.log(content)
24
+
25
+ const { content: partial } = await editor.read({
26
+ url: 'https://example.com/app.js',
27
+ offset: 100,
28
+ limit: 50,
29
+ })
30
+ console.log(partial)
31
+ }
32
+
33
+ // Example: Edit a script (exact string replacement)
34
+ async function editScript() {
35
+ const cdp = await getCDPSession({ page })
36
+ const editor = createEditor({ cdp })
37
+ await editor.enable()
38
+
39
+ await editor.edit({
40
+ url: 'https://example.com/app.js',
41
+ oldString: 'const DEBUG = false',
42
+ newString: 'const DEBUG = true',
43
+ })
44
+
45
+ const dryRunResult = await editor.edit({
46
+ url: 'https://example.com/app.js',
47
+ oldString: 'old code',
48
+ newString: 'new code',
49
+ dryRun: true,
50
+ })
51
+ console.log('Dry run result:', dryRunResult)
52
+ }
53
+
54
+ // Example: Search across all scripts
55
+ async function searchScripts() {
56
+ const cdp = await getCDPSession({ page })
57
+ const editor = createEditor({ cdp })
58
+ await editor.enable()
59
+
60
+ const matches = await editor.grep({ regex: /console\.log/ })
61
+ console.log(matches)
62
+
63
+ const todoMatches = await editor.grep({
64
+ regex: /TODO|FIXME/i,
65
+ pattern: /app/,
66
+ })
67
+ console.log(todoMatches)
68
+ }
69
+
70
+ // Example: Write entire script content
71
+ async function writeScript() {
72
+ const cdp = await getCDPSession({ page })
73
+ const editor = createEditor({ cdp })
74
+ await editor.enable()
75
+
76
+ const { content } = await editor.read({ url: 'https://example.com/app.js' })
77
+ const newContent = content.replace(/console\.log/g, 'console.debug')
78
+
79
+ await editor.write({
80
+ url: 'https://example.com/app.js',
81
+ content: newContent,
82
+ })
83
+ }
84
+
85
+ // Example: Edit an inline script (scripts without URL get inline://{id} URLs)
86
+ async function editInlineScript() {
87
+ const cdp = await getCDPSession({ page })
88
+ const editor = createEditor({ cdp })
89
+ await editor.enable()
90
+
91
+ const matches = await editor.grep({ regex: /myFunction/ })
92
+ if (matches.length > 0) {
93
+ const { url } = matches[0]
94
+ console.log('Found in:', url)
95
+
96
+ await editor.edit({
97
+ url,
98
+ oldString: 'return false',
99
+ newString: 'return true',
100
+ })
101
+ }
102
+ }
103
+
104
+ // Example: List and read CSS stylesheets
105
+ async function readStylesheet() {
106
+ const cdp = await getCDPSession({ page })
107
+ const editor = createEditor({ cdp })
108
+ await editor.enable()
109
+
110
+ const stylesheets = await editor.list({ pattern: /\.css/ })
111
+ console.log('Stylesheets:', stylesheets)
112
+
113
+ if (stylesheets.length > 0) {
114
+ const { content, totalLines } = await editor.read({
115
+ url: stylesheets[0],
116
+ })
117
+ console.log('Total lines:', totalLines)
118
+ console.log(content)
119
+ }
120
+ }
121
+
122
+ // Example: Edit a CSS stylesheet
123
+ async function editStylesheet() {
124
+ const cdp = await getCDPSession({ page })
125
+ const editor = createEditor({ cdp })
126
+ await editor.enable()
127
+
128
+ await editor.edit({
129
+ url: 'https://example.com/styles.css',
130
+ oldString: 'color: red',
131
+ newString: 'color: blue',
132
+ })
133
+ }
134
+
135
+ // Example: Search CSS for specific properties
136
+ async function searchStyles() {
137
+ const cdp = await getCDPSession({ page })
138
+ const editor = createEditor({ cdp })
139
+ await editor.enable()
140
+
141
+ const matches = await editor.grep({
142
+ regex: /background-color/,
143
+ pattern: /\.css/,
144
+ })
145
+ console.log(matches)
146
+ }
147
+
148
+ export { listScripts, readScript, editScript, searchScripts, writeScript, editInlineScript, readStylesheet, editStylesheet, searchStyles }
package/src/editor.ts ADDED
@@ -0,0 +1,390 @@
1
+ import type { ICDPSession, CDPSession } from './cdp-session.js'
2
+
3
+ export interface ReadResult {
4
+ content: string
5
+ totalLines: number
6
+ startLine: number
7
+ endLine: number
8
+ }
9
+
10
+ export interface SearchMatch {
11
+ url: string
12
+ lineNumber: number
13
+ lineContent: string
14
+ }
15
+
16
+ export interface EditResult {
17
+ success: boolean
18
+ stackChanged?: boolean
19
+ }
20
+
21
+ /**
22
+ * A class for viewing and editing web page scripts via Chrome DevTools Protocol.
23
+ * Provides a Claude Code-like interface: list, read, edit, grep.
24
+ *
25
+ * Edits are in-memory only and persist until page reload. They modify the running
26
+ * V8 instance but are not saved to disk or server.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const cdp = await getCDPSession({ page })
31
+ * const editor = new Editor({ cdp })
32
+ * await editor.enable()
33
+ *
34
+ * // List available scripts
35
+ * const scripts = editor.list({ search: 'app' })
36
+ *
37
+ * // Read a script
38
+ * const { content } = await editor.read({ url: 'https://example.com/app.js' })
39
+ *
40
+ * // Edit a script
41
+ * await editor.edit({
42
+ * url: 'https://example.com/app.js',
43
+ * oldString: 'console.log("old")',
44
+ * newString: 'console.log("new")'
45
+ * })
46
+ * ```
47
+ */
48
+ export class Editor {
49
+ private cdp: CDPSession
50
+ private enabled = false
51
+ private scripts = new Map<string, string>()
52
+ private stylesheets = new Map<string, string>()
53
+ private sourceCache = new Map<string, string>()
54
+
55
+ constructor({ cdp }: { cdp: ICDPSession }) {
56
+ // Cast to CDPSession for internal type safety - at runtime both are compatible
57
+ this.cdp = cdp as CDPSession
58
+ this.setupEventListeners()
59
+ }
60
+
61
+ private setupEventListeners() {
62
+ this.cdp.on('Debugger.scriptParsed', (params) => {
63
+ if (!params.url.startsWith('chrome') && !params.url.startsWith('devtools')) {
64
+ const url = params.url || `inline://${params.scriptId}`
65
+ this.scripts.set(url, params.scriptId)
66
+ this.sourceCache.delete(params.scriptId)
67
+ }
68
+ })
69
+
70
+ this.cdp.on('CSS.styleSheetAdded', (params) => {
71
+ const header = params.header
72
+ if (header.sourceURL?.startsWith('chrome') || header.sourceURL?.startsWith('devtools')) {
73
+ return
74
+ }
75
+ const url = header.sourceURL || `inline-css://${header.styleSheetId}`
76
+ this.stylesheets.set(url, header.styleSheetId)
77
+ this.sourceCache.delete(header.styleSheetId)
78
+ })
79
+ }
80
+
81
+ /**
82
+ * Enables the editor. Must be called before other methods.
83
+ * Scripts are collected from Debugger.scriptParsed events.
84
+ * Reload the page after enabling to capture all scripts.
85
+ */
86
+ async enable(): Promise<void> {
87
+ if (this.enabled) {
88
+ return
89
+ }
90
+ await this.cdp.send('Debugger.disable')
91
+ await this.cdp.send('CSS.disable')
92
+ this.scripts.clear()
93
+ this.stylesheets.clear()
94
+ this.sourceCache.clear()
95
+ const resourcesReady = new Promise<void>((resolve) => {
96
+ let timeout: ReturnType<typeof setTimeout>
97
+ const listener = () => {
98
+ clearTimeout(timeout)
99
+ timeout = setTimeout(() => {
100
+ this.cdp.off('Debugger.scriptParsed', listener)
101
+ this.cdp.off('CSS.styleSheetAdded', listener)
102
+ resolve()
103
+ }, 100)
104
+ }
105
+ this.cdp.on('Debugger.scriptParsed', listener)
106
+ this.cdp.on('CSS.styleSheetAdded', listener)
107
+ timeout = setTimeout(() => {
108
+ this.cdp.off('Debugger.scriptParsed', listener)
109
+ this.cdp.off('CSS.styleSheetAdded', listener)
110
+ resolve()
111
+ }, 100)
112
+ })
113
+ await this.cdp.send('Debugger.enable')
114
+ await this.cdp.send('DOM.enable')
115
+ await this.cdp.send('CSS.enable')
116
+ await resourcesReady
117
+ this.enabled = true
118
+ }
119
+
120
+ private getIdByUrl(url: string): { scriptId: string } | { styleSheetId: string } {
121
+ const scriptId = this.scripts.get(url)
122
+ if (scriptId) {
123
+ return { scriptId }
124
+ }
125
+ const styleSheetId = this.stylesheets.get(url)
126
+ if (styleSheetId) {
127
+ return { styleSheetId }
128
+ }
129
+ const allUrls = [...Array.from(this.scripts.keys()), ...Array.from(this.stylesheets.keys())]
130
+ const available = allUrls.slice(0, 5)
131
+ throw new Error(`Resource not found: ${url}\nAvailable: ${available.join(', ')}${allUrls.length > 5 ? '...' : ''}`)
132
+ }
133
+
134
+ /**
135
+ * Lists available script and stylesheet URLs. Use pattern to filter by regex.
136
+ * Automatically enables the editor if not already enabled.
137
+ *
138
+ * @param options - Options
139
+ * @param options.pattern - Optional regex to filter URLs
140
+ * @returns Array of URLs
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * // List all scripts and stylesheets
145
+ * const urls = await editor.list()
146
+ *
147
+ * // List only JS files
148
+ * const jsFiles = await editor.list({ pattern: /\.js/ })
149
+ *
150
+ * // List only CSS files
151
+ * const cssFiles = await editor.list({ pattern: /\.css/ })
152
+ *
153
+ * // Search for specific scripts
154
+ * const appScripts = await editor.list({ pattern: /app/ })
155
+ * ```
156
+ */
157
+ async list({ pattern }: { pattern?: RegExp } = {}): Promise<string[]> {
158
+ await this.enable()
159
+ const urls = [...Array.from(this.scripts.keys()), ...Array.from(this.stylesheets.keys())]
160
+
161
+ if (!pattern) {
162
+ return urls
163
+ }
164
+ return urls.filter((url) => {
165
+ const matches = pattern.test(url)
166
+ pattern.lastIndex = 0
167
+ return matches
168
+ })
169
+ }
170
+
171
+ /**
172
+ * Reads a script or stylesheet's source code by URL.
173
+ * Returns line-numbered content like Claude Code's Read tool.
174
+ * For inline scripts, use the `inline://` URL from list() or grep().
175
+ *
176
+ * @param options - Options
177
+ * @param options.url - Script or stylesheet URL (inline scripts have `inline://{id}` URLs)
178
+ * @param options.offset - Line number to start from (0-based, default 0)
179
+ * @param options.limit - Number of lines to return (default 2000)
180
+ * @returns Content with line numbers, total lines, and range info
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * // Read by URL
185
+ * const { content, totalLines } = await editor.read({
186
+ * url: 'https://example.com/app.js'
187
+ * })
188
+ *
189
+ * // Read a CSS file
190
+ * const { content } = await editor.read({ url: 'https://example.com/styles.css' })
191
+ *
192
+ * // Read lines 100-200
193
+ * const { content } = await editor.read({
194
+ * url: 'https://example.com/app.js',
195
+ * offset: 100,
196
+ * limit: 100
197
+ * })
198
+ * ```
199
+ */
200
+ async read({ url, offset = 0, limit = 2000 }: { url: string; offset?: number; limit?: number }): Promise<ReadResult> {
201
+ await this.enable()
202
+ const id = this.getIdByUrl(url)
203
+ const source = await this.getSource(id)
204
+
205
+ const lines = source.split('\n')
206
+ const totalLines = lines.length
207
+ const startLine = Math.min(offset, totalLines)
208
+ const endLine = Math.min(offset + limit, totalLines)
209
+ const selectedLines = lines.slice(startLine, endLine)
210
+
211
+ const content = selectedLines.map((line, i) => `${String(startLine + i + 1).padStart(5)}| ${line}`).join('\n')
212
+
213
+ return {
214
+ content,
215
+ totalLines,
216
+ startLine: startLine + 1,
217
+ endLine,
218
+ }
219
+ }
220
+
221
+ private async getSource(id: { scriptId: string } | { styleSheetId: string }): Promise<string> {
222
+ if ('styleSheetId' in id) {
223
+ const cached = this.sourceCache.get(id.styleSheetId)
224
+ if (cached) {
225
+ return cached
226
+ }
227
+ const response = await this.cdp.send('CSS.getStyleSheetText', { styleSheetId: id.styleSheetId })
228
+ this.sourceCache.set(id.styleSheetId, response.text)
229
+ return response.text
230
+ }
231
+ const cached = this.sourceCache.get(id.scriptId)
232
+ if (cached) {
233
+ return cached
234
+ }
235
+ const response = await this.cdp.send('Debugger.getScriptSource', { scriptId: id.scriptId })
236
+ this.sourceCache.set(id.scriptId, response.scriptSource)
237
+ return response.scriptSource
238
+ }
239
+
240
+ /**
241
+ * Edits a script or stylesheet by replacing oldString with newString.
242
+ * Like Claude Code's Edit tool - performs exact string replacement.
243
+ *
244
+ * @param options - Options
245
+ * @param options.url - Script or stylesheet URL (inline scripts have `inline://{id}` URLs)
246
+ * @param options.oldString - Exact string to find and replace
247
+ * @param options.newString - Replacement string
248
+ * @param options.dryRun - If true, validate without applying (default false)
249
+ * @returns Result with success status
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * // Replace a string in JS
254
+ * await editor.edit({
255
+ * url: 'https://example.com/app.js',
256
+ * oldString: 'const DEBUG = false',
257
+ * newString: 'const DEBUG = true'
258
+ * })
259
+ *
260
+ * // Edit CSS
261
+ * await editor.edit({
262
+ * url: 'https://example.com/styles.css',
263
+ * oldString: 'color: red',
264
+ * newString: 'color: blue'
265
+ * })
266
+ * ```
267
+ */
268
+ async edit({
269
+ url,
270
+ oldString,
271
+ newString,
272
+ dryRun = false,
273
+ }: {
274
+ url: string
275
+ oldString: string
276
+ newString: string
277
+ dryRun?: boolean
278
+ }): Promise<EditResult> {
279
+ await this.enable()
280
+ const id = this.getIdByUrl(url)
281
+ const source = await this.getSource(id)
282
+
283
+ const matchCount = source.split(oldString).length - 1
284
+ if (matchCount === 0) {
285
+ throw new Error(`oldString not found in ${url}`)
286
+ }
287
+ if (matchCount > 1) {
288
+ throw new Error(`oldString found ${matchCount} times in ${url}. Provide more context to make it unique.`)
289
+ }
290
+
291
+ const newSource = source.replace(oldString, newString)
292
+ return this.setSource(id, newSource, dryRun)
293
+ }
294
+
295
+ private async setSource(
296
+ id: { scriptId: string } | { styleSheetId: string },
297
+ content: string,
298
+ dryRun = false
299
+ ): Promise<EditResult> {
300
+ if ('styleSheetId' in id) {
301
+ await this.cdp.send('CSS.setStyleSheetText', { styleSheetId: id.styleSheetId, text: content })
302
+ if (!dryRun) {
303
+ this.sourceCache.set(id.styleSheetId, content)
304
+ }
305
+ return { success: true }
306
+ }
307
+ const response = await this.cdp.send('Debugger.setScriptSource', {
308
+ scriptId: id.scriptId,
309
+ scriptSource: content,
310
+ dryRun,
311
+ })
312
+ if (!dryRun) {
313
+ this.sourceCache.set(id.scriptId, content)
314
+ }
315
+ return { success: true, stackChanged: response.stackChanged }
316
+ }
317
+
318
+ /**
319
+ * Searches for a regex across all scripts and stylesheets.
320
+ * Like Claude Code's Grep tool - returns matching lines with context.
321
+ *
322
+ * @param options - Options
323
+ * @param options.regex - Regular expression to search for in file contents
324
+ * @param options.pattern - Optional regex to filter which URLs to search
325
+ * @returns Array of matches with url, line number, and line content
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * // Search all scripts and stylesheets for "color"
330
+ * const matches = await editor.grep({ regex: /color/ })
331
+ *
332
+ * // Search only CSS files
333
+ * const matches = await editor.grep({
334
+ * regex: /background-color/,
335
+ * pattern: /\.css/
336
+ * })
337
+ *
338
+ * // Regex search for console methods in JS
339
+ * const matches = await editor.grep({
340
+ * regex: /console\.(log|error|warn)/,
341
+ * pattern: /\.js/
342
+ * })
343
+ * ```
344
+ */
345
+ async grep({ regex, pattern }: { regex: RegExp; pattern?: RegExp }): Promise<SearchMatch[]> {
346
+ await this.enable()
347
+
348
+ const matches: SearchMatch[] = []
349
+ const urls = await this.list({ pattern })
350
+
351
+ for (const url of urls) {
352
+ let source: string
353
+ try {
354
+ const id = this.getIdByUrl(url)
355
+ source = await this.getSource(id)
356
+ } catch {
357
+ continue
358
+ }
359
+
360
+ const lines = source.split('\n')
361
+ for (let i = 0; i < lines.length; i++) {
362
+ if (regex.test(lines[i])) {
363
+ matches.push({
364
+ url,
365
+ lineNumber: i + 1,
366
+ lineContent: lines[i].trim().slice(0, 200),
367
+ })
368
+ regex.lastIndex = 0
369
+ }
370
+ }
371
+ }
372
+
373
+ return matches
374
+ }
375
+
376
+ /**
377
+ * Writes entire content to a script or stylesheet, replacing all existing code.
378
+ * Use with caution - prefer edit() for targeted changes.
379
+ *
380
+ * @param options - Options
381
+ * @param options.url - Script or stylesheet URL (inline scripts have `inline://{id}` URLs)
382
+ * @param options.content - New content
383
+ * @param options.dryRun - If true, validate without applying (default false, only works for JS)
384
+ */
385
+ async write({ url, content, dryRun = false }: { url: string; content: string; dryRun?: boolean }): Promise<EditResult> {
386
+ await this.enable()
387
+ const id = this.getIdByUrl(url)
388
+ return this.setSource(id, content, dryRun)
389
+ }
390
+ }