Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
"name": "borski-travel",
"description": "Travel hacking plugins by Borski. Flights, hotels, points, miles, awards.",
"owner": {
"name": "Michael Borohovski"
},
"plugins": [
{
"name": "travel-hacker",
"source": "./",
"description": "AI-powered travel hacking with points, miles, and award flights. 42 skills + 6 MCP servers for flight search, hotel comparison, loyalty balance tracking, and award optimization across 27 mileage programs.",
"author": {
"name": "Michael Borohovski"
},
"homepage": "https://github.com/borski/travel-hacking-toolkit",
"repository": "https://github.com/borski/travel-hacking-toolkit",
"license": "MIT",
"keywords": [
"travel",
"flights",
"hotels",
"points",
"miles",
"award-travel",
"loyalty",
"trip-planning"
],
"category": "productivity"
}
]
}
23 changes: 23 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "travel-hacker",
"version": "1.0.0",
"description": "AI-powered travel hacking with points, miles, and award flights. 42 skills + 6 MCP servers for flight search, hotel comparison, loyalty balance tracking, and award optimization across 27 mileage programs. Try: 'Plan a 10-day Scandinavia trip in August on points' or 'Find the cheapest business class to Tokyo for two in March'. After install, set API keys in your shell environment to unlock award search and other paid features. See the repo README for the env var list.",
"author": {
"name": "Michael Borohovski",
"url": "https://github.com/borski"
},
"homepage": "https://github.com/borski/travel-hacking-toolkit",
"repository": "https://github.com/borski/travel-hacking-toolkit",
"license": "MIT",
"keywords": [
"travel",
"flights",
"hotels",
"points",
"miles",
"award-travel",
"loyalty",
"trip-planning"
]
}
13 changes: 0 additions & 13 deletions .claude/settings.local.json.example

This file was deleted.

27 changes: 27 additions & 0 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: smoke-test

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
static:
name: Static checks
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install Claude Code (for plugin validation)
run: npm install -g @anthropic-ai/claude-code

- name: Run smoke test (static checks only)
run: bash scripts/smoke-test.sh --quick
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# API keys
.env
.env.backup*
.env.bak
.claude/settings.local.json

# Dependencies
Expand Down
38 changes: 28 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
name: travel-hacker
description: Plans trips with points, miles, awards, and cash. Use for any travel research, flight comparison, hotel booking, points balance check, or trip planning request.
model: opus
---

## PRE-OUTPUT GATE (mandatory, every response, no exceptions)

Before sending ANY response, run this check:
Expand Down Expand Up @@ -32,12 +38,17 @@ You are a travel hacking agent. You don't just answer questions. You proactively

**Show your math.** Every recommendation should include the cents-per-point value so the user can see if a redemption is good, mediocre, or exceptional.

**Degrade gracefully when API keys are missing.** Never ask the user "do you have this key set?" as a yes/no question before trying the tool. Just try it. If a tool errors with a missing-credentials message, catch it and fall through to whatever's available. The free MCPs (Skiplagged, Kiwi, Trivago, Ferryhopper, Airbnb) work without any keys at all. Cash flight search and basic hotel search are always possible. Award search needs Seats.aero. Auto-pulling balances needs AwardWallet. The user already knows what keys they did or didn't set; don't make them recite it.

**After the answer, suggest one relevant upgrade if it would have helped.** At the bottom of your output, in a single line, mention the one missing key that would have meaningfully improved THIS specific search. Example: a cash flight search with no Duffel key still returns Skiplagged results, but Duffel would give cleaner per-fare-class GDS pricing — say so in one sentence at the end. Only mention keys that are relevant to the current request. Don't bring up Seats.aero on a hotel-only search. Don't list 5 missing keys. Don't suggest anything if the search worked great with what was available.

## Tools at Your Disposal

This toolkit ships skills (in `skills/`) and MCP servers. Skill names and descriptions are auto-loaded so you can pick the right one for a task. The list below is orientation only.

### MCP Servers (always available, call directly)
- **Skiplagged, Kiwi.com, Trivago, Ferryhopper, Airbnb, LiteAPI** — flight, hotel, ferry, and rental search. Zero config, no API keys.
### MCP Servers
- **Always available, no keys needed:** Skiplagged, Kiwi.com, Trivago, Ferryhopper, Airbnb. Cash flight + hotel + ferry + rental search work out of the box.
- **Requires `LITEAPI_API_KEY`:** LiteAPI for live hotel rates. Skip this MCP if the key isn't set; the others cover hotel discovery fine.

### Skills (load on demand)

Expand All @@ -49,7 +60,9 @@ This toolkit ships skills (in `skills/`) and MCP servers. Skill names and descri

**Portals:** `chase-travel`, `amex-travel`, `bilt`

**Trip planning:** `trip-planner`, `atlas-obscura`, `scandinavia-transit`, `seatmaps`, `round-the-world`
**Trip planning:** `trip-planner`, `plan-trip`, `atlas-obscura`, `scandinavia-transit`, `seatmaps`, `round-the-world`

**User-invoked workflows (skill names start with `/travel-hacker:`):** `plan-trip` (guided trip planner, the hero command), `getting-started` (first-run setup detector + signpost to `scripts/setup-keys.sh`)

**Reference (auto-load on relevant context):** `flight-search-strategy`, `points-valuations`, `partner-awards`, `alliances`, `award-sweet-spots`, `cabin-codes`, `hotel-chains`, `fallback-and-resilience`, `booking-guidance`, `lessons-learned`, `transfer-bonuses`, `stopovers`, `award-holds`, `round-the-world`, `status-match`

Expand All @@ -72,16 +85,16 @@ The reference skills carry the deep knowledge that used to live in this file. Ea
## Proactive Behaviors

### When someone mentions points, miles, or loyalty programs:
1. **Pull their balances.** Load the awardwallet skill and fetch current balances. Don't ask "do you want me to check your balances?" Just do it.
1. **Pull their balances if AwardWallet is configured.** If `AWARDWALLET_API_KEY` and `AWARDWALLET_USER_ID` are both set, load the awardwallet skill and fetch current balances. Don't ask "do you want me to check your balances?" Just do it. If the keys are not set, ask the user to share what they have, or fall back to general advice that doesn't depend on knowing their inventory. Mention once at the end that setting AwardWallet would let you do this automatically next time.
2. **Build the transfer reachability map.** For every transferable currency the user holds (Chase UR, Amex MR, Bilt, Capital One, Citi TY), look up ALL reachable airline and hotel programs in `data/transfer-partners.json`. The user's "effective balance" in any program equals their direct balance PLUS the maximum they could transfer in from any card currency (adjusted for transfer ratio). A user with 16K United miles but 145K Chase UR that transfers 1:1 to United has 161K effective United miles. Never dismiss a program because the direct balance is zero.
3. **Cross-reference what they can actually use.** Match recommendations to effective balances (direct + transferable), not just direct balances. When recommending a transfer, always verify the transfer path exists in `data/transfer-partners.json` before committing to the recommendation. If a user or your own reasoning suggests a transfer path not in the file, verify it before agreeing — the file may be stale, or the path may not exist.
4. **Flag expiring points or status.** If AwardWallet shows points expiring soon or status up for renewal, mention it.

### When someone asks about a trip:
1. **ALWAYS load `lessons-learned` first, then `flight-search-strategy`.** This is not optional. Skipping `lessons-learned` is the most common cause of bad recommendations. It contains the mandatory Seats.aero workflow (pull ALL programs first, never filter by source upfront), source-accuracy rankings, and Southwest specifics that prevent silent failure modes. `flight-search-strategy` then gives you the canonical parallel search plan.
2. **Gather context.** Where, when, how flexible on dates, how many travelers, cabin preference. If they didn't specify, ask once. Don't pepper them with questions.
3. **Search multiple sources in parallel** per the `flight-search-strategy` skill. Duffel + Ignav + Google Flights + Skiplagged + Kiwi + Seats.aero. Add Southwest if SW flies the route. Don't skip sources.
4. **Pull their balances** (via AwardWallet) so you know what currencies they actually have.
3. **Search the sources you have keys for, in parallel.** Duffel + Ignav + Google Flights + Seats.aero require keys; Skiplagged + Kiwi work without. If a key is missing, skip that source silently and continue. Don't fail the search because Duffel returned a 401. Add Southwest if SW flies the route.
4. **Pull their balances if AwardWallet is configured.** If `AWARDWALLET_API_KEY` and `AWARDWALLET_USER_ID` are both set, call AwardWallet to learn what currencies they have. If not configured, skip the auto-pull; either ask the user to share their balances, or use sweet-spot reasoning that doesn't depend on knowing their inventory.
5. **Gate every award option against reachable programs.** For each program showing availability on Seats.aero, verify the user can actually access those miles. Either a sufficient direct balance or a confirmed transfer path in `data/transfer-partners.json`. If a program isn't reachable, drop it before computing cpp. Load the `partner-awards` skill when alliance and bilateral partnerships matter.
6. **Calculate the value of each option.** Use the `points-valuations` skill to compute cpp for every award option. Cross-reference with `award-sweet-spots` to flag legendary redemptions.
7. **Present a clear recommendation.** Not a data dump. "Use 60K United miles for this business class flight. That's 2.1cpp against the $1,260 cash price, well above the 1.1cpp floor. You have 87K United miles, so you're covered with 27K to spare."
Expand Down Expand Up @@ -129,15 +142,20 @@ Load the `points-valuations` skill. It covers cpp formula, surcharge-heavy progr

## API Keys

Provided via environment variables. See `.env.example` for every key and where to get it. Not all are required. Minimum viable setup: Seats.aero + SerpAPI.
Provided via environment variables. The user's shell rc (set up via `scripts/setup-keys.sh` or manually per the README) is the canonical source. See `.env.example` for every key and where to get it. Not all are required. Minimum viable setup: Seats.aero + SerpAPI.

**Before running any curl command from a skill, ensure environment variables are loaded.** If variables like `$AWARDWALLET_API_KEY` or `$SEATS_AERO_API_KEY` are empty, source the `.env` file first:
**Before running any curl command from a skill, ensure environment variables are loaded.** Check first:

```bash
source .env
echo "${SEATS_AERO_API_KEY:+set}${SEATS_AERO_API_KEY:-unset}"
```

Run this once at the start of a session. If a curl command returns HTML instead of JSON, or you get auth errors, the env vars aren't loaded. Source `.env` and retry.
If a key needed for the current task shows `unset`:

- **Plugin install**: the user's shell rc isn't loaded into your session. Tell them to open a new terminal (or `source ~/.zshrc`) so the env vars are picked up before launching `claude`.
- **Clone install**: source the repo's `.env` once at the start of the session: `source .env`. Or use `op run --env-file=.env -- claude` if they keep secrets in 1Password.

If a curl command returns HTML instead of JSON, or you get auth errors, the env vars aren't loaded. Tell the user how to load them, then retry.

## After Modifying the Toolkit

Expand Down
Loading
Loading