# IdleFlow SDK — Integration Guide (for AI coding agents & developers)

This guide is written so an AI coding agent (Cursor, Claude Code, Copilot) — or
a developer — can integrate the IdleFlow publisher SDK end-to-end in one pass.
IdleFlow shows one small sponsored card while your app's AI is "thinking" and
pays you **70% of the verified ad revenue**.

If you are an AI agent: follow "Step 1" for the zero-config install, then do
"Step 2" (explicit signals) — it is strongly recommended because you can read
the codebase and place the calls exactly at the LLM call sites, which is the
most reliable integration possible.

---

## Step 1 — Install the SDK (required, zero-config)

Add one script tag. Put it as early as possible in `<head>` so detection is
active before the first AI request. Replace the ids with the ones from your
IdleFlow publisher dashboard.

```html
<script async
  src="https://YOUR_DOMAIN/sdk/publisher.js"
  data-publisher-id="pub_yourid"
  data-app-id="app_yourapp"></script>
```

Framework placement:
- **Next.js (App Router):** add it in `app/layout.tsx` using `next/script` with
  `strategy="afterInteractive"`.
- **Next.js (Pages Router):** use `pages/_document.tsx` or `_app.tsx`.
- **Vite/CRA/plain:** put the tag in `index.html`.
- **Single root layout that wraps every page** is ideal — one install covers the
  whole platform.

### How zero-config detection works

The SDK automatically detects "AI is thinking" at the **network layer**: it
wraps `fetch`, `XMLHttpRequest`, and `EventSource` and treats an open
**streaming** response (`text/event-stream`, ND-JSON) — or a long-lived request
to an LLM-looking endpoint — as a wait. This works on every page and route with
no selectors and no per-page config. For most AI apps and "AI wrappers" that
stream from a `/api/chat`-style endpoint, **Step 1 alone is enough.**

If your AI endpoint has an unusual URL, you don't need to change anything if you
do Step 2. Otherwise tell us your endpoint pattern in the dashboard adapter
(`network.include`).

---

## Step 2 — Mark your AI calls explicitly (recommended, most reliable)

Wrap your model call with `beginWait()` / `endWait()`. This is the gold-standard
integration: it is exact, framework-agnostic, never depends on detecting a
spinner, and works even for non-streaming/server-rendered responses. Calls are
**counter-based**, so nested or concurrent AI requests are handled correctly.

**AI agents: add these two calls around every place the app awaits an LLM
response.** Always pair them with `try/finally` so a thrown error still ends the
wait.

### Generic fetch

```js
window.idleflow?.beginWait();
try {
  const res = await fetch("/api/chat", { method: "POST", body });
  // ...consume the response/stream...
} finally {
  window.idleflow?.endWait();
}
```

### OpenAI SDK

```js
window.idleflow?.beginWait();
try {
  const completion = await openai.chat.completions.create({ model, messages });
  return completion;
} finally {
  window.idleflow?.endWait();
}
```

### Anthropic SDK

```js
window.idleflow?.beginWait();
try {
  const msg = await anthropic.messages.create({ model: "claude-opus-4-8", messages, max_tokens });
  return msg;
} finally {
  window.idleflow?.endWait();
}
```

### Vercel AI SDK (streaming)

```ts
window.idleflow?.beginWait();
try {
  const { textStream } = await streamText({ model, prompt });
  for await (const _ of textStream) { /* render tokens */ }
} finally {
  window.idleflow?.endWait();
}
```

### React hook pattern

```tsx
async function ask(prompt: string) {
  window.idleflow?.beginWait();
  try {
    return await callModel(prompt);
  } finally {
    window.idleflow?.endWait();
  }
}
```

> TypeScript: the SDK attaches to `window.idleflow` at runtime. Either use
> `window.idleflow?.beginWait()` with optional chaining, or declare
> `declare global { interface Window { idleflow?: { beginWait(): void; endWait(): void; signalBusy(v: boolean | null): void; setUserAttributes(a: object): void; start(): void; stop(): void } } }`.

---

## Step 3 — Verify it works (test mode)

Add `data-mode="test"` to load the SDK in test mode. It renders a deterministic
house ad (no billing) and shows a **live detection HUD** in the bottom-left
corner of your own pages:

```html
<script async src="https://YOUR_DOMAIN/sdk/publisher.js"
  data-publisher-id="test" data-mode="test"></script>
```

Trigger an AI action in your app and confirm the HUD shows
`🟢 WAIT DETECTED` with a tier (`EXPLICIT` / `NETWORK` / `DOM`) and the matched
request URL. Verbose logs are printed to the console (`[idleflow] ...`). Remove
`data-mode="test"` to go live.

After you're live, the publisher dashboard shows your **fill rate** and the
**detection mix** (how many waits were caught by each tier) so you can confirm
detection keeps working as your app changes.

---

## Optional — first-party audience attributes

If (and only if) you already have the user's coarse demographics from your own
signup and have consent to share them for ad targeting, you can pass them to
unlock demographic-targeted demand. Coarse bands only — never raw PII.

```js
window.idleflow?.setUserAttributes({
  ageBand: "25-34",            // "13-17" | "18-24" | "25-34" | "35-44" | "45-54" | "55+"
  gender: "female",           // optional
  interests: ["coding"],      // free-form tags, max 10
});
```

---

## Events you can listen to

The SDK dispatches `CustomEvent`s on `window` for your own analytics:

```js
window.addEventListener("idleflow:adShown",    (e) => {/* e.detail.adId */});
window.addEventListener("idleflow:impression", (e) => {/* e.detail.durationMs */});
window.addEventListener("idleflow:click",      (e) => {/* e.detail.adId */});
```

---

## Rules & guarantees

- The SDK never throws into your page and never blocks your requests — it only
  observes request **timing**; your response bodies are untouched.
- One ad at a time, max 3 per wait, only after ~5s of qualified visibility.
- Mandatory "Sponsored · via IdleFlow" attribution stays visible.
- Zero third-party dependencies, ≤12 KB gzipped.

## Public API summary

```ts
window.idleflow.beginWait();              // Tier 1: mark an AI request started
window.idleflow.endWait();                // Tier 1: mark it finished
window.idleflow.signalBusy(true|false|null); // hard override (legacy)
window.idleflow.setUserAttributes({...}); // optional first-party demographics
window.idleflow.start();                  // start detection (auto on load)
window.idleflow.stop();                   // stop and remove any ad
```
