Spaces:
Sleeping
Sleeping
| /* 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' | |
| ) | |
| })() | |