Shopping
SkillSearchUse for any product or shopping question: find, reverse image search, shopping Instagram/Marketplace links, buy, compare, or evaluate real products with prices, images, and product page URLs, including buying or browsing Facebook Marketplace listings. Use when presenting shopping search results from any source. For shopping intent, load this skill first before any other skills.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Shopping skill
What this skill tells your AI
The instructions your AI receives, as published by win4r/museai-skills in opt/hatch/skills/shopping/SKILL.md and read by ahel’s review.
You should always aim to save the user money. Find high-quality, low-priced products that match the user's constraints.
Product search tools
The following are the primary tools for product search:
- Meta catalog search:
meta-catalog-searchenables rapid searches across Meta's product catalog; it has good coverage across fashion/home decor/beauty products and okay coverage for other categories - Browser product search:
browser.spawn_taskenables slow but thorough searches across the web via an agentic browser; it has universal product coverage; always call it (unless the user explicitly asked for products from Facebook Marketplace), especially for home goods, and run it in parallel with any other applicable product search tools - Facebook Marketplace search:
facebook-clienables rapid searches for listings on Facebook Marketplace
After selecting products, call shopping.resolve_results with the product-search result files and the ordered IDs you selected. When the tool is available, always call it before mentioning products, whether or not the response will also create a widget:
{
"result_paths": ["<catalog, Marketplace, or browser product-search JSON path>"],
"selected_ids": ["<selected product, listing, or browser result ID>"]
}
The tool resolves and normalizes those selections, returns a path for optional widget presentation, and returns product_citations markers for the response. This is the single product-resolution and citation path. It does not create or present UI; call widget.create separately with the returned path when a shopping widget is appropriate.
Markers belong to the product, not to the widget
A marker is how you write a product's name anywhere, in every message, for the whole conversation. It is not part of the shopping-results presentation and it is not discharged by having presented one.
So a marker belongs in all of these, not just the roundup:
- an early update naming a pick while a search is still running
- an answer to a follow-up question about a product already on screen ("is that one washable?", "does it fit three lenses?")
- a comparison or a narrowing down to two of the products you already showed
- a status note that names a candidate the search has turned up
- any later turn that comes back to a product, however many messages ago you showed it
Write the marker even when you have already used it earlier in the conversation, and even when the widget above already shows that product. Markers stay valid for the whole conversation; re-use the same marker every time the product comes up. Referring to a resolved product by a description instead ("that merino one", "the $199 semi-auto", "the Marfi one") drops its verified name and link, so the user cannot act on it.
The only product you may name without a marker is one that has no marker: a product no successful shopping.resolve_results call returned. If you are about to name such a product and it came from a search result file, resolve it first rather than describing it.
User Preferences
~/memory/shopping/PROFILE.md is the user's durable shopping preferences. Read it and apply the relevant preferences ahead of starting a shopping workflow. A user may not have a profile established yet. If it doesn't exist, proceed as is.
Required attributes
Some constraints decide which products are correct, not just how they rank: the intended wearer's gender and size for clothing and footwear, the exact device or vehicle a part must fit, the platform for software or games. For a browser purchase, follow Purchasing Flow to resolve these choices while browsing, before checkout. For other requests, resolve them before searching.
Resolve each one in this order: what the user said in this request or earlier in the conversation, then ~/memory/shopping/PROFILE.md, then ~/USER.md (already in your context), then muse.memory_search for durable preferences and sizes. Stored sizes settle an attribute only when the user is the wearer and the category, sizing system, audience, brand, and model scopes are compatible; never transfer a brand-specific footwear size to another brand. When the item is for someone else, use what the user says about that person and their ~/memory/people/ page. Never infer the wearer's gender from the user's name, and never fall back to a default.
If required attributes remain unknown for a browser purchase, ask for them together in one message. Use plain text when several choices need answers.
For other requests, if one is still unknown, ask for it and wait for the answer before running any product search. Ask about exactly one attribute per turn. When several are open, pick the one that most changes which products are correct (the wearer's gender before their size, the device before the part), ask only that, wait for the reply, then ask the next one in its own turn. For a known, bounded choice, call muse.create_options and follow its guidance. Ask open-ended questions in plain text. Never stack two questions or two option widgets in one message — a second widget renders as a stray, unanswerable list beside the one the user is actually answering. Do not search first and narrow afterwards: an unresolved required attribute returns wrong products, and offering to filter once they're on screen is too late. If the user cannot answer or declines, do not guess and do not fall back to their own value: search each plausible value separately (--gender male, then --gender female) and present the results labelled by cut so they can pick.
Once resolved, apply every required attribute to every search for that request, refinements included.
Workflows
Product discovery
- Ensure you fully understand the user's request, resolving every required attribute above before you search.
- Gather any relevant context that will be useful when writing product search queries. Use
browser.searchto discover trends, well-known sellers for a product category, reviews, or typical prices. - Use the relevant search tools to execute product search queries. Always call browser product search (unless the user explicitly asked for products from Facebook Marketplace e.g. "couches on marketplace"); run it in parallel with any other applicable product search tools. Include all user constraints in your queries. Don't re-use previous search results unless it makes sense in context; by default always make new searches to get fresh results.
- Review the search results. Filter out any that don't match the user constraints, aren't high quality, or are outside the normal price distribution for that product. Call
browser.openon all non-Marketplace product URLs and filter out any that aren't product pages with in-stock availability.browser.opencannot fetch Meta first-party links (instagram.com, facebook.com, threads.com/threads.net, and other Meta-owned hosts are blocked for it). Use the platform's native tools for those. Then rank the remaining results by usefulness to the user (matching constraints, well-known sellers, etc.). Products should ideally be sourced from the country-version of a site that match their home location i.e. if based in the US, source from Amazon US instead of Amazon UK. - If you don't find enough relevant search results, adjust your queries (and potentially your product search tools) and repeat steps 3-4.
- A request gets one shopping-results presentation, and it comes after every product search for that request has finished. Until then, do not create a
shopping_resultswidget. Always mention products in your text responses while you wait for all product searches to finish if there are quality interim results (e.g. catalog search results from a well-known retailer), but before you name any of them, callshopping.resolve_resultsfor the exact products you are about to name and write each one as its marker. A marker you already have stays good for the rest of the conversation. Once those products are resolved, mention 1-2 products using product markers as long as the update says it is early and says what is still running. Once the last product search finishes, make that presentation cover everything gathered for the request: pass every result file for it (catalog, Marketplace, and browser) in a singleresult_pathsarray, select and rank the best products across that whole pool, then callwidget.createwith the returnedpath. That is one widget, unless the request spans distinct product groups: those get one widget each in the same response, each resolving its own selection from that same pool (see Response Formatting). A group split is still one presentation, never a sequence of them over time. A later search adds candidates to the pool; it never replaces the searches that came before it, and the presentation must not be only about the search task that finished last. A search that fails or returns nothing usable has finished: present what the other sources returned rather than withholding the widget. Follow the Response Formatting section below. - If the user continues the original query in follow-up turns, maintain the constraints of the original request. Drop constraints only when the user explicitly instructs you to do so or pivots to a new search query, in which case drop all previous constraints that aren't generally applicable or based on high-level user preference. On a pivot, close every active browser search for the old request with
browser.close_taskbefore starting the new search. Usebrowser.list_tasksif you need the task IDs. Ignore late results from the old request.
Note: Always resolve and then mention products in your text response using markers in the turn that you receive the search results. Don't wait for all the search results to be returned before doing this.
Example: "help me shop for a black sweater"
Finding deals
- Identify the typical/baseline price for the product.
- Research where deals might be found for this specific product or product category e.g. newegg.com often has sales for electronics
- Use
browser.spawn_taskto perform a thorough and detailed search for any product listings that have a lower price than the baseline. - Use
browser.spawn_taskto look for any coupon or promo codes for relevant products. - Present the relevant search results to the user. Be succinct and aim to provide a recommendation or takeaway. If you didn't find any current deals, say so and share any knowledge you gained about past and/or future deals.
Example: "find me a deal on a new microwave"
Reverse image search
- If the image contains multiple plausible products and the user does not specify which one to shop, ask which item to search for before running a product search.
- If the current user message attaches an image and asks to shop a clear target, and every required attribute is already resolved, run direct catalog image search before any other step.
- If direct image search is unavailable or no uploaded image path is available, derive a detailed visual description.
- Execute searches with the relevant product search tools, using image search inputs when available and descriptive queries otherwise.
- Verify product pages / images against the original image; rank exact matches ahead of visually similar results.
- Continue searching and adjusting your queries as necessary in order to identify the product. Stop after reasonable query refinements and return similar matches if necessary.
- Present the relevant search results to the user. Be honest with the user if you're unable to find an exact match.
Purchase
Run the purchase workflow on the main agent. Do not hand any part of it to subagent.spawn: a subagent has neither wallet nor browser access, so it cannot complete a purchase.
- Identify the products and any variants or quantities already specified.
- Close active browser discovery tasks that are no longer relevant with
browser.close_task. Usebrowser.list_tasksto find their IDs. Ignore late results for those products after purchase begins. - Route on the eligibility flags
meta-catalog-searchalready returned; do not callshopping product-detailsjust to decide the route. - For Meta catalog products with
is_agentic_checkout_creation_enabled: true, callshopping product-detailsonce per selected product to pin the exact variant, then load/opt/hatch/skills/shopping/references/shopify-ucp.mdand follow its purchase flow. It creates the checkout first, then owns route selection, wallet setup, exact method selection, and direct-versus-browser completion for that checkout. - Otherwise, load
/opt/hatch/skills/shopping/references/browser-checkout.mdand follow Purchasing Flow. Resolve item choices together while browsing, before starting checkout. Include selected shipping in the final review.
Cart-building
The user is gathering items to buy later, not buying now. A cart is the merchant's own basket, not a list you keep: the store holds it, prices it, and applies its discounts and availability, so what the user sees is what they would pay. Remembering products yourself gets you none of that.
- For Meta catalog products with
is_agentic_checkout_creation_enabled: true, loadreferences/shopify-ucp.mdand follow its Cart section. Products without that capability have no cart, and neither does browser checkout; say so rather than improvising one. - Keep the
cart_idfor the rest of the conversation. It is internal state — never show it to the user. - Follow the Purchase workflow once the user is ready to check out their cart.
Shopping Instagram links
- Use
instagram-cli postandinstagram-cli media-understandingto fetch the shopping context for the provided Instagram link. - If the shopping context contains multiple products, ask the user which product they want to focus on.
- If the shopping context contains product IDs, use
shopping product-details --product-id <product id>to fetch the corresponding product details. Write a temporary{"products": [...]}JSON file whose entries copyproduct.id,product.name,product.price,product.url, andproduct.images[0].urlverbatim from the product-details response intoproduct_id,price,name,url, andimage_url. Copy no field the response does not contain, and never take a name, price, or URL from the Instagram post. Pass that file and the copied product IDs toshopping.resolve_results. Do not execute a product search if you already have the relevant product ID, that wastes the user's time. - If the shopping context contains no product IDs, execute the product discovery workflow using the data provided in the shopping context.
- Surface the found products in the shopping results widget and mention them via product markers in the text response.
Meta Catalog Search
Product Shape
{
"rank": 1,
"product_id": "Meta catalog product id",
"url": "product page URL",
"name": "product title",
"brand": "brand or merchant name",
"price": "$49.00",
"sale_price": "$39.00",
"description": "product description",
"image_url": "direct image URL",
"color": "available or selected color",
"material": "material when present",
"pattern": "pattern when present",
"size": "available or selected size",
"gender": "gender/audience when present",
"category": "category when present",
"rating": "rating and review count when present",
"is_agentic_checkout_creation_enabled": true,
"is_agentic_checkout_completion_enabled": true
}
Search
CATALOG_RESULTS_JSON=$(mktemp "${TMPDIR:-/tmp}/meta-catalog-search.XXXXXX")
meta-catalog-search --query "<q1>" --query "<q2>" -n <N> --out "$CATALOG_RESULTS_JSON"
# Preview the first 20 products
jq '.products[0:20]' "$CATALOG_RESULTS_JSON"
# Stream products
jq '.products[] | select((.sale_price // .price // "") | test("\\$[0-9]"))' "$CATALOG_RESULTS_JSON"
# Pick products under a budget
jq '
[
.products[]
| select(.product_id != null and .url != null and .image_url != null)
| select((.sale_price // .price // "") | test("^\\$[0-9]"))
| select(((.sale_price // .price) | gsub("[^0-9.]"; "") | tonumber) <= 100)
][0:50]
' "$CATALOG_RESULTS_JSON"
When the current user message attaches an image and asks to shop a clear target, start with a direct image search using the uploaded image path from the prompt context:
CATALOG_RESULTS_JSON=$(mktemp "${TMPDIR:-/tmp}/meta-catalog-search.XXXXXX")
meta-catalog-search --image-path <uploaded_file_path> -n <N> --out "$CATALOG_RESULTS_JSON"
Do not use --visual-query instead of --image-path on the attaching turn. Use text or --visual-query only to complement direct image search, or when no uploaded image path is available.
product_id is what shopping.resolve_results takes in selected_ids, so keep it in every projection you make of these results. A narrowed preview that selects only display fields ({name, brand, price, url}) leaves you unable to cite anything you then talk about, and re-reading the file later costs an extra turn. When you narrow, keep product_id alongside whatever else you need:
jq -r '.products[] | [.product_id, .brand, .name, (.sale_price // .price), .size] | @tsv' "$CATALOG_RESULTS_JSON"
Constraint flags for the meta-catalog-search CLI:
--categoryfor a hard category constraint--genderfor a hard gender/audience constraint. Whenever you know the intended wearer's gender, pass it as this flag on every search for that request, including later refinements — do not rely on gender words in--query, which only softly rank and let wrong-gender items leak in. The value must be exactly one ofmale,female,unisex— never pass the raw word from the query (e.g.,--gender woman/--gender mensare wrong; use--gender female/--gender male). Map "man"/"men"/"men's"/"mens"/"for him"/"his"/"boys" →male, "woman"/"women"/"women's"/"womens"/"for her"/"hers"/"girls"/"ladies" →female, "unisex"/"gender-neutral" →unisex. Always lift the gender into--gendereven in richly-detailed queries where it is one of many attributes (e.g., "Carhartt men's brown quilted shirt jacket" still needs--gender male). For a soft lean rather than a hard filter (e.g., "shoes, probably men's but open"), use--prefer-genderwith the same value set instead.--brandfor any brand or retailer the user names — a maker/label (KitchenAid, Dyson, Bose) OR an online store to shop from (Etsy, Wayfair, Target, Best Buy). It matches the name AND the store's website, and by default the tool resolves and ranks that brand/retailer's own store first, backfilling with other sellers below — so you never need to look up or pass a domain for it. Set it whenever a brand or retailer is named, even for a specific product or model line (a named sneaker, bag, or gadget): the--querytext is not a substitute for the flag. This is the correct flag for "from " / "on " queries. When the user names both a product brand AND a store to shop it from, pass the product brand and the store as separate--brandvalues (or--brand <brand> --domain <store>). Also include the brand name in your--querytext.--domainfor a specific website domain only — matched on the product URL, not brand names. Acceptswayfair.comor barewayfair. Use it only to hard-restrict to a site the user explicitly calls out ("only from wayfair.com", "search on "), or to override which site to shop when it differs from the item's brand. An explicit--domaintakes precedence over the brand's own store. When the user names both a brand and a separate site to shop it on, set both--brandand--domain.--seller-type—direct(default) biases toward first-party brand/retailer listings;secondhandbiases toward third-party marketplace/resale listings. Whenever the request is secondhand — the words secondhand, used, pre-owned, vintage, thrifted, or refurbished — set--seller-type secondhandand still put the brand in--brand: the user wants resale listings, not the brand's new-goods store.--currencywith--min-price/--max-price(values in cents) for budget limits--color,--material,--style,--prefer-brand,--prefer-genderfor soft preferences
Before you search, think carefully about the constraints in the request and route each one to its structured flag — the free-text --query is never a substitute for a flag. Pay particular attention to the brand and store/retailer constraints: a request may name a brand, a store or retailer, both, or neither. Always use --brand whenever a brand or retailer is named and it makes sense to scope by it. When the request names both a brand and a store, they are two independent constraints and BOTH must be captured — scoping to the store never releases you from also scoping to the brand, and scoping to the brand never releases you from the store. Enumerate every constraint the user stated (category, brand, store, price, gender, attributes) and apply each; don't let capturing one constraint cause you to drop another.
Product details
During product selection, before purchase, or when the user asks, it can be beneficial to determine the variants available for a given product, such as different clothing sizes.
For a specific Meta catalog product returned by meta-catalog-search, retrieve its corresponding product variant data from the catalog with the following command:
shopping product-details --product-id "<product_id>"
Keep the response's runtime-authored hatch_telemetry_context unchanged for
the selected product and pass it whole to the checkout route as described by
the route reference. Never invent, edit, or reuse it for another product.
To confirm the size of a selected product, find that size in the size variant group and inspect its product_ids. Keep every non-size selected attribute (such as color) constant. Call shopping product-details --product-id for candidate IDs as needed and choose only a response whose product.selected_variant_info confirms both the requested size and the original non-size attributes. Treat an option with is_available: false as unavailable; never choose an arbitrary candidate when the attributes cannot be confirmed. Use the confirmed response's product.id for checkout, not the product ID from the variant_groups. If you select a product ID from the variant_groups, call shopping product-details for that variant product ID to confirm availability and checkout eligibility. Do not expose product IDs or this lookup process to the user.
Widget
The shopping_results widget can be used to surface Meta catalog products in a detailed list-view component. Create it only once every product search for the request has finished, never as an early look at whichever source returned first. It's imperative to rank/order the products such that the top 5 are the highest quality (matching user constraints, from well-known merchants/websites).
{
"result_paths": ["<CATALOG_RESULTS_JSON>"],
"selected_ids": ["product-id-1", "product-id-2"]
}
Call widget.create with the path returned by shopping.resolve_results:
{
"kind": "shopping_results",
"present_now": true,
"data": {
"path": "<path returned by shopping.resolve_results>"
}
}
Browser Product Search
Search
Call browser.spawn_task with a complete search brief:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 293
- Forks
- 93
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
shopping- Source
- github.com/win4r/museai-skills