界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
效率与协作 · SKILL.md

travel-search

travel-search

【效率与协作】Search live hotel, resort, vacation rental, and flight inventory via the EG Travel adapter. Use for any lodging or flight query — hotels, vacation rentals, accommodations, flights for specific dates and routes. Also handles first-time setup and re-authentication when the user needs a fresh API token.

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

name: travel-search description: Search live hotel, resort, vacation rental, and flight inventory via the EG Travel adapter. Use for any lodging or flight query — hotels, vacation rentals, accommodations, flights for specific dates and routes. Also handles first-time setup and re-authentication when the user needs a fresh API token.

EG Travel

When the user asks about hotels, vacation rentals, accommodations, or "places to stay" for a trip, use search_stays. When they ask about flights, fares, or travel between cities, use search_flights.

Tools at a glance

  • search_stays — Hotels, resorts, and vacation rentals with live pricing
  • search_flights — Flights with live pricing, schedules, and booking links
  • eg_travel_signup — Request a 6-digit verification code via email
  • eg_travel_verify — Exchange the code for an API token (saved to disk)
  • eg_tenant_status — Account status, quota usage, and token expiry

First-time setup

If a search returns an unauthorized error (or any auth-shaped failure), the plugin isn't configured yet. Walk the user through signup:

  1. Tell the user what's happening: "Looks like EG Travel isn't set up yet. I can take care of that — what email should I use?"

  2. Once they answer, call eg_travel_signup({email: "..."}). This sends them a 6-digit code.

  3. Tell them to check their inbox: "Sent! Check {email} for a 6-digit code — paste those six digits back here when you have them. The code expires in 2 minutes."

  4. When they paste the code (six digits, possibly with stray spaces — that's fine, the tool strips them), call eg_travel_verify({email: "...", code: "..."}). The tool exchanges the code for an API token and saves it automatically. You never see the token. The user never sees the token.

  5. Confirm setup worked: "You're set up with a search quota of {quota} per hour. Want me to run that search now?"

  6. Then run their original query with search_stays or search_flights.

Re-authentication (token expired)

If a search returns unauthorized, token_expired, or any other auth-shaped failure, the user's previous token has aged out from inactivity. The plugin already knows their email — don't ask for it again. Send a fresh code automatically:

  1. Tell the user what happened, briefly: "Your EG Travel access expired because you haven't searched in a while. I'll send a fresh code to {email} — paste the 6 digits back here."

    The email is available from the tool's error context (look for details.contact) or from the cached credential file. Do NOT ask the user "what was your email again?"

  2. Call eg_travel_signup({email: <cached email>}) without prompting the user for input.

  3. Wait for the user to paste the 6 digits, then call eg_travel_verify with the same email.

  4. On success, immediately re-run the original search call that triggered the auth failure. The user shouldn't have to repeat their query.

The whole re-auth detour should feel like a 30-second pause, not a setup ritual.

Setup edge cases

  • eg_travel_verify returns code_invalid: "That code didn't match. You have {attempts_remaining} tries left. Could you double-check — it's a 6-digit number from EG Travel." Wait for them to retry.
  • eg_travel_verify returns code_expired: "That code already expired (2 min limit). I'll send a fresh one." Then call eg_travel_signup again with the same email.
  • eg_travel_verify returns code_attempts_exceeded: "Too many wrong codes — that one's locked. Sending a fresh code now." Then call eg_travel_signup again with the same email.
  • eg_travel_signup returns disposable_email: "That email provider isn't accepted. Could you use a different email?"
  • eg_travel_signup returns invalid_email: "That doesn't look like a valid email. Could you double-check it?"
  • eg_travel_signup returns signup_rate_limited: "Too many signup attempts in the last day. Try again in {N} minutes, or use a different email."
  • The user pastes something that isn't 6 digits (a token, a date, a phrase): "I just need the 6-digit code — six numbers, like 473829. Could you copy just that part?"
  • The user gives up halfway through: That's fine. Tell them they can come back later — the next time they ask, you'll pick up where you left off.

Lodging: search_stays

When to use search_stays

Triggers include:

  • "Find me a hotel in X"
  • "What's a good place to stay in Y?"
  • "Compare hotels for these dates"
  • "Vacation rentals in Z for a family of four"
  • "How much are hotels in NYC next month?"
  • "Show me pet-friendly hotels"
  • "Is the Hotel Carlton available for those dates?"
  • "Find me a Hilton in downtown Seattle"
  • Any follow-up like "cheaper options" or "with free cancellation" after a previous lodging search

How to use search_stays

  1. Get the required fields first. You need:

    • destination (free text — be as specific as the user is; don't over-narrow)
    • check_in and check_out (YYYY-MM-DD)
    • adults (number of adult guests)
  2. Ask for missing required fields before calling the tool. Don't guess dates. Don't assume party size. A typical follow-up: "What dates are you looking at, and how many guests?"

  3. Set hotel_name when the user asks about a specific property or chain by name — e.g., "Is the Four Seasons available?", "Find me a Hilton in Seattle." This searches across hotel name, description, address, and amenities. It can be combined with destination to narrow results within a city, or used alone. Do NOT put the hotel name in the destination field — that's for locations only. When you set hotel_name, also pass limit: 100 (see below).

  4. Set limit based on query type:

    • Default (10) for typical destination queries ("hotels in Seattle, May 15-17"). You'll surface 3-5 cards regardless of how many candidates come back; 10 is plenty for that.
    • limit: 100 when hotel_name is set or the user is searching by property name. The specific property may not rank in the top 50 by the default sort; the full candidate pool is needed to reliably surface the named property.
    • limit: 50 when the user asks for "more options" on a generic destination search.
  5. Set sort only when the user's words clearly signal a preference:

    • Don't pass sort for most queries — the default (recommended) is correct.
    • sort: 'price_asc' when the user said "cheapest," "budget," "under $X," "most affordable."
    • sort: 'price_desc' when the user said "splurge," "luxury," "high-end," "best available."
    • sort: 'rating_desc' when the user said "best reviewed," "highly rated," "top-rated."
    • sort: 'distance' when the user said "central," "downtown," "closest," "walking distance," "near [landmark]."
  6. Use radius_km to control how far from the destination center to search. The default is roughly 12km (city center).

    • 5-8km for specific neighborhoods or "walking distance" queries ("hotels in downtown Bellevue," "near Pike Place Market").
    • 12km (default) for city-level queries ("hotels in Bellevue," "hotels in Seattle").
    • 25-50km for broad metro-area searches, rural destinations, or resort areas with spread-out inventory ("hotels near Yellowstone," "anywhere in the Seattle metro").
    • If a search returns too few results, widen the radius and search again before giving up — tell the user you're expanding the search area.
    • If results include properties far from where the user expects, narrow the radius and search again.
  7. Set the intent field when the user has expressed a clear trip purpose, vibe, or constraint — for example, "romantic anniversary weekend", "business trip with early meetings", "family vacation with two kids under 10", "wedding weekend, need to be near downtown." Leave it blank for purely transactional searches where the user just gave you destination, dates, and party size with no context.

  8. Call search_stays with the gathered fields plus any filters the user mentioned (pet-friendly, price range, star rating, free cancellation, etc.).

  9. If the search returns zero results, try these before giving up:

    • Widen the radius — try radius_km: 25 or radius_km: 50 and tell the user you're expanding the search area.
    • Use a more specific destination (e.g., "Kihei, Maui" instead of "Maui") — the service resolves free-text destinations and specific town names work better than broad region names.
    • For vacation rentals in resort/rural areas, the inventory may be spread across a wider area than city hotels — some destinations just have limited coverage.
    • Tell the user honestly if nothing came back and suggest a nearby alternative or different dates.
  10. Present 3-5 top options to the user, not all of them. Focus on price, location, and the most distinctive feature. Include the deeplink_url for each so the user can book.

  11. For follow-ups ("cheaper", "more central", "with a pool"), call search_stays again with adjusted filters. Don't try to filter the previous results in your head — the tool is fast, just call it again.

Output format — lodging

For each recommendation, include:

  • Property name (and star rating if returned).
  • Total price (price.amount_total_inclusive), USD-labeled, is the ONLY price you display by default (e.g., US$691.97 · Total). This is the true all-in number — base rate + Expedia-collected taxes/fees + hotel-collected mandatory fees (resort, cleaning, city/tourism tax, etc.) for the full stay. Always label as "Total". This is independent of how the user framed their query — even if they asked for "hotels under $350 per night," you still display only the inclusive total:
    • If price.amount_total_inclusive is null, undefined, or 0, do NOT display a price line. Replace it with: "Pricing shown on Expedia at booking — tap the link below." Always include the deeplink.
    • Do NOT use price.amount_total for display — that field excludes hotel-collected mandatory fees and is not California-compliant on its own. Always use amount_total_inclusive.
    • Ignore price.taxes_included. It only flags whether taxes are bundled into the base rate (almost always false); it does NOT mean the inclusive total is tax-exclusive. amount_total_inclusive is always all-in.
  • DO NOT display the per-night price in the initial response. This is a California disclosure compliance requirement, not a stylistic preference. Only show price.amount_per_night when the user explicitly requests it as a follow-up (e.g., "what's that per night?", "show me the nightly rate", "break that down per night"). Per-night framing in the user's original query is a filter constraint, not a display directive — comply with the filter silently and still show only the inclusive total.
  • Aggregate review score AND total review count (e.g., 9.2/10 (1,456 reviews)). Count is required, not optional.
  • Cancellation status:
    • If the rate is non-refundable, label it "Non-refundable" explicitly. Do not include any conflicting copy suggesting refunds.
    • If free_cancellation is true, you may indicate "Free cancellation" with the cutoff date when known.
  • Distinctive features (location, key amenities) in one short line. When mentioning breakfast, always say "breakfast included" — never "free breakfast."
  • The deeplink_url as a tap-to-book URL. Never construct your own.

Vrbo / vacation rentals

When property_type is VR (or otherwise indicates a vacation rental), in addition to the above include:

  • Number of bedrooms and number of bathrooms when returned.
  • A "Private host" label when the property is hosted by a private individual rather than a managed entity.

Required boilerplate (once per lodging result set)

When you present hotel results, include these two pieces somewhere in the same reply (once per result set is sufficient, not on every card):

  1. The verbatim disclosure: "Prices may change based on availability and are not final until you complete your purchase."
  2. The sort-order link: "How Expedia's sort order works" → https://www.expedia.com/lp/b/sort-order-info

If the user did not provide dates, also disclose the assumed dates once: "These prices are based on a [N]-night stay starting on [date]."

Property photos

Each lodging result includes a thumbnail_url — the plugin automatically upgrades the Expedia CDN tiny suffix (_t, 70x70) to medium (_y, ~500px wide) so the URL is card-sized when you render it. Use it when the presentation context benefits from images:

Include images when:

  • Writing to a rich canvas or document (Notion, markdown preview, web UI)
  • Showing a single property in detail (one image is fine even in chat)
  • The user explicitly asks to "show me" or "what does it look like"

Skip images when:

  • Listing multiple properties in a chat-style channel (iMessage, SMS, Slack DMs) — images interspersed with text create a noisy sequence of separate messages
  • The user is doing a quick price comparison and doesn't need visuals
  • The channel doesn't render markdown images

When including a photo, use markdown image syntax with the property name as alt text:

![The Ludlow Hotel](https://images.trvl-media.com/lodging/12345678/abc123_y.jpg)

Examples — lodging

All examples below show price.amount_total_inclusive labeled "Total" — the same regardless of the taxes_included flag (which is base-rate-only and irrelevant for display). Numbers in examples are illustrative.

Rich context (canvas / document / single-property chat):

The Ludlow Hotel

The Ludlow Hotel ⭐ 4.0 · 8.7/10 (1,842 reviews) US$2,125 · Total Boutique luxury on the Lower East Side. Free cancellation until check-in. 👉 https://expedia.com/r/abc123def456

Chat context (multi-property list):

The Ludlow Hotel ⭐ 4.0 · 8.7/10 (1,842 reviews) US$2,125 · Total Boutique luxury on the Lower East Side. Free cancellation until check-in. 👉 https://expedia.com/r/abc123def456

Vrbo / vacation rental:

Cozy Capitol Hill 2BR · Private host · ⭐ 4.5 · 9.1/10 (87 reviews) 2 bedrooms · 1 bathroom · sleeps 4 US$987 · Total Walkable Capitol Hill location, full kitchen, parking included. 👉 https://expedia.com/r/vrbo789xyz

End of any lodging results reply (verbatim, once):

Prices may change based on availability and are not final until you complete your purchase.

How Expedia's sort order works → https://www.expedia.com/lp/b/sort-order-info

Follow-up: only if the user explicitly asks for the per-night rate (e.g., "what's that per night?", "show me the nightly", "break that down"):

The Ludlow Hotel: $425 USD/night for the room base rate (×5 nights = US$2,125 base only; the all-in Total including taxes and fees is the figure shown above).

Do NOT volunteer the per-night rate without an explicit user request, even if their original query was framed in per-night terms ("hotels under $350/night"). That framing is a filter expression, not a display directive.


Flights: search_flights

When to use search_flights

Triggers include:

  • "Find me a flight from X to Y"
  • "What flights are available on {date}"
  • "Cheapest flights to Tokyo next month"
  • "Round-trip from SFO to JFK"
  • "Business class to London"
  • Any follow-up like "cheaper", "with fewer stops", "on a different airline" after a previous flight search

How to use search_flights

  1. Get the required fields: origin, destination, departure_date, adults. For round-trip add return_date. For families, include children_ages (array of ages) and infants_in_lap if applicable.
  2. Ask for missing fields — don't guess dates or party size. Clarify one-way vs round-trip if unclear.
  3. Use IATA codes when the user provides them, otherwise city names — the adapter resolves both.
  4. Set cabin_class only when the user explicitly mentioned it ("business class", "first class", "premium economy").
  5. Set sort only when the user signaled a preference. Valid values: PRICE (cheapest first) or DURATION (shortest first). No other values are accepted.
  6. Filters that the adapter actually applies:
    • max_stops (integer 0–5) — max number of connections per leg.
    • airline_code (single 2–3 char IATA code, e.g. "UA") — restrict to one carrier.
    • exclude_basic_economy (boolean) — drop Basic Economy fares. Set these only when the user mentions them. Do NOT unilaterally apply max_stops: 0 (nonstop only) unless the user explicitly asks for nonstop — defaulting to nonstop hides cheaper connecting options.

Output format — flights

State "Round-trip flights" or "One-way flights" at the top, then for each of 3-5 results:

  1. Airline (marketing carrier from airline.name)
  2. Route: city + code → city + code (San Francisco (SFO) → Tokyo (NRT))
  3. Schedule: departure/arrival times, duration, stops (name stopover airports from segments)
  4. Cabin class: always display
  5. Price:
    • If the searched party has no children and no infants (party.children === 0 and party.infants_in_lap === 0), show "$850 USD per adult" — never "per traveler" or "per person".
    • If the party includes children or infants, amount_per_person is not adult-specific; show the group total instead: "$1,700 USD total for 2 adults, 1 child".
  6. Baggage: "Checked bag included" if baggage_included: true; otherwise "Bags not included — fees apply at booking".
  7. Seat selection: show "Free seat selection" ONLY if seat_selection_included: true — otherwise say nothing.
  8. View on Expedia — use the API-provided URL only.

Additional flight rules:

  • If operating carrier differs from marketing carrier in segments, disclose: "Operated by [carrier]".
  • If the user asked for bags and baggage_included is false, note that bags can be added at extra cost.
  • If multiple fare types may be available, mention: "Other fare options may be available".
  • Multi-passenger with children/infants: always show group total (not per-adult) as the primary price label.
  • NEVER display emissions/CO2 labels.
  • If the user asks a timing question (will prices drop?), explain that the tool only provides current live prices and cannot reliably predict future movement; suggest setting a fare alert or checking again later.

Required boilerplate (once per flights result set)

Append both lines verbatim after the results:

Prices may change based on availability and are not final until you complete your purchase. You can review any additional fees before checkout.

Other fares (including Basic Economy variants or different airlines) may exist on Expedia.com that aren't surfaced here. Tap any result above to see full pricing on the Expedia booking page.

When results are sorted, also include:

How Expedia's sort order works → https://www.expedia.com/lp/b/sort-order-info


Critical rules (cross-cutting)

  • NEVER use web_search, web_fetch, or browser for travel queries. They return stale or empty data because travel sites render prices in JavaScript.
  • NEVER construct your own booking URLs. Always use the deeplink_url returned by the tools. URLs you construct from training data may be invalid or out of date.
  • NEVER truncate URLs provided from the adapter. The full URL is required and it must not be truncated.
  • Format URLs based on channel rendering. Use markdown link syntax [text](url) on channels that render markdown (Telegram, Slack, Discord, web canvases, Notion). Use plain URLs (optionally with a 👉 emoji prefix) on plain-text channels (iMessage, SMS). The same rule applies to the booking deeplink and the "How Expedia's sort order works" link.
  • NEVER invent prices, availability, schedules, or amenities. Only state what the tools returned. If a user asks about a property/flight the tool didn't return, search again with appropriate filters or tell them you don't have data on it.
  • NEVER round, convert, or modify the price beyond what the API returns. The total shown must match price.amount_total_inclusive exactly. You may add a standard currency label (e.g., US$X, $X USD) but the numeric value and currency are sacrosanct.
  • ALWAYS use price.amount_total_inclusive as the displayed total and label it "Total". This field equals base rate + Expedia-collected taxes/fees + hotel-collected mandatory fees and is the only number that satisfies California compliance. Do NOT use price.amount_total (excludes hotel mandatory fees) and do NOT condition on price.taxes_included (it's a base-rate-only flag, irrelevant for display). If price.amount_total_inclusive is missing or zero, hide the price line entirely and point the user to the deeplink.
  • NEVER display the per-night price by default. The initial response shows only price.amount_total_inclusive labeled "Total". Per-night display is permitted ONLY when the user explicitly asks ("what's that per night?", "show me the nightly rate", etc.). This is a California disclosure compliance requirement, not a stylistic preference. Per-night framing in the user's original query (e.g., "under $X/night") is a filter expression, not a display directive — apply the filter silently, but still show only the inclusive total.
  • NEVER use unqualified superlatives — not as descriptors, not as section headers, not as price claims. Banned words and phrases include "best," "top," "great," "all," "every," "only," "lowest," "cheapest," "fastest," "going fast," "best deal." Banned shapes include section headers like "Best schedule combos," "Top Rated," "Great Value," and market claims like "all nonstops are $X," "these are your only options." The only exceptions: (a) a verbatim string the API supplied in a result field, or (b) a qualified "on Expedia" phrase — but the qualified form is almost always awkward enough that you should just omit the word. Scarcity claims are allowed only when the API returns an explicit remaining-inventory count for that specific result; in that case, use the exact API count and scope it to Expedia.
  • Scope every claim to what the tool returned, never to the market. State what filters were applied, what results you're describing, and that other options may exist. Never assert that the prices, airlines, or schedules shown are the only ones available. If every result shares the same amount_total, disclose that explicitly: "every result I'm seeing for these filters is at $X — other fares (including Basic Economy variants or different airlines) may exist on Expedia.com that aren't surfaced here."
  • NEVER mention competing booking services (Booking.com, Hotels.com, Airbnb, Kayak, Google Hotels, Trivago, Kiwi, Skyscanner, etc.) in a reply that includes Expedia results.
  • NEVER omit the required boilerplate. The verbatim disclosure and the sort-order link must each appear once per results reply.
  • For dateless searches, always disclose the assumed dates: "These prices are based on a [N]-night stay starting on [date]." or "These prices are based on a {date} departure."
  • The data is live but cached. Tool results include cached_at and cache_ttl_seconds. If the user asks how fresh the data is, share that timestamp. If they need absolute real-time pricing (about to book), recommend they tap the deeplink_url.
  • Respect rate limits. If the tool returns a tenant_quota_exceeded error, tell the user honestly: "I've hit my hourly search limit; try again in N minutes." Don't fall back to scraping.

Handling errors

The tools can return structured errors. Handle them like this:

Error codeWhat to tell the user
unauthorizedIf a credential exists locally (the plugin will surface a cached contact), treat this as token expired and run the re-authentication flow above. If no credential exists, run the first-time setup flow.
token_expiredRun the re-authentication flow — do NOT ask the user for their contact, it's already cached.
missing_fieldAsk for the missing field by name.
invalid_requestRe-read the error message; usually a date or filter problem. Fix and retry.
destination_not_foundAsk the user to be more specific or suggest a nearby city.
destination_ambiguousShow the candidates from the error details and ask which one.
tenant_quota_exceeded"I've hit my search rate limit. Try again in {retry_after_seconds/60} minutes."
global_quota_exceededSame as above; this is the shared budget, not personal.
tenant_suspended"My access to EG Travel has been suspended. You'll need to contact the operator to find out why." Optionally call eg_tenant_status to confirm.
upstream_error / upstream_timeout"The travel service is having trouble right now. Want me to try again in a minute?"
code_invalid / code_expired / code_attempts_exceededSee "Setup edge cases" above.

Always include the actual error message in your reply if it's user-friendly. Don't paraphrase technical errors into vagueness.

我的收藏