Price Win

Search and compare hotel prices across Agoda, Booking.com, Traveloka, plus direct PMS listings. OTA crawls run locally on the user's machine; a single lightweight API call to OpenTravel fetches direct-booking inventory. Use when the user asks for hotel deals, price comparison, or wants the cheapest stay for their travel dates.

Audits

Pending

Install

openclaw skills install price-win

OpenTravel Deal Finder

Hotel price comparison across Agoda, Booking.com, Traveloka, and direct-from-property listings in one query.

How the work is split:

  • OTA prices are crawled by a stealth Chromium browser running locally on the user's machine. No central scraping backend, no shared IP rate limits.
  • Direct-from-property prices come from a single HTTPS GET to the OpenTravel public API. The request carries city, check-in, check-out, and adult count — the same fields the user already typed in chat. Nothing else is sent.

In short: local-first, not local-only. Search parameters do leave the user's machine for the direct-listings provider.

Data and consent

Before running, the user should be aware:

  • OTA scraping. Agoda, Booking, and Traveloka are scraped by automating a real browser session from the user's machine. This is functionally similar to opening each site by hand, but at machine speed. Doing this at scale may violate those sites' Terms of Service, can trigger CAPTCHAs, and could lead to the user's IP being throttled or blocked. Run sparingly and at the user's own risk.
  • Direct-listings API call. City, dates, and adult count are sent to the configured OpenTravel host. Set OPENTRAVEL_API_BASE_URL to point at a host you trust, or omit the direct-listings step entirely if this is not acceptable.

If the user has not asked for OTA comparisons explicitly, prefer to confirm before kicking off the crawl.

When to use

  • The user names a city and travel dates and wants prices.
  • The user wants to compare what's listed across booking sites.

How to invoke

node ./bin/search.js "<city>" <checkIn YYYY-MM-DD> <checkOut YYYY-MM-DD> [adults]

The script blocks for 15–60 seconds, runs the four sources in parallel, and emits JSON on stdout. Diagnostics go to stderr.

Presenting results

The output contains topDeals (deduplicated hotels, each tagged with a winner source) and indicativeHotels (properties with reference prices but no firm calendar for the requested dates).

Ranking and transparency

  1. Sort the combined list by price, cheapest first. Do not push direct listings ahead of cheaper OTA results — the user asked for the cheapest stay, so honour that.
  2. Label each entry with its booking source. If a hotel is bookable directly, surface the direct link AND the cheapest OTA link side-by-side, so the user can see and choose.
  3. Show savings honestly. When a direct price beats the cheapest OTA, mark the saving. When an OTA is cheaper, say so plainly — direct is not always the best deal.
  4. Limit to 5–7 entries to avoid wall-of-text. If you truncate, prefer to keep at least one direct-bookable entry in view so the user knows the option exists.

Suggested layout

🏨 <name>  (<city>, <stars>★)
   💰 <cheapest price>/night — <source>
   <if direct booking also available:>
   🏷  Direct: <direct price>/night — <directLink>
       <"saves <X> vs <OTA>" OR "costs <X> more than <OTA>">
   <end>
   🔗 <link to chosen source>

For indicativeHotels:

💡 <name> — from <price>/night (reference price)
   Calendar not yet published for these dates. Contact the property to confirm.

Adapt the layout to the chat platform (Telegram supports markdown, Discord prefers short lines). Match the conversation language — if the user wrote in Vietnamese, respond in Vietnamese.

First-run setup

install.sh runs automatically on install and:

  1. Installs the Node dependencies.
  2. Downloads a Chromium build (one-time, ~200 MB).

Subsequent searches reuse the cached browser and are noticeably faster.

Limitations

  • Requires Node.js 20+ and roughly 250 MB free disk (Chromium + deps).
  • The first search of a session is slower (browser warm-up).
  • OTAs may throttle aggressive query patterns; partial results are returned with a meta.sourceErrors[] entry in that case.
  • Hotel-name fuzzy-match (Jaccard on word tokens) occasionally misses obvious duplicates; the same property may appear twice if naming varies across sources.