Skip to main content
Glama
context.ts6.24 kB
import type { CommandName, CommandPayload, CommandResult, DomSnapshot, DomSnapshotEntry, ToolResponse, } from "@yetidevworks/shared"; import { ExtensionBridge } from "./bridge.js"; export class ExtensionContext { private readonly snapshotHistory: SnapshotRecord[] = []; constructor(private readonly bridge: ExtensionBridge) {} async call<K extends CommandName>( command: K, payload: CommandPayload<K> | undefined = undefined, ): Promise<CommandResult<K>> { const finalPayload = (payload ?? ({} as CommandPayload<K>)); return (await this.bridge.send(command, finalPayload)) as CommandResult<K>; } async captureSnapshot(statusMessage = ""): Promise<ToolResponse> { const [{ url }, { title }, snapshotResult] = await Promise.all([ this.call("getUrl"), this.call("getTitle"), this.call("snapshot"), ]); const record: SnapshotRecord = { capturedAt: snapshotResult.raw.capturedAt, message: statusMessage, snapshot: snapshotResult.raw, formatted: snapshotResult.formatted, url, title, }; this.snapshotHistory.push(record); if (this.snapshotHistory.length > 20) { this.snapshotHistory.shift(); } const index = this.snapshotHistory.length; const statusLines = [statusMessage, `Snapshot #${index} captured at ${record.capturedAt}`] .filter(Boolean) .join("\n"); const prefix = statusLines ? `${statusLines}\n` : ""; const text = `${prefix}- Page URL: ${url}\n- Page Title: ${title}\n` + `- Page Snapshot\n\`\`\`yaml\n${snapshotResult.formatted}\n\`\`\`\n`; return { content: [ { type: "text", text, }, ], }; } async diffLatestSnapshots(): Promise<ToolResponse> { if (this.snapshotHistory.length < 2) { return { content: [ { type: "text", text: "At least two snapshots are required to compute a diff. Capture another snapshot first.", }, ], isError: true, }; } const current = this.snapshotHistory.at(-1)!; const previous = this.snapshotHistory.at(-2)!; const diff = diffSnapshots(previous.snapshot, current.snapshot); const summaryLines: string[] = []; summaryLines.push( `Diffing snapshot captured ${current.capturedAt} (Snapshot #${this.snapshotHistory.length}) against ${previous.capturedAt}`, ); summaryLines.push(`Current URL: ${current.url}`); if (current.url !== previous.url) { summaryLines.push(`Previous URL: ${previous.url}`); } summaryLines.push("Summary:"); summaryLines.push(`- Added elements: ${diff.added.length}`); summaryLines.push(`- Removed elements: ${diff.removed.length}`); summaryLines.push(`- Changed elements: ${diff.changed.length}`); const formatEntry = (entry: DomSnapshotEntry) => `selector: ${entry.selector}\n role: ${entry.role}\n name: ${entry.name}`; if (diff.added.length) { summaryLines.push("Added:"); for (const entry of diff.added.slice(0, 5)) { summaryLines.push(` - ${entry.selector} (${entry.role}) → "${entry.name}"`); } if (diff.added.length > 5) { summaryLines.push(` - … ${diff.added.length - 5} more`); } } if (diff.removed.length) { summaryLines.push("Removed:"); for (const entry of diff.removed.slice(0, 5)) { summaryLines.push(` - ${entry.selector} (${entry.role}) → "${entry.name}"`); } if (diff.removed.length > 5) { summaryLines.push(` - … ${diff.removed.length - 5} more`); } } if (diff.changed.length) { summaryLines.push("Changed:"); for (const change of diff.changed.slice(0, 5)) { summaryLines.push( ` - ${change.selector}\n before: role=${change.before.role}, name="${change.before.name}"\n after: role=${change.after.role}, name="${change.after.name}"`, ); } if (diff.changed.length > 5) { summaryLines.push(` - … ${diff.changed.length - 5} more`); } } if (!diff.added.length && !diff.removed.length && !diff.changed.length) { summaryLines.push("No element-level differences detected."); } return { content: [ { type: "text", text: summaryLines.join("\n"), }, ], }; } getConnectionInfo(): ConnectionInfo { return { wsPort: this.bridge.getPort(), connected: this.bridge.isConnected(), extension: this.bridge.getHelloInfo(), }; } } interface SnapshotRecord { capturedAt: string; message: string; snapshot: DomSnapshot; formatted: string; url: string; title: string; } interface SnapshotDiff { added: DomSnapshotEntry[]; removed: DomSnapshotEntry[]; changed: Array<{ selector: string; before: DomSnapshotEntry; after: DomSnapshotEntry }>; } interface ConnectionInfo { wsPort: number; connected: boolean; extension: { client: string; version?: string } | undefined; } function diffSnapshots(previous: DomSnapshot, current: DomSnapshot): SnapshotDiff { const prevMap = new Map<string, DomSnapshotEntry>(); const currentMap = new Map<string, DomSnapshotEntry>(); for (const entry of previous.entries) { if (!prevMap.has(entry.selector)) { prevMap.set(entry.selector, entry); } } for (const entry of current.entries) { if (!currentMap.has(entry.selector)) { currentMap.set(entry.selector, entry); } } const added: DomSnapshotEntry[] = []; const removed: DomSnapshotEntry[] = []; const changed: Array<{ selector: string; before: DomSnapshotEntry; after: DomSnapshotEntry }> = []; for (const [selector, entry] of currentMap.entries()) { const previousEntry = prevMap.get(selector); if (!previousEntry) { added.push(entry); continue; } if (previousEntry.role !== entry.role || previousEntry.name !== entry.name) { changed.push({ selector, before: previousEntry, after: entry }); } } for (const [selector, entry] of prevMap.entries()) { if (!currentMap.has(selector)) { removed.push(entry); } } return { added, removed, changed }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yetidevworks/yetibrowser-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server