const express = require("express"); const puppeteer = require("puppeteer-core"); const app = express(); app.use(express.json()); // CORS — allow requests from any frontend app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); if (req.method === "OPTIONS") return res.sendStatus(200); next(); }); const CF_ACCOUNT_ID = process.env.CF_ACCOUNT_ID; const CF_API_TOKEN = process.env.CF_API_TOKEN; const CF_MODEL = "@cf/meta/llama-3.1-8b-instruct"; app.get("/health", (req, res) => { res.json({ status: "ok" }); }); app.get("/debug", async (req, res) => { const hasAccountId = !!process.env.CF_ACCOUNT_ID; const hasToken = !!process.env.CF_API_TOKEN; let networkTest = "not attempted"; try { const r = await fetch("https://api.cloudflare.com/client/v4/ips"); networkTest = `reached Cloudflare, status ${r.status}`; } catch (err) { networkTest = `failed: ${err.message}`; } res.json({ hasAccountId, hasToken, networkTest }); }); async function getPlanFromAI(instruction) { const url = `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/ai/run/${CF_MODEL}`; const response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${CF_API_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ messages: [ { role: "system", content: 'You convert a user\'s browsing instruction into a single JSON object: {"url": ""}. ' + "Only output valid JSON, nothing else. If no clear URL or site is mentioned, guess the most likely official site.", }, { role: "user", content: instruction }, ], }), }); const data = await response.json(); if (!data.success) { throw new Error("Cloudflare AI error: " + JSON.stringify(data.errors)); } const raw = data.result.response; try { return JSON.parse(raw); } catch { throw new Error("AI did not return valid JSON: " + raw); } } app.post("/run-task", async (req, res) => { const { instruction } = req.body; if (!instruction) return res.status(400).json({ error: "Missing 'instruction' in request body" }); let plan; try { plan = await getPlanFromAI(instruction); } catch (err) { return res.status(502).json({ success: false, stage: "ai", error: err.message }); } if (!plan.url) { return res.status(502).json({ success: false, error: "AI plan missing 'url'", plan }); } let browser; try { browser = await puppeteer.launch({ executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, args: ["--no-sandbox", "--disable-setuid-sandbox"], headless: true, }); const page = await browser.newPage(); await page.goto(plan.url, { waitUntil: "domcontentloaded", timeout: 30000 }); const title = await page.title(); res.json({ success: true, instruction, plan, title }); } catch (err) { res.status(500).json({ success: false, stage: "puppeteer", error: err.message }); } finally { if (browser) await browser.close(); } }); const PORT = process.env.PORT || 7860; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));