drivethru-adidas-click

内容来源:clawhub · 原始地址 · 查看安装指南

原始内容


name: drivethru-adidas-click description: Browser-driven adidas Click B2B toolkit — places purchase orders and runs live inventory / wholesale-pricing checks on the adidas Click portal with Playwright, since adidas exposes no public ordering API. Ordering accepts style number, color, size, quantity, a purchase-order number, and a ship-to address, drives the cart/checkout, and (on confirm) submits the order. Checks reuse the same flow but never buy — inventory reads product pages (no cart), pricing fills a throwaway "DO NOT BUY" cart, reads the priced checkout, and deletes it. Use whenever the user needs to place/draft a PO on adidas Click, or check live stock levels or wholesale pricing. version: 0.6.0 emoji: 👟 homepage: https://b2bportal.adidas-group.com metadata: openclaw: requires: bins: [python3] envVars: ADIDAS_CLICK_USERNAME: required: false description: > adidas Click B2B portal username (typically an email). Optional — may instead be passed inline in a tool's stdin JSON. Treat as a secret. ADIDAS_CLICK_PASSWORD: required: false description: adidas Click portal password. Optional env cache; treat as a secret. ADIDAS_CLICK_BASE_URL: required: false description: > Override for the adidas Click base URL. Defaults to https://b2bportal.adidas-group.com. Set this if the account uses a region-specific host. ADIDAS_CLICK_HEADLESS: required: false description: > Force headless when set to a truthy value (true/1/yes/on). Default is HEADED, because adidas's Akamai Bot Manager stalls headless Chromium. On Windows/macOS a headed browser uses the native display, so nothing extra is needed. Only on a Linux host with no display server (e.g. a headless container) does the skill auto-start an Xvfb virtual display (requiring the xvfb system package); Windows/macOS never need it. Headless will most likely time out at the adidas login page. ADIDAS_CLICK_USER_AGENT: required: false description: > Override the browser User-Agent. Defaults to a current desktop-Chrome UA so adidas's Akamai edge serves the automated browser instead of stalling the response. Only change it if the default UA gets blocked. install: uv: # Browser automation is the only surface. This installs the Playwright # package; the Chromium binary (a separate ~150 MB download that pip # can't fetch, and OpenClaw has no post-install hook for) is installed # automatically on the first run — see the browser-binary note below. - playwright>=1.40


adidas Click B2B toolkit

adidas Click is a B2B ordering portal with no public API, so this skill places purchase orders by driving the portal with Playwright. Every tool is reached through one CLI entrypoint:

echo '<json-args>' | python3 scripts/adidas.py <action>

The action is the first CLI argument; arguments are a JSON object on stdin. Each call prints a single JSON object on stdout, or {"error": {"type": ..., "message": ...}} with a non-zero exit code on failure.

Playwright is imported lazily, so the skill loads without it. The Playwright package is a declared uv dependency; its Chromium browser binary (a separate ~150 MB download that pip/uv doesn't fetch, and for which OpenClaw has no install-time hook) is installed automatically on the first run if missing — so no manual python -m playwright install chromium is needed. That first run therefore includes a one-time browser download. If the auto-install fails (e.g. no network, or missing system libraries), the tool returns a clear config_error pointing at the manual command.

Build status — full order flow implemented. The skill drives a purchase order end to end: login → add line quantities to the active cart → checkout (Customer PO, delivery location — default / saved / one-time dropship — and shipping method) → Next → Calc. Net Price (waits for the "Done!" tell so the wholesale discount always applies) → Order Now → confirmation number parsed from the redirect URL. confirm: false is a full dry run; confirm: true places a real order (no sandbox). The assembled flow has been built and unit-checked selector-by-selector but not yet run against the live site in one pass — watch the first real end-to-end run. See references/order_flow_notes.md for the step map and captured selectors.

Inventory / pricing checks (check-inventory-pricing) reuse the same captured steps — product navigation + size read (inventory) and, for pricing, the throwaway-cart → checkout → Calc. Net Price walk — but stop before Order Now and delete the cart. No new selectors: it composes the existing, live-validated ordering steps, so its browser surface rides on the same capture. See the "Inventory & pricing checks" section below.

When to use this skill

Reach for create-purchase-order when the request involves placing (or drafting) a purchase order on adidas Click: given a style number, color, size, quantity, a PO number, and a ship-to address, put the order into the portal cart/checkout and — on explicit confirmation — submit it.

Reach for check-inventory-pricing when the request is to look up live stock levels and/or wholesale (net) pricing on adidas Click without buying anything — e.g. "how much JW4306 is in stock in L?" or "what's our wholesale cost on 24× JW4306 M?". It reuses the ordering flow but never places an order (see below).

Do not use it for other footwear/apparel vendors, and never invent portal selectors from prose — the flow lives in the captured references/order_flow_notes.md.

Actions

Action Risk stdin JSON (key fields)
create-purchase-order high — portal write (browser) {purchase_order: {po_number, lines: [{style, size, quantity}], ship_to?, delivery_location_id?, ship_method?, on_insufficient_stock?, spread_delivery?}, confirm}
check-inventory-pricing medium — read-only intent, but a pricing check briefly creates then deletes a throwaway cart (browser) {check: "inventory"|"pricing"|"both", lines: [{style, size?, quantity?}], po_number?}

Run python3 scripts/adidas.py with no action to print the action list.

create-purchase-order input

The order can be passed nested under purchase_order or as top-level fields:

{
  "purchase_order": {
    "po_number": "PO-12345",
    "lines": [
      {"style": "JW4306", "size": "M", "quantity": 24},
      {"style": "JW4306", "size": "L", "quantity": 12}
    ],
    "ship_to": null,
    "delivery_location_id": null,
    "ship_method": null,
    "spread_delivery": false,
    "notes": null
  },
  "confirm": false
}

Each line is {style, size, quantity}. color is optional — adidas article numbers encode the color (e.g. JW4306 = the black colorway), so navigating to the article lands on the right color with no picker.

Delivery location precedence: delivery_location_id (pick a saved location) > ship_to (add a one-time / dropship location) > default (leave the cart's preset). A one-time ship_to.state accepts a 2-letter abbreviation or the full name. ship_method is left at the cart default unless set, and accepts a FedEx service code (FDGP, FEDE, FED4, …) or its exact label ("FedEx Ground"). po_number is capped at 18 characters and may contain only letters, numbers, space, and / _ . ? &; any other character (e.g. a hyphen) is auto-replaced with _ and the substitution is reported in the result's warnings.

By default (new_cart: true) the run creates its own fresh cart (named with the PO) right after login and makes it active, so on a shared account it never adds to — or checks out — a teammate's cart. If a cart with the same PO name already exists (e.g. a leftover from an errored prior run) it is deleted first so the run never piles onto stale quantities — only carts with that exact PO name are removed, and any deletion is noted in warnings. Pass new_cart: false to use the current active cart instead.

Pass screenshot_path to capture the filled page for review.

Out-of-stock handling (read this)

Before entering quantities, the tool reads each size's availability (scrolling the horizontal size row as needed to load off-screen sizes). A size is either backorderable (stock below the requested quantity, incl. 0 with a restock date) or not available (the "X" cell — will never be restocked, cannot be ordered at all; it can only be removed or substituted). When a line's full quantity is not available, behavior depends on on_insufficient_stock:

  • pause (default) — nothing is ordered (even with confirm: true). The result comes back status: "needs_confirmation" with an out_of_stock list ({style, size, requested, available}) and a message. The agent must stop and confirm with the user, then re-run.
  • order — order the short line(s) anyway, accepting delayed delivery (adidas spreads the backordered portion over future dates).
  • skipremove the out-of-stock line(s) and order the rest.

Agent guidance: if the user's request pre-authorizes a choice, set the flag and skip the pause — e.g. "go ahead and order anything out of stock"on_insufficient_stock: "order"; "remove any out-of-stock items without asking"on_insufficient_stock: "skip". Otherwise leave it at pause; on a needs_confirmation result, message the user with the out_of_stock details and the three choices, wait for their answer, then resume:

  1. Order them anyway (delayed delivery) → re-run with on_insufficient_stock: "order".
  2. Don't order them → re-run with on_insufficient_stock: "skip".
  3. Substitute (e.g. a different size/style so items match) → edit the lines accordingly and re-run (optionally with a policy for any still-short substitutes).

spread_delivery is the lower-level knob behind order: on a per-line spread prompt, false (default) declines (single delivery), true accepts.

The result reports total_quantity (summed pieces, on dry runs too) and, on a placed order (confirm: true), each line's net line_total and unit_price (net ÷ quantity) plus the order order_total (net wholesale), read from the priced review page.

Inventory & pricing checks (check-inventory-pricing)

A read-only lookup that reuses the ordering flow but never places an order. Pick the mode with check:

check What it does Cart?
inventory Reads each line's live size-tile stock level straight off the product page. No cart created — inventory needs no add-to-cart.
pricing Fills a throwaway cart, advances to the priced checkout screen (the only place wholesale net pricing shows), runs Calc. Net Price, reads the net unit/line prices + order total, then deletes the cart. Yes — created, priced, deleted.
both (default) pricing plus the inventory levels read while filling the cart. Yes — same as pricing.
{
  "check": "both",
  "lines": [
    {"style": "JW4306", "size": "M", "quantity": 24},
    {"style": "JW4306", "size": "L"}
  ]
}

Each line is {style, size?, quantity?}:

  • size optional (inventory only): omit it (or pass "*" / "all") to report every size of that style. For a pricing line a specific size is needed (a price is per size).
  • quantity optional: defaults to 1. It only affects a pricing line's line total (unit price is the same regardless); an inventory read reports the raw level independent of quantity.

Why a cart at all for pricing? adidas only reveals the discounted wholesale net price on the final checkout screen, after Calc. Net Price runs — there is no price API and no price on the product page beyond a "from" figure. So a pricing check has to fill the cart and walk to that screen, exactly like an order, then stop before Order Now and delete the cart.

The "DO NOT BUY" marker. A pricing check names its throwaway cart and the Customer PO with a DO NOT BUY {random} marker (e.g. DO NOT BUY 7F3K9) so that if a run dies mid-flight, the leftover cart is unmistakably safe. The full "AUTOMATED CHECK - DO NOT PURCHASE - …" wording does not fit: adidas hard- caps the Customer PO at 18 characters and allows only letters, numbers, space, and / _ . ? &, so this is the clearest imperative that still leaves room for a 5-char random suffix (cart names must be unique). Override the marker with po_number (still ≤18 chars, same charset).

Never pauses on stock. Because a check never buys and deletes its cart, it does not stop on short/out-of-stock lines: a pause policy is upgraded to order so every orderable line still gets priced. Sizes that are flatly not available (the portal's "X" cell) are reported with no price.

Result (status: "checked"): a lines list of {style, size, color, requested_quantity, available, available_count, status ("in_stock"|"backorder"|"unavailable"), in_stock, unit_price, line_total, note}, plus order_total (summed net, pricing only), total_quantity, po_number (the marker used, null for inventory-only), and cart_deleted (true/false once a pricing check tried to remove its cart; null when no cart was made). If the throwaway cart could not be auto-deleted (e.g. it was the account's only cart), a warning says so and names it — remove it in the portal.

Credentials

The adidas Click portal login can be supplied three ways (later wins):

  1. Environment variables (preferred for a deployed agent):

    ADIDAS_CLICK_USERNAME=...
    ADIDAS_CLICK_PASSWORD=...
    ADIDAS_CLICK_BASE_URL=https://b2bportal.adidas-group.com   # override for region host
    
  2. Inline in the stdin JSON — pass username, password, and base_url alongside the tool's own arguments.

  3. CLI flags--username / --password / --base-url after the action. Handy for local runs with no env vars and no secrets in the order JSON:

    echo '{"purchase_order": {...}, "confirm": false}' \
      | python3 scripts/adidas.py create-purchase-order --username "$U" --password "$P"
    

    Command-line arguments are visible to other processes (ps) and your shell history — for sensitive/automated use, prefer env vars or the stdin JSON.

If no credentials are present via any of these, the tool exits with {"error": {"type": "config_error", ...}} (exit code 2) — treat that as a signal to ask the user for the missing fields. Never guess or reuse credentials across accounts, or paste secrets the user did not provide.

Write safety

create-purchase-order is the only order-placing action.

  • It requires "confirm": true to place the order. Without it, it logs in, fills the cart + checkout, and returns a dry-run preview without submitting. Confirm with the user before placing.
  • Placing an order on adidas Click is a real purchase — there is no sandbox. Only run confirm: true for an order the user actually intends to place.

check-inventory-pricing never places an order — there is no confirm path. inventory mode touches no cart at all. pricing/both create a throwaway DO NOT BUY … cart only to reach the priced checkout screen, stop before Order Now, and delete the cart on the way out. It does mutate portal state briefly (the cart is created and removed), so it is not purely read-only, but it cannot buy anything.

Reachability & bot mitigation

adidas Click sits behind Akamai Bot Manager, which fingerprints the client and stalls the HTTP response (rather than cleanly refusing it) for traffic that looks automated. The tell is a navigation that hangs to a timeout (Page.goto: Timeout … exceeded) even though the network path is healthy — a browser-UA curl to the same host returns 200 in well under a second, while a bare curl or a default headless Chromium (UA contains HeadlessChrome, navigator.webdriver=true) gets tarpitted.

A spoofed User-Agent alone is not enough: a headless Chromium is still fingerprinted (TLS/JA3, HeadlessChrome internals, no GPU/canvas) and tarpitted even with a real UA. The reliable path is a headed browser on a virtual display, which presents an ordinary browser fingerprint. So the skill:

  • runs headed by default (set ADIDAS_CLICK_HEADLESS=true to force headless, which will most likely time out at the login page);
  • launches Chromium with a real desktop-Chrome User-Agent, a normal viewport/locale, --disable-blink-features=AutomationControlled, a navigator.webdriver mask, and a longer navigation timeout;
  • when headed on a Linux host with no display server, auto-starts an Xvfb virtual display (spawned directly, so no xauth needed) and points $DISPLAY at it — this requires the xvfb system package in the image (apt-get install -y xvfb). This is Linux-only: the skill first checks the platform, so on Windows/macOS (native GUI) and on a Linux host that already has an X11/Wayland display, it launches headed directly and never requires Xvfb.

Overrides: ADIDAS_CLICK_USER_AGENT if the default UA ages out. If headed on a virtual display still stalls, the block is at the egress IP (a datacenter IP Akamai distrusts) — route through an IP adidas allowlists, the same way drivethru-sanmar documents needing its caller IP allowlisted. No browser/code change fixes an IP-reputation block.

Error model

Failures print {"error": {...}} and exit non-zero:

  • config_error (exit 2) — missing/invalid credentials or an un-captured step.
  • api_error — the portal rejected an action or a page did not match expectations. Includes surface, operation, retryable.
  • connection_error — network / browser-launch / navigation failure (retryable: true).
  • validation_error — bad input JSON or a missing required field.
  • usage / unknown_action (exit 2) — bad CLI invocation; the message lists the valid actions.

Surface the human-readable message to the user. Do not retry on config_error or validation_error.

References

  • references/order_flow_notes.md — the reverse-engineered portal ordering flow (step map, captured selectors, and the capture checklist for the next step). Start here to continue the build.