# Handoff Prompt: Festool Product Data Connector

Paste this into Claude Code in the Cydekick VS Code project.

---

## Context

We're adding a new plugin/connector, **Festool Product Data**, to pull product
information from festool.co.uk by SKU. I'm a Festool trade dealer with a list
of SKUs but only limited product data from Festool directly. This connector
will resolve full product data per SKU and sync it into our PIM.

Follow the existing plugin conventions in this codebase — the **Channels
plugin is the reference standard** for structure, naming, and patterns. Do
not invent new conventions where an existing pattern already covers the case.

## Step 0 — Explore first (do this before writing any code)

- Review the Channels plugin structure end to end (folders, service classes,
  jobs, config, how it registers with the rest of the app).
- Review how the existing Allmakes PSP / JLR EPC integrations are structured,
  since those are also reverse-engineered third-party API pulls — reuse
  whatever pattern they already established for HTTP clients, rate limiting,
  and error handling rather than creating a new one.
- Report back on the structure found before writing the new plugin, so we
  confirm the plan matches before code starts.

## What we know about the Festool endpoints (reverse-engineered via DevTools)

**Resolver endpoint** (confirmed working):
```
GET https://www.festool.co.uk/api/products/ecommerce/productData
    ?culture=en-GB
    &productNumber={SKU}
    &getAvailabilityTeaser=true
```
Returns JSON: `productNumber`, `productUrl`, `imageUrl`, `accessoryUrl`.
This is a SKU → URL resolver only — it does NOT contain name, price,
description, specs, or "Contains" list.

**Full product data** (not yet confirmed): either a richer JSON API we
haven't captured yet, or embedded JSON in the rendered product page HTML
(e.g. a `__NEXT_DATA__`-style script tag, if the frontend is React/Next.js
based). I'll confirm which by inspecting a live product page and paste the
result back before this part is built — don't guess at a schema.

## What to build

1. **`FestoolProductLookupService`** with:
   - `resolveBySku(string $sku): array` — calls the productData endpoint,
     returns `productUrl` + `imageUrl`.
   - `fetchFullProduct(string $productUrl): array` — fetches the product
     page and extracts full product data (method TBD pending the Step 0
     confirmation above — build this as a swappable strategy, not hardcoded,
     since we may switch between "parse embedded JSON" and "parse DOM").
   - `syncBySku(string $sku): void` — orchestrates both calls and upserts
     the result into the PIM, following the same upsert pattern used by the
     Channels plugin's listing sync.

2. **Config**: base URL, culture code, request timeout, and a rate limit /
   delay between requests (this is an unofficial endpoint — be conservative,
   don't hammer it).

3. **A sync command/job** that accepts a list of SKUs (from file or array)
   and runs `syncBySku` across them, with logging of failures per SKU rather
   than aborting the whole batch on one failure.

4. **Basic tests** mocking the HTTP responses for both endpoints, covering:
   SKU not found, malformed response, and the happy path end to end.

## Out of scope for this pass

- Don't build a UI/admin screen yet — service + command only.
- Don't hardcode the full-product parsing logic until I confirm the actual
  response shape from a live page.

## One thing to flag back to me

If while exploring you find we already have a generic "fetch + parse
third-party HTML/JSON" helper (from the JLR EPC or Allmakes work), reuse it
here instead of writing a new HTTP client from scratch.
