=== Seekmodo Search ===
Contributors: numinix
Tags: search, ai-search, autocomplete, woocommerce, typeahead
Requires at least: 6.2
Tested up to: 6.6
Requires PHP: 8.0
Stable tag: 0.3.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

AI-powered search for WordPress and WooCommerce. Vector + keyword recall, commerce-aware reranking, native search permanently armed as a fallback.

== Description ==

**Seekmodo Search** replaces the built-in WordPress site search (and the
WooCommerce product search, when WooCommerce is active) with an AI-powered
search experience served by the Seekmodo platform at mcp.seekmodo.com.
One plugin, two activatable verticals:

* **Content vertical** — WordPress core search via `pre_get_posts` +
  `posts_pre_query`. Auto-detects public Custom Post Types so blog posts,
  pages, docs, and your custom CPTs all participate in the same ranked
  result set.
* **Commerce vertical** — WooCommerce product search via
  `woocommerce_product_query` + `posts_search`. Indexes products plus
  variations, stock state, price, SKU, and attributes. Auto-on when
  WooCommerce is detected; dormant otherwise.

What you get:

* AI search out of the box — vector recall + commerce-aware reranking
  on top of Typesense, with no relevance tuning required to start.
* Typo tolerance, partial-word matching, and prefix-matched typeahead
  via a single REST endpoint your theme can hit from any autocomplete
  widget.
* Click-through telemetry feeds a self-tuning learn-to-rank pipeline.
  Relevance gets better as your shoppers interact.
* Always-on safety net — every gateway-talking entry point falls
  through to native `WP_Query` (or `WC_Query` for products) inside the
  same request when the platform is unreachable. Visitors never see an
  empty results page. A per-tenant circuit breaker prevents repeated
  timeouts from compounding.
* Multi-environment safe — Seekmodo locks each tenant to a canonical
  storefront host. Staging / dev clones are auto-detected and return
  no-op from every gateway entry, so your production index is never
  polluted by a non-production deploy.
* Settings tunable remotely — flip search mode, adjust ranking knobs,
  and toggle the "Powered by Seekmodo" badge from
  <https://admin.seekmodo.com>; the connector picks the change up on
  the next 5-min cadence.

**External service dependency.** Seekmodo Search is the WordPress
connector for the Seekmodo AI search platform. The platform is a
hosted service operated by Numinix Technology Inc. at
<https://mcp.seekmodo.com> and <https://admin.seekmodo.com>. The
plugin connects your site to a Seekmodo tenant via an HMAC-signed
pairing flow; once paired, the plugin sends indexable post / product
data to mcp.seekmodo.com and queries it for search results, typeahead
suggestions, and click telemetry.

**Free trial.** Every Seekmodo plan ships with a 14-day free trial at
$0 — full features, unlimited searches, no card required upfront.
Cancel before day 14 and you pay nothing. After the trial, plans
start at $29/month. Pricing details, including how the plan auto-sizes
to your real traffic during the trial: <https://seekmodo.com/pricing>.

**What happens if you stop paying.** The plugin does not break your
site. If the gateway is unreachable, your subscription lapses, or
you simply uninstall the plugin, every search entry point falls back
to native WordPress / WooCommerce search inside the same request. No
white screens, no fatal errors, no broken search box.

**Privacy + data handling.**
<https://seekmodo.com/legal/privacy> lays out the full picture, but
in short: searchable post / product content you choose to index is
sent to mcp.seekmodo.com; visitor search queries and clicks are sent
to mcp.seekmodo.com as telemetry; no personal data about your
visitors leaves your site unless you opt in to additional
analytics integrations from the Seekmodo admin. The plugin
self-checks for stale indexes, configuration drift, and update
availability — all such phone-home requests are vanilla HTTPS GETs
that carry no tenant ID and no site-identifying data; they're
static-cacheable from our perspective.

== Installation ==

Either:

1. In WordPress admin, go to **Plugins → Add New** and search for
   "Seekmodo".
2. Click **Install Now** on "Seekmodo Search", then **Activate**.

Or upload manually:

1. Download the latest zip from
   <https://seekmodo.com/plugins/wordpress>.
2. Upload via **Plugins → Add New → Upload Plugin** → choose the
   zip → **Install Now** → **Activate**.

Then in either case:

3. Go to **Settings → Seekmodo Search → Connect to Seekmodo**.
4. Click **Start free trial / sign in** and approve the pairing on
   seekmodo.com.
5. (Optional) Run `wp seekmodo index --full` to seed your tenant
   immediately. (The plugin will otherwise reindex incrementally on
   its own.)
6. Flip search mode from `shadow` to `active` in the connector
   settings once you've spot-checked a few searches.

Headless / CI install:

`wp plugin install seekmodo --activate`
`wp seekmodo connect --tenant=<tenant_id> --secret=<shared_secret>`
`wp seekmodo index --full`
`wp option update seekmodo_wp_mode shadow`

== Frequently Asked Questions ==

= Do I need a Seekmodo account? =

Yes. Seekmodo Search is the WordPress connector for the Seekmodo
hosted AI search platform; the plugin itself is free and GPLv2, but
it needs a paired Seekmodo tenant to do its job. Every plan ships
with a 14-day free trial at $0 with full features and unlimited
searches. After the trial, plans start at $29/month. Pricing details:
<https://seekmodo.com/pricing>.

= Do I need WooCommerce? =

No. The **content vertical** works on any WordPress site (posts,
pages, public custom post types). The **commerce vertical** activates
automatically when WooCommerce is detected, and idles otherwise.

= What happens if the gateway is down? =

Every gateway-talking entry point falls through to native `WP_Query`
(or `WC_Query` for products) inside the same request. Visitors never
see an empty results page. A per-tenant circuit breaker prevents
repeated timeouts from compounding, and Seekmodo health metrics
auto-demote the connector back to native search if our gateway is
unhealthy.

= What happens if I stop paying / cancel my subscription? =

Same as the previous answer. The plugin stays installed, the
gateway-talking entry points start returning "no results" (since the
tenant is gated), and every entry point falls back to native search
in the same request. Your site keeps working; you just stop getting
AI relevance. Re-pairing with a paid tenant restores full
functionality.

= Will this work on a staging clone? =

Yes — Seekmodo locks each tenant to a canonical storefront host.
Staging / dev hosts are auto-detected and return no-op from every
gateway-talking entry point, so your production search index is never
overwritten by a non-production deploy.

= Where are my credentials stored? =

In `wp_options` (`seekmodo_wp_tenant_id`, `seekmodo_wp_shared_secret`).
`wp seekmodo connect` can be re-run any time to rotate the pair.

= Where does the plugin send data? =

Indexable post / product content goes to `mcp.seekmodo.com` (the
gateway). Visitor search queries and clicks go to `mcp.seekmodo.com`
as telemetry. The update check (12 h cadence) goes to
`seekmodo.com/api/plugins/seekmodo`. Configuration pull (5 min
cadence) goes to `mcp.seekmodo.com/v1/tenant/snapshot`. All
gateway-bound requests carry an HMAC signature derived from the
pairing secret. Full details in
<https://seekmodo.com/legal/privacy>.

= Does the plugin support translations? =

The admin UI is localised via the `seekmodo-wordpress` text domain
and `/languages/` directory. We ship English (`en_US`) as the source
locale; contributions for other locales are welcome.

= How do I get help? =

* Support email: <support@seekmodo.com>
* Docs: <https://seekmodo.com/docs/wordpress>
* WordPress.org support forum (replies within ~1 business day during
  weekdays)

== Screenshots ==

1. **Settings → Seekmodo Search.** Pair status, storefront-host lock
   indicator, mode and circuit-breaker readouts, indexable post-type
   selector.
2. **Dashboard → Seekmodo health.** Live p95 latency, 5xx rate,
   fallback rate, indexer cursor, and events queue depth.
3. **Storefront typeahead.** Gateway-ranked results render in the
   built-in `[seekmodo_search]` shortcode or any third-party
   typeahead component you wire to the REST endpoint.

== Upgrade Notice ==

= 0.3.2 =
Critical fix: the "Connect to Seekmodo" pairing flow has been rebuilt
on the canonical install_token + signed-JWT protocol. v0.3.0 / v0.3.1
sent legacy `nonce` / `return_to` query params that seekmodo.com
rejects with "This link is incomplete" — pairing through the browser
was impossible. Mandatory upgrade for anyone who hasn't paired yet.

= 0.3.1 =
Bug fix: the "Connect to Seekmodo" button silently redirected to the
WP admin homepage instead of seekmodo.com (wp_safe_redirect() rejecting
the off-site host). Pairing was impossible without manual wp-cli
fallback. Superseded by v0.3.2 — install that one instead.

= 0.3.0 =
Commerce vertical realigned to the gateway's Typesense schema —
WooCommerce indexing now actually works (was 400'ing every upsert).
Schema-version auto-promotes a one-time full reindex on the
commerce side; content vertical is undisturbed. Ships the
self-service "Promote dev to production" admin card so merchants
can flip their canonical from a dev site to production without an
operator round-trip. Worth picking up before standing up your
WooCommerce dev site.

= 0.1.4 =
Adds WordPress.org Plugin Directory listing support: the `Update URI`
header now routes update checks to seekmodo.com so we can ship
hot-fixes faster than the w.org SVN sync cadence. No upgrade work
required; just update.

= 0.1.3 =
Per-tenant "Powered by Seekmodo" branding toggle. Off by default;
opt-in for partner sites.

= 0.1.0 =
Initial public release.

== Changelog ==

= 0.3.2 =

* **Fixed**: the "Connect to Seekmodo" pairing flow has been
  rebuilt on the canonical `install_token` + signed-JWT protocol
  (the same protocol Seekmodo for Zen Cart has used since v1.0.x).
  v0.3.0 and v0.3.1 sent legacy `nonce` + `return_to` query params
  that seekmodo.com rejects with "This link is incomplete" — the
  redirect-back side of that protocol was an aspirational design
  that was never implemented on the seekmodo.com side. Pairing
  through the browser was impossible until this release.

  - `Admin\ConnectFlow::initiate()` now mints a 32-hex
    `install_token` (10-min TTL) and sends
    `?install_token=<t>&callback=<rest-url>` to seekmodo.com/connect.
  - The new `Admin\ConnectFlow::handleCallback()` is registered as a
    public REST endpoint at
    `wp-json/seekmodo/v1/connect/callback`. seekmodo.com POSTs
    `{token, install_token}` to that URL with the
    `X-Seekmodo-Install-Token` header.
  - The new `Connect\Pairing` class verifies the EdDSA-signed JWT
    against `https://seekmodo.com/.well-known/jwks.json`, validates
    the standard claims (iss/aud/exp/install_token), and persists
    `tenant_id` + `shared_secret` + `mcp_url`.

* **Removed**: `Gateway\Client::verifyClaimToken()` and the matching
  `wp seekmodo connect --token=<...>` CLI flag. They both round-tripped
  through a `/v1/connect/verify` endpoint that the gateway never
  implemented; users hitting that path were getting silent failures.
  The CLI pair still works via `--tenant=` `--secret=` for
  headless / CI installs.

* **Required**: PHP `sodium` extension (ext-sodium) -- needed for the
  JWT signature verify. It's part of core PHP since 7.2 and shipped
  on default cPanel/EasyApache builds, but if it's not loaded the
  pair-callback surfaces a cPanel-specific install hint
  (`yum install -y ea-php82-php-sodium`).

= 0.3.1 =

* **Fixed**: `Admin\ConnectFlow::initiate()` called
  `wp_safe_redirect('https://seekmodo.com/connect?...')` without
  whitelisting the connect host. WP's redirect validator silently
  rewrote the destination back to the site root, landing the
  operator on the WP admin homepage instead of the connect page —
  pairing was impossible via the browser flow. The `wp seekmodo
  connect` CLI path was unaffected. Now registers a one-shot
  `allowed_redirect_hosts` filter for the connect host (overridable
  via the existing `seekmodo_connect_base_url` filter) for the
  duration of the redirect call only.

= 0.3.0 =

* **Changed**: Commerce vertical doc shape realigned to the gateway
  Typesense schema. `Commerce\ProductFields::buildDoc()` now emits
  gateway-canonical field names — `name`, `url`, `in_stock` (bool),
  `category_id[]`, `category_breadcrumbs[]`, `image_url`,
  `description` (combined excerpt + content_plain, HTML-stripped, 4
  KB cap), `last_indexed_at`, plus optional `on_sale`, `sale_price`,
  `currency`. Removes pre-v0.3.0 WP-shaped names (`title`,
  `permalink`, `stock_status` string, `regular_price`, `excerpt`,
  `content_plain`, `attributes`, `variations[]`, `categories`,
  `post_type`, `extra`) that the gateway's strict-schema Typesense
  was rejecting on every upsert.
* **Changed**: Commerce schema version split out from the content
  schema version so a commerce-only realignment doesn't trigger an
  unnecessary content-side full reindex. New
  `Options::OPT_COMMERCE_SCHEMA_VERSION` (`commerce_schema_version`
  in wp_options) is consulted by `Commerce\Indexer::tick()` and
  triggers a one-shot full reindex on upgrade — same trick as the
  v0.1.1 content-side bump.
* **Added**: Self-service "Promote dev → production" card on the
  Seekmodo Search settings page. Surfaces when the connector's
  current host looks like a non-prod environment
  (`dev|staging|stage|test|qa|preview|sandbox`) and the canonical
  lock matches that host. One-click POST to the gateway's new
  `tenant.domain.promote` MCP tool flips `locked_domain` to the
  selected production host. Within 5 min the dev plugin's next
  `tenant.snapshot` pull sees the new lock and goes silent
  (DomainLock mismatch), so dev / staging mirrors stop polluting
  production search analytics automatically. Closes the operator
  round-trip the v0.2.x flow required.
* **Added**: `Gateway\Client::promoteCanonicalDomain()` thin HTTP
  helper for the new tool. Same HMAC envelope as every other
  gateway call.
* **Added**: Admin-app mirror — `tenant-settings-form.tsx` now shows
  a "Promote dev to production" hint band + one-click action when
  the tenant is locked to a non-prod host and a prod-looking
  connector has authenticated. Pre-fills the locked-domain field
  with the most-recently-seen prod host; operator clicks Save to
  apply.

= 0.1.4 =

* **Added**: WordPress.org Plugin Directory submission readiness.
  Plugin header `Update URI: https://seekmodo.com/plugins/seekmodo`
  (WP 5.8+) routes update checks to our marketing-site endpoint, so
  the plugin lists at <https://wordpress.org/plugins/seekmodo/> for
  initial install + searchability while all subsequent
  version-checks and zip downloads happen against seekmodo.com.
* **Added**: `Numinix\SeekmodoWordPress\Updates\WporgUpdateClient` —
  hooks `update_plugins_seekmodo.com` (the filter key WP core
  synthesises from our `Update URI` header) and the
  `plugins_api → plugin_information` "View details" modal. Reads
  from `https://seekmodo.com/api/plugins/seekmodo`, cached 12 h on
  success and 5 min on failure. Endpoint is filterable via
  `seekmodo_wporg_update_endpoint` for staging.
* **Changed**: Plugin name shortened from "Seekmodo Search for
  WordPress" to "Seekmodo Search" (w.org plugin-review guideline:
  the "for WordPress" suffix is discouraged when the plugin is
  listed there).
* **Changed**: Plugin description rewritten for w.org's plugin-card
  preview, with explicit SaaS-dependency disclosure and free-trial
  note matching the Mailchimp / Algolia / Akismet pattern.
* **Changed**: `Tested up to: 6.6` added to plugin header + readme so
  the WP-admin compatibility filter and the w.org directory listing
  reflect the latest tested WordPress release.
* **Fixed**: `tools/build_release.py --auto-pr` now writes a manifest
  entry whose schema matches the live `platforms.zen_cart` entry
  exactly (`sha256` / `sig` / `sig_kid` / `signed_at` / `signed_with`
  / `released_at` / `release_notes_url`) instead of the orphan
  `sha256_url` / `signature_url` / `released_at_utc` shape it
  produced before. Also replaces a Unix-only
  `subprocess.check_output(["date", ...])` with portable
  `datetime.now(timezone.utc)`.

= 0.1.3 =

* Add: per-tenant "Powered by Seekmodo" branding toggle. Off by default;
  operators turn it on from the admin.seekmodo.com tenant settings page
  for partner / specific-deal sites. New helpers
  `numinix_seekmodo_show_branding()` and `numinix_seekmodo_brand_url()`
  for themes, with `seekmodo_show_branding` and `seekmodo_brand_url`
  filters.

= 0.1.2 =

* Fix: `AutoPromoter` substate FSM is now actually wired up. Previous
  versions never advanced the `active+shadow → active+enforce`
  substate, and the SearchSwaps consulted the headline mode instead
  of the effective mode, so `auto_promote=true` was decorative.
  `MODE=active` now correctly bakes into enforce on healthy metrics
  and demotes back to shadow on a breach.
* Fix: `MODE=active` + `auto_promote=false` now correctly collapses
  to a hard enforce (operator opt-out of the safety hop).

= 0.1.1 =

* Fix: Content indexer doc shape aligned with the gateway's strict
  Typesense `content` collection schema. Previous releases would
  have 400'd on every `index.upsert`.
* Bumped `Options::SCHEMA_VERSION` to force a full reindex on
  upgrade.

= 0.1.0 =

* Initial release. Sprints 0-5 shipped: content + commerce verticals,
  mode FSM with auto-promoter, shadow-mode diff logger, typeahead,
  click beacon, events queue, health widget, signed-zip release
  pipeline.
