( command: string, args: string[], session: TabSession, bm?: BrowserManager, )
| 209 | } |
| 210 | |
| 211 | export async function handleReadCommand( |
| 212 | command: string, |
| 213 | args: string[], |
| 214 | session: TabSession, |
| 215 | bm?: BrowserManager, |
| 216 | ): Promise<string> { |
| 217 | const page = session.getPage(); |
| 218 | // Frame-aware target for content extraction |
| 219 | const target = session.getActiveFrameOrPage(); |
| 220 | |
| 221 | switch (command) { |
| 222 | case 'text': { |
| 223 | return getCleanText(target); |
| 224 | } |
| 225 | |
| 226 | case 'html': { |
| 227 | const selector = args[0]; |
| 228 | if (selector) { |
| 229 | const resolved = await session.resolveRef(selector); |
| 230 | if ('locator' in resolved) { |
| 231 | return stripLoneSurrogates(await resolved.locator.innerHTML({ timeout: 5000 })); |
| 232 | } |
| 233 | return stripLoneSurrogates(await target.locator(resolved.selector).innerHTML({ timeout: 5000 })); |
| 234 | } |
| 235 | // page.content() is page-only; use evaluate for frame compat |
| 236 | const doctype = await target.evaluate(() => { |
| 237 | const dt = document.doctype; |
| 238 | return dt ? `<!DOCTYPE ${dt.name}>` : ''; |
| 239 | }); |
| 240 | const html = await target.evaluate(() => document.documentElement.outerHTML); |
| 241 | return stripLoneSurrogates(doctype ? `${doctype}\n${html}` : html); |
| 242 | } |
| 243 | |
| 244 | case 'links': { |
| 245 | const links = await target.evaluate(() => |
| 246 | [...document.querySelectorAll('a[href]')].map(a => ({ |
| 247 | text: a.textContent?.trim().slice(0, 120) || '', |
| 248 | href: (a as HTMLAnchorElement).href, |
| 249 | })).filter(l => l.text && l.href) |
| 250 | ); |
| 251 | return links.map(l => `${l.text} → ${l.href}`).join('\n'); |
| 252 | } |
| 253 | |
| 254 | case 'forms': { |
| 255 | const forms = await target.evaluate(() => { |
| 256 | return [...document.querySelectorAll('form')].map((form, i) => { |
| 257 | const fields = [...form.querySelectorAll('input, select, textarea')].map(el => { |
| 258 | const input = el as HTMLInputElement; |
| 259 | return { |
| 260 | tag: el.tagName.toLowerCase(), |
| 261 | type: input.type || undefined, |
| 262 | name: input.name || undefined, |
| 263 | id: input.id || undefined, |
| 264 | placeholder: input.placeholder || undefined, |
| 265 | required: input.required || undefined, |
| 266 | value: input.type === 'password' |
| 267 | || (input.name && /(^|[_.-])(token|secret|key|password|credential|auth|jwt|session|csrf|sid)($|[_.-])|api.?key/i.test(input.name)) |
| 268 | || (input.id && /(^|[_.-])(token|secret|key|password|credential|auth|jwt|session|csrf|sid)($|[_.-])|api.?key/i.test(input.id)) |
no test coverage detected