#!/usr/bin/env node /** * Fast browser automation via Puppeteer * * Usage: node browser.js [args] * * Commands: * tabs - List open tabs * goto - Navigate to URL * screenshot [url] [output] - Screenshot (current page or URL) * text [url] - Get page text * html [selector] - Get HTML * click - Click element * type - Type into field * eval - Run JavaScript * close [index] - Close tab * amazon - Amazon: cart, search , buy-again * x - X/Twitter: feed, search , user * tv - TradingView quote */ const puppeteer = require('puppeteer-core'); const fs = require('fs'); const BROWSER_URL = 'http://127.0.0.1:9222'; async function connect() { return puppeteer.connect({ browserURL: BROWSER_URL, defaultViewport: null }); } async function getPage(browser, urlFilter) { const pages = await browser.pages(); if (urlFilter) { return pages.find(p => p.url().includes(urlFilter)) || pages[0]; } return pages[0]; } const commands = { async tabs(browser) { const pages = await browser.pages(); pages.forEach((p, i) => console.log(`[${i}] ${p.url()}`)); }, async goto(browser, [url]) { if (!url) throw new Error('Need URL'); const page = (await browser.pages())[0] || await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }); console.log(`→ ${page.url()}`); }, async screenshot(browser, [url, output = '/tmp/screenshot.png']) { const page = url && url !== '-' ? await (async () => { const p = await browser.newPage(); await p.goto(url, {waitUntil: 'networkidle2'}); return p; })() : (await browser.pages())[0]; await page.screenshot({ path: output }); console.log(`📸 ${output}`); }, async text(browser, [url]) { let page = (await browser.pages())[0]; if (url) { page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }); } const text = await page.evaluate(() => document.body.innerText); console.log(text.substring(0, 10000)); }, async html(browser, [selector]) { const page = (await browser.pages())[0]; const html = await page.evaluate(s => { const el = s ? document.querySelector(s) : document.body; return el ? el.innerHTML : null; }, selector); console.log(html?.substring(0, 10000) || 'Not found'); }, async click(browser, [selector]) { const page = (await browser.pages())[0]; await page.click(selector); console.log(`✓ Clicked ${selector}`); }, async type(browser, [selector, ...text]) { const page = (await browser.pages())[0]; await page.type(selector, text.join(' ')); console.log(`✓ Typed into ${selector}`); }, async eval(browser, args) { const page = (await browser.pages())[0]; const result = await page.evaluate(args.join(' ')); console.log(JSON.stringify(result, null, 2)); }, async close(browser, [index = 0]) { const pages = await browser.pages(); if (pages[+index]) { await pages[+index].close(); console.log(`Closed tab ${index}`); } }, // Amazon shortcuts async amazon(browser, [subcmd, ...args]) { let page = await getPage(browser, 'amazon.com'); if (!page?.url().includes('amazon')) { page = await browser.newPage(); } switch (subcmd) { case 'cart': await page.goto('https://www.amazon.com/gp/cart/view.html', { waitUntil: 'networkidle2' }); const cartItems = await page.evaluate(() => { const items = document.querySelectorAll('.sc-list-item-content'); return Array.from(items).map(item => { const title = item.querySelector('.sc-product-title')?.textContent?.trim(); const price = item.querySelector('.sc-product-price')?.textContent?.trim(); return { title, price }; }).filter(i => i.title); }); console.log('🛒 Cart:'); cartItems.forEach(i => console.log(` - ${i.title} (${i.price})`)); if (!cartItems.length) console.log(' (empty)'); break; case 'search': const q = args.join(' '); await page.goto(`https://www.amazon.com/s?k=${encodeURIComponent(q)}`, { waitUntil: 'networkidle2' }); console.log(`🔍 Amazon search: ${q}`); break; case 'buy-again': await page.goto('https://www.amazon.com/gp/buyagain', { waitUntil: 'networkidle2' }); console.log('📦 Buy Again page loaded'); break; default: console.log('Amazon subcmds: cart, search , buy-again'); } }, // X/Twitter shortcuts async x(browser, [subcmd, ...args]) { let page = await getPage(browser, 'x.com'); if (!page?.url().includes('x.com')) { page = await browser.newPage(); } switch (subcmd) { case 'feed': await page.goto('https://x.com/home', { waitUntil: 'networkidle2' }); await page.screenshot({ path: '/tmp/x-feed.png' }); console.log('📸 /tmp/x-feed.png'); break; case 'search': const q = args.join(' '); await page.goto(`https://x.com/search?q=${encodeURIComponent(q)}&f=live`, { waitUntil: 'networkidle2' }); console.log(`🔍 X search: ${q}`); await page.screenshot({ path: '/tmp/x-search.png' }); console.log('📸 /tmp/x-search.png'); break; case 'user': const handle = args[0]?.replace('@', ''); await page.goto(`https://x.com/${handle}`, { waitUntil: 'networkidle2' }); console.log(`👤 @${handle}`); await page.screenshot({ path: `/tmp/x-${handle}.png` }); console.log(`📸 /tmp/x-${handle}.png`); break; default: console.log('X subcmds: feed, search , user '); } }, // TradingView quote async tv(browser, [symbol]) { if (!symbol) throw new Error('Need symbol'); let page = await getPage(browser, 'tradingview'); if (!page?.url().includes('tradingview')) { page = await browser.newPage(); } await page.goto(`https://www.tradingview.com/symbols/${symbol.toUpperCase()}/`, { waitUntil: 'networkidle2' }); const data = await page.evaluate(() => { const price = document.querySelector('[data-symbol-last-value]')?.textContent; const change = document.querySelector('[data-symbol-change]')?.textContent; return { price, change }; }); console.log(`📈 ${symbol.toUpperCase()}: ${data.price || 'N/A'} (${data.change || 'N/A'})`); } }; async function main() { const [,, cmd, ...args] = process.argv; if (!cmd || cmd === 'help') { console.log(`Browser automation commands: tabs - List open tabs goto - Navigate screenshot [url] [out] - Screenshot text [url] - Get page text click - Click type - Type eval - Run JS amazon cart|search|buy-again x feed|search|user tv - Stock quote`); return; } if (!commands[cmd]) { console.error(`Unknown: ${cmd}`); process.exit(1); } const browser = await connect(); try { await commands[cmd](browser, args); } finally { browser.disconnect(); } } main().catch(e => { console.error('Error:', e.message); process.exit(1); });