=== Ooky AI Visibility ===
Contributors: cloudweld
Tags: ai, llms, llms-txt, seo, analytics
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 0.3.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Serve AI crawlers a clean, distilled version of your pages plus schema.org JSON-LD and /llms.txt, and track which AI bots visit your site.

== Description ==

Ooky AI Visibility makes your WordPress site discoverable and accurate to AI assistants: ChatGPT, Claude, Perplexity, Google's AI Overviews, and other LLM-driven crawlers.

The plugin does four things:

1. **Serves distilled "cleaned HTML" to AI bots.** When a known AI crawler hits a content page and an eligible artifact has been published, the plugin returns a noise-free, parity-gated version of that page (no nav/footer/scripts) in place of your theme's markup, so models read your facts instead of your chrome. Humans always get your normal page.

2. **Injects schema.org JSON-LD into your pages.** Your latest published structured data is added to `<head>` so AI and search crawlers can index your brand facts.

3. **Serves brand intelligence at well-known AI paths.** When an AI bot fetches `/llms.txt`, `/llms-full.txt`, `/agents.md`, `/.well-known/ai-manifest.json`, or `/.well-known/mcp`, the plugin returns a structured summary of your business, revalidated against the current Ooky hostname owner on every request.

4. **Tracks AI-bot visits.** On every front-end request—including the discovery paths—the plugin checks the User-Agent against a maintained registry. Detected visits enter a persistent, retrying local queue with stable IDs before delivery to Ooky.

Cleaned-HTML serving and JSON-LD injection read the same published artifacts as Ooky's managed edge through an authenticated per-page API. They are revalidated rather than stale-served; if nothing is published or a fetch fails, your page renders normally. Removing the local API key disables both behaviors. A site with a strict `Content-Security-Policy` (no `unsafe-inline` for `script-src`) may need to allow the injected JSON-LD block.

The same integration is also available as a Node/Next.js SDK (`@ooky/sdk`) and a Cloudflare Worker, all sharing an identical API contract.

You need a free Ooky account and the reveal-once domain API key. Without that key, manifest serving, cleaned HTML, JSON-LD, and analytics stay inactive; this prevents a former domain owner from fetching the successor's intelligence after a transfer.

= What you get =

* `/llms.txt` answered by your own domain, not a third-party
* AI-bot visit log per model, per page, per day in the Ooky dashboard
* No file writes, so it works on read-only managed hosts (WP Engine, Kinsta, Pantheon)
* Request-local event queueing; WP-Cron delivers and retries outside the visitor response
* Compatible with caching plugins (W3 Total Cache, WP Rocket, LiteSpeed) for your normal content
* Honors WordPress permalinks; no Apache mod_rewrite or nginx config needed

= Who it's for =

* Brands worried that ChatGPT or Claude is making things up about their company
* SaaS, ecommerce, and content sites that want to be cited by AI assistants
* Agencies managing several client sites who want a single dashboard for AI visibility
* Anyone curious which AI crawlers are actually visiting their site

== Installation ==

1. In WordPress admin, go to **Plugins → Add New** and search for **Ooky AI Visibility**.
2. Click **Install Now**, then **Activate**. For a manual install, upload `ooky-ai-visibility.zip` via **Plugins → Add New → Upload Plugin**.
3. Go to **Settings → Ooky** and paste your API key from your Ooky dashboard.
4. (Optional) Override the registered domain if it differs from this site's host.
5. Visit `/llms.txt` on your site to confirm the manifest is being served.

If `/llms.txt` returns 404 after activation, go to **Settings → Permalinks** and click **Save** to flush rewrite rules. The plugin attempts this automatically on activate, but managed hosts occasionally cache the rules table and need a manual flush.

== Frequently Asked Questions ==

= Do I need a paid Ooky account? =

No. The free plan covers a single domain. Paid plans add more brands, more LLM coverage, and longer retention. See [https://ooky.ai/pricing](https://ooky.ai/pricing).

= What data does the plugin send to ooky.ai? =

Two event types are sent. Bot events contain the bot name, User-Agent, request path/method, domain, and optional two-letter country. AI-referral events for human visits contain the classified AI source, the AI referrer's origin only (for example `https://chatgpt.com`, never its path/query/fragment), the destination path/method, and optional country. The plugin sends no IP addresses, cookies, session IDs, page bodies, or form input. See Privacy below.

= Is serving cleaned HTML to AI bots cloaking? =

No. Cloaking means showing search engines different content than people in order to manipulate rankings, and the cleaned HTML is the opposite of that on every count:

* It is derived from the same page, not independent copy. Ooky requires at least 95% visible-token containment plus a similarity sanity floor. A failed page is excluded unless an authorized user explicitly accepts that exact artifact; the decision is recorded.
* It is never routed to search or social crawlers. Only positively categorized AI bots receive the distilled representation; Googlebot, Bingbot, link-preview bots, uncategorized bots, and humans receive your origin page.
* Humans always get your real page. Only known AI crawlers (GPTBot, ClaudeBot, and similar) receive the cleaned version; every human visitor gets your normal theme output, unchanged.
* It is installation-scoped and reversible. The plugin serves only artifacts you explicitly publish, and removing its local API key disables cleaned-HTML serving and JSON-LD injection.

= /llms.txt returns 404: what now? =

Most often, WordPress hasn't flushed rewrite rules. Go to **Settings → Permalinks** and click **Save** (don't change anything). If the route still 404s, check that your permalink structure is set to anything other than "Plain"; rewrite rules only work in pretty-permalink mode.

= Does it work with caching plugins? =

Yes, with one caveat: make sure your cache plugin doesn't shadow `/llms.txt`, `/agents.md`, or the `/.well-known/*` paths. Most cache plugins skip these automatically because they aren't visited by humans. If you see stale content at `/llms.txt`, purge your cache or add those paths to the exclude list.

= I updated my brand intelligence in Ooky, but /llms.txt still shows the old version =

The plugin asks Ooky for the current published artifact on every request and sends a private, no-store response. If an upstream hosting or CDN cache still shows an older response, purge that cache and exclude the well-known Ooky paths; the plugin itself does not retain a manifest body.

= How do I rotate the API key? =

Open your domain in the Ooky dashboard, click **Integrations → WordPress → Rotate key**, copy the new key, paste it into **Settings → Ooky** here, and save. The old key is invalidated immediately.

= Is the plugin compatible with WordPress Multisite? =

It is built to work per-site: each subsite stores its own API key and registered domain, and uninstall clears both per-site and network-level options. Network activation across a large number of subsites has not yet been exhaustively tested, so if you run a big network, validate on a staging subsite first.

= Where can I see the plugin's bot registry? =

The plugin ships with a built-in list of ~30 known AI crawlers and refreshes it hourly from `https://api.ooky.ai/api/public/bots`. The refresh is non-blocking and falls back to the bundled list if the API is unreachable.

== Screenshots ==

1. Distilled cleaned HTML: when an AI bot hits a page, Ooky serves a parity-checked, chrome-free version (semantic HTML + schema.org JSON-LD) in place of your theme's markup. Humans always get your normal page.
2. The Settings → Ooky page where you paste the API key.
3. The AI Sessions dashboard in your Ooky workspace, showing which crawlers visited.
4. A live `/llms.txt` response served from a WordPress site.

== Changelog ==

= 0.3.3 =
* Adds a no-store, nonce-bound health proof at `/__ooky/health` so a live WordPress connection can establish its hostname claim without a separate setup step.

= 0.3.2 =
* Security: manifests, cleaned HTML, and JSON-LD are revalidated on every request and are never stale-served from WordPress transients. This prevents content from a hostname's former Ooky owner being replayed after an explicit ownership transfer.
* Security: served manifests use private, no-store cache headers; legacy manifest transients are cleared once on upgrade, while legacy dynamic page transients are ignored and expire naturally.
* Security: manifest and page fetches carry the domain Bearer key plus a fresh nonce. Successful responses must echo that nonce with the complete Ooky hostname claim, generation, and exact edge namespace; revoked/former-owner keys and replayed intermediary 200s fail closed.

= 0.3.1 =
* Reliability: analytics events now use an atomic per-site database outbox. Concurrent visitor requests cannot overwrite one another, and a new event arriving during a flush remains queued.
* Reliability: WP-Cron workers claim rows with short leases before sending. Parallel flushes cannot deliver the same live claim, crashed claims recover automatically, failed sends retain the original event ID with bounded exponential backoff, and no network request runs while a database transaction or lock is held.
* Operations: the 1,000-row safety cap and drop counter are concurrency-safe; activation/upgrades migrate the legacy option queue, and uninstall removes the outbox table.

= 0.3.0 =
* New: **cleaned-HTML serving.** AI bots hitting a content page are now served the published, parity-gated distilled HTML for that page (`text/html`) in place of your theme's render, the same distillation as Ooky's managed edge, without a DNS change. Humans and search/social crawlers always get your normal page.
* New: **JSON-LD injection.** Your published schema.org structured data is injected into `<head>` on normal page renders so AI/search crawlers index your brand facts. Escaping matches the SDK/Worker/edge serializers; a `ooky_jsonld` filter lets you adjust or drop blocks (e.g. to avoid duplicating another SEO plugin's Organization schema).
* Both features read an authenticated per-page API, used a 1h local cache in version 0.3.0, and use a 3s hot-path timeout. They fall through to your normal page whenever nothing is published or a fetch fails; removing the local API key disables them. Version 0.3.2 removes artifact caching at the hostname-ownership boundary.

= 0.2.1 =
* Security: the API key is no longer rendered into the settings form. The field now ships empty and shows only a masked hint (last 4 chars); a blank save preserves the saved key, and a new "Remove key" checkbox clears it explicitly.
* New: events now include the visitor country (from Cloudflare's CF-IPCOUNTRY header) so the geo panel populates for WordPress sites behind Cloudflare. No IP addresses are sent.
* Hardening: the advanced "API base" override is validated on save; it must be an http(s) URL ending in `/api` and must answer a live probe of `/public/bots`, surfacing typos in wp-admin instead of as silent 404s.
* Hardening: this release added short-TTL negative caching on manifest and bot-registry fetch failures and lowered public-path fetch timeouts to 3s. Version 0.3.2 removes manifest negative caching; bot-registry caching remains safe and enabled.
* New: `you` and `phind` added to UTM-source referral detection, matching the SDK and Worker tiers.
* Fix: a bare `{}` MCP POST is now handled on the legacy path (parity with the SDK and Worker) instead of being batch-rejected.
* Hardening: passes the WordPress.org Plugin Check clean: the text domain matches the plugin slug (so translations load just-in-time), every request input is unslashed and sanitized, and manifest output is served with documented escaping. Tested up to WordPress 7.0.

= 0.2.0 =
* New: AI referral attribution: human visits arriving from ChatGPT, Perplexity, Claude, Gemini, Copilot and other AI platforms (Referer header or `utm_source`) now fire `ai_referral` events, powering the attribution views in your Ooky dashboard. Same platform list as the Ooky SDK and Worker tiers.
* New: real MCP server: `POST /mcp` (and `/.well-known/mcp`) now answers standard MCP JSON-RPC 2.0 (`initialize`, `tools/list`, `tools/call get_brand_info`) so AI agents can query your brand directly. The legacy `{tool, arguments}` protocol keeps working.
* New: bare `/mcp` path served, matching the SDK and Worker tiers (some platforms intercept `/.well-known/*`).

= 0.1.1 =
* Fix: activation hook now uses a failsafe second-chance flush on init, so rewrite rules survive deactivate/reactivate cycles on managed hosts that cache the `rewrite_rules` option.
* Fix: bot-event firing moved from init priority 1 to priority 50, after rewrite-rule registration. Resolves a race where a slow DNS lookup to the Ooky API could starve route registration for the same request.
* Fix: plugin's own served paths (`/llms.txt`, `/agents.md`, etc.) no longer fire analytics events. Eliminates a possible response hang when the upstream API is unreachable.
* Hardening: settings sanitize callbacks accept malformed input safely; `Plugin::getApiBase()` re-validates stored URLs on read; admin render dies with a proper 403 for non-admin users.
* Added `uninstall.php`: full plugin uninstall now clears all options, transients, and scheduled cron events.

= 0.1.0 =
* Initial release. Serves `/llms.txt`, `/llms-full.txt`, `/agents.md`, `/.well-known/ai-manifest.json`, `/.well-known/mcp`. Fires bot-detection events to the Ooky ingest endpoint.

== Upgrade Notice ==

= 0.3.3 =
Adds the connection-proof route used by Ooky. Rewrite rules refresh automatically on upgrade.

= 0.3.2 =
Security release: prevents a former workspace's cached artifacts from being replayed after hostname ownership transfers. Recommended for all users.

= 0.3.1 =
Reliability release: migrates the analytics retry queue to an atomic database outbox so concurrent requests and cron flushes cannot lose events. Recommended for all users.

= 0.2.1 =
Security and hardening release: the API key is no longer echoed into the settings page, the API-base override is validated on save, and outages no longer trigger a thundering herd of blocking fetches. Recommended for all users.

= 0.2.0 =
Feature release: AI referral attribution and a real MCP server endpoint. No settings changes required.

= 0.1.1 =
Bug-fix release. Fixes rewrite-rule reliability after deactivate/reactivate and removes a possible response hang on the served paths. Recommended for all users.

== Privacy ==

The plugin transmits data to the Ooky API (default: `https://api.ooky.ai`) in two situations.

**Manifest fetch.** When an AI bot (or anyone) requests one of the plugin's served paths (`/llms.txt`, etc.), the plugin fetches your brand manifest from `https://api.ooky.ai/api/public/manifest/<your-domain>/<kind>`. It sends the registered domain in the URL, the reveal-once API key as a Bearer credential, and a fresh random nonce. Ooky uses them only to bind the response to the domain's current hostname claim and echoes the nonce; the plugin does not send visitor data in this fetch.

**Bot event.** When the User-Agent of an incoming front-end request matches a known AI bot (e.g. GPTBot, ClaudeBot), the plugin POSTs a small event to `https://api.ooky.ai/api/ingest/events`. The event contains: the matched bot name, the User-Agent string, the request path, the HTTP method, the request timestamp, a client-generated event ID, and (when present) the visitor's two-letter country code from Cloudflare's CF-IPCOUNTRY header (never the IP address itself). The event includes your API key as a Bearer token to authenticate the source.

**AI-referral event.** For a human visit referred by a recognized AI platform, the plugin sends the classified source, only the referrer's scheme and host (never its path, query, or fragment), the destination path/method, timestamp, event ID, and optional two-letter country. Events first enter a capped per-site database outbox; WP-Cron atomically claims and retries delivery with the same event ID, and Settings → Ooky shows queue depth, the last successful delivery, and any safety-cap drops.

The plugin does **not** transmit:

* Human visitor IP addresses, cookies, session identifiers, or full AI conversation/search URLs
* Page content or POST bodies
* Logged-in user identities
* Form data, search terms, or any input fields

All outbound calls are made over HTTPS. The API endpoint and base URL can be overridden via the **Settings → Ooky → API base** field for self-hosted Ooky deployments.

If you uninstall the plugin via the **Delete** action in **Plugins**, WordPress runs `uninstall.php`, which removes every option, transient, scheduled event, and per-site outbox table the plugin created. After uninstall no Ooky data remains in your database.

For our full privacy policy and DPA, see [https://ooky.ai/privacy](https://ooky.ai/privacy).
