> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trybluesky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Install the Bluesky Cloudflare Worker

> Deploy the Bluesky Agent Edge Network as a Cloudflare Worker — from the dashboard (no CLI) or with wrangler.

If your site runs on Cloudflare, you install the Agent Edge Network as a **Worker**
that sits in front of your origin. It passes every request straight through to your
site, except verified AI answer-engine crawlers, which it serves your pre-generated
AEO artifact.

<Note>
  This is the drop-in v0 Worker — self-contained, zero dependencies, verified by
  published IP ranges. It is the Cloudflare counterpart of the
  [Next.js middleware](/guides/nextjs-middleware). **You do not need wrangler** — you
  can deploy entirely from the Cloudflare dashboard (see the tabs below).
</Note>

## What the Worker does

On every request to your site, the Worker:

* **Passes through** anything that isn't a known AI crawler — your site behaves exactly as before.
* **Never touches search bots** — Googlebot, Bingbot and Applebot always get your real page (the [cloaking firewall](/guides/cloaking-firewall)).
* **Verifies** answer-engine crawlers by their published IP ranges, then — in `default` mode — serves them your [optimized artifact](/guides/optimized-pages) instead of raw HTML.
* **Reports every hit** to [Agent Analytics](/guides/agent-analytics), so you see who crawled what and whether it was served.

There is no LLM call and no round-trip to your origin for served pages — artifacts are generated offline and cached.

## Before you start

* Your domain is **on Cloudflare** (DNS proxied — the orange cloud).
* An **edge token** from **Agent Analytics → Settings** in the Bluesky app (format `aen_...`).
* The [`worker.js`](#the-worker-code) file below.

## The Worker code

This is the single file you deploy. It ships in `shadow` mode (log-only, serves your
normal page) — change `SERVE_MODE` to `"default"` when you're ready to serve.

```js worker.js theme={null} theme={null}
// worker.js — Bluesky Agent Edge Network (drop-in Cloudflare Worker, zero deps).
// Runs in front of your site on Cloudflare. It passes EVERYTHING through to your
// origin, except verified AI answer-engine crawlers, which get your optimized AEO
// artifact. Classic search bots (Googlebot/Bingbot/Applebot) always pass through.

const AEN_BASE = "https://app.trybluesky.com";
const SERVE_MODE = "shadow"; // "shadow" | "default"  ← flip to "default" to actually serve
const ENABLED = new Set(["USER_TRIGGERED", "RETRIEVAL"]);

let botsetCache = null; // per-isolate best-effort cache

async function getBots(token) {
  if (botsetCache && Date.now() - botsetCache.at < 3_600_000) return botsetCache.bots;
  try {
    const r = await fetch(`${AEN_BASE}/api/v1/edge/botset`, { headers: { authorization: `Bearer ${token}` } });
    const j = await r.json();
    const bots = j?.botset?.bots ?? [];
    botsetCache = { at: Date.now(), bots };
    return bots;
  } catch {
    return botsetCache?.bots ?? [];
  }
}

function matchBot(ua, bots) {
  const low = ua.toLowerCase();
  let best = null;
  for (const b of bots) {
    const t = b.uaToken.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    if (new RegExp(`(?:^|[^a-z0-9])${t}(?:[^a-z0-9]|$)`, "i").test(low)) {
      if (!best || b.uaToken.length > best.uaToken.length) best = b;
    }
  }
  return best;
}

function ipInCidrs(ip, cidrs = []) {
  for (const c of cidrs) {
    const [net, bitsRaw] = c.split("/");
    const bits = Number(bitsRaw);
    if (ip.includes(":") || net.includes(":")) continue; // IPv4 only in this drop-in
    const a = ipToInt(ip), b = ipToInt(net);
    if (a === null || b === null) continue;
    const mask = bits === 0 ? 0 : (~0 << (32 - bits)) >>> 0;
    if ((a & mask) === (b & mask)) return true;
  }
  return false;
}
function ipToInt(ip) {
  const p = ip.split(".");
  if (p.length !== 4) return null;
  let n = 0;
  for (const o of p) { const v = Number(o); if (!Number.isInteger(v) || v < 0 || v > 255) return null; n = (n << 8) | v; }
  return n >>> 0;
}
async function sha256hex(s) {
  const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s));
  return [...new Uint8Array(d)].map((x) => x.toString(16).padStart(2, "0")).join("");
}

export default {
  async fetch(request, env, ctx) {
    const token = env.AEN_EDGE_TOKEN;
    if (!token) return fetch(request); // not configured → transparent passthrough

    const ua = request.headers.get("user-agent") || "";
    const ip = request.headers.get("cf-connecting-ip") || "";
    const bots = await getBots(token);
    const bot = matchBot(ua, bots);
    if (!bot) return fetch(request);

    // CLOAKING FIREWALL: classic search bots → always the real origin page.
    if (bot.isSearchIndex) return fetch(request);

    const verified = ipInCidrs(ip, bot.cidrs);
    const eligible = verified && ENABLED.has(bot.purpose);
    const url = new URL(request.url);

    let served = "PASSTHROUGH";
    let response = null;

    if (eligible && SERVE_MODE === "default") {
      try {
        const uh = await sha256hex(url.href);
        const ar = await fetch(`${AEN_BASE}/api/v1/edge/artifact?u=${uh}`, { headers: { authorization: `Bearer ${token}` } });
        if (ar.ok) {
          const { artifact } = await ar.json();
          if (artifact?.markdown) {
            served = "VARIANT";
            response = new Response(artifact.markdown, {
              status: 200,
              headers: { "content-type": "text/markdown; charset=utf-8", "x-aen-served": "variant", "cache-control": "no-store" },
            });
          }
        }
      } catch { /* fall through to passthrough */ }
    }
    if (!response) response = await fetch(request); // passthrough to origin

    // fire-and-forget telemetry (never blocks the response)
    const hit = {
      botName: bot.uaToken, path: url.pathname, statusCode: response.status, userAgent: ua, botIp: ip,
      trustLevel: verified ? "IP_VERIFIED" : "UA_ONLY", purposeClass: bot.purpose,
      verificationMethod: verified ? "cidr" : null,
      served: eligible ? (SERVE_MODE === "default" ? served : "PASSTHROUGH") : "PASSTHROUGH",
    };
    ctx.waitUntil(
      fetch(`${AEN_BASE}/api/v1/edge/telemetry`, {
        method: "POST",
        headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
        body: JSON.stringify({ hits: [hit] }),
      }).catch(() => {}),
    );

    return response;
  },
};
```

## Deploy it

Pick whichever you prefer — both do exactly the same thing.

<Tabs>
  <Tab title="Cloudflare dashboard (no CLI)">
    The fastest path if you'd rather not touch a terminal.

    <Steps>
      <Step title="Create the Worker">
        In the Cloudflare dashboard, go to **Workers & Pages → Create → Create Worker**, name it
        (e.g. `bluesky-aen`), and click **Deploy**. Then click **Edit code**, replace the starter
        code with the `worker.js` above, and **Deploy** again.
      </Step>

      <Step title="Add your edge token as a secret">
        Open the Worker → **Settings → Variables and Secrets → Add**. Choose **Secret** (encrypted),
        name it `AEN_EDGE_TOKEN`, paste the key from **Agent Analytics → Settings**, and **Deploy**.
      </Step>

      <Step title="Put it in front of your site">
        **Settings → Triggers → Routes → Add route** → enter `yourdomain.com/*` and pick your zone.
        The Worker now runs on every request to your site.
      </Step>
    </Steps>
  </Tab>

  <Tab title="wrangler (CLI)">
    Faster if you're comfortable in a terminal.

    <Steps>
      <Step title="Set up the project">
        Put `worker.js` (above) and the `wrangler.toml` below in a folder. Install the CLI with
        `npm i -g wrangler`, run `wrangler login`, and set your domain in `wrangler.toml`.

        ```toml wrangler.toml theme={null}
        name = "bluesky-aen"
        main = "worker.js"
        compatibility_date = "2024-11-01"

        routes = [
          { pattern = "example.com/*", zone_name = "example.com" }
        ]
        ```
      </Step>

      <Step title="Add your edge token">
        ```bash theme={null}
        wrangler secret put AEN_EDGE_TOKEN
        # paste the key from Agent Analytics → Settings when prompted
        ```
      </Step>

      <Step title="Deploy">
        ```bash theme={null}
        wrangler deploy
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Test it

The Worker ships in `shadow` mode — it serves your normal page to everyone and only
**logs** what it would have served, so there is zero risk while you validate.

<Steps>
  <Step title="Trigger a real AI crawler">
    Open **ChatGPT** (or Perplexity) with browsing and ask a question that makes it look up your
    site. Its crawler fetches your page from a published OpenAI IP.
  </Step>

  <Step title="Watch the hit land">
    The Worker detects the crawler as `IP_VERIFIED` and the visit appears in
    **Agent Analytics**, broken down by agent and verification level.
  </Step>

  <Step title="Go live">
    Once you see verified hits, change `SERVE_MODE` to `"default"` and redeploy (paste + Deploy, or
    `wrangler deploy`). Verified answer-engine agents now receive the optimized artifact. See
    [Serve modes](/guides/serve-modes).
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="No hits appear in Agent Analytics">
    Confirm the route covers your site (`yourdomain.com/*`), the `AEN_EDGE_TOKEN` secret is set, and
    you actually triggered a browsing AI. Only verified crawlers are counted.
  </Accordion>

  <Accordion title="My site looks unchanged">
    That's expected in `shadow` mode, and for every human visitor in any mode — the Worker only
    changes the response for verified AI crawlers. To confirm it's running, check the Worker's logs
    in the Cloudflare dashboard while a crawler visits.
  </Accordion>

  <Accordion title="The variant is never served (default mode)">
    The request must be `IP_VERIFIED`, the crawler's purpose must be enabled, and Bluesky must have
    generated an artifact for that URL. Check the page's status in Agent Analytics.
  </Accordion>

  <Accordion title="Will this slow my site down?">
    No. Human and search-bot traffic passes straight through, and served artifacts are pre-generated
    and cached — there is no LLM call on the request path.
  </Accordion>
</AccordionGroup>

## How it differs from the Next.js middleware

* The Worker runs **in front of your origin**. Passthrough is `fetch(request)` — Cloudflare routes
  that subrequest to your origin (it does not re-run the Worker), so there is no loop.
* The client IP comes from Cloudflare's `cf-connecting-ip` header.
* Telemetry is sent with `ctx.waitUntil()` so it never delays the response.

Everything else — the [cloaking firewall](/guides/cloaking-firewall),
[verification levels](/guides/detection-verification), [serve modes](/guides/serve-modes),
and offline artifact generation — behaves exactly as it does for the middleware.
