/* beacon inject — runs in any browser page Auto-injected via CDP on every page, or load manually: bookmarklet: javascript:(function(){var s=document.createElement('script');s.src='__BEACON_BASE__/inject.js';document.head.appendChild(s)})() console: await fetch('__BEACON_BASE__/inject.js').then(r=>r.text()).then(eval) */ ;(function () { if (window.__beacon) return // already injected const BASE = '__BEACON_BASE__' const _log = (msg, style = 'color:#7df;font-family:monospace;font-size:13px') => console.log('%c' + msg, style) const _err = (msg) => console.error('%c[beacon] ' + msg, 'color:#f77;font-family:monospace') const _post = (path, body) => fetch(BASE + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }).then(r => r.json()) const _get = (path) => fetch(BASE + path).then(r => r.text()) // ── Core beacon interface ───────────────────────────────────────────────── window.__beacon = { base: BASE, agent: async (url) => { try { const text = await _get(`/raw?url=${encodeURIComponent(url)}`) _log(text) return text } catch (e) { _err(e.message) } }, here: async () => window.__beacon.agent(location.href), ask: async (question) => { try { _log(`[beacon] thinking...`, 'color:#aaa;font-style:italic') const { reply, error } = await _post('/chat', { message: question, context: location.href, }) if (error) { _err(error); return } _log(reply, 'color:#afc;font-family:monospace;font-size:13px;line-height:1.5') return reply } catch (e) { _err(e.message) } }, feed: async (url) => { try { const xml = await _get(`/feed?url=${encodeURIComponent(url || location.href)}`) _log(xml) return xml } catch (e) { _err(e.message) } }, json: async (url) => { try { const r = await fetch(`${BASE}/json?url=${encodeURIComponent(url || location.href)}`) const data = await r.json() console.table(data) return data } catch (e) { _err(e.message) } }, save: async (name) => { const n = name || location.hostname.replace(/\./g, '_') const { saved } = await _post(`/session/${n}`, {}) _log(`[beacon] session saved → ${saved}`) }, } // Flat console shortcuts ;['agent','here','ask','feed','json','save'].forEach(k => { window[k] = window.__beacon[k] }) // ── Chrome DevTools third-party tool registration ───────────────────────── // Exposes beacon as callable tools to Chrome DevTools MCP agents // (mcp__chrome-devtools__execute_developer_tool / list_developer_tools) const BEACON_TOOLS = [ { name: 'beacon_fetch', description: 'Fetch clean readable text from any URL through the owner\'s authenticated browser session.', schema: { type: 'object', properties: { url: { type: 'string', description: 'URL to fetch' } }, required: ['url'], }, execute: async ({ url }) => window.__beacon.agent(url), }, { name: 'beacon_ask', description: 'Ask the beacon LLM a question with the current page as context.', schema: { type: 'object', properties: { question: { type: 'string', description: 'What to ask about this page' } }, required: ['question'], }, execute: async ({ question }) => window.__beacon.ask(question), }, { name: 'beacon_feed', description: 'Generate an RSS feed from any URL or the current page.', schema: { type: 'object', properties: { url: { type: 'string', description: 'URL (omit for current page)' } }, }, execute: async ({ url } = {}) => window.__beacon.feed(url), }, { name: 'beacon_json', description: 'Return structured article JSON (title, author, content) from any URL.', schema: { type: 'object', properties: { url: { type: 'string', description: 'URL (omit for current page)' } }, }, execute: async ({ url } = {}) => window.__beacon.json(url), }, { name: 'beacon_save_session', description: 'Save the current authenticated session for this domain so the beacon can replay it.', schema: { type: 'object', properties: { name: { type: 'string' } } }, execute: async ({ name } = {}) => window.__beacon.save(name), }, ] // Chrome DevTools MCP third-party tool protocol // The DevTools MCP agent calls window.__chrome_devtools_tools__() to discover tools window.__chrome_devtools_tools__ = () => BEACON_TOOLS.map(t => ({ name: t.name, description: t.description, inputSchema: t.schema, })) // Execution handler — DevTools MCP calls this to invoke a tool window.__chrome_devtools_execute_tool__ = async (name, args) => { const tool = BEACON_TOOLS.find(t => t.name === name) if (!tool) return { error: `unknown tool: ${name}` } try { const result = await tool.execute(args || {}) return { result } } catch (e) { return { error: e.message } } } // ── WebMCP registration (experimental — Chrome 149+ with flags) ─────────── // Flags: chrome://flags/#devtools-webmcp-support + #enable-webmcp-testing // When stable, exposes beacon tools to Chrome's built-in AI panel (Gemini etc.) // and any WebMCP-compatible agent — LLM-agnostic, your session, any backend. function registerWebMCP() { if (!window.navigator?.registerMCPTool) return // flag not enabled for (const tool of BEACON_TOOLS) { navigator.registerMCPTool({ name: tool.name, description: tool.description, inputSchema: tool.schema, execute: tool.execute, }) } _log('[beacon] WebMCP tools registered — visible to Chrome AI panel', 'color:#fa0;font-size:11px') } // Retry registration once DOM is ready (API may initialise late) if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', registerWebMCP) } else { registerWebMCP() } // ── Banner ──────────────────────────────────────────────────────────────── console.log( '%c 📡 beacon %c active\n' + '%c ask("summarize this") here() feed() json() save()\n' + '%c DevTools MCP: __chrome_devtools_tools__() WebMCP: auto', 'background:#1a1a2e;color:#7df;font-weight:bold;padding:3px 8px;border-radius:3px 0 0 3px', 'background:#0f3460;color:#eee;padding:3px 8px;border-radius:0 3px 3px 0', 'color:#888;font-family:monospace;font-size:11px', 'color:#555;font-family:monospace;font-size:10px' ) })()