Install
$ agentstack add skill-omarsaleh506-skills-shop-scout ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
shop-scout
Stop hopping between store tabs. Give shop-scout a product or a shopping list and it will, for each item: find who sells it, rank sellers by the price you'll actually pay after discounts and shipping, judge whether each seller is safe, and dig up working coupons — then recommend the best buy and, across a list, the smartest single-store bundle.
This skill is agent-agnostic. It assumes only three generic capabilities, named however your runtime provides them:
- web search — issue a query, get back result URLs/titles/snippets.
- fetch a URL — retrieve a page's content as text/markdown.
- run N tasks concurrently — sub-agents where your runtime has them,
otherwise just sequential or concurrent calls.
Quality depends heavily on how you fetch pages. Major retailers (Amazon.sa, Noon, …) render prices with JavaScript and block naive fetchers. So shop-scout prefers a real scraping backend (Firecrawl) and falls back to plain fetch only when nothing better is configured. Read Backend tiers next — they are not equivalent.
Backend tiers (auto-detected — "use what you have")
Detect the backend in this exact order and announce which one you're using:
FIRECRAWL_API_URLis set → self-hosted Firecrawl. ⭐ Recommended.
Your own Firecrawl (Docker) at that base URL. No account, no API key, no quota — and reliable extraction on JS-heavy stores. This is the setup that answers the usual objection to Firecrawl ("I don't want an account/quota"). One-time setup is in references/self-host-firecrawl/ (a docker-compose.yml you bring up with docker compose up -d, then export FIRECRAWL_API_URL=http://localhost:3002).
- Else if
FIRECRAWL_API_KEYis set → Firecrawl cloud.
Base URL https://api.firecrawl.dev/v2, header Authorization: Bearer fc-…. Same endpoints as self-host, plus server-side structured (json) extraction. Subject to your plan's quota.
- Else → built-in web search + fetch. Best-effort fallback only.
Zero setup, but it hits bot walls and JS-rendered prices on major retailers and can't reliably read live prices. On this backend you do not print a comparison table — you output the honest research summary (see Output: decide the format first) and point the user at Firecrawl.
SHOP_BACKEND overrides detection: auto (default) | firecrawl (force Firecrawl; requires URL or key) | builtin (force native fetch).
> Don't present the three tiers as equal in your output. If you ended up on > built-in, say so and point the user at self-host. If on Firecrawl, just use it.
How to actually call Firecrawl (endpoints, params, the search-first-then-scrape workflow, cloud vs self-host differences) lives in references/firecrawl-usage.md — read it before your first Firecrawl call. A ready-made wrapper, $SKILL_DIR/scripts/firecrawl.sh, auto-detects self-host vs cloud from the same env vars and works from any agent that can run a shell command.
> $SKILL_DIR — this skill's own directory (the folder containing this > SKILL.md, references/, and scripts/). Resolve it per install: on a Claude > Code plugin install it's ${CLAUDE_PLUGIN_ROOT}/skills/shop-scout; for an > npx skills / manual / other-agent install it's wherever this SKILL.md > lives. Always reference bundled files through it, never a hardcoded path.
Configuration (env vars — all optional, sane defaults)
| Var | Default | Meaning | |---|---|---| | SHOP_BACKEND | auto | auto \| firecrawl \| builtin | | FIRECRAWL_API_URL | — | Self-host base URL, e.g. http://localhost:3002. Highest-priority backend. | | FIRECRAWL_API_KEY | — | Firecrawl cloud key (fc-…). Never write it to a file or commit it. | | SHOP_SCOPE | both | saudi \| global \| both — which store list to search. | | SHOP_STORES | — | Comma list of pinned/preferred stores; extends the bundled lists. | | SHOP_PARALLEL | 3 | Max products researched concurrently. Higher = faster but more tokens and hits rate limits sooner. | | SHOP_MAX_LISTINGS | 5 | Max listings scraped per product (token control). |
Read env vars from the environment at runtime. A documented template lives in .env.example — copy it to .env, fill values, and load it however your runtime does (.env is gitignored). The store lists are in references/stores.md and are meant to be edited.
The 4-step pipeline (run per product)
For each product, do these four steps in order. Stay disciplined about tokens (see Token-efficiency rules) — search broadly, scrape narrowly, extract fields, never dump whole pages.
Step 1 — Find sellers (deterministic selection)
The seller set and its order must be reproducible: same inputs → same shortlist → same row order, every run. The friend who called this skill "different every time" was hitting an unstable seller set. Follow these rules exactly — they are what fix it:
- Scoped search first. Build the store list from
SHOP_SCOPE(+ any
SHOP_STORES) — see references/stores.md — and search scoped to those domains: site:noon.com OR site:amazon.sa OR … . One scoped search per product where possible.
- One general fallback, only if thin — and defend against a flaky search.
A search that returns 0 results is often a transient backend hiccup, so retry it once before believing it (the live /search layer genuinely returns 0 then 10 for the same query seconds apart). If the scoped search still yields fewer than SHOP_MAX_LISTINGS distinct in-list sellers, run exactly one unscoped search (" buy"). The candidate pool is the de-duplicated union of the scoped search and that single fallback — a transient empty response must never silently shrink the shortlist. Do not keep firing extra searches to "find a cheaper one" — that is what makes the seller set (and the winner) drift between runs.
- De-duplicate by seller. One listing per seller — the canonical product
page. If a seller appears more than once, keep the first by the order below.
- Fixed priority / tie-break order (this is also the output row order):
- pinned
SHOP_STORES, in the order the user listed them; - first-party listings — the store sells its own stock (a retailer like
Jarir or extra, or the brand's own store), or the marketplace itself holds the buy box ("sold by ");
- all other sellers — including a third-party marketplace seller (e.g.
eKart on Noon), which is not first-party. Tier 1 is ordered by the user's pin order. Tiers 2 and 3 have no user-given order, so within each, sort by domain alphabetically — never by search rank or by price (both jitter run-to-run, and ordering by them is the bug).
- Cap deterministically. After ordering, take the first
SHOP_MAX_LISTINGS
and drop the rest. Don't reshuffle later.
The result is a fixed, ordered shortlist of candidate listing URLs. Steps 2–4 only enrich these rows — the row set and order never change after Step 1.
→ Firecrawl: POST /search. Built-in: your native web search.
Step 2 — Compare price & discount
Scrape only the top SHOP_MAX_LISTINGS listing URLs from step 1. From each, extract just these fields: price, list/original price, discount %, currency, in-stock, shipping cost / free-shipping threshold, and the seller/merchant name (important on marketplaces like Noon/Amazon/AliExpress where third parties sell under one storefront).
Confirm product identity before you compare. Each listing must be the same model and variant — capacity, size, edition (colour aside). A different SKU (e.g. a 24,000 mAh vs a 27,000 mAh power bank) is not the same product: note the mismatch in its row, don't merge it into the comparison as if identical. Don't assume free shipping either — if the page doesn't state shipping, mark it n/a and leave it out of effective price (an invented "free ship" silently understates what the user pays).
Rank by effective price = price − applicable coupon + shipping, in a single currency. "Applicable" is the strict Step-4 sense — only a coupon classified ✅ applicable reduces effective price. If the scope mixes regions and you must convert, pin one FX source and stamp it — e.g. FX: 1 USD = 3.75 SAR (google.com/finance, ) — and use that one rate for the entire run, so the ranking can't flip on a second lookup. A nominally cheaper listing with paid shipping or no stock often loses.
→ Firecrawl: POST /scrape with formats:["markdown"], onlyMainContent:true, and location.country set to the store's region (e.g. SA). On cloud you may add a json extraction format to get the fields back structured in one call (see references/firecrawl-usage.md). On self-host without an LLM key, the json format is unavailable — scrape markdown and extract the fields yourself.
Step 3 — Judge seller safety
Give each shortlisted seller a verdict: ✅ Trusted / ⚠️ Mixed / ❌ Risky, with a one-line reason and — always — the reference link(s) the verdict is based on (Trustpilot / Reddit / scam report / the listing's own rating page) so the user can verify the risk rating themselves. A verdict with no source link is incomplete — never ship one. Base it on:
- On-listing seller signals — read these first; they're the most reliable.
The seller's rating, number of ratings, and a small sample of the lowest-star reviews (that's where fraud, fakes, and non-delivery show up) — a few top and a few lowest, not all. On a marketplace, judge the specific third-party seller holding the buy box, not just the storefront.
- Reputation search: Trustpilot / Reddit / " scam / reviews /
complaints". One focused search is usually enough — but Trustpilot and Reddit routinely antibot-block scrapers (even self-host Firecrawl), so when the page won't load, cite the search snippet (review count / score) rather than dropping the source.
- Heuristics: brand-new seller + far-below-market price + no ratings = ❌.
Established marketplace, high rating, many reviews, official/brand store = ✅.
Never invent a verdict — if you couldn't find enough signal, say "insufficient data" rather than guessing.
Step 4 — Find coupons (with a strict applicability test)
Hunt for codes that apply to the shortlisted listing(s) via coupon aggregators, the store's own first-order / newsletter / app promos, and social media (X/Twitter, Instagram bios, regional Telegram channels). Sources and search patterns are in references/coupon-sources.md.
Classify every code before it can touch effective price — this is what stops the winner from flipping on a coupon you can't actually use:
- ✅ applicable — all of: not expired, min-spend ≤ this cart, category
matches the product, and (if first-order-only) the user is actually new. Only applicable codes are folded into effective price.
- ❔ unknown — any single condition can't be verified (no expiry shown,
unclear min-spend, "selected items" with no list). Show it in the coupon column as an FYI; never fold it into effective price.
- ❌ not applicable — a condition is known to fail (expired, min-spend above
cart, wrong category, first-order-only for a returning user). Don't show it.
For each code you show, print: the copy-ready code, savings (% or amount), how to apply (e.g. paste into the cart/checkout "Promo code" / "Coupon Code or Gift Card" box, or "auto-applied via link"), its conditions, its classification, and a literal "verify at checkout" — aggregator codes are frequently dead.
Stacking: never assume two codes stack. Apply only one unless the store's own T&Cs state stacking is allowed — and then link that T&C.
Multi-product handling
Input can be: an inline list, a file (shopping-list.md / .txt, one item per line, # comments ignored), or interactive (ask for items). If the user gives a file path, read it.
Fan out up to SHOP_PARALLEL items at a time (default 3): run the 4-step pipeline per item concurrently if your runtime supports it, else sequentially. Higher parallelism is faster but burns tokens faster and trips rate limits sooner — say so if the user asks to crank it up.
Output for a list = one comparison table (or honest summary, per the gate below) per item, plus a consolidated cart summary with a grand total and a single-store bundle tip. The exact layout is pinned in Worked example 2 under the output contract.
Output: decide the format first
Before writing anything, run the data-floor gate. It decides whether you've earned a comparison table or must degrade to an honest summary — this is the gate that stops shop-scout from ever printing a fake, made-up table:
- Built-in backend → always the honest summary, never a table. Built-in fetch
can't reliably read prices off JS-heavy stores, so a table built on it would be fiction. (This was the #1 reason past runs looked "stupid.")
- Firecrawl backend (self-host or cloud): a price comparison is only
meaningful if you actually read prices. Count the shortlisted listings from Step 1: if half or more have no real price (blocked, n/a, or never loaded), degrade to the honest summary. Otherwise, print the comparison table. (Stock and shipping enrich the table but don't gate it — a missing stock value just becomes n/a in its column; only missing prices sink the table, because price is the whole comparison.)
A short honest summary always beats a confident fake table.
A) Honest research summary (degraded mode)
Use this whenever the gate says "no table." Be useful and truthful — no invented prices, no fabricated verdicts:
- What it is — the product, plus any spec you actually pinned down.
- Where to buy — the candidate seller links you found (direct product URLs
where you have them), as a plain list.
- Real prices obtained — only prices you actually read, each labelled with
its source link. A price from a comparison/aggregator site (KSAPrice, Pricena, …) is not a store read: label it indicative and still link the store's own page. Got none? Say so: "no live prices could be read on this backend."
- Seller-safety notes — only verdicts you can back with a source link; skip
the rest.
Then append the upgrade block verbatim. This is the one place it's defined — reuse it wherever the honest summary is shown:
> ⚡ Want real price comparison? Enable Firecrawl — built-in fetch is blocked > by big stores, so it can't read live prices. > - Recommended — self-host (no account, no quota): in > references/self-host-firecrawl/, run docker compose up -d, then > export FIRECRAWL_API_URL=http://localhost:3002 and re-run shop-scout. > - Or cloud (free tier): create an account at → > Dashboard → API Keys → copy the fc-… key → > export FIRECRAWL_API_KEY=fc-… and re-run shop-scout.
B) Comparison table contract (only when the data floor is met)
This layout is a contract, not a suggestion — same columns, same order, every run. Don't add, drop, reorder, or rename columns. Every row carries everything needed to buy without re-searching. Fill each column exactly per this schema:
| Column | Format (fill exactly like this) | |---|---| | Store | the storefront you'd buy from (Noon, Amazon.sa, Jarir, …); when a marketplace listing is sold by a third party, append the seller — Noon (eKart) | | Price | currency + amount, always — SAR 349 (never a bare number) | | Discount | -22% only if the list price is known, else n/a (don't compute one you can't source) | | Effective price | price − applicable coupon + shipping, same currency, bold — **SAR 312** | | **Sto
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: OmarSaleh506
- Source: OmarSaleh506/skills
- License: MIT
- Homepage: https://skills.sh/OmarSaleh506/skills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.