For People Who Fly Often

Set It Once
Fly It Every Time

Save the preferences and routes you use most. Every search ranks real flights against what matters for this trip, including the airport rides you actually need.

Every booking starts from scratch, and getting to or from the airport still gets treated as a separate problem.

The external problem

You’re comparing flights across five browser tabs, re-answering the same budget and airline questions you answered last trip, and sorting out rides to or from the airport after you’ve picked a flight.

The internal problem

The tedium of repeating answers you already know, plus the specific annoyance of discovering a flight only works if you accept a bad connection on the ground.

What's at stake

Money left on the table from a flight you didn’t compare closely enough, and a fragile airport connection that turns one delay into a missed flight or hours of waiting.

From a trip request to a plan that accounts for the whole journey.

Confirm a route, preferences, and airport rides for this trip. The scripts fetch the options, then make the filtering, ranking, and shuttle math reproducible.

01

Resolve the Trip

Say where and when. A saved trip preset skips repeated airport research; otherwise the skill resolves realistic nearby alternates and asks you to confirm the plan.

02

Confirm What Matters

Confirm saved budget, airline, nonstop, time, and duration preferences, plus whether you need a ride to the airport, from it, both, or neither this time.

03

Fetch & Evaluate

Pulls every approved route and date through Google Flights data, then applies the common filters or a purpose-built rule for the constraint you actually care about.

04

Rank the Full Route

Returns a ranked flight list with each soft tradeoff shown in dollars, then pairs only the configured shuttle directions you confirmed and shows total cost and buffer time.

Preferences, repeat routes, and shuttle logic are all explicit.

Every ranking starts with a config file: hard or soft preferences, repeat-trip aliases, maximum duration, and per-direction shuttle details. The model can explain the tradeoffs, but it does not eyeball the search, timing, or ranking math.

Reference

Preferences & Presets

Full schema

{
  "serpapi_key": "abc123 or null",

  "budget_usd": 200,
  "budget_strength": "soft",

  "airline_preferences": ["UA", "DL"],
  "airline_preference_strength": "soft",

  "nonstop_preference": "preferred",
  "nonstop_strength": "soft",

  "time_priority_bands": ["11:00-14:00", "14:00-22:00"],
  "time_priority_strength": "soft",

  "max_duration_min": null,
  "max_duration_strength": "soft",

  "home_airport": null,
  "frequent_destinations": [],

  "trip_presets": {},
  "shuttle_service": null
}

Field reference

serpapi_key

  • Type: string or null
  • SerpAPI key for Google Flights queries. If null, the skill falls back to WebSearch (less comprehensive — see SKILL.md).
  • Get one at https://serpapi.com/manage-api-key.

budget_usd

  • Type: int — max flight price in USD.

budget_strength

  • Type: "hard" or "soft"
  • hard = filter out flights above budget. soft = include them but rank cheaper ones higher.

airline_preferences

  • Type: array of IATA airline codes (e.g. ["UA", "DL"]). Empty array = any airline.
  • A connecting itinerary counts as matching only if every leg is on a preferred airline.

airline_preference_strength

  • Type: "hard" or "soft"

nonstop_preference

  • Type: "required", "preferred", or "no_preference"

nonstop_strength

  • Type: "hard" or "soft"

- required + hard = filter out anything with stops

- required + soft = treat stopovers as a downside but still show them

- preferred + soft = rank nonstops first, show stops second (typical setup)

- no_preference = strength is ignored

time_priority_bands

  • Type: array of "HH:MM-HH:MM" ranges in 24-hour time.
  • Departures inside the first band rank highest; second band second; outside all bands lowest.

time_priority_strength

  • Type: "hard" or "soft"

max_duration_min

  • Type: int or null — cap on total itinerary minutes (gate + gate, layovers included).
  • Useful when nonstop_preference is soft: it lets one-stops through without letting a 14-hour triple-connection through with them.

max_duration_strength

  • Type: "hard" or "soft"

home_airport

  • Type: IATA code or null. Suggested as the origin in Phase 1 (but the skill still asks).

frequent_destinations

  • Type: array of IATA codes. Surfaced as suggestions when asking for a destination.

trip_presets

  • Type: object mapping a short slug to a saved route. This is the fast path — when the user names a preset (or says something that clearly matches its aliases), the skill skips airport research entirely and goes straight to confirming dates.
"trip_presets": {
  "home-to-school": {
    "label": "NJ -> Purdue",
    "aliases": ["to school", "back to campus", "nj to purdue"],
    "origins": ["EWR", "LGA", "JFK"],
    "destinations": ["IND", "ORD"],
    "shuttle_legs": "arrival",
    "notes": "IND drops me 1h from campus; ORD is 2.5h but usually cheaper."
  },
  "school-to-home": {
    "label": "Purdue -> NJ",
    "aliases": ["home", "back to nj", "purdue to nj"],
    "origins": ["IND", "ORD"],
    "destinations": ["EWR", "LGA", "JFK"],
    "shuttle_legs": "departure"
  }
}

Per-preset fields, all optional except origins/destinations:

FieldPurpose
labelHuman-readable direction, shown when confirming
aliasesPhrases that should match this preset in Phase 1
origins / destinationsIATA lists passed straight to --routes / --from / --to
routesOptional explicit ["EWR-IND", "EWR-ORD"] list, when the full cross-product isn't wanted
shuttle_legsWhich end of *this* direction normally needs a ride: "departure", "arrival", "both", or "none". See below.
notesFree text shown to the user during confirmation
Any preference keyPer-preset override of a top-level preference (e.g. a higher budget_usd for a long route)

shuttle_legs — a default, never an assumption

A configured shuttle does not mean a needed shuttle. Someone flying out of a hub near where they live needs no ride on the home end, and someone who normally rides might be getting dropped off, driving, or stopping somewhere on the way this time.

shuttle_legs records which end of a given direction *usually* needs one, so the skill proposes the right thing instead of pairing a shuttle to an airport where the user has a car. It maps to pair_shuttles.py --legs:

ValueMeaning--legs
"departure"Ride from home to the departure airportpre
"arrival"Land, then ride to the destinationpost
"both"Ride on both endsboth
"none"No ground shuttle for this direction; skip pairing entirely(skip)

Note that it's direction-specific and usually asymmetric. The same person flying NJ → Purdue needs a ride only on arrival; flying Purdue → NJ they need one only on departure. Two presets, two different values.

The skill must still confirm it out loud every run. It appears as its own line in the Phase 2 confirmation block, not folded into a general "still your prefs?" yes. A wrong guess here never surfaces as an error — it just quietly ranks every option around a cost that was never going to be paid.

A preset never bypasses confirmation — the skill still shows the resolved plan and waits for a yes. It only removes the research step.

shuttle_service

  • Type: object or null. If non-null, the skill pairs flights with ground shuttle runs on either end. Full sub-schema in references/shuttle_schedules.md.

Example configs

Frequent flier, one home airport, picky about times

{
  "serpapi_key": "sk_xxx",
  "budget_usd": 350,
  "budget_strength": "soft",
  "airline_preferences": ["DL"],
  "airline_preference_strength": "hard",
  "nonstop_preference": "required",
  "nonstop_strength": "hard",
  "time_priority_bands": ["07:00-10:00", "17:00-20:00"],
  "time_priority_strength": "soft",
  "max_duration_min": null,
  "max_duration_strength": "soft",
  "home_airport": "JFK",
  "frequent_destinations": ["LAX", "SFO", "SEA"],
  "trip_presets": {},
  "shuttle_service": null
}

Casual traveler, cost-sensitive, flexible

{
  "serpapi_key": null,
  "budget_usd": 250,
  "budget_strength": "hard",
  "airline_preferences": [],
  "airline_preference_strength": "soft",
  "nonstop_preference": "preferred",
  "nonstop_strength": "soft",
  "time_priority_bands": [],
  "time_priority_strength": "soft",
  "max_duration_min": 600,
  "max_duration_strength": "soft",
  "home_airport": null,
  "frequent_destinations": [],
  "trip_presets": {},
  "shuttle_service": null
}

Student flying a fixed route both ways, with a shuttle at the campus end

{
  "serpapi_key": "sk_xxx",
  "budget_usd": 200,
  "budget_strength": "soft",
  "airline_preferences": [],
  "airline_preference_strength": "soft",
  "nonstop_preference": "preferred",
  "nonstop_strength": "soft",
  "time_priority_bands": ["11:00-14:00", "14:00-22:00"],
  "time_priority_strength": "soft",
  "max_duration_min": 480,
  "max_duration_strength": "soft",
  "home_airport": null,
  "frequent_destinations": ["EWR", "IND", "ORD"],
  "trip_presets": {
    "home-to-school": {
      "label": "NJ -> Purdue",
      "aliases": ["to school", "to purdue"],
      "origins": ["EWR", "LGA", "JFK"],
      "destinations": ["IND", "ORD"]
    },
    "school-to-home": {
      "label": "Purdue -> NJ",
      "aliases": ["home", "to nj"],
      "origins": ["IND", "ORD"],
      "destinations": ["EWR", "LGA", "JFK"]
    }
  },
  "shuttle_service": {
    "home_label": "West Lafayette / Purdue",
    "home_timezone": "America/New_York",
    "providers": [
      {"name": "Campus Shuttle Co", "airports": ["IND", "ORD"],
       "schedule_urls": ["https://example.com/schedule"]}
    ],
    "costs": {"IND": 30, "ORD": 60},
    "airport_timezones": {"IND": "America/New_York", "ORD": "America/Chicago"},
    "min_buffer_min": 90,
    "min_connect_min": 60,
    "max_wait_min": 240,
    "reservation_lead_hours": 24
  }
}

Strength semantics recap

StrengthBehavior
hardFilter applied strictly — non-matching flights are excluded from output
softFilter applied as a rank weight — non-matching flights still shown, flagged with soft_filter_violations and sorted below matching ones

When hard filters return zero results, the skill runs filter_flights.py --cluster-analysis to see which constraint(s) eliminated which flight counts, then proposes specific relaxations.

Use it now. It's free.

Works on all platforms. Pick yours and get set up in under a minute.

Download & upload in 60 seconds

One click downloads the zip and opens Claude.ai.

01

Click below — the zip downloads and Claude.ai opens.

02

Click the + button in the skills column on the left.

03

Select Create a skill.

04

Select Upload a skill and upload the zip.

05

Start your flight planning session by running /estack-flight-planner.

Flight Planner ships in E-Stack, a set of 20 free skills installed by one command. See the whole stack.