--- name: citilink-connector description: Use this skill when the operator needs Citilink electronics data — search or a product card. Trigger on "ситилинк", "citilink", "ситилинк цена". Always needs the operator's Chrome over CDP (Qrator rate block + gRPC-web). Skip for non-Citilink tasks. --- # Citilink Connector Reads Citilink inside the operator's Chrome over CDP. The whole domain answers HTTP 429 (stricter than DNS), and its data transport is gRPC-web — a binary protobuf protocol that is not worth reversing. A real Chrome passes Qrator and renders the pages, so the connector reads the DOM instead. ## When to use - Search Citilink by text - One product's price, old price, availability ## Tools available - `citilink_search(query)` — tiles from the rendered search page - `citilink_card(product_url)` — one card. The URL carries `/product//`, where the slug ends in a numeric id: `/product/noutbuk-lenovo-2169270/`. **Not an MCP tool:** `citilink_selfcheck()` is a tri-state canary. It is CLI-only — `marketplace-mcp doctor` runs every connector's canary at once. ## Why the price used to come back null Citilink renders the currency glyph in a **separate element** from the digits: ```html 59 990₽ ``` The old extractor filtered text lines for `руб|₽` and so never saw a line carrying both. Title parsed, price did not — on every product. ## Read the data-meta attributes, not the class names Citilink's class names are build-hashed (`app-catalog-51bw0j-…`) and change with every deploy, so they are useless as selectors. The site publishes a stable `data-meta-*` contract for its own analytics, and that is what the extractor keys on: | attribute | holds | |---|---| | `data-meta-name="Snippet__title"` | the product name anchor | | `data-meta-name="Snippet__price"` | the current price (glyph in a child span) | | `data-meta-name="Snippet__old-price"` | the strikethrough price, no glyph | | `data-meta-price="59990"` | the exact amount, machine-readable | `data-meta-price` needs no parsing and cannot be confused with an instalment or a badge, so it is preferred; the display strings are the fallback. If prices start coming back null, check whether those attributes still exist before touching anything else — a class-name change is expected and harmless, a `data-meta-*` change is the real drift. ## Numbers in a tile that are not the price A tile also carries `- 10%` (discount badge), `в 1356 пунктов` (delivery points), `(от 8 дней)` (delivery estimate), a rating and an opinion count. Only a number attached to the currency glyph is treated as the price. Also note the **first product anchor in a tile is an empty overlay link** — it has no text, so the title comes from the `Snippet__title` anchor. ## Gotchas - gRPC-web is deliberately not reversed: DOM extraction is the maintainable route. - A green `citilink_selfcheck` proves the page rendered and the extractor returned a shape. It does not prove the numbers are right — for that, open the product page in the same Chrome and compare. - From a datacenter address the domain answers 429, so nothing can be verified there. This connector only means anything from the operator's own machine. - A missing price is `null`, never `0`: `price_rub: null` means Citilink had no usable price for the tile — treat it as no data, never as a free item. ## DSH activation In DeepSeek Harness, the default profile exposes only `compare_prices` and `compare_sources` through the cheap compare mount. Per-marketplace tools and `marketplace_sources` require `RU_MARKETPLACE_MCP_FULL=1` and a profile restart; do not call them in the default mode.