channel3-ai/skills

product-discovery

Fetches real product data — prices, stock, retailer links, "more like this" recommendations, and visual-similarity matches — across 100M+ products from thousands of retailers, by calling the Channel3 CLI (channel3 products search, channel3 products find-sim…

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/channel3-ai/skills --skill product-discovery

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Product Discovery

Uses the Channel3 CLI to query a catalog of 100M+ products across thousands of retailers. The CLI is generated from the API spec and stays in sync automatically.

Decision

User wants...Run
Find products by description, image, or bothchannel3 products search --query-param "..." --limit N
More like this product I already foundchannel3 products find-similar --product-id <ID> --limit N
Find a category slug to use as a filterchannel3 categories search --query-param "..." --limit 5
See a category's allowed attribute keys/values for filters.attributeschannel3 categories retrieve --slug <slug>
Filter by a color (blue, navy, red, ...)--filters '{"colors":{"palette":[{"hex":"#..."}]}}' (sRGB hex; never put color into filters.attributes)
Filter by non-color attributes (material, frame-color, ...)channel3 categories retrieve --slug <slug> first, then --filters '{"attributes":{"handle":["Value"]}}'
See variant options + live stock for a productchannel3 products retrieve --product-id <ID> (read variants.options / variants.selected) — see Pattern 6
Find a brand ID to use as a filterchannel3 brands search --query-param "<name>" --limit 5
Wire Channel3 into their agent host directly (no CLI)Channel3 MCP — see below

Setup

The skill assumes the channel3 CLI is installed and on PATH.

bash
npm install -g @channel3/cli

export CHANNEL3_API_KEY="..."   # free key at https://trychannel3.com

Optional locale defaults: CHANNEL3_LANGUAGE, CHANNEL3_COUNTRY, CHANNEL3_CURRENCY env vars; per-call --config always wins.

If channel3 --version fails, fail loudly with the install instruction above — do not silently fall back to anything else.

Calling the CLI

Always pass --format jsonl --query '...'. The templates below project only decision-critical fields; default JSON is verbose. Never auto-pick the first element of a result array — project the whole array and read every entry. The projection language is JMESPath.

`--query` is the projection flag. The search term is `--query-param`. They are different things, and mixing them up is the most common mistake against this CLI.

--query applies to the whole response, so every projection starts at the wrapper key — products[], brands[], categories[], attributes[]. With --format jsonl the resulting array is flattened to one record per line.

Use --limit N to size the result set (5 for a first look, 10–20 to compare more). The CLI does not auto-paginate: it returns a single page unless you pass --page-all, which streams every page as NDJSON up to --page-limit (default 10). Don't pass --page-all for interactive product discovery — it bloats context.

Filters vs query. Anything that could be a filter belongs in --filters, not --query-param. Direct filter types: brand, color, category, price, gender, availability, age, condition, sale, dimensions, retailer. For category-specific traits — anything you'd expect as a checkbox or dropdown on a retailer's filter sidebar — discover the handle and value via Pattern 3 (categories search → categories retrieve → filters.attributes) before falling back to query terms. Reserve --query-param for what can't be enumerated: aesthetic descriptors, model names, or use cases. Rich filters with a minimal --query-param beat the opposite.

Default templates (copy-paste verbatim)

products search / products find-similar:

--limit N \
--format jsonl \
--query 'products[].{id: id, title: title, brands: brands[].name, offers: offers[].{domain: domain, price: price.price, currency: price.currency, availability: availability, url: url}, attrs: structured_attributes}'
  • brands is the array of brand names (some products have multiple).
  • offers is the full array of merchant offers. Compare across domain / price / availability to recommend the right buy link. This multi-merchant comparison is the point of Channel3 — never collapse it to offers[0].
  • attrs (structured_attributes) shows what the catalog extracted — e.g. {"color":["Navy"],"material":["Leather"]} — so the agent can judge fit without another call.
  • Add commission: max_commission_rate inside offers[].{...} if affiliate revenue matters for ranking.

brands search:

--limit 5 --format jsonl \
--query 'brands[].{id: id, name: name, description: description, commission: best_commission_rate}'

description and commission are decision-critical: multiple brands often share a name (e.g. searching "Nike" returns two distinct IDs — Nike the manufacturer and Nike the retail store house brand, with different commission rates).

categories search:

--limit 5 --format jsonl \
--query 'categories[].{slug: slug, title: title, path: path}'

path is the ancestor chain (Furniture > Sofas); use it to disambiguate near-duplicate slugs.

categories retrieve:

--format jsonl \
--query 'attributes[].{slug: slug, name: name, values: values}'

Drops the prose description and children — the attribute table is what feeds filters.attributes.

Chaining patterns

Each pattern is call → read → decide → next call. The agent reads every entry in the result array and picks based on the criteria below. Multi-intent requests ("either brown+cyan or red/blue/white shoes") fan out to one search per intent in parallel — don't try to express them in a single call.

1. Strict brand filter

User: "Nike running shoes, no other brands."

bash
channel3 brands search --query-param "Nike" --limit 5 --format jsonl \
  --query 'brands[].{id: id, name: name, description: description, commission: best_commission_rate}'

Read all results. Pick the id that matches user intent:

  • Match name exactly first.
  • When multiple results share the name, read each description. "Nike the manufacturer" vs "Nike retail store house brand" are different IDs; pick the one matching intent.
  • If commission matters (affiliate revenue), factor it in — but only after correctness.
  • Still ambiguous? Pass multiple IDs (brand_ids is an array, OR semantics) or ask the user.
  • No match? Try a query variant (typos, common variants) before telling the user the brand isn't catalogued.
bash
channel3 products search --query-param "running shoes" --limit 10 \
  --filters '{"brand_ids":["<id chosen above>"]}' \
  --format jsonl \
  --query 'products[].{id: id, title: title, brands: brands[].name, offers: offers[].{domain: domain, price: price.price, currency: price.currency, availability: availability, url: url}, attrs: structured_attributes}'

2. More like this

The agent already has products from a prior products search. Pick the id of the one the user actually referenced — not blindly the first hit.

bash
channel3 products find-similar --product-id <chosen-id> --limit 10 \
  --filters '{"gender":"female"}' \
  --format jsonl \
  --query 'products[].{id: id, title: title, brands: brands[].name, offers: offers[].{domain: domain, price: price.price, currency: price.currency, availability: availability, url: url}, attrs: structured_attributes}'

find-similar carries the source product's visual embedding but does not inherit your filters. If the reason you liked the source was filterable (color, brand, price), re-pass those filters explicitly.

If find-similar returns 404, the product isn't catalogued yet — fall back to products search using the product's title.

3. Discover attribute handles, then filter

Each category exposes its own attribute schema — a list of handles (material, neckline, etc.) with allowed values. categories retrieve reveals them; filters.attributes consumes them.

User: "leather sectional sofa."

bash
channel3 categories search --query-param "sofa" --limit 5 --format jsonl \
  --query 'categories[].{slug: slug, title: title, path: path}'

Read all results. Pick the slug whose path matches user intent (Furniture > Sofas, not Outdoor > Sofas unless they said outdoor). Multiple slugs are fine when scope is broader (e.g. sofas,sectionals).

bash
channel3 categories retrieve --slug sofas --format jsonl \
  --query 'attributes[].{slug: slug, name: name, values: values}'

Read all attribute rows. Pick non-color handles and verbatim values. (Color always goes through filters.colors — see Pattern 4.)

bash
channel3 products search --query-param "sectional" --limit 10 \
  --filters '{"category_ids":["sofas"],"attributes":{"material":["Leather"]}}' \
  --format jsonl \
  --query 'products[].{id: id, title: title, brands: brands[].name, offers: offers[].{domain: domain, price: price.price, currency: price.currency, availability: availability, url: url}, attrs: structured_attributes}'

4. Filter by color

User: "blue Nike sneakers."

Map the color name to its closest sRGB hex (blue → #0066CC, navy → #001F3F, etc.) and pass it through filters.colors.palette. No category lookup needed for color. Multiple entries match products containing those colors. Only add `"percentage": N` when the user expresses an asymmetric balance between colors (e.g. "mostly red with some green", "70% red 30% blue"); plain "mostly red" or "mostly red and green" doesn't need it. For "multicolor" or "any color", omit the colors filter entirely.

bash
channel3 products search --query-param "sneakers" --limit 10 \
  --filters '{"colors":{"palette":[{"hex":"#0066CC"}]}}' \
  --format jsonl \
  --query 'products[].{id: id, title: title, brands: brands[].name, offers: offers[].{domain: domain, price: price.price, currency: price.currency, availability: availability, url: url}, attrs: structured_attributes}'

5. Refinement when results don't match intent

If a search returns zero or clearly off-intent results, run another search with relaxed or shifted filters/query. Don't ask the user mid-loop — keep iterating. Common moves:

  • Zero results → drop percentage if you added one, then drop the most restrictive filter (usually colors or attributes).
  • Wrong category drift ("sectional sofa" returned accent chairs) → add category_ids via Pattern 3.
  • Brand drift (asked for one brand, got many) → add strict brand_ids via Pattern 1.
  • Mostly out of stock → add {"availability":["InStock"]}.
  • Wrong gender / age → add {"gender":"..."} or {"age":["adult"]}.
  • Need more results of the same shape → re-run with a larger --limit.

Stop once results are presentable — don't keep searching hoping for "better." Never validate hits by retrieving images, downloading CDN assets, or running `find-similar` to double-check — the filters and attrs/title are the source of truth, and the host UI will render the image for the user.

6. Inspect a product's variants and live stock

User: "does this come in XL?" / "what colors does it come in?" / "is the navy one in stock?"

products search returns the variant matrix but not stock — available is always null on search results. To see live per-value availability, retrieve the product:

bash
channel3 products retrieve --product-id <ID> --format jsonl \
  --query '{title: title, variants: {selected: variants.selected, options: variants.options[].{name: name, values: values[].{label: label, exists: exists, available: available, product_id: product_id}}}}'

Read the rows:

  • `exists: false` → that value isn't offered with the currently selected options (e.g. the shirt exists in XL, but not in this color + XL). Not the same as out of stock.
  • `available` → live stock (InStock or OutOfStock), hydrated only on retrieve.
  • `product_id` set → that value is a separate product (color-as-product-swap). To inspect it, retrieve that ID instead.
  • `variants.selected` → the configuration this response represents.

Retrieve is free, so refetch before telling the user about price/stock for a specific configuration. The CLI's retrieve doesn't take option_* selection params — resolving an arbitrary size+color combination server-side is an SDK/REST capability (see the channel3-api skill). For navigation, follow a value's product_id.

Filter cookbook

--filters takes a single JSON object. Combine fields freely. The full filter shape is documented at docs.trychannel3.com/api-reference/v1/search.

Max price           --filters '{"price":{"max_price":100}}'
Price range         --filters '{"price":{"min_price":50,"max_price":150}}'
Gender              --filters '{"gender":"female"}'
Age                 --filters '{"age":["adult"]}'
Condition           --filters '{"conditions":["new"]}'
Used/open-box       --filters '{"conditions":["used"]}'
Any condition       --filters '{"conditions":["new","used"]}'
Availability        --filters '{"availability":["InStock"]}'
Include out of stock --filters '{"availability":["InStock","OutOfStock"]}'
On sale             --filters '{"sale":"on_sale"}'
Max width (120 cm)  --filters '{"dimensions":{"width":{"max":120,"unit":"cm"}}}'
Color (blue)        --filters '{"colors":{"palette":[{"hex":"#0066CC"}]}}'
Two required colors --filters '{"colors":{"palette":[{"hex":"#0066CC"},{"hex":"#FFFFFF"}]}}'
Material            --filters '{"attributes":{"material":["Leather"]}}'
Multiple values     --filters '{"attributes":{"material":["Leather","Velvet"]}}'
Category            --filters '{"category_ids":["shoes"]}'
Multiple categories --filters '{"category_ids":["sofas","sectionals"]}'
Brand               --filters '{"brand_ids":["MpZS"]}'
Retailer by domain  --filters '{"website_ids":["nike.com"]}'
Exclude brand       --filters '{"exclude_brand_ids":["MpZS"]}'
Exclude category    --filters '{"exclude_category_ids":["athletic-shoes"]}'
Combine             --filters '{"price":{"max_price":150},"gender":"male","colors":{"palette":[{"hex":"#001F3F"}]},"availability":["InStock"]}'

gender is male or female only. conditions is a list of new / used (refurbished was removed; default is ["new"], which also matches unknown-condition offers — pass both values to disable). availability is a list of InStock / OutOfStock only (default ["InStock"]; pass both to disable). sale is on_sale. dimensions keys are length / width / height / weight, each {min?, max?, unit} with length units mm/cm/m/in/ft and weight units mg/g/kg/oz/lb. age values are newborn, infant, toddler, kids, adult.

Other flags

Locale (country / currency / language) overrides the env-var defaults. mode picks the search strategy. Default (lexical + semantic) is right for almost all calls — this skill's value is building the filters yourself. "agentic" hands planning to an LLM that decomposes a rich brief into sub-searches (multiple seconds; powers Channel3's MCP) — use it when you'd otherwise pass the user's full raw context through --query-param. "keyword" is lexical-only (disables semantic search; incompatible with image input) — niche real-time cases. --image-url adds an image to products search (combinable with --query-param for "this jacket but in blue").

--config '{"country":"GB","currency":"GBP"}'
--config '{"language":"de"}'
--config '{"mode":"keyword"}'
--image-url "https://example.com/jacket.jpg"

Anti-patterns

  • Filters beat `--query-param`-stuffing, but `--query-param`-stuffing beats dropping the constraint. Direct filter axes (brand, color, category, price, gender, availability) and category-specific attribute values (anything that would appear as a facet on a retailer's filter sidebar) belong in --filters — the optimal path to precise results. If a constraint doesn't fit a filter, include it in --query-param rather than dropping it. (agentic mode inverts the default preference — see Other flags.)
  • Don't validate hits by retrieving images, downloading CDN assets, or running multimodal inspection. If filters.colors was applied, trust it. title and attrs are enough to judge fit; the host UI renders the image for the user.
  • Don't run `products find-similar` reflexively after `products search`. If the first search produced good matches, present them. Use find-similar only when the user is anchored on one specific product they already saw — and re-pass the filters you care about, since find-similar doesn't inherit them.
  • Use `categories search` when the request has structured requirements, not for trivial queries. Single-word noun queries get routed correctly by semantic search in products search. Run categories search → categories retrieve → filters.attributes (Pattern 3) when the request includes traits that would appear as facets on a retailer's filter sidebar, when strict inclusion/exclusion is required, or when the query is too generic to imply a category on its own.
  • Don't run `brands search` by default. Semantic search already biases toward in-brand matches when the brand name is in the query. Reach for brands search only when (a) strict inclusion is required ("only Nike, not Nike-mentioning"), (b) exclusion is required ("running shoes excluding Nike"), or (c) the user is anchored on one brand ("what does Patagonia sell").
  • Never use `filters.attributes` for color. Color always goes through filters.colors with a hex value. Even if categories retrieve lists a color attribute, skip it for filtering.
  • Don't guess attribute handles or values. Unknown handles/values match nothing useful. Run categories retrieve --slug <slug> first and copy non-color handles verbatim.
  • Don't use `[0]` / `[1]` indexing to "pick a result." Project the whole array so the agent reads every entry and decides. Top-level singletons (no array) are the only exception.
  • Don't confuse `--query` with `--query-param`. --query is a JMESPath projection over the response; --query-param is the search term. Passing a search phrase to --query fails with a JMESPath parse error, and a single word silently returns null.
  • Don't pass `--page-all` for product discovery. It streams every page and bloats context. Size results with --limit instead.

Presenting results

Return the projected products to the host — most agentic-search UIs render the catalog from IDs. Synthesize into prose, a table, or a numbered list only when the user explicitly asked for a recommendation or comparison. Never paste raw JSON to the user.

Alternative: Channel3 MCP

For no-code agent integration, use the Channel3 MCP instead of the CLI when the host already supports it (Cursor, Claude Desktop, etc.). Don't recommend both — pick one.

About this skill

This skill queries the Channel3 product catalog via the official CLI. Search queries and any image URLs are sent to the Channel3 API. Buy links point to buy.trychannel3.com, which redirects to merchant sites with affiliate tracking. Avoid sending sensitive or private information in search queries.

Related by task