/** * Forage P1 — Page Signal Capture * * Run via Claude's javascript_tool on any web page you want to add to Forage: * * javascript_tool({ text: , tabId: }) * * What it does: * 1. Reads the page title, URL, word count, and meta description. * 2. POSTs to /capture — registers the page as a Forage item (idempotent) * and fires a "view" signal for USER_ID. * 3. After DWELL_MS milliseconds, fires a "dwell" signal if still on page. * (The dwell timer is cancelled if the snippet runs again before it fires.) * * Configuration: * Change USER_ID to match the Forage user you are browsing as (1, 2, or 3 * for the seed users; any positive integer for a new user). * Set TOKEN to the value passed with --token when starting the server, * or leave empty ('') if the server was started without --token. */ (function forageCapture() { const SERVER = 'http://localhost:4242'; const USER_ID = 1; const TOKEN = ''; const DWELL_MS = 30_000; const url = location.href; const canonicalUrl = document.querySelector('link[rel="canonical"]')?.href || ''; const title = document.title || url; const words = document.body.innerText.trim().split(/\s+/).length; const readingTimeMin = Math.max(1, Math.round(words / 200)); const description = document.querySelector('meta[name="description"]')?.content || document.querySelector('meta[property="og:description"]')?.content || ''; const source = (() => { try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return ''; } })(); // Cancel any previous dwell timer from this snippet on the same page. if (window.__forageCaptureDwellTimer) { clearTimeout(window.__forageCaptureDwellTimer); window.__forageCaptureDwellTimer = null; } let itemId = null; const authHeaders = TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}; fetch(`${SERVER}/capture`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders }, body: JSON.stringify({ url, canonical_url: canonicalUrl, title, source, reading_time_min: readingTimeMin, description, user_id: USER_ID, }), }) .then((r) => r.json()) .then((d) => { itemId = d.item_id; console.log( `[Forage] Captured: "${title}" — id=${itemId}, ~${readingTimeMin}min read` ); }) .catch((e) => console.warn('[Forage] /capture failed:', e.message)); // Fire dwell signal after DWELL_MS if still on the page. window.__forageCaptureDwellTimer = setTimeout(() => { if (itemId == null) return; fetch(`${SERVER}/signal`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders }, body: JSON.stringify({ user_id: USER_ID, item_id: itemId, signal_type: 'dwell', duration_ms: DWELL_MS, }), }) .then(() => console.log(`[Forage] Dwell: "${title}" (${DWELL_MS / 1000}s)`) ) .catch(() => {}); window.__forageCaptureDwellTimer = null; }, DWELL_MS); return `[Forage] Capturing "${title}" for user ${USER_ID}`; })();