Product research and comparison shopping assistant. Use this skill whenever the user wants to find, compare, or filter products on any online shopping platform (Amazon, Rakuten, Yahoo Shopping, IKEA, Costco, Taobao, JD, etc.). Triggers include "find me a product", "search for X on Amazon", "help me buy Y", "compare products", "I need a [product]", "search [platform] for", "shop for", "looking for something to buy", any mention of shopping criteria like price range, star ratings, product specifications, or when the user names a product category and an online store. Also triggers when the user changes filtering criteria mid-search (e.g., "relax the rating to 3.9", "what if we increase the budget"). This skill caches all search results in a local SQLite database so criteria changes can be answered instantly without re-searching.
This skill requires agent-browser for all web interaction. Before starting any search:
agent-browser skill is available in your skill listskill tool to load agent-browsernpx skills add agent-browser
Then load the skill after installation.Do NOT proceed with any search steps until agent-browser is confirmed working. Quick check:
agent-browser --version
If the CLI itself is missing, install it: brew install agent-browser or npm i -g agent-browser, then agent-browser install to download the browser.
Online product research is tedious: you search, open dozens of tabs, compare specs, lose track of what you've already checked, and when criteria change, you start over. This skill solves that by:
User request
↓
[1] Clarify requirements (platform, criteria, budget, language)
↓
[2] Search platform via agent-browser → extract product list
↓
[3] Cache basic info in SQLite → immediately filter by rating/price/review_count → candidate list
↓
[4] Verify candidates only (parallel batch — visit product pages, extract specs, store page_text)
↓
[5] Present results (table + recommendation)
↓
[6] User changes criteria? → Re-query cached DB, only re-search if data insufficient
Before searching, always ask the user:
| Question | Why it matters |
|---|---|
| Platform | Which site to search (Amazon.co.jp, Rakuten, etc.) |
| Product type | What they're looking for |
| Budget | Price ceiling |
| Key physical/technical criteria | Weight, size, material, features — whatever matters for this product |
| Rating threshold | Minimum star rating (default: suggest 4.0+) |
| Review requirements | e.g., "must have Japanese reviews", minimum review count |
| Quantity needed | How many candidates to present (default: 10) |
| Site language | What language should the site display in? (default: infer from platform — e.g., Amazon.co.jp → Japanese, Amazon.com → English). If unclear, ask the user. |
Use the user's language throughout. If they write in Chinese, respond in Chinese. If Japanese, respond in Japanese.
Use agent-browser to search the target platform. Construct search queries using the user's product description + key criteria keywords in the platform's language.
agent-browser starts with a clean browser (no cookies, no language preferences). Many sites default to English even for local domains (e.g., Amazon.co.jp shows English to new visitors). Before any search, switch the site to the correct language.
Amazon language switch examples:
https://www.amazon.co.jp/-/ja/ or append ?language=ja_JP to any URLhttps://www.amazon.co.jp/-/zh/ or ?language=zh_CNhttps://www.amazon.co.jp/-/en/ (usually the default)For other platforms: look for a language/locale selector in the page header or footer, or set the Accept-Language header if supported.
Why this matters:
product_pages will be in the site language — if English, Japanese keyword searches on cached data won't workSearch target: 100+ products. Broad coverage here means better filtering in Step 3 and less re-searching when criteria change. Aim for 100 unique products before proceeding. Fallback rules:
Search strategy:
total_in_db output (UNIQUE(platform, product_id) deduplicates, so this count reflects unique products even when queries overlap). Stop paginating once you reach 100+.Extraction pattern (Amazon example):
// Run via agent-browser eval to extract search results
Array.from(document.querySelectorAll('[data-asin]'))
.filter(el => el.dataset.asin.length > 3)
.map(el => ({
product_id: el.dataset.asin,
name: (el.querySelector('h2 span') || {}).textContent || '',
rating: /* extract star rating */,
price: /* extract price */,
review_count: /* extract review count */,
url: 'https://www.amazon.co.jp/dp/' + el.dataset.asin
}))
Adapt the selectors for each platform. The goal: get product_id, name, rating, price, review_count, and URL from the search results page itself.
CRITICAL: Write extracted data to a JSON file, not to LLM context. Large product lists (50+ items) will be truncated or corrupted if passed through the LLM. Use this pattern:
// In agent-browser eval: extract products and write JSON file directly
const products = Array.from(document.querySelectorAll('[data-asin]'))
.filter(el => el.dataset.asin.length > 3)
.map(el => ({ /* extraction logic */ }));
// Write to temp file
require('fs').writeFileSync('/tmp/products_page1.json', JSON.stringify(products));
products.length; // return count only
Then insert via file:
python3 product_db.py insert --db <DB_PATH> --json-file /tmp/products_page1.json --platform amazon.co.jp
The insert command outputs received, inserted, skipped counts. If received is much less than what you extracted, the JSON file was corrupted. If skipped is high, products were duplicates from overlapping searches (expected).
Store basic info (name, price, rating, review_count, URL) for all extracted products in SQLite. This is cheap — just search result data, no page visits needed. Then immediately filter to identify candidates for detailed verification.
Database directory: ~/.cache/smart-shopper/
Filename format: {YYYY-MM-DD}-{topic-slug}-{4hex}.db
Example: 2026-03-29-garden-table-set-a3f1.db
Every DB contains a _meta table with created_at, last_accessed, description, and search_queries (JSON array). Use this for auto-matching.
Start of skill invocation
│
├─ Already have DB_PATH in this conversation?
│ └─ YES → reuse it, skip discovery
│
├─ Run: product_db.py discover --query "user's search terms" --auto-cleanup
│
├─ 0 databases found → create new DB
│
├─ Results with match_score > 0?
│ ├─ Exactly 1 match → auto-select, tell user: "Continuing with [description]"
│ └─ Multiple matches → show list, ask user to pick
│
├─ All results have match_score = 0 (no keyword overlap)?
│ ├─ Any DB accessed in last 24h → show it as option, ask if related
│ └─ Otherwise → create new DB
│
└─ discover also prunes DBs not accessed in 30+ days
The key rule: never auto-select a DB without query matching. Recency alone is not enough — a "keyboard" search must not silently attach to yesterday's "chair" DB.
Auto-discovery command (also prunes >30 day old DBs):
python3 /path/to/smart-shopper/scripts/product_db.py discover --cache-dir ~/.cache/smart-shopper --query "user's search terms" --auto-cleanup
This returns matching DBs sorted by relevance score, and silently removes any DB not accessed in 30+ days.
python3 /path/to/smart-shopper/scripts/product_db.py create --topic "garden table set"
This generates a safe filename internally (sanitized slug + UTC date + random hex) and returns the path in JSON:
{"status": "ok", "db": "/Users/you/.cache/smart-shopper/2026-03-29-garden-table-set-a3f1.db"}
Never construct DB filenames in shell commands — always use the create command to avoid injection risks.
Always reuse the same DB. Multiple product categories can coexist (tracked by search_query column).
Cleanup is built into the discover command — when --auto-cleanup is passed, any DB with last_accessed older than 30 days is automatically removed. No separate cleanup step needed.
Use the helper script to manage the database:
python3 /path/to/smart-shopper/scripts/product_db.py create --topic "garden table set"
python3 /path/to/smart-shopper/scripts/product_db.py insert --db <DB_PATH> --json-file /tmp/products.json --platform amazon.co.jp
python3 /path/to/smart-shopper/scripts/product_db.py query --db <DB_PATH> --min-rating 4.2 --max-price 100000
python3 /path/to/smart-shopper/scripts/product_db.py update-detail --db <DB_PATH> --product-id "B09T97Z57Y" --page-text-file /tmp/page.txt --specs '{"weight": "16.74kg", ...}'
python3 /path/to/smart-shopper/scripts/product_db.py update-detail --db <DB_PATH> --product-id "B09T97Z57Y" --verified
python3 /path/to/smart-shopper/scripts/product_db.py query --db <DB_PATH> --spec-filter "table_weight_kg>=12" --spec-match "material=wood"
Schema:
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL,
product_id TEXT NOT NULL,
name TEXT,
price REAL,
price_raw TEXT, -- original text e.g. "¥12,800" / "$128.00"
currency TEXT DEFAULT 'JPY',
rating REAL,
review_count INTEGER,
search_rank INTEGER, -- position in search results (quality signal)
url TEXT,
search_query TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
specs_json TEXT,
verified INTEGER DEFAULT 0,
UNIQUE(platform, product_id)
);
CREATE TABLE product_pages ( -- separate table to keep products lean
product_id TEXT NOT NULL,
platform TEXT NOT NULL,
page_text TEXT, -- full innerText for later extraction
fetched_at TIMESTAMP,
PRIMARY KEY (platform, product_id)
);
CREATE TABLE _meta (key TEXT PRIMARY KEY, value TEXT);
Key design decisions:
product_pages is a separate table so filter queries on products stay fast (no 100KB blobs scanned).price_raw preserves the original display text; price is the parsed numeric value for SQL comparison.search_rank captures result position — a free quality signal.specs_json stores extracted specifications as a JSON object (e.g., {"table_weight_kg": 16.74, "chair_weight_kg": 7.64, "material": "PS wood"}).UNIQUE(platform, product_id) prevents duplicates when searches overlap.verified flag tracks which products have been individually checked.Right after inserting products, filter using the data already available from search results. Do NOT visit any product pages yet — this filter uses only what's in the products table.
SELECT * FROM products
WHERE rating >= 4.2 -- replace with user's threshold
AND price <= 100000 -- replace with user's budget
AND review_count >= 10 -- default minimum; replace with user's if specified
ORDER BY rating DESC, review_count DESC;
Default minimum review count is 10 unless the user specifies otherwise. Products with fewer than 10 reviews have unreliable ratings and waste page visits.
This produces the candidate list — only these products proceed to Step 4 (page visits + detailed verification). The remaining products stay in the DB for potential re-query if the user relaxes criteria later, but their pages are never visited.
Note: criteria that can't be expressed in SQL (e.g., "Japanese reviews exist", "chair weight per component") require visiting the product page — these are checked in Step 4, not here.
If insufficient products match, tell the user how many matched and suggest criteria relaxation. Show the distribution: "Found 3 products at ≥4.2★. Relaxing to ≥3.9★ would add 4 more."
Step 3 filtered using basic search-result data (rating, price, review count). But many user criteria — weight, material, dimensions, review language — are only available on the product page. Step 4 has three phases:
Example flow:
61 candidates (from Step 3) → batch page visits → extract specs → detailed filter (weight≥12kg, chair≥7kg) → 5 finalists → cross-verify + checklist
Visiting product pages is the biggest time bottleneck (~10-15 seconds per page). Two approaches available — choose based on candidate count and environment stability.
Visit candidate pages sequentially in the parent agent. This is simpler, more reliable, and often faster than parallel sub-agents because:
Candidate ordering (visit most-likely-qualifying products first):
Resume check — before visiting any pages, skip products already extracted:
SELECT product_id, url FROM products
WHERE specs_json IS NULL
AND rating >= ? -- Step 3 filter criteria
AND price <= ?
ORDER BY price DESC, rating DESC;
This makes the workflow resumable after network interruptions — just re-run and it picks up where it left off.
For each candidate:
agent-browser open {url} → wait for page loadpython3 {script} update-detail --db {db} --product-id {id} --page-text-file /tmp/{id}_page.txtpython3 {script} update-detail --db {db} --product-id {id} --specs '{...}'Use only when: (a) ≥ 20 candidates, (b) network is stable, (c) you've confirmed agent-browser supports parallel sessions.
CRITICAL: Each sub-agent MUST use a unique --session flag to avoid browser daemon conflicts:
agent-browser --session batch-1 open {url}
agent-browser --session batch-1 eval "..."
Batch strategy:
Sub-agent delegation template:
task(
category="unspecified-low",
load_skills=["agent-browser"],
run_in_background=true,
description="Extract product details batch N/M",
prompt="
TASK: Visit product pages and extract detailed specs. Data extraction only — do NOT make pass/fail judgments.
BROWSER SESSION: Use --session batch-{N} for ALL agent-browser commands to avoid conflicts with other agents.
Example: agent-browser --session batch-{N} open {url}
DB: {db_path}
SCRIPT: python3 {path/to/product_db.py}
PRODUCTS (batch N of M):
1. {product_id} | {url} | {name}
2. ...
SPECS TO EXTRACT (based on user criteria):
- {spec 1, e.g., table weight in kg}
- {spec 2, e.g., chair weight in kg}
- {spec 3, e.g., material type}
- ...
FOR EACH PRODUCT:
1. agent-browser --session batch-{N} open {url}
2. Scroll to load dynamic content (reviews, spec tables often lazy-load)
3. Extract full page text → write to a temp file, then store:
python3 {script} update-detail --db {db} --product-id {id} --page-text-file /tmp/{id}_page.txt
4. Extract ALL specs listed above from the structured spec table (not marketing copy)
5. Store specs in specs_json:
python3 {script} update-detail --db {db} --product-id {id} --specs '{...}'
WHEN DONE: Close your browser session:
agent-browser --session batch-{N} close
MUST NOT:
- Set verified=1 (that happens later, after detailed filtering)
- Skip storing data for any product — even if specs look bad (data is valuable for later criteria changes)
- Retry more than once if agent-browser fails on a product — skip and move on
- Use agent-browser WITHOUT --session batch-{N} (will conflict with other agents)
RETURN: For each product: product_id, extracted specs summary, any specs that were NOT found on the page.
"
)
After all sub-agents complete:
agent-browser --session batch-{N} close for each batchNow that specs_json is populated from page visits, filter again using the detailed specs. This is the second filter — it uses product page data that wasn't available in Step 3.
SELECT p.*, pp.page_text FROM products p
LEFT JOIN product_pages pp ON p.product_id = pp.product_id AND p.platform = pp.platform
WHERE (json_extract(p.specs_json, '$.table_weight_kg') >= 12
OR json_extract(p.specs_json, '$.table_weight_kg') IS NULL)
AND (json_extract(p.specs_json, '$.chair_weight_kg') >= 7
OR json_extract(p.specs_json, '$.chair_weight_kg') IS NULL)
AND (json_extract(p.specs_json, '$.material') NOT LIKE '%cedar%'
OR json_extract(p.specs_json, '$.material') IS NULL)
ORDER BY p.rating DESC, p.review_count DESC;
The IS NULL clauses are critical — without them, products where the sub-agent couldn't extract a spec are silently dropped. These are exactly the products that need cross-verification in Step 4b.
This typically reduces the candidate set dramatically (e.g., 61 → 5). Only these finalists proceed to cross-verification and the full checklist.
If a spec couldn't be extracted (sub-agent reported it missing), don't discard the product — include it in the finalists for manual cross-verification in Step 4b.
Report the funnel to the user:
"61 candidates → page visits complete → detailed filter (weight, material) → 5 finalists"
Only for the small set of finalists from Step 4a, and only when a critical spec is missing or ambiguous.
When to cross-verify:
How to cross-verify safely:
"manufacturer name" + "model number" + "specs" or the equivalent in the relevant languagespecs_json in the DB and note the sourceDo NOT hardcode any specific verification site (e.g., Kakaku.com is Japan-only). Use whatever search capability the agent currently has. The model number is the key — it's globally unique and searchable on any search engine.
Identity matching rules:
Do NOT cross-verify when:
Only if the user's criteria involve something that can't be verified from text alone (e.g., "can this chair really lie flat?" or "what does the design look like?"). Don't screenshot by default — it's slow. If you do screenshot, use agent-browser screenshot + look_at tool to analyze the image.
After all data is collected (page extraction, detailed filtering, cross-verification), go through EVERY user criterion one by one for each finalist before marking it as verified. For each criterion:
[direct] = read from page, [cross-verified] = from external source, [estimated] = derived/calculated (must resolve via 4b first), [unverified] = could not confirmOnly set verified = 1 when ALL user criteria show [direct] or [cross-verified]. Products with any [estimated] or [unverified] criteria must NOT be marked verified — either resolve them via cross-verification or present them to the user with the tag visible.
Criterion type → verification method:
| Criterion Type | Examples | How to Verify |
|---|---|---|
| Numeric spec | weight ≥ 7kg, price ≤ ¥10k | Read the exact value from a structured spec table on the product page. If only a related value exists (e.g., total weight but not per-component), treat the specific value as missing → cross-verify (4b). |
| Review quality | "Japanese reviews exist", "≥10 reviews" | Open the review section of the product page (scroll/click to load it). Count reviews matching the requirement. A star rating or review count alone does NOT satisfy a review-language requirement. |
| Visual/behavioral | "fully flat recline", "fits 4 people" | Screenshot verification (4c), or find an explicit spec value (e.g., "recline angle: 180°"). Product title claims do not count. |
| Material/durability | "no cedar", "waterproof" | Read material from the spec table. Assess durability based on the material type identified. |
| Availability | "Prime eligible", "in stock" | Check the specific section on the product page. |
Use a Markdown table with the most relevant columns for this specific product search. Always include:
After the table, add a brief recommendation section organized by use case:
| 重视什么 | 推荐 | 理由 |
|---|---|---|
| コスパ | ①商品名 | 最安で基準クリア |
| 品質 | ③商品名 | 最高評価+レビュー多数 |
When the user changes criteria:
product_pages): run Step 4 batch extraction for themproduct_pages exists, specs_json is NULL): skip extraction, go directly to cross-verification (Step 4b)This is the key advantage of the caching design: what previously required 20 minutes of re-searching now takes 2 seconds.
If the user asks for 10 products but only 6 genuinely meet all criteria, say so. Don't pad the list with products that don't qualify. Suggest criteria relaxation instead.
Product titles and bullet points often exaggerate. "180° fully flat" might be 150°. "Lightweight" might be 11kg. When a specific claim matters to the user, verify it on the actual product page's structured specification table, not from marketing copy or search results summaries.
When a specification determines whether a product meets user criteria, the value MUST come from direct reading on a product page spec table, manufacturer spec sheet, or cross-verified external source.
Never derive a user-critical spec through calculation. Example: if the page shows total weight (34kg) and table weight (18kg), do NOT calculate bench weight as (34-18)/2 = 8kg. Instead, treat bench weight as "not directly stated" and trigger cross-verification (Step 4b).
Why this matters: mathematical derivation introduces compounding errors (shipping weight vs. product weight, accessories included vs. excluded, rounding). A derived value that passes a threshold by a small margin (8kg vs. 7kg requirement) is especially unreliable.
When presenting results, briefly note how many total products were found, how many passed each filter, and how many survived to the final list. This helps the user understand the market and make informed decisions about relaxing criteria.
Example:
Searched 3 keyword variations, paginated 4-5 pages each → 127 products cached. After filtering (★≥4.2, ¥≤100,000, reviews≥10): 31 remain. After weight verification (table≥12kg, chair≥7kg): 5 qualify.
Amazon.co.jp / .com:
[data-asin] attributeshttps://www.amazon.co.jp/dp/{ASIN}Rakuten:
Other platforms:
Searching shopping sites involves many things that can go wrong. Handle them gracefully instead of retrying blindly.
Shopping platforms aggressively block automation. If you encounter a CAPTCHA, access-denied page, or unusual redirect:
busy_timeout). If you still see this error during Step 4 parallel extraction, do NOT create a new DB — retry once after 5 seconds. Only create a new DB if the error persists outside of parallel extraction (e.g., during initial setup).sqlite3.DatabaseError, skip that DB in discovery and inform the user. Don't crash the entire workflow.agent-browser crashes or times out, try once more. If it fails again, close the browser (agent-browser close) and inform the user.