Beacon / src /inject.browser.js
Ig0tU
beacon v1 — authenticated web proxy + AGENTS.md
770ac3d
Raw
History Blame Contribute Delete
7.05 kB
/* 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'
)
})()