Skill: marcorabelo/flight-search-skill (version 1.0.2)

--- name: flight-search description: Search flights, compare prices, and monitor airfare using Amadeus API author: marco-rabelo version: 1.0.2 triggers: - "search flights" - "find flights" - "flight prices" - "airfare" - "plane tickets" - "book flights" - "flight status" credentials: required: - name: AMADEUS_API_KEY description: Amadeus API key for flight search how_to_get: Visit developers.amadeus.com, create account, and create an app - name: AMADEUS_API_SECRET description: Amadeus API secret for authentication how_to_get: Provided when you create an app on developers.amadeus.com optional: - name: AVIATIONSTACK_API_KEY description: AviationStack API key for flight status (optional) how_to_get: Visit aviationstack.com and sign up for free tier (100 requests/month) --- # Flight Search Search flights, compare prices across airlines, and monitor airfare drops. Supports flexible dates, price alerts, and flight status tracking. ## ⚠️ Required Credentials **This skill requires API credentials to function:** ### **Required (Amadeus):** - `AMADEUS_API_KEY` - Get from [developers.amadeus.com](https://developers.amadeus.com) - `AMADEUS_API_SECRET` - Provided when you create an app **Free Tier:** 2,000 searches/month (sandbox) or production free tier with real prices ### **Optional (AviationStack):** - `AVIATIONSTACK_API_KEY` - Get from [aviationstack.com](https://aviationstack.com) **Free Tier:** 100 requests/month (very limited, only for flight status) ## Configuration After installing, copy `config.example.json` to `config.json` and add your credentials: ```json { "apis": { "amadeus": { "api_key": "YOUR_AMADEUS_API_KEY", "api_secret": "YOUR_AMADEUS_API_SECRET", "sandbox_mode": true }, "aviationstack": { "enabled": false, "api_key": "YOUR_AVIATIONSTACK_API_KEY" } } } ``` **🔒 Security:** - `config.json` is in `.gitignore` by default - Never commit API keys to version control - Use environment variables or secure config files - Rotate keys if accidentally exposed ## What it does Searches flights using Amadeus API, compares prices, finds the best routes, and can monitor prices over time to alert you when they drop. Optional AviationStack integration for real-time flight status. ## Prerequisites 1. **Get API Keys:** - **Amadeus (Required):** developers.amadeus.com - **Test/Sandbox (FREE):** 2,000 flight searches/month - ⚠️ **TEST DATA ONLY** - **Production (FREE + pay-as-you-go):** FREE quota + pay only for extra calls - ✅ **REAL PRICES** - Sign up and create an app - Get API Key and API Secret **✅ GREAT NEWS:** - **Both environments have FREE tiers!** - **Test:** 2,000 requests/month FREE (test data) - **Production:** FREE quota + pay-as-you-go (real data) - **Production gives REAL prices** for free (within quota) - **Bonus:** 90% discount on search calls if you create bookings! **⚠️ CRITICAL:** - **Sandbox = Test data** (prices are NOT real) - **Production = Real data** (actual prices, availability) - **Production is NOT €30/month** - it has a FREE tier! - Only pay if you exceed the free quota - **AviationStack (Optional):** aviationstack.com - Free tier: 100 requests/month ⚠️ **VERY LIMITED** - For flight status tracking only - Consider paid plan ($49.99+) for production 2. **Configure the skill:** Edit `config.json` with your API keys: ```json { "apis": { "amadeus": { "api_key": "YOUR_KEY", "api_secret": "YOUR_SECRET", "sandbox_mode": true // false for Production (real prices, also FREE tier!) } } } ``` **Recommendation:** - Start with **Sandbox** (free) to test the skill functionality - Switch to **Production** (also FREE tier!) for **real prices** - Only pay if you exceed the free quota (pay-as-you-go) **Recommendation:** Start with Amadeus only. Add AviationStack only if you need flight status tracking and have budget for paid plan. ## Usage ### Basic Search ``` "Search flights from JFK to LHR" "Find flights São Paulo to Lisbon in March" "How much to fly to Thailand in December?" ``` ### With Dates ``` "Flights JFK to LAX December 15 to January 10" "Flights 01/03 to 15/03 GRU to MIA" "Search tickets to Paris departing in January" ``` ### Flexible Dates ``` "Cheapest time to fly to Europe?" "Best prices to Thailand next 3 months" "When is it cheapest to visit Paris?" ``` ### Monitor Prices ``` "Monitor prices JFK to LHR" "Alert me when flights to Rome drop" "Set up price alert for Paris" ``` ### Flight Status (Limited) ``` "Check status of flight AA123" "Is flight LA1234 on time?" "Track flight G3 4567" ``` **Note:** Flight status requires AviationStack API. Free tier is extremely limited (100 requests/month). Consider: - Use sparingly for important flights only - Upgrade to paid plan ($49.99+/month) for regular use - Alternative: Use airline's official app/website ### Compare Airlines ``` "Compare airlines flying to Asia" "Which airline is cheapest?" "Best route to Thailand" ``` ## Features - ✅ Flight search via Amadeus API - ✅ Price comparison across airlines - ✅ Flexible date search - ✅ Price monitoring and alerts - ✅ Multi-city routes - ✅ Filter by stops, airline, duration - ✅ Currency conversion - ✅ Flight status tracking (AviationStack) - ✅ International airport support ## API Information | API | Coverage | Best For | Free Tier | Paid Plans | |-----|----------|----------|-----------|------------| | **Amadeus** | Global | Flight search, prices, routes | 2,000/month | From €30/month | | **AviationStack** | Global | Flight status, tracking | 100/month ⚠️ | From $49.99/month | **Recommendation:** Start with Amadeus only. Add AviationStack only if flight status tracking is critical for your use case and you're willing to upgrade to paid plan. ## Tips - Use airport codes (JFK, LHR, BKK) for accuracy - Specify flexible dates for best prices - Set up monitoring for long-term planning - Book Tuesday-Thursday for lower prices - Book 2-3 months in advance for international flights - Check flight status before heading to airport ## Examples See `examples/` folder for: - Basic searches - Multi-city trips - Price monitoring setup - Advanced filtering - Route optimization - Flight status tracking ## Troubleshooting ### "No flights found" - Verify airport codes (use IATA codes) - Try broader date ranges - Check API key configuration - Verify API quota not exceeded ### "API error" - Verify API keys are valid - Check API rate limits - Ensure API is enabled in config - Check network connectivity ### "Prices seem wrong" - Prices update frequently - Try refreshing search - Sandbox mode uses test data - Check currency settings ### "Flight status unavailable" - Enable AviationStack in config - Verify flight number format (e.g., AA123) - Check if flight exists for date - Try without date for current status ## API Rate Limits | API | Free Tier | Rate Limit | Notes | |-----|-----------|------------|-------| | **Amadeus** | 2,000/month | ~10/second | Sandbox mode only | | **AviationStack** | 100/month | Varies | ⚠️ Very limited free tier | **Important Notes:** - **Amadeus** sandbox mode provides test data suitable for development. For production use with real prices, upgrade to production API. - **AviationStack** free tier is extremely limited (100 requests/month). For production use, consider: - Paid plans start at $49.99/month (Basic plan: 5,000 requests) - Professional plan: $149.99/month (50,000 requests) - Enterprise: Custom pricing - **Alternative:** If you need flight status, consider using Amadeus's limited flight status endpoints or airline-specific APIs. ## Contributing Contributions welcome! Please: 1. Fork the repository 2. Create a feature branch 3. Make your changes 4. Submit a pull request ## License MIT License - see LICENSE file for details. ## Credits - Amadeus for Developers: developers.amadeus.com - AviationStack: aviationstack.com

path: CHANGELOG.md
content: # Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [1.0.2] - 2026-03-02

### 📚 Documentation
- **Added credential metadata** - Declared required/optional API keys in SKILL.md frontmatter
- **Clear credential requirements** - Users now know what credentials are needed before installing
- **Configuration instructions** - Added step-by-step guide for setting up config.json
- **Security warnings** - Emphasized importance of not committing API keys

### 🔧 Changed
- **Metadata now matches reality** - Registry will show required credentials
- **No code changes** - Documentation-only update
- **Better user experience** - Users know upfront what's required

---

## [1.0.1] - 2026-03-02

### 📚 Documentation
- **Simplified SECURITY.md** - Reduced from 47KB to 3.8KB for better clarity
- **Moved security history** - Detailed bug history moved to CHANGELOG.md
- **Clear status message** - Added "Production Ready" status at top of SECURITY.md
- **Improved presentation** - Focus on current security practices instead of past issues

### 🔧 Changed
- SECURITY.md now focuses on security features and best practices
- Historical security fixes remain documented in CHANGELOG.md
- No code changes - this is a documentation-only update

---

## [1.0.0] - 2026-03-01

### 🎉 Initial Release

First public release of Flight Search skill with comprehensive security audit.

### ✨ Added
- Initial release of Flight Search skill
- Amadeus API integration for flight search
- AviationStack API integration for flight status
- Price monitoring and alerts
- 3 bash scripts (search, monitor, status)
- Complete documentation suite
- MIT License

### 🔒 Security Fixes (All Resolved)

**✅ All 18 security issues identified during development have been FIXED in v1.0.0**

**Critical (P1) - 8 issues:**
- Command injection in `search_flights.sh` - FIXED (removed eval)
- Command injection in `check_status.sh` - FIXED (removed eval)
- Python injection in `monitor_price.sh` - FIXED (safe argv passing)
- Cleartext HTTP in AviationStack - FIXED (HTTPS by default)
- Config.json tracked in git - FIXED (added to .gitignore)
- AviationStack config guard missing - FIXED (validation added)
- JSON contamination from stderr - FIXED (separate streams)
- Amadeus error handling - FIXED (proper exit codes)

**Medium (P2) - 10 issues:**
- Hardcoded AviationStack URL - FIXED (configurable)
- Numeric flight number handling - FIXED (auto-detect)
- Hardcoded alert threshold - FIXED (configurable)
- Monitoring baseline calculation - FIXED (min() function)
- HTTP URLs in documentation - FIXED (HTTPS everywhere)
- Silent failures in monitor - FIXED (show errors)
- AviationStack error handling - FIXED (exit codes)
- Ambiguous None in responses - FIXED (distinguish not-found vs error)
- Amadeus 400 responses - FIXED (exit 1 on bad requests)
- Config validation - FIXED (friendly errors for both APIs)

**Total: 18 vulnerabilities fixed (100% resolution rate)**

### 📚 Documentation
- Added `SECURITY.md` - Security policy and best practices
- Added `CONFIGURATION.md` - Complete configuration guide
- Added `PRICING.md` - Amadeus pricing explanation
- Added `WARNINGS.md` - Important warnings about API keys
- Added `config.example.json` - Example configuration file
- Updated `README.md` with better examples
- Updated `SKILL.md` with production pricing info

### 🐛 Fixed
- Corrected Amadeus pricing documentation (Production has FREE tier!)
- Added both test and production URLs to config.json
- Updated Python client to read URLs from config
- Fixed config.json structure (was missing production URLs)

### 🔧 Changed
- Removed hardcoded API URLs from Python client
- Made configuration more flexible
- Improved error handling
- Better argument validation

---

## Version History

| Version | Date | Changes |
|---------|------|---------|
| **1.0.2** | 2026-03-02 | Added credential metadata to SKILL.md |
| **1.0.1** | 2026-03-02 | Documentation improvements (simplified SECURITY.md) |
| **1.0.0** | 2026-03-01 | Initial release with security fixes |

---

## Upgrade Guide

### From Pre-release to 1.0.0

**1. Update scripts:**
```bash
# Replace old scripts with new secure versions
cp scripts/search_flights.sh scripts/search_flights.sh.old
cp scripts/check_status.sh scripts/check_status.sh.old
# Download new versions
```

**2. Update config.json:**
```json
{
  "apis": {
    "amadeus": {
      "base_url_test": "https://test.api.amadeus.com",
      "base_url_production": "https://api.amadeus.com",
      "sandbox_mode": true
    }
  }
}
```

**3. Verify security:**
```bash
# Test with malicious input (should be safe now)
./scripts/search_flights.sh "CNF; echo INJECTED" "BKK" "2026-12-15"
# Should search for "CNF; echo INJECTED" as literal airport code
# Should NOT execute echo command
```

---

## Roadmap

### v1.1.0 (Planned)
- [ ] Hotel search integration
- [ ] Multi-city trip planning
- [ ] Price history charts
- [ ] Email notifications

### v1.2.0 (Future)
- [ ] More API providers (Duffel, Skyscanner)
- [ ] Machine learning price predictions

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

---

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.


path: CONFIGURATION.md
content: # ⚙️ Configuration Guide

---

## 📋 config.json Structure

```json
{
  "apis": {
    "amadeus": {
      "enabled": true,
      "base_url_test": "https://test.api.amadeus.com",
      "base_url_production": "https://api.amadeus.com",
      "api_key": "YOUR_API_KEY_HERE",
      "api_secret": "YOUR_API_SECRET_HERE",
      "sandbox_mode": true
    },
    "aviationstack": {
      "enabled": false,
      "base_url": "https://api.aviationstack.com/v1",
      "api_key": "YOUR_API_KEY_HERE"
    }
  },
  "defaults": {
    "currency": "USD",
    "locale": "en-US",
    "max_results": 20,
    "max_stops": 2
  },
  "alerts": {
    "enabled": true,
    "check_interval_hours": 6,
    "price_drop_threshold_percent": 5
  }
}
```

---

## 🔧 Configuration Options

### **Amadeus API**

| Field | Type | Description | Default |
|-------|------|-------------|---------|
| `enabled` | boolean | Enable/disable Amadeus API | `true` |
| `base_url_test` | string | Test/Sandbox API URL | `https://test.api.amadeus.com` |
| `base_url_production` | string | Production API URL | `https://api.amadeus.com` |
| `api_key` | string | Your Amadeus API Key | Required |
| `api_secret` | string | Your Amadeus API Secret | Required |
| `sandbox_mode` | boolean | Use test (true) or production (false) | `true` |

### **AviationStack API**

| Field | Type | Description | Default |
|-------|------|-------------|---------|
| `enabled` | boolean | Enable/disable AviationStack | `false` |
| `base_url` | string | AviationStack API URL | `https://api.aviationstack.com/v1` |
| `api_key` | string | Your AviationStack API Key | Required if enabled |

### **Defaults**

| Field | Type | Description | Default |
|-------|------|-------------|---------|
| `currency` | string | Default currency (BRL, USD, EUR) | `USD` |
| `locale` | string | Default locale | `en-US` |
| `max_results` | int | Maximum results per search | `20` |
| `max_stops` | int | Maximum stops (null = any) | `2` |

### **Alerts**

| Field | Type | Description | Default |
|-------|------|-------------|---------|
| `enabled` | boolean | Enable price monitoring | `true` |
| `check_interval_hours` | int | Hours between checks | `6` |
| `price_drop_threshold_percent` | int | Alert threshold (%) | `5` |

---

## 🔄 Switching Environments

### **Test/Sandbox Mode (Default)**

```json
{
  "apis": {
    "amadeus": {
      "sandbox_mode": true,
      "api_key": "YOUR_SANDBOX_KEY",
      "api_secret": "YOUR_SANDBOX_SECRET"
    }
  }
}
```

**Use for:**
- ✅ Development
- ✅ Testing
- ✅ Learning the API

**Data:**
- ❌ Test data (prices are NOT real)

---

### **Production Mode**

```json
{
  "apis": {
    "amadeus": {
      "sandbox_mode": false,
      "api_key": "YOUR_PRODUCTION_KEY",
      "api_secret": "YOUR_PRODUCTION_SECRET"
    }
  }
}
```

**Use for:**
- ✅ Real flight searches
- ✅ Actual bookings
- ✅ Production apps

**Data:**
- ✅ Real data (prices are REAL)

**Cost:**
- ✅ FREE tier available
- 💰 Pay-as-you-go for extra calls

---

## 🌍 Currency Configuration

### **Supported Currencies:**

| Currency | Code | Example |
|----------|------|---------|
| Brazilian Real | `BRL` | R$ 1.234,56 |
| US Dollar | `USD` | $ 1,234.56 |
| Euro | `EUR` | € 1.234,56 |
| British Pound | `GBP` | £ 1,234.56 |
| Japanese Yen | `JPY` | ¥ 123,456 |

### **Change Default Currency:**

```json
{
  "defaults": {
    "currency": "USD"
  }
}
```

### **Override per Search:**

```bash
# Search in USD
python3 lib/amadeus_client.py --origin GRU --destination LHR --departure 2026-03-15 --currency USD
```

---

## ⚠️ Security Best Practices

### **DO:**
- ✅ Use environment variables for API keys
- ✅ Keep `config.json` out of git (use `.gitignore`)
- ✅ Use different keys for test and production
- ✅ Rotate keys periodically

### **DON'T:**
- ❌ Commit API keys to git
- ❌ Share keys publicly
- ❌ Use production keys for testing
- ❌ Hardcode keys in scripts

---

## 🔐 Using Environment Variables

### **Create .env file:**

```bash
# .env (add to .gitignore!)
AMADEUS_API_KEY=your_key_here
AMADEUS_API_SECRET=your_secret_here
AVIATIONSTACK_API_KEY=your_key_here
```

### **Load in Python:**

```python
import os
from dotenv import load_dotenv

load_dotenv()

[REDACTED_SECRET]"AMADEUS_API_KEY")
api_secret = os.getenv("AMADEUS_API_SECRET")
```

---

## 📝 Example Configurations

### **Minimal (Sandbox Only):**

```json
{
  "apis": {
    "amadeus": {
      "api_key": "YOUR_KEY",
      "api_secret": "YOUR_SECRET",
      "sandbox_mode": true
    }
  },
  "defaults": {
    "currency": "USD"
  }
}
```

### **Full (Both APIs):**

```json
{
  "apis": {
    "amadeus": {
      "enabled": true,
      "base_url_test": "https://test.api.amadeus.com",
      "base_url_production": "https://api.amadeus.com",
      "api_key": "YOUR_KEY",
      "api_secret": "YOUR_SECRET",
      "sandbox_mode": false
    },
    "aviationstack": {
      "enabled": true,
      "base_url": "https://api.aviationstack.com/v1",
      "api_key": "YOUR_KEY"
    }
  },
  "defaults": {
    "currency": "USD",
    "locale": "en-US",
    "max_results": 20,
    "max_stops": 2
  },
  "alerts": {
    "enabled": true,
    "check_interval_hours": 6,
    "price_drop_threshold_percent": 5
  }
}
```

---

## 🧪 Testing Configuration

### **Test Amadeus Connection:**

```bash
python3 lib/amadeus_client.py --origin GRU --destination LHR --departure 2026-03-15 --max 1
```

### **Test AviationStack Connection:**

```bash
python3 lib/aviationstack_client.py --flight AA100
```

### **Expected Output:**

```json
[
  {
    "api": "amadeus",
    "price": 2500.00,
    "currency": "USD",
    "airline": "TK"
  }
]
```

---

## ❓ FAQ

**Q: Do I need to change base_url?**
- A: No! The URLs are already configured. Just change `sandbox_mode`.

**Q: Can I use both Sandbox and Production?**
- A: Yes! Create two config files or change `sandbox_mode` as needed.

**Q: Which URL is used when sandbox_mode is true?**
- A: `base_url_test` (https://test.api.amadeus.com)

**Q: Which URL is used when sandbox_mode is false?**
- A: `base_url_production` (https://api.amadeus.com)

**Q: Do I need different API keys for test and production?**
- A: Yes! Create separate apps in Amadeus dashboard.

---

**Need help?** Check SKILL.md or create an issue on GitHub!


path: PRICING.md
content: # 💰 Amadeus Pricing - Quick Reference

---

## ✅ GREAT NEWS: Production is FREE too!

---

## 📊 Environment Comparison

| Feature | Test/Sandbox | Production |
|---------|--------------|------------|
| **Cost** | FREE ✅ | FREE + pay-as-you-go ✅ |
| **Free Quota** | 2,000/month | FREE quota/month |
| **Data** | Test ❌ | Real ✅ |
| **Prices** | Fake ❌ | Real ✅ |
| **TPS** | 10 | 40 |
| **Credit Card** | Not required | Not required (free tier) |
| **Best For** | Development | Production |

---

## 🎯 Key Points

### **1. Both Environments are FREE**
- ✅ Test/Sandbox: 100% FREE
- ✅ Production: FREE tier + pay-as-you-go

### **2. Production is NOT €30/month**
- ❌ Myth: "Production costs €30/month"
- ✅ Reality: Production has FREE tier
- 💰 Only pay if you exceed free quota

### **3. Real Prices in Production**
- ✅ Production gives REAL prices
- ✅ Production has FREE tier
- ✅ No need to pay for real data

### **4. 90% Discount with Bookings**
- 💰 Create bookings via Flight Create Orders
- 💰 Get 90% discount on search calls
- 💰 Massive savings for agencies

---

## 📈 Cost Examples

### **Personal Use (100-500 searches/month)**
- Sandbox: FREE ✅
- Production: FREE ✅
- **Your cost: $0/month**

### **Moderate Use (1,000-2,000 searches/month)**
- Sandbox: FREE ✅
- Production: FREE ✅ (within quota)
- **Your cost: $0/month**

### **Heavy Use (5,000+ searches/month)**
- Sandbox: FREE ✅
- Production: FREE quota + small fee for extra
- **Your cost: ~$5-15/month**

### **Agency with Bookings**
- Production with bookings
- 90% discount on searches
- **Your cost: 90% less**

---

## 🚀 Quick Start

### **For Testing:**
1. Create Sandbox app
2. Use test data
3. **Cost: FREE**

### **For Real Prices:**
1. Create Production app
2. Use free tier
3. **Cost: FREE** (in most cases)

---

## 🔗 Official Links

- **Pricing Page:** https://developers.amadeus.com/pricing
- **Create App:** https://developers.amadeus.com/user/apps
- **Documentation:** https://developers.amadeus.com/self-service/apis-docs

---

**Bottom Line:** Production is FREE too! Use it for real prices! ✅


path: QUICKSTART.md
content: # 🚀 Quick Start Guide

---

## ⚠️ IMPORTANT: Setup Steps

### **Step 1: Create Your Config File**

**NEVER commit API keys to git!** Follow these steps:

```bash
# 1. Copy the example config
cp config.example.json config.json

# 2. Edit with your API keys
nano config.json  # or your favorite editor

# 3. Verify config.json is in .gitignore (it should be!)
git status
# config.json should NOT appear in tracked files
```

---

## 📋 Getting API Keys

### **Amadeus (Required)**

1. Visit: https://developers.amadeus.com
2. Create FREE account
3. Create new app
4. **Choose environment:**
   - **Test/Sandbox** (FREE) - Test data only
   - **Production** (FREE tier + pay-as-you-go) - Real prices!
5. Copy API Key and API Secret
6. Paste in `config.json`

### **AviationStack (Optional)**

1. Visit: https://aviationstack.com
2. Sign up for FREE tier (100 requests/month)
3. Copy API Key
4. Paste in `config.json`
5. Set `"enabled": true`

---

## 🔧 Editing config.json

**Minimal Config (Sandbox/Test):**

```json
{
  "apis": {
    "amadeus": {
      "api_key": "YOUR_SANDBOX_KEY",
      "api_secret": "YOUR_SANDBOX_SECRET",
      "sandbox_mode": true
    }
  }
}
```

**Full Config (Production + Flight Status):**

```json
{
  "apis": {
    "amadeus": {
      "api_key": "YOUR_PRODUCTION_KEY",
      "api_secret": "YOUR_PRODUCTION_SECRET",
      "sandbox_mode": false
    },
    "aviationstack": {
      "enabled": true,
      "api_key": "YOUR_AVIATIONSTACK_KEY"
    }
  },
  "defaults": {
    "currency": "USD"
  },
  "alerts": {
    "price_drop_threshold_percent": 10
  }
}
```

---

## 🧪 Testing Your Setup

### **Test Amadeus:**

```bash
./scripts/search_flights.sh JFK LHR 2026-03-15
```

**Expected:**
- ✅ Returns flight results (test data in sandbox, real in production)
- ✅ No authentication errors

### **Test AviationStack:**

```bash
./scripts/check_status.sh AA100
```

**Expected:**
- ✅ Returns flight status (if enabled)
- ✅ No API key errors

### **Test Monitoring:**

```bash
./scripts/monitor_price.sh JFK LHR 2026-03-15
```

**Expected:**
- ✅ Shows "Monitoring active!"
- ✅ Uses threshold from config.json

---

## 🔒 Security Best Practices

### **✅ DO:**
- ✅ Copy `config.example.json` to `config.json`
- ✅ Edit `config.json` with your API keys
- ✅ Verify `config.json` is in `.gitignore`
- ✅ Use environment variables if possible
- ✅ Use different keys for test and production
- ✅ Rotate keys periodically

### **❌ DON'T:**
- ❌ Commit `config.json` to git
- ❌ Share API keys publicly
- ❌ Use production keys for testing
- ❌ Hardcode keys in scripts
- ❌ Push to public repositories with keys

---

## 🚨 Common Mistakes

### **Mistake #1: Editing config.example.json**

```bash
# ❌ WRONG! This file is tracked in git
nano config.example.json

# ✅ CORRECT! Copy first, then edit
cp config.example.json config.json
nano config.json
```

### **Mistake #2: Committing config.json**

```bash
# ❌ DANGER! API keys will be in git history
git add config.json
git commit -m "Add my config"

# ✅ CHECK FIRST! Should be in .gitignore
git status
# config.json should NOT appear

# If it appears, add to .gitignore:
echo "config.json" >> .gitignore
```

### **Mistake #3: Using Sandbox for Real Searches**

```json
{
  "apis": {
    "amadeus": {
      "sandbox_mode": true  // ← Test data!
    }
  }
}
```

**Result:** Prices are fake!

**Fix:** Set `"sandbox_mode": false` for real prices

---

## 📚 Next Steps

1. ✅ Create `config.json` from example
2. ✅ Add your API keys
3. ✅ Test with simple search
4. ✅ Read [SKILL.md](SKILL.md) for advanced usage
5. ✅ Check [CONFIGURATION.md](CONFIGURATION.md) for all options

---

## ❓ FAQ

**Q: Why do I need to copy config.example.json?**
- A: `config.json` is in `.gitignore` to prevent accidental commits of API keys. The example file is a template.

**Q: Can I commit config.json if I remove API keys first?**
- A: NO! It's too easy to make a mistake. Keep `config.json` out of git entirely.

**Q: What if I accidentally committed config.json?**
- A: 
  1. Remove from git: `git rm --cached config.json`
  2. Add to `.gitignore`
  3. Commit: `git commit -m "Remove config from git"`
  4. **ROTATE YOUR API KEYS IMMEDIATELY!**

**Q: Can I use environment variables instead?**
- A: Yes! See [CONFIGURATION.md](CONFIGURATION.md) for details.

---

## 🔗 Helpful Links

- **Amadeus Dashboard:** https://developers.amadeus.com/user/apps
- **AviationStack Dashboard:** https://aviationstack.com/dashboard
- **Config Guide:** [CONFIGURATION.md](CONFIGURATION.md)
- **Full Docs:** [SKILL.md](SKILL.md)

---

**Remember:** 🔒 **NEVER commit `config.json` to git!** 🔒


path: README.md
content: # Flight Search Skill

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![ClawHub](https://img.shields.io/badge/ClawHub-Publish-green.svg)](https://clawhub.ai)

Search flights, compare prices, and monitor airfare using Amadeus API with price drop alerts.

---

## ✨ Features

- ✅ **Flight Search** - Search flights via Amadeus API
- ✅ **Price Comparison** - Compare prices across airlines
- ✅ **Flexible Dates** - Search across date ranges
- ✅ **Price Monitoring** - Track prices and get alerts
- ✅ **Flight Status** - Real-time flight tracking (AviationStack)
- ✅ **Multi-City Routes** - Support for complex itineraries
- ✅ **Filters** - Filter by stops, airline, duration, class
- ✅ **Currency Support** - Multiple currencies (BRL, USD, EUR, etc.)

---

## 🚀 Quick Start

### 1. Get API Keys

**Amadeus (Required):**
1. Visit: https://developers.amadeus.com
2. Create account (FREE)
3. Create new app
4. **Choose environment:**
   - **Test/Sandbox** (FREE) - ⚠️ Test data only (prices are not real)
   - **Production** (FREE tier + pay-as-you-go) - ✅ Real prices!
5. Copy API Key and API Secret

**✅ GREAT NEWS:** 
- **Both environments have FREE tiers!**
- **Test:** 2,000 flight searches/month FREE
- **Production:** FREE quota + pay only for extra calls
- **Production has REAL prices** (not test data)
- **Bonus:** 90% discount if you create bookings!

**⚠️ IMPORTANT:** 
- **Sandbox mode** uses test data - prices shown are **NOT real**
- For **real flight prices**, use **Production** (also has free tier!)
- AviationStack free tier (100 requests/month) provides real flight status data

**AviationStack (Optional - for flight status):**
1. Visit: https://aviationstack.com
2. Sign up for free tier (100 requests/month)
3. Copy API Key

### 2. Configure

**⚠️ SECURITY: Never commit API keys to git!**

```bash
# 1. Copy example config (DO NOT edit config.example.json directly!)
cp config.example.json config.json

# 2. Edit config.json with your API keys
nano config.json
```

Edit `config.json`:

```json
{
  "apis": {
    "amadeus": {
      "api_key": "YOUR_API_KEY",
      "api_secret": "YOUR_API_SECRET"
    },
    "aviationstack": {
      "enabled": false,
      "api_key": "YOUR_API_KEY"
    }
  }
}
```

**See [QUICKSTART.md](QUICKSTART.md) for detailed setup instructions!**

### 3. Use

```bash
# Search flights
./scripts/search_flights.sh GRU LHR 2026-03-15

# Check flight status
./scripts/check_status.sh AA100

# Monitor prices
./scripts/monitor_price.sh CNF BKK 2026-12-15 2027-01-10
```

---

## 📖 Documentation

- **QUICKSTART.md** - 🚀 **START HERE** - Step-by-step setup guide
- **CONFIGURATION.md** - ⚙️ Configuration guide - How to configure APIs, currencies, environments
- **PRICING.md** - 💰 **MUST READ** - Amadeus pricing explained (Production is FREE too!)
- **WARNINGS.md** - ⚠️ **IMPORTANT** - API keys and data accuracy
- **SKILL.md** - Full documentation
- **SECURITY.md** - 🔒 Security audit and vulnerability fixes
- **examples/** - Usage examples
- **config.example.json** - Example configuration file

---

## 🔧 API Information

| API | Coverage | Free Tier | Best For | Security |
|-----|----------|-----------|----------|----------|
| **Amadeus** | Global | ✅ 2,000/month (Sandbox & Production!) | Flight search, prices | ✅ HTTPS |
| **AviationStack** | Global | 100/month ⚠️ | Flight status | ✅ HTTPS |

**✅ BONUS:** Amadeus Production also has FREE tier (not just Sandbox)!
**🔒 SECURITY:** All API communications use HTTPS encryption!

---

## 💡 Use Cases

- **Personal Travel** - Find best prices for your trips
- **Price Monitoring** - Track routes and get alerts on price drops
- **Flight Status** - Check real-time flight information
- **Trip Planning** - Compare routes, airlines, and schedules
- **Business Travel** - Optimize corporate travel costs

---

## 🌟 Why This Skill?

| Feature | This Skill | Others |
|---------|-----------|--------|
| **API** | Amadeus (official) | Google Flights (scraping) |
| **Price Monitoring** | ✅ Yes | ❌ No |
| **Flight Status** | ✅ Yes | ❌ No |
| **Documentation** | ✅ Complete | ⚠️ Basic |
| **Templates** | ✅ 3 templates | ❌ No |
| **Scripts** | ✅ 3 scripts | ❌ No |
| **Free Tier** | ✅ Yes (Production too!) | Varies |
| **Real Prices** | ✅ Yes (Production) | Varies |
| **Security** | ✅ Audited & Secure | ⚠️ Unknown |

---

## 📊 Examples

### Search Flights
```bash
./scripts/search_flights.sh JFK LHR 2026-03-15
```

### Round-Trip
```bash
./scripts/search_flights.sh CNF BKK 2026-12-15 2027-01-10
```

### Flight Status
```bash
./scripts/check_status.sh AA100
```

### Monitor Prices
```bash
./scripts/monitor_price.sh GRU CDG 2026-06-01 2026-06-15
```

---

## 🛠️ Technical Details

**Structure:**
```
flight-search-skill/
├── SKILL.md              # Documentation
├── LICENSE               # MIT License
├── config.json           # API configuration
├── scripts/              # Shell scripts
│   ├── search_flights.sh
│   ├── monitor_price.sh
│   └── check_status.sh
├── lib/                  # Python libraries
│   ├── amadeus_client.py
│   └── aviationstack_client.py
├── templates/            # Response templates
│   ├── results.md
│   ├── alert.md
│   └── status.md
└── examples/             # Usage examples
    ├── basic_search.md
    ├── advanced_search.md
    └── monitoring.md
```

**Requirements:**
- Python 3.7+
- `requests` library
- Amadeus API credentials

---

## 🤝 Contributing

Contributions welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request

---

## 📝 License

MIT License - see [LICENSE](LICENSE) file for details.

---

## 🙏 Credits

- **Amadeus for Developers**: https://developers.amadeus.com
- **AviationStack**: https://aviationstack.com
- **Created by**: Marco Rabelo
- **ClawHub**: https://clawhub.ai

---

## 📮 Support

- **Issues**: [GitHub Issues](https://github.com/marco-rabelo/flight-search-skill/issues)
- **ClawHub**: https://clawhub.ai/skills/flight-search
- **Documentation**: See `SKILL.md`

---

**Made with ❤️ for the OpenClaw community**


path: SECURITY.md
content: # 🔒 Security Policy

## ✅ Current Status: Production Ready

**All security issues identified during development have been resolved in v1.0.0.**

This skill has undergone comprehensive security auditing and all vulnerabilities have been fixed.

---

## 🛡️ Security Features

### **Input Validation**
- ✅ All user inputs are sanitized and validated
- ✅ No command injection vulnerabilities
- ✅ No Python code injection vulnerabilities
- ✅ Safe handling of special characters

### **Credential Protection**
- ✅ API keys stored in `config.json` (not in source code)
- ✅ `config.json` is in `.gitignore` by default
- ✅ `config.example.json` provided as template (no real keys)
- ✅ Credentials never logged or exposed in error messages

### **Network Security**
- ✅ HTTPS only for all API communications
- ✅ No cleartext transmission of sensitive data
- ✅ Proper timeout handling for network requests

### **Error Handling**
- ✅ Proper exit codes on errors (non-zero for failures)
- ✅ Clear error messages without exposing sensitive data
- ✅ Graceful handling of network/API failures
- ✅ Distinguishes between "not found" and actual errors

### **Configuration Validation**
- ✅ Validates config structure before use
- ✅ Checks for missing/empty API keys
- ✅ Friendly error messages for config issues
- ✅ No Python tracebacks on config errors

---

## 🔐 API Key Security

### **Amadeus API**
- **Required:** Yes (for flight search)
- **Free Tier:** 2,000 searches/month (sandbox), production also has free tier
- **Get Keys:** https://developers.amadeus.com
- **Storage:** `config.json` (not in git)

### **AviationStack API**
- **Required:** No (optional, for flight status only)
- **Free Tier:** 100 requests/month (very limited)
- **Get Keys:** https://aviationstack.com
- **Storage:** `config.json` (not in git)

---

## 📋 Security Checklist

Before using this skill in production:

- [ ] Copy `config.example.json` to `config.json`
- [ ] Add your Amadeus API credentials
- [ ] (Optional) Add AviationStack API credentials
- [ ] Verify `config.json` is in `.gitignore`
- [ ] Never commit `config.json` to version control
- [ ] Use production API keys for real prices (not sandbox)
- [ ] Review code if you have security concerns

---

## 🔍 Code Review

This skill is open source. We encourage security review:

- **Repository:** Check SKILL.md for links
- **Files:** All Python and Bash scripts are readable
- **No obfuscation:** All code is human-readable
- **No external dependencies:** Only standard Python libraries + requests

---

## 🚨 Reporting Security Issues

If you discover a security vulnerability:

1. **Do NOT** open a public issue
2. Email the maintainer directly
3. Include detailed reproduction steps
4. Allow time for response before disclosure

---

## 📜 Security History

**v1.0.0 (March 2026):**
- ✅ 18 security issues identified and fixed during development
- ✅ All P1 (critical) vulnerabilities resolved
- ✅ All P2 (medium) vulnerabilities resolved
- ✅ Comprehensive security audit completed
- ✅ Production-ready status achieved

**Details:** See `CHANGELOG.md` for complete list of security fixes.

---

## ⚠️ Disclaimer

This skill requires API keys from third-party services (Amadeus, AviationStack). 

- You are responsible for your API usage and costs
- Review the terms of service of each API provider
- Monitor your API usage to avoid unexpected charges
- This skill is provided "as is" without warranty

---

## 📚 Best Practices

1. **Start with Sandbox:** Test with Amadeus sandbox mode first
2. **Monitor Usage:** Track your API calls to stay within limits
3. **Rotate Keys:** Change API keys periodically
4. **Secure Storage:** Never commit `config.json` to git
5. **Review Code:** Always review code before running with API keys

---

**Questions?** Check the documentation or open an issue on the repository.


path: WARNINGS.md
content: # ⚠️ IMPORTANT WARNINGS

## About API Keys and Data Accuracy

---

## 🎯 Critical Information

### **Amadeus API Environments**

| Environment | Cost | Data | Free Quota | Use Case |
|-------------|------|------|------------|----------|
| **Test/Sandbox** | ✅ FREE | ❌ **TEST DATA** | 2,000 searches/month | Development, testing |
| **Production** | ✅ FREE + 💰 Pay-as-you-go | ✅ **REAL DATA** | FREE quota + pay extra | Real searches, bookings |

---

## ✅ GREAT NEWS: Production has FREE Tier!

**Contrary to popular belief, Production is NOT €30/month!**

### **Production Environment Pricing:**
- ✅ **FREE request quota** every month
- 💰 **Pay-as-you-go** (only pay for calls beyond free quota)
- ✅ **Real prices** and availability
- ✅ **40 TPS** (vs 10 TPS in test)
- 💰 **90% discount** on search calls if you create bookings

---

## 📊 Actual Pricing Structure

### **Test/Sandbox Environment:**
- ✅ **FREE** - No credit card required
- ✅ 2,000 flight searches/month FREE
- ✅ 10 TPS (Transactions Per Second)
- ❌ Test data (prices are NOT real)
- ✅ Perfect for development

### **Production Environment:**
- ✅ **FREE tier** + pay-as-you-go
- ✅ FREE request quota every month (same as test!)
- 💰 Pay only for calls beyond free quota
- ✅ Real-time data (prices are REAL)
- ✅ 40 TPS (4x faster than test)
- 💰 90% discount if you create bookings
- ✅ Perfect for production use

---

## 💰 Cost Examples

### **Scenario 1: Personal Use (Light)**
- Monthly searches: ~100 flight searches
- **Test Environment:** FREE ✅
- **Production Environment:** FREE ✅
- **Your cost:** **$0/month**

### **Scenario 2: Personal Use (Moderate)**
- Monthly searches: ~500 flight searches
- **Test Environment:** FREE ✅
- **Production Environment:** FREE ✅ (within free quota)
- **Your cost:** **$0/month**

### **Scenario 3: Heavy Use**
- Monthly searches: ~5,000 flight searches
- **Test Environment:** FREE ✅ (but test data)
- **Production Environment:** FREE quota + small fee for extra ~3,000 calls
- **Your cost:** ~$5-15/month (only for extra calls)

### **Scenario 4: With Bookings**
- You create bookings via Flight Create Orders
- **Production Environment:** 90% discount on search calls!
- **Your cost:** **90% less** than normal pricing

---

## ⚠️ SANDBOX MODE LIMITATIONS

When using **Sandbox** (test environment):

### **What Works:**
- ✅ API structure and endpoints
- ✅ Response format
- ✅ Search functionality
- ✅ Skill integration
- ✅ Development and testing

### **What Doesn't Work:**
- ❌ **Prices are NOT real** (test data)
- ❌ **Availability may be inaccurate**
- ❌ **Routes may be limited**
- ❌ **Cannot actually book flights**

---

## ✅ PRODUCTION MODE BENEFITS

When using **Production** (real environment):

### **What You Get:**
- ✅ **FREE tier** (yes, it's free!)
- ✅ **Real prices** (actual market prices)
- ✅ **Real availability** (live data)
- ✅ **All routes available**
- ✅ **Can actually book** flights
- ✅ **40 TPS** (faster than test)
- ✅ **Full parallelization**
- 💰 **90% discount** with bookings

### **When You Pay:**
- 💰 Only when you EXCEED the free quota
- 💰 Pay-as-you-go (no monthly fees)
- 💰 Example: If free quota is 2,000 and you make 2,500 calls, you pay for 500 extra calls

---

## 🔧 How to Switch Environments

### **Sandbox → Production:**

1. **Get Production credentials:**
   - Login to developers.amadeus.com
   - Create **Production** app (not Test!)
   - NO credit card required for free tier
   - Get Production API Key + Secret

2. **Update config.json:**
   ```json
   {
     "apis": {
       "amadeus": {
         "api_key": "YOUR_PRODUCTION_KEY",
         "api_secret": "YOUR_PRODUCTION_SECRET",
         "sandbox_mode": false  // Changed to false!
       }
     }
   }
   ```

3. **Test with real search:**
   ```bash
   ./scripts/search_flights.sh CNF BKK 2026-12-15
   ```

4. **See REAL prices!** ✅

---

## 💡 Recommendations

### **For Development:**
1. ✅ Use **Sandbox** (free)
2. ✅ Test all features
3. ✅ Verify skill works
4. ✅ **Cost: $0**

### **For Personal Use:**
1. ✅ Use **Production** (FREE tier!)
2. ✅ Get **real prices**
3. ✅ Stay within free quota
4. ✅ **Cost: $0** (in most cases)

### **For Heavy Use:**
1. ✅ Use **Production**
2. 💰 Pay-as-you-go for extra calls
3. 💰 Consider creating bookings for 90% discount
4. 💰 **Cost: $5-30/month** (depending on usage)

### **For Publishing:**
1. ✅ Publish skill with **Sandbox configuration**
2. ✅ Document clearly in README
3. ✅ Let users choose their environment
4. ✅ **Your cost: $0** (users pay for their own APIs)

---

## 🚨 Common Mistakes

### **Mistake 1: Thinking Production costs €30/month**
- ❌ "I can't afford Production"
- ⚠️ **Reality:** Production has a FREE tier!
- ✅ **Fix:** Just try Production - it's free!

### **Mistake 2: Using Sandbox for real searches**
- ❌ "I found a flight for $200!"
- ⚠️ **Reality:** That's test data, not real price
- ✅ **Fix:** Switch to Production (also free!)

### **Mistake 3: Not reading documentation**
- ❌ "The skill is broken, prices are fake!"
- ⚠️ **Reality:** User didn't switch to Production
- ✅ **Fix:** Read this file and use Production

### **Mistake 4: Thinking you need to pay upfront**
- ❌ "I need to subscribe to use Production"
- ⚠️ **Reality:** No subscription needed for free tier
- ✅ **Fix:** Just create Production app and use it!

---

## 📊 Quick Comparison

| Feature | Sandbox | Production |
|---------|---------|------------|
| **Cost** | FREE | FREE + pay-as-you-go |
| **Free Quota** | ✅ 2,000/month | ✅ FREE quota/month |
| **Data** | ❌ Test | ✅ Real |
| **Prices** | ❌ Fake | ✅ Real |
| **Booking** | ❌ No | ✅ Yes |
| **Speed** | 10 TPS | 40 TPS |
| **Credit Card** | ❌ No | ❌ No (for free tier) |
| **Best For** | Development | Production use |

---

## 🎯 Summary

| Mode | Best For | Cost | Data | Recommendation |
|------|----------|------|------|----------------|
| **Sandbox** | Testing, development | FREE | Test | ✅ Use for testing skill |
| **Production** | Real searches, bookings | FREE + pay-as-you-go | Real | ✅ **Use for real prices!** |

**Choose Production for real prices - it's also FREE!**

---

## ❓ FAQ

**Q: Is Production really free?**
- A: YES! Production has a free tier with monthly quota. Only pay if you exceed it.

**Q: Do I need a credit card for Production?**
- A: NO! You can use the free tier without a credit card.

**Q: What happens if I exceed the free quota?**
- A: You pay only for the extra calls (pay-as-you-go). No monthly fees.

**Q: Can I use Sandbox for personal searches?**
- A: Yes, but prices will be inaccurate. For real prices, use Production (also free!).

**Q: Do I need Production to publish the skill?**
- A: No! Users configure their own API keys. You pay $0.

**Q: Is AviationStack also free?**
- A: Yes! AviationStack free tier provides real flight status data (but limited to 100 requests/month).

**Q: What if I can't afford extra calls?**
- A: Stay within the free quota! Most personal users never exceed it.

**Q: How do I get 90% discount?**
- A: Create bookings via Flight Create Orders API in Production. Amadeus gives 90% discount on search calls.

---

## 🔗 Useful Links

- **Amadeus Pricing:** https://developers.amadeus.com/pricing
- **Create App:** https://developers.amadeus.com/user/apps
- **Documentation:** https://developers.amadeus.com/self-service/apis-docs
- **FAQ:** https://developers.amadeus.com/support/faq

---

**Last Updated:** March 2026
**Author:** Marco Rabelo
**Based on:** Official Amadeus pricing page (March 2026)


path: config.example.json
content: {
  "apis": {
    "amadeus": {
      "enabled": true,
      "base_url_test": "https://test.api.amadeus.com",
      "base_url_production": "https://api.amadeus.com",
      "api_key": "YOUR_API_KEY_HERE",
      "api_secret": "YOUR_API_SECRET_HERE",
      "sandbox_mode": true,
      "note": "Set sandbox_mode to false for Production (real prices, also FREE tier!)"
    },
    "aviationstack": {
      "enabled": false,
      "base_url": "https://api.aviationstack.com/v1",
      "api_key": "YOUR_API_KEY_HERE",
      "note": "Free tier: 100 requests/month only. Enable for flight status tracking."
    }
  },
  "defaults": {
    "currency": "USD",
    "locale": "en-US",
    "max_results": 20,
    "max_stops": 2
  },
  "alerts": {
    "enabled": true,
    "check_interval_hours": 6,
    "price_drop_threshold_percent": 5
  }
}


path: examples/advanced_search.md
content: # Advanced Flight Search Examples

## Multi-City Trips

```
"Search flights GRU to LIS, then LIS to CDG, then CDG to GRU"
```

**Note:** Currently requires separate searches for each leg. Future versions may support multi-city in one query.

---

## Flexible Date Range

```
"Find cheapest flights to Thailand between December 2026 and January 2027"
```

**What happens:**
- Searches multiple dates
- Finds best prices across date range
- Returns optimal departure/return dates

---

## With Constraints

```
"Flights JFK to BKK December 15 to January 10, minimum 20 days, maximum 2 stops"
```

**Parameters:**
- **Minimum stay:** 20 days
- **Maximum stops:** 2
- **Date range:** Dec 15 - Jan 10

---

## Filter by Airline

```
"Search United Airlines flights to Asia"
```

**What happens:**
- Filters results by United Airlines
- Shows only United and partners
- Useful for loyalty programs

---

## Filter by Class

```
"Business class flights to Europe"
```

**Available Classes:**
- ECONOMY
- PREMIUM_ECONOMY
- BUSINESS
- FIRST

---

## Price Range

```
"Flights to Orlando under $500"
```

**What happens:**
- Searches all dates
- Filters results under $500
- Returns only affordable options

---

## Long-Term Planning

```
"Best time to fly to Japan in 2027"
```

**What happens:**
- Searches across multiple months
- Compares seasonal prices
- Returns cheapest periods

---

## Specific Route Optimization

```
"Best route from Belo Horizonte to Bangkok with connection via Europe"
```

**What happens:**
- Searches CNF → BKK
- Filters for European connections
- Compares routes via:
  - Lisbon (TAP)
  - Frankfurt (Lufthansa)
  - Istanbul (Turkish)

---

## Using Scripts Directly

### Search Flights
```bash
./scripts/search_flights.sh CNF BKK 2026-12-15 2027-01-10
```

### With Custom Currency
```bash
python3 lib/amadeus_client.py \
  --origin CNF \
  --destination BKK \
  --departure 2026-12-15 \
  --return 2027-01-10 \
  --currency USD \
  --max 10
```

### Maximum Stops
```bash
python3 lib/amadeus_client.py \
  --origin JFK \
  --destination BKK \
  --departure 2026-12-15 \
  --max-stops 1
```

### Business Class
```bash
python3 lib/amadeus_client.py \
  --origin GRU \
  --destination CDG \
  --departure 2026-06-15 \
  --class BUSINESS
```

---

## Complex Queries

### Example 1: Marco's Thailand Trip

```
"Search flights CNF to BKK December 2026 to January 2027,
minimum 20 days, maximum 2 stops, prefer United or Turkish Airlines"
```

**Breaking it down:**
1. **Origin:** CNF (Confins, Belo Horizonte)
2. **Destination:** BKK (Bangkok)
3. **Period:** Dec 2026 - Jan 2027
4. **Duration:** 20+ days
5. **Stops:** Maximum 2
6. **Airlines:** United, Turkish (good connections via USA/Japan or Istanbul)

---

### Example 2: Family Trip to Orlando

```
"Flights GRU to MCO July 2027 for 4 adults, 2 children, under R$4000 per person"
```

**Parameters:**
- **Route:** GRU → MCO (Orlando)
- **Month:** July 2027
- **Passengers:** 4 adults + 2 children
- **Budget:** R$4000/person (R$24000 total)

---

### Example 3: Multi-Destination Europe

```
"Best route: GRU → LIS (7 days) → CDG (5 days) → FCO (4 days) → GRU"
```

**Strategy:**
1. Search GRU → LIS
2. Search LIS → CDG
3. Search CDG → FCO
4. Search FCO → GRU
5. Calculate total cost
6. Compare with open-jaw tickets

---

## Performance Tips

### Reduce API Calls
- Search specific dates instead of ranges
- Use max results parameter
- Cache results for repeated searches

### Better Results
- Search Tuesday-Thursday
- Avoid holidays and events
- Book 2-3 months in advance
- Use flexible dates

### Rate Limits
- Amadeus: 2,000 requests/month (free tier)
- AviationStack: 100 requests/month (free tier)
- Space out flexible date searches

---

## Troubleshooting Advanced Searches

### "Too many API calls"
- Reduce date range
- Increase days between searches
- Use cached results

### "No results with filters"
- Relax constraints (more stops, different dates)
- Try different airlines
- Check if route exists

### "Prices inconsistent"
- Sandbox mode uses test data
- Prices update frequently
- Currency conversion varies


path: examples/basic_search.md
content: # Basic Flight Search Examples

## Simple One-Way Search

```
"Search flights from JFK to LHR"
```

**What happens:**
- Searches for flights JFK → LHR
- Returns results for next available dates
- Shows cheapest options first

---

## Round-Trip with Dates

```
"Flights JFK to LAX December 15 to January 10"
```

**What happens:**
- Searches JFK → LAX departing Dec 15
- Returning Jan 10
- Compares round-trip prices

---

## Specific Month

```
"Find flights to Paris in March"
```

**What happens:**
- Searches all dates in March
- Finds cheapest days to fly
- Returns best prices for the month

---

## With Number of Passengers

```
"Search flights GRU to MIA for 2 adults"
```

**What happens:**
- Searches with 2 adult passengers
- Calculates total price
- Returns availability for group

---

## From Brazilian Airports

```
"Passagens CNF para LIS dezembro"
```

**What happens:**
- Searches CNF (Confins) → LIS (Lisbon)
- For December
- Returns prices in BRL (if configured)

---

## Quick Price Check

```
"How much to fly to Tokyo?"
```

**What happens:**
- Asks for departure airport if needed
- Searches for best prices to Tokyo
- Shows cheapest options across airlines

---

## Using Airport Codes

```
"Search flights GRU to CDG"
```

**Airport Codes Reference:**

| City | IATA Code |
|------|-----------|
| São Paulo (Guarulhos) | GRU |
| São Paulo (Congonhas) | CGH |
| Belo Horizonte (Confins) | CNF |
| Rio de Janeiro (Galeão) | GIG |
| Rio de Janeiro (Santos Dumont) | SDU |
| New York (JFK) | JFK |
| London (Heathrow) | LHR |
| Paris (Charles de Gaulle) | CDG |
| Bangkok | BKK |
| Tokyo (Haneda) | HND |
| Tokyo (Narita) | NRT |

---

## Tips for Better Results

✅ **Use IATA codes** (3 letters) for accuracy
✅ **Specify dates** for exact pricing
✅ **Be flexible** with dates for better prices
✅ **Include return date** for round-trip deals
✅ **Mention passengers** if more than 1

---

## Common Mistakes

❌ "Flights to Paris" (no origin)
❌ "Flights tomorrow" (no destination)
❌ "Flights December" (no year)
❌ "Flights JFK-LHR" (use spaces, not dashes)


path: examples/monitoring.md
content: # Price Monitoring Examples

## Basic Monitoring

### Start Monitoring

```
"Monitor prices JFK to LHR December"
```

**What happens:**
1. Adds route to monitoring list
2. Records current best price
3. Checks periodically (every 6 hours)
4. Alerts if price drops >5%

---

### Monitor with Specific Dates

```
"Monitor flights CNF to BKK December 15 to January 10"
```

**What happens:**
- Monitors exact dates
- Tracks price changes
- Alerts on significant drops

---

### View Monitored Flights

```
"Show my monitored flights"
```

**Returns:**
- List of all monitored routes
- Current prices
- Price history
- Last check time

---

## How Monitoring Works

### Check Frequency

| Type | Frequency | Notes |
|------|-----------|-------|
| **Heartbeat** | Every 6 hours | Automatic background check |
| **Manual** | On demand | "Check flight prices now" |
| **Alert** | Immediate | When price drops detected |

---

### Price Drop Threshold

- **Default:** 5% drop triggers alert
- **Configurable:** Can be changed in config.json
- **Example:** R$5000 → R$4750 = 5% drop = Alert

---

## Using Monitor Script

### Start Monitoring

```bash
./scripts/monitor_price.sh CNF BKK 2026-12-15 2027-01-10
```

**Output:**
```
🔍 Checking current prices...
💰 Current best price: 4850.00 BRL
✅ Added to monitoring: CNF-BKK-2026-12-15-2027-01-10
📊 Monitoring 1 flights total

🔔 Monitoring active!
   • Check HEARTBEAT.md for periodic checks
   • Alert threshold: 5% price drop
   • View all: cat .monitored_flights.json
```

---

### View Monitored Flights

```bash
cat .monitored_flights.json
```

**Example:**
```json
{
  "monitored_flights": [
    {
      "id": "CNF-BKK-2026-12-15-2027-01-10",
      "origin": "CNF",
      "destination": "BKK",
      "departure_date": "2026-12-15",
      "return_date": "2027-01-10",
      "initial_price": 4850.00,
      "last_price": 4720.00,
      "currency": "BRL",
      "threshold_percent": 5,
      "created_at": "2026-03-01T10:30:00",
      "last_check": "2026-03-01T16:30:00",
      "check_count": 2,
      "alerts_sent": 0
    }
  ]
}
```

---

## HEARTBEAT Integration

Add to your `HEARTBEAT.md`:

```markdown
## Flight Price Monitoring

### Every 6 hours:
- [ ] Check prices for monitored routes
- [ ] If price drops >5%, send alert
- [ ] Update last known prices

### Configuration:
```json
{
  "monitored_routes": [
    {
      "id": "cnf-bkk-2026",
      "origin": "CNF",
      "destination": "BKK",
      "departure_range": ["2026-12-01", "2026-12-20"],
      "return_range": ["2027-01-05", "2027-01-15"],
      "min_duration_days": 20,
      "last_price": 4850,
      "threshold_percent": 5,
      "created_at": "2026-02-28"
    }
  ]
}
```
```

---

## Alert Example

### When Price Drops

```
🔔 PRICE ALERT!

📍 Route: CNF → BKK
📅 Dates: Dec 15, 2026 - Jan 10, 2027

📉 PRICE DROPPED!
- Before: R$ 4,850.00
- Now: R$ 4,420.00
- Savings: R$ 430.00 (-8.9%)

✈️ Turkish Airlines
- Outbound: Dec 15, 18:15 (1 stop: IST)
- Return: Jan 10, 22:00 (1 stop: IST)

🔗 Book now: [link]

⚠️ Valid for: 48h
```

---

## Monitoring Strategies

### Strategy 1: Single Date Monitoring

**Best for:** Fixed travel dates

```
"Monitor flights CNF to BKK December 15 to January 10"
```

- Monitors exact dates
- Alerts on any price drop
- Good for firm plans

---

### Strategy 2: Flexible Date Monitoring

**Best for:** Flexible travel dates

```
"Monitor flights CNF to BKK December 2026 to January 2027"
```

- Monitors entire month
- Finds cheapest dates
- Alerts on best opportunities

---

### Strategy 3: Multi-Route Monitoring

**Best for:** Comparing destinations

```
"Monitor flights to Asia: Bangkok, Tokyo, Singapore"
```

- Monitors multiple destinations
- Compares prices
- Alerts on best deal

---

## Stopping Monitoring

### Remove Single Route

```
"Stop monitoring CNF to BKK"
```

### Remove All

```
"Clear all monitored flights"
```

### Using Script

```bash
# Edit .monitored_flights.json manually
# Remove the route from the array
```

---

## Best Practices

### ✅ Do

- Monitor 2-3 months before travel
- Set realistic thresholds (5-10%)
- Check alerts promptly
- Book when price is right
- Monitor multiple dates

### ❌ Don't

- Monitor too many routes (API limits)
- Set threshold too low (1-2%)
- Wait too long to book
- Ignore alerts
- Monitor more than 6 months ahead

---

## Rate Limit Considerations

| Monitoring Type | API Calls | Monthly Impact |
|-----------------|-----------|----------------|
| **1 route, 6h checks** | ~120/month | 6% of Amadeus limit |
| **3 routes, 6h checks** | ~360/month | 18% of Amadeus limit |
| **10 routes, 6h checks** | ~1200/month | 60% of Amadeus limit |

**Recommendation:** Monitor max 3-5 routes simultaneously on free tier.

---

## Advanced Monitoring

### Conditional Alerts

```
"Alert me when CNF to BKK drops below R$4500"
```

### Multi-Person Monitoring

```
"Monitor flights for family trip: 4 adults, 2 children"
```

### Seasonal Monitoring

```
"Monitor Christmas flights to Europe, alert in September"
```

---

## Troubleshooting

### "Not receiving alerts"

**Check:**
- HEARTBEAT.md is configured
- Monitoring is active (check .monitored_flights.json)
- Price drops exceed threshold
- API quota not exceeded

### "Price increased but got alert"

**Explanation:**
- Alert based on initial price, not last price
- Once triggered, alert won't repeat for same route
- Manual re-monitor to reset baseline

### "Too many alerts"

**Solutions:**
- Increase threshold (e.g., 10% instead of 5%)
- Monitor specific dates instead of ranges
- Reduce number of monitored routes


path: lib/amadeus_client.py
content: #!/usr/bin/python3
"""
Amadeus Flight Search API Client
Supports flight search, price comparison, and monitoring
"""

import requests
import json
import sys
import os
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any

class AmadeusClient:
    """Client for Amadeus Flight Search API"""
    
    def __init__(self, api_key: str, api_secret: str, sandbox: bool = True, 
                 base_url_test: str = None, base_url_production: str = None):
        self.[REDACTED_SECRET]
        self.api_secret = api_secret
        self.sandbox = sandbox
        
        # Use URLs from config or defaults
        test_url = base_url_test or "https://test.api.amadeus.com"
        prod_url = base_url_production or "https://api.amadeus.com"
        
        self.auth_url = f"{test_url if sandbox else prod_url}/v1/security/oauth2/token"
        self.api_url = test_url if sandbox else prod_url
        self.token = None
        self.token_expires_at = None
    
    def authenticate(self) -> bool:
        """Get OAuth2 token from Amadeus API"""
        url = self.auth_url
        data = {
            "grant_type": "client_credentials",
            "client_id": self.api_key,
            "client_secret": self.api_secret
        }
        
        try:
            response = requests.post(url, data=data, timeout=30)
            
            if response.status_code == 200:
                token_data = response.json()
                self.[REDACTED_SECRET]"access_token"]
                # Token expires in ~30 minutes, set expiry 5 min early for safety
                self.token_expires_at = datetime.now() + timedelta(seconds=token_data.get("expires_in", 1800) - 300)
                return True
            else:
                print(f"Authentication failed: {response.status_code} - {response.text}", file=sys.stderr)
                return False
        except Exception as e:
            print(f"Authentication error: {e}", file=sys.stderr)
            return False
    
    def _ensure_authenticated(self) -> bool:
        """Ensure we have a valid token"""
        if self.token is None or (self.token_expires_at and datetime.now() >= self.token_expires_at):
            return self.authenticate()
        return self.token is not None
    
    def search_flights(
        self,
        origin: str,
        destination: str,
        departure_date: str,
        return_date: Optional[str] = None,
        adults: int = 1,
        currency: str = "BRL",
        max_results: int = 20,
        max_stops: Optional[int] = None,
        travel_class: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Search for flights using Amadeus Flight Low-Fare Search API
        
        Args:
            origin: IATA code of origin airport (e.g., "CNF")
            destination: IATA code of destination airport (e.g., "BKK")
            departure_date: Departure date in YYYY-MM-DD format
            return_date: Optional return date in YYYY-MM-DD format
            adults: Number of adult passengers
            currency: Currency code (default: BRL)
            max_results: Maximum number of results to return
            max_stops: Maximum number of stops (None = any)
            travel_class: Travel class (ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST)
        
        Returns:
            List of flight offers with pricing and itinerary details
            None on error (network, API failure)
            Empty list [] on valid search with no results
        """
        if not self._ensure_authenticated():
            return None  # P1 fix: Return None on auth failure
        
        url = f"{self.api_url}/v2/shopping/flight-offers"
        params = {
            "originLocationCode": origin.upper(),
            "destinationLocationCode": destination.upper(),
            "departureDate": departure_date,
            "adults": adults,
            "currencyCode": currency,
            "max": max_results
        }
        
        if return_date:
            params["returnDate"] = return_date
        
        if max_stops is not None:
            params["maxStops"] = max_stops
        
        if travel_class:
            params["travelClass"] = travel_class
        
        headers = {"Authorization": f"Bearer {self.token}"}
        
        try:
            response = requests.get(url, headers=headers, params=params, timeout=30)
            
            if response.status_code == 200:
                return self._parse_flight_results(response.json())
            elif response.status_code == 400:
                # P2 fix: HTTP 400 = bad request (invalid dates, airport codes, etc.)
                # This is NOT a valid empty result - it's a user input error!
                error_data = response.json()
                error_detail = error_data.get('errors', [{}])[0].get('detail', 'Unknown error')
                print(f"Search error: {error_detail}", file=sys.stderr)
                print("  • Check date format (YYYY-MM-DD)", file=sys.stderr)
                print("  • Verify airport codes are valid IATA (e.g., CNF, GRU, JFK)", file=sys.stderr)
                return None  # P2 fix: Return None to indicate error
            else:
                print(f"Search failed: {response.status_code} - {response.text}", file=sys.stderr)
                return None  # P1 fix: Return None on API error
        except requests.exceptions.Timeout:
            print("Search error: Request timeout", file=sys.stderr)
            return None  # P1 fix: Return None on timeout
        except requests.exceptions.ConnectionError:
            print("Search error: Connection failed", file=sys.stderr)
            return None  # P1 fix: Return None on connection error
        except Exception as e:
            print(f"Search error: {e}", file=sys.stderr)
            return None  # P1 fix: Return None on other errors
    
    def _parse_flight_results(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Parse Amadeus flight search results into standardized format"""
        flights = []
        
        for offer in data.get("data", []):
            try:
                # Extract price
                price_info = offer.get("price", {})
                price = float(price_info.get("total", 0))
                currency = price_info.get("currency", "BRL")
                
                # Extract airline (first validating airline)
                airlines = offer.get("validatingAirlineCodes", [])
                airline = airlines[0] if airlines else "Unknown"
                
                # Parse itineraries (outbound and return)
                itineraries = []
                for itinerary in offer.get("itineraries", []):
                    segments = []
                    for segment in itinerary.get("segments", []):
                        segments.append({
                            "departure": segment["departure"]["at"],
                            "arrival": segment["arrival"]["at"],
                            "carrier": segment["carrierCode"],
                            "flight_number": segment["number"],
                            "duration": segment.get("duration", ""),
                            "from": segment["departure"]["iataCode"],
                            "to": segment["arrival"]["iataCode"],
                            "aircraft": segment.get("aircraft", {}).get("code", "Unknown")
                        })
                    
                    itineraries.append({
                        "duration": itinerary.get("duration", ""),
                        "segments": segments
                    })
                
                flight = {
                    "api": "amadeus",
                    "id": offer.get("id", ""),
                    "price": price,
                    "currency": currency,
                    "airline": airline,
                    "itineraries": itineraries,
                    "source": "amadeus_sandbox" if self.sandbox else "amadeus_production"
                }
                
                flights.append(flight)
            except Exception as e:
                print(f"Parse error for offer: {e}", file=sys.stderr)
                continue
        
        return flights
    
    def search_flexible_dates(
        self,
        origin: str,
        destination: str,
        start_date: str,
        end_date: str,
        trip_duration_days: Optional[int] = None,
        **kwargs
    ) -> Dict[str, List[Dict[str, Any]]]:
        """
        Search flights across a date range for best prices
        
        Args:
            origin: IATA code of origin
            destination: IATA code of destination
            start_date: Start of date range (YYYY-MM-DD)
            end_date: End of date range (YYYY-MM-DD)
            trip_duration_days: Desired trip duration (optional)
            **kwargs: Additional search parameters
        
        Returns:
            Dictionary with dates as keys and flight lists as values
        """
        results = {}
        
        # Parse dates
        start = datetime.strptime(start_date, "%Y-%m-%d")
        end = datetime.strptime(end_date, "%Y-%m-%d")
        
        # Search each date in range (limited to prevent API abuse)
        current = start
        max_days = 30  # Limit to 30 days to avoid rate limiting
        days_searched = 0
        
        while current <= end and days_searched < max_days:
            date_str = current.strftime("%Y-%m-%d")
            
            # Calculate return date if trip duration specified
            return_date = None
            if trip_duration_days:
                return_date = (current + timedelta(days=trip_duration_days)).strftime("%Y-%m-%d")
            
            flights = self.search_flights(
                origin=origin,
                destination=destination,
                departure_date=date_str,
                return_date=return_date,
                **kwargs
            )
            
            if flights:
                results[date_str] = sorted(flights, key=lambda x: x["price"])
            
            current += timedelta(days=1)
            days_searched += 1
        
        return results


def main():
    """CLI interface for Amadeus client"""
    import argparse
    
    parser = argparse.ArgumentParser(description="Search flights with Amadeus API")
    parser.add_argument("--origin", required=True, help="Origin IATA code (e.g., CNF)")
    parser.add_argument("--destination", required=True, help="Destination IATA code (e.g., BKK)")
    parser.add_argument("--departure", required=True, help="Departure date (YYYY-MM-DD)")
    parser.add_argument("--return", dest="return_date", help="Return date (YYYY-MM-DD)")
    parser.add_argument("--adults", type=int, default=1, help="Number of adults")
    parser.add_argument("--currency", default="BRL", help="Currency code")
    parser.add_argument("--max", type=int, default=20, help="Maximum results")
    parser.add_argument("--max-stops", type=int, help="Maximum stops")
    parser.add_argument("--class", dest="travel_class", help="Travel class (ECONOMY, BUSINESS)")
    parser.add_argument("--config", default="config.json", help="Path to config file")
    
    args = parser.parse_args()
    
    # Load configuration
    config_path = os.path.join(os.path.dirname(__file__), "..", args.config)
    if not os.path.exists(config_path):
        config_path = args.config
    
    try:
        with open(config_path) as f:
            config = json.load(f)
    except Exception as e:
        print(f"Error loading config: {e}", file=sys.stderr)
        sys.exit(1)
    
    # P2 fix: Validate Amadeus config structure
    if "apis" not in config:
        print("Error: Invalid config.json - missing 'apis' section", file=sys.stderr)
        print("  See config.example.json for required format", file=sys.stderr)
        sys.exit(1)
    
    if "amadeus" not in config["apis"]:
        print("Error: Invalid config.json - missing 'apis.amadeus' section", file=sys.stderr)
        print("  See config.example.json for required format", file=sys.stderr)
        sys.exit(1)
    
    amadeus_config = config["apis"]["amadeus"]
    
    # P2 fix: Validate required Amadeus keys
    required_keys = ["api_key", "api_secret"]
    missing_keys = [key for key in required_keys if key not in amadeus_config]
    
    if missing_keys:
        print(f"Error: Invalid config.json - missing Amadeus keys: {', '.join(missing_keys)}", file=sys.stderr)
        print("  See config.example.json for required format", file=sys.stderr)
        sys.exit(1)
    
    # Validate non-empty values
    if not amadeus_config.get("api_key") or not amadeus_config.get("api_secret"):
        print("Error: Invalid config.json - api_key and api_secret cannot be empty", file=sys.stderr)
        sys.exit(1)
    
    # Create client
    client = AmadeusClient(
        [REDACTED_SECRET]"api_key"],
        api_secret=amadeus_config["api_secret"],
        sandbox=amadeus_config.get("sandbox_mode", True),
        base_url_test=amadeus_config.get("base_url_test"),
        base_url_production=amadeus_config.get("base_url_production")
    )
    
    # Check authentication
    if not client.authenticate():
        print("Error: Failed to authenticate with Amadeus API", file=sys.stderr)
        print("Check your API key and secret in config.json", file=sys.stderr)
        sys.exit(1)
    
    # Search flights
    results = client.search_flights(
        origin=args.origin,
        destination=args.destination,
        departure_date=args.departure,
        return_date=args.return_date,
        adults=args.adults,
        currency=args.currency,
        max_results=args.max,
        max_stops=getattr(args, "max_stops", None),
        travel_class=args.travel_class
    )
    
    # P1 fix: Check if search actually succeeded
    # None indicates error (network, API failure), [] indicates valid no results
    if results is None:
        print("Error: Failed to search flights (network/API error)", file=sys.stderr)
        sys.exit(1)
    
    # Output results
    print(json.dumps(results, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()


path: lib/aviationstack_client.py
content: #!/usr/bin/python3
"""
AviationStack Flight Status API Client
Supports real-time flight status, tracking, and historical data
"""

import requests
import json
import sys
import os
from typing import Dict, Optional, Any
from datetime import datetime

class AviationStackClient:
    """Client for AviationStack Flight Status API"""
    
    def __init__(self, api_key: str, base_url: str = None):
        self.[REDACTED_SECRET]
        # Default to HTTPS for security (API key sent in query params)
        self.base_url = base_url or "https://api.aviationstack.com/v1"
        self.request_count = 0
        self.monthly_limit = 100  # Free tier limit
    
    def get_flight_status(
        self,
        flight_iata: Optional[str] = None,
        flight_icao: Optional[str] = None,
        flight_number: Optional[str] = None,
        date: Optional[str] = None,
        dep_iata: Optional[str] = None,
        arr_iata: Optional[str] = None
    ) -> Optional[Dict[str, Any]]:
        """
        Get flight status information
        
        Args:
            flight_iata: Flight IATA code (e.g., "AA123")
            flight_icao: Flight ICAO code (e.g., "AAL123")
            flight_number: Flight number only (e.g., "123")
            date: Flight date in YYYY-MM-DD format
            dep_iata: Departure airport IATA code
            arr_iata: Arrival airport IATA code
        
        Returns:
            Flight status information or None if not found
            None on error (network, API failure)
        """
        # Check rate limit
        if self.request_count >= self.monthly_limit:
            print("Warning: Approaching monthly rate limit", file=sys.stderr)
        
        url = f"{self.base_url}/flights"
        params = {"access_key": self.api_key}
        
        # Add optional parameters
        if flight_iata:
            params["flight_iata"] = flight_iata.upper()
        elif flight_icao:
            params["flight_icao"] = flight_icao.upper()
        
        if flight_number:
            params["flight_number"] = flight_number
        
        if date:
            params["flight_date"] = date
        
        if dep_iata:
            params["dep_iata"] = dep_iata.upper()
        
        if arr_iata:
            params["arr_iata"] = arr_iata.upper()
        
        try:
            response = requests.get(url, params=params, timeout=30)
            self.request_count += 1
            
            if response.status_code == 200:
                data = response.json()
                return self._parse_flight_data(data)
            else:
                print(f"Status check failed: {response.status_code}", file=sys.stderr)
                return None  # P2 fix: Return None on HTTP error
        
        except requests.exceptions.Timeout:
            print("Status check error: Request timeout", file=sys.stderr)
            return None  # P2 fix: Return None on timeout
        except requests.exceptions.ConnectionError:
            print("Status check error: Connection failed", file=sys.stderr)
            return None  # P2 fix: Return None on connection error
        except Exception as e:
            print(f"Status check error: {e}", file=sys.stderr)
            return None  # P2 fix: Return None on other errors
    
    def _parse_flight_data(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        Parse AviationStack response into standardized format
        
        Returns:
            Dict with flight data if found
            {} (empty dict) if valid response but no flights found
            None if data is invalid/malformed
        """
        if not data.get("data"):
            # Check if this is a valid response with no data vs invalid response
            # HTTP 200 with empty "data" array = valid "not found"
            if "data" in data and data["data"] == []:
                return {}  # P2 fix: Empty dict = valid "not found"
            return None  # Invalid/malformed response
        
        # Get first result
        flight = data["data"][0]
        
        # ... rest of parsing ...
        
        # Determine status emoji and text
        status = flight.get("flight_status", "unknown")
        status_map = {
            "scheduled": {"emoji": "⏰", "text": "Scheduled"},
            "active": {"emoji": "✈️", "text": "In Flight"},
            "landed": {"emoji": "✅", "text": "Landed"},
            "cancelled": {"emoji": "❌", "text": "Cancelled"},
            "incident": {"emoji": "⚠️", "text": "Incident"},
            "diverted": {"emoji": "🔀", "text": "Diverted"}
        }
        status_info = status_map.get(status, {"emoji": "❓", "text": "Unknown"})
        
        # Parse departure info
        departure = flight.get("departure", {})
        arrival = flight.get("arrival", {})
        airline = flight.get("airline", {})
        flight_info = flight.get("flight", {})
        aircraft = flight.get("aircraft", {})
        
        result = {
            "api": "aviationstack",
            "flight_number": flight_info.get("iata") or flight_info.get("icao"),
            "airline": {
                "name": airline.get("name"),
                "iata": airline.get("iata"),
                "icao": airline.get("icao")
            },
            "status": {
                "code": status,
                "emoji": status_info["emoji"],
                "text": status_info["text"]
            },
            "departure": {
                "airport": departure.get("airport"),
                "iata": departure.get("iata"),
                "icao": departure.get("icao"),
                "terminal": departure.get("terminal"),
                "gate": departure.get("gate"),
                "scheduled": departure.get("scheduled"),
                "estimated": departure.get("estimated"),
                "actual": departure.get("actual"),
                "delay": departure.get("delay", 0)
            },
            "arrival": {
                "airport": arrival.get("airport"),
                "iata": arrival.get("iata"),
                "icao": arrival.get("icao"),
                "terminal": arrival.get("terminal"),
                "gate": arrival.get("gate"),
                "baggage": arrival.get("baggage"),
                "scheduled": arrival.get("scheduled"),
                "estimated": arrival.get("estimated"),
                "actual": arrival.get("actual"),
                "delay": arrival.get("delay", 0)
            },
            "aircraft": {
                "registration": aircraft.get("registration"),
                "iata": aircraft.get("iata"),
                "icao": aircraft.get("icao")
            },
            "live": flight.get("live")
        }
        
        return result
    
    def search_flights_by_route(
        self,
        dep_iata: str,
        arr_iata: str,
        date: Optional[str] = None
    ) -> Optional[list]:
        """
        Search flights by route (departure and arrival airports)
        
        Args:
            dep_iata: Departure airport IATA code
            arr_iata: Arrival airport IATA code
            date: Optional date in YYYY-MM-DD format
        
        Returns:
            List of flights on this route
            None on error (network, API failure)
            Empty list [] on valid search with no results
        """
        url = f"{self.base_url}/flights"
        params = {
            "access_key": self.api_key,
            "dep_iata": dep_iata.upper(),
            "arr_iata": arr_iata.upper()
        }
        
        if date:
            params["flight_date"] = date
        
        try:
            response = requests.get(url, params=params, timeout=30)
            self.request_count += 1
            
            if response.status_code == 200:
                data = response.json()
                flights = []
                
                for flight in data.get("data", []):
                    parsed = self._parse_flight_data({"data": [flight]})
                    if parsed:
                        flights.append(parsed)
                
                return flights
            else:
                print(f"Route search failed: {response.status_code}", file=sys.stderr)
                return None  # P2 fix: Return None on HTTP error
        
        except requests.exceptions.Timeout:
            print("Route search error: Request timeout", file=sys.stderr)
            return None  # P2 fix: Return None on timeout
        except requests.exceptions.ConnectionError:
            print("Route search error: Connection failed", file=sys.stderr)
            return None  # P2 fix: Return None on connection error
        except Exception as e:
            print(f"Route search error: {e}", file=sys.stderr)
            return None  # P2 fix: Return None on other errors


def main():
    """CLI interface for AviationStack client"""
    import argparse
    
    parser = argparse.ArgumentParser(description="Check flight status with AviationStack API")
    parser.add_argument("--flight", help="Flight number (IATA: AA123 or number: 123)")
    parser.add_argument("--date", help="Flight date (YYYY-MM-DD)")
    parser.add_argument("--dep", help="Departure airport IATA code")
    parser.add_argument("--arr", help="Arrival airport IATA code")
    parser.add_argument("--config", default="config.json", help="Path to config file")
    
    args = parser.parse_args()
    
    if not args.flight and not (args.dep and args.arr):
        parser.error("Either --flight or both --dep and --arr must be specified")
    
    # Load configuration
    config_path = os.path.join(os.path.dirname(__file__), "..", args.config)
    if not os.path.exists(config_path):
        config_path = args.config
    
    try:
        with open(config_path) as f:
            config = json.load(f)
    except Exception as e:
        print(f"Error loading config: {e}", file=sys.stderr)
        sys.exit(1)
    
    # Check if AviationStack is configured
    apis_config = config.get("apis", {})
    avs_config = apis_config.get("aviationstack", {})
    
    if not avs_config:
        print("AviationStack is not configured in config.json", file=sys.stderr)
        print("Add 'aviationstack' section to config.json to enable flight status", file=sys.stderr)
        sys.exit(1)
    
    if not avs_config.get("enabled", False):
        print("AviationStack is disabled in config", file=sys.stderr)
        sys.exit(1)
    
    # P2 fix: Validate AviationStack config keys
    if "api_key" not in avs_config:
        print("Error: Invalid config.json - missing 'apis.aviationstack.api_key'", file=sys.stderr)
        print("  See config.example.json for required format", file=sys.stderr)
        sys.exit(1)
    
    # Validate non-empty value
    if not avs_config.get("api_key"):
        print("Error: Invalid config.json - api_key cannot be empty", file=sys.stderr)
        sys.exit(1)
    
    # Create client
    client = AviationStackClient(
        [REDACTED_SECRET]"api_key"],
        base_url=avs_config.get("base_url")
    )
    
    # Get flight status
    if args.flight:
        # Auto-detect if input is IATA code (AA123) or flight number (123)
        flight_input = args.flight.strip()
        
        # Check if it's numeric only (flight number)
        if flight_input.isdigit():
            # Pure numeric - use flight_number parameter
            result = client.get_flight_status(
                flight_number=flight_input,
                date=args.date
            )
        elif len(flight_input) >= 2 and flight_input[:2].isalpha():
            # Starts with letters - IATA code (e.g., AA123, LA1234)
            result = client.get_flight_status(
                flight_iata=flight_input.upper(),
                date=args.date
            )
        else:
            # Default to IATA for backwards compatibility
            result = client.get_flight_status(
                flight_iata=flight_input.upper(),
                date=args.date
            )
    else:
        results = client.search_flights_by_route(
            dep_iata=args.dep,
            arr_iata=args.arr,
            date=args.date
        )
        
        # P2 fix: Check for error vs empty results
        if results is None:
            print("Error: Failed to search flights by route (network/API error)", file=sys.stderr)
            sys.exit(1)
        
        print(json.dumps(results, indent=2, ensure_ascii=False))
        return
    
    # Single flight status check
    if result:
        # Success - found flight
        print(json.dumps(result, indent=2, ensure_ascii=False))
    elif result is None:
        # P2 fix: Network/API error
        print("Error: Failed to check flight status (network/API error)", file=sys.stderr)
        sys.exit(1)
    elif result == {}:
        # P2 fix: Valid response but flight not found
        print("Flight not found", file=sys.stderr)
        print("  • Check flight number (IATA code: AA123 or number: 123)", file=sys.stderr)
        print("  • Verify flight exists for this date", file=sys.stderr)
        sys.exit(1)
    else:
        # Unexpected result type
        print("Error: Unexpected response format", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/check_status.sh
content: #!/bin/bash
# Check flight status using AviationStack API
# Usage: ./check_status.sh FLIGHT_NUMBER [DATE]

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
PYTHON_SCRIPT="$SKILL_DIR/lib/aviationstack_client.py"

# Check arguments
if [ $# -lt 1 ]; then
    echo "Usage: $0 FLIGHT_NUMBER [DATE]"
    echo ""
    echo "Example:"
    echo "  $0 AA123"
    echo "  $0 LA1234 2026-03-15"
    echo "  $0 G34567"
    exit 1
fi

FLIGHT="$1"
DATE="${2:-}"

# Execute (safe - no eval, direct argument passing)
echo "✈️ Checking flight status: $FLIGHT" >&2
[ -n "$DATE" ] && echo "📅 Date: $DATE" >&2
echo "" >&2

if [ -n "$DATE" ]; then
    python3 "$PYTHON_SCRIPT" \
        --flight "$FLIGHT" \
        --date "$DATE"
else
    python3 "$PYTHON_SCRIPT" \
        --flight "$FLIGHT"
fi


path: scripts/monitor_price.sh
content: #!/bin/bash
# Monitor flight prices and alert when they drop
# Usage: ./monitor_price.sh ORIGIN DEST DEPARTURE_DATE [RETURN_DATE]

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
SEARCH_SCRIPT="$SCRIPT_DIR/search_flights.sh"
MONITOR_FILE="$SKILL_DIR/.monitored_flights.json"
CONFIG_FILE="$SKILL_DIR/config.json"

# Check arguments
if [ $# -lt 3 ]; then
    echo "Usage: $0 ORIGIN DEST DEPARTURE_DATE [RETURN_DATE]"
    echo ""
    echo "Example:"
    echo "  $0 CNF BKK 2026-12-15"
    echo "  $0 CNF BKK 2026-12-15 2027-01-10"
    echo ""
    echo "This will:"
    echo "  1. Add flight to monitoring list"
    echo "  2. Store current best price"
    echo "  3. Check periodically for price drops"
    echo "  4. Alert if price drops >5%"
    exit 1
fi

ORIGIN="$1"
DESTINATION="$2"
DEPARTURE="$3"
RETURN="${4:-}"

# Read threshold from config.json (P2 fix: honor configuration)
if [ -f "$CONFIG_FILE" ]; then
    THRESHOLD_PERCENT=$(python3 -c "import json; f=open('$CONFIG_FILE'); config=json.load(f); print(config.get('alerts', {}).get('price_drop_threshold_percent', 5))" 2>/dev/null || echo "5")
else
    THRESHOLD_PERCENT=5
fi

# Create monitor ID
MONITOR_ID="${ORIGIN}-${DESTINATION}-${DEPARTURE}"
[ -n "$RETURN" ] && MONITOR_ID="${MONITOR_ID}-${RETURN}"

# Initialize monitor file if doesn't exist
if [ ! -f "$MONITOR_FILE" ]; then
    echo '{"monitored_flights": []}' > "$MONITOR_FILE"
fi

# Search current prices (SAFE - no eval)
echo "🔍 Checking current prices..." >&2

# P1 fix: Capture ONLY stdout (JSON), let stderr go to terminal
# Do NOT use 2>&1 here - it mixes progress messages into JSON!
# Exit code indicates success/failure
if ! RESULTS=$("$SEARCH_SCRIPT" "$ORIGIN" "$DESTINATION" "$DEPARTURE" ${RETURN:+"$RETURN"}); then
    # Command failed (non-zero exit code)
    # Stderr already shown to user (progress/error messages)
    echo "" >&2
    echo "❌ Failed to search flights" >&2
    echo "   Possible causes:" >&2
    echo "   • Invalid/missing config.json" >&2
    echo "   • API authentication error" >&2
    echo "   • Network connection issue" >&2
    echo "   • Invalid airport codes or dates" >&2
    echo "" >&2
    echo "   Run manually to debug:" >&2
    echo "   $SEARCH_SCRIPT $ORIGIN $DESTINATION $DEPARTURE ${RETURN:-}" >&2
    exit 1
fi

if [ -z "$RESULTS" ] || [ "$RESULTS" = "[]" ]; then
    echo "❌ No flights found for this route/date" >&2
    echo "   Try different dates or check airport codes" >&2
    exit 1
fi

# Extract best price (lowest fare from all results)
# P2 fix: Don't assume data[0] is cheapest - find minimum
BEST_PRICE=$(echo "$RESULTS" | python3 -c "import sys, json; data = json.load(sys.stdin); print(min(data, key=lambda x: x['price'])['price'] if data else 0)")
CURRENCY=$(echo "$RESULTS" | python3 -c "import sys, json; data = json.load(sys.stdin); print(min(data, key=lambda x: x['price'])['currency'] if data else 'BRL')")

if [ -z "$BEST_PRICE" ] || [ "$BEST_PRICE" = "0" ]; then
    echo "❌ Could not extract price" >&2
    exit 1
fi

echo "💰 Current best price: $BEST_PRICE $CURRENCY" >&2

# Add to monitored flights (SAFE - no heredoc injection)
# Pass data via command-line arguments instead of interpolating
python3 - "$MONITOR_FILE" "$MONITOR_ID" "$ORIGIN" "$DESTINATION" "$DEPARTURE" "${RETURN:-}" "$BEST_PRICE" "$CURRENCY" "$THRESHOLD_PERCENT" << 'PYEOF'
import json
import sys
from datetime import datetime

# Read arguments (SAFE - no shell interpolation)
monitor_file = sys.argv[1]
monitor_id = sys.argv[2]
origin = sys.argv[3]
dest = sys.argv[4]
departure = sys.argv[5]
return_date = sys.argv[6] if sys.argv[6] else None
best_price = float(sys.argv[7])
currency = sys.argv[8]
threshold_percent = int(sys.argv[9])

# Load existing data
try:
    with open(monitor_file, 'r') as f:
        data = json.load(f)
except:
    data = {"monitored_flights": []}

# Check if already monitoring
existing = [f for f in data["monitored_flights"] if f["id"] == monitor_id]

if existing:
    # Update existing
    existing[0]["last_price"] = best_price
    existing[0]["last_check"] = datetime.now().isoformat()
    existing[0]["check_count"] = existing[0].get("check_count", 0) + 1
    print(f"✅ Updated monitoring: {monitor_id}", file=sys.stderr)
else:
    # Add new
    data["monitored_flights"].append({
        "id": monitor_id,
        "origin": origin,
        "destination": dest,
        "departure_date": departure,
        "return_date": return_date,
        "initial_price": best_price,
        "last_price": best_price,
        "currency": currency,
        "threshold_percent": threshold_percent,
        "created_at": datetime.now().isoformat(),
        "last_check": datetime.now().isoformat(),
        "check_count": 1,
        "alerts_sent": 0
    })
    print(f"✅ Added to monitoring: {monitor_id}", file=sys.stderr)

# Save
with open(monitor_file, 'w') as f:
    json.dump(data, f, indent=2)

print(f"📊 Monitoring {len(data['monitored_flights'])} flights total", file=sys.stderr)
PYEOF

echo "" >&2
echo "🔔 Monitoring active!" >&2
echo "   • Check HEARTBEAT.md for periodic checks" >&2
echo "   • Alert threshold: $THRESHOLD_PERCENT% price drop" >&2
echo "   • View all: cat $MONITOR_FILE" >&2


path: scripts/search_flights.sh
content: #!/bin/bash
# Search flights using Amadeus API
# Usage: ./search_flights.sh ORIGIN DEST DEPARTURE_DATE [RETURN_DATE]

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
PYTHON_SCRIPT="$SKILL_DIR/lib/amadeus_client.py"

# Check arguments
if [ $# -lt 3 ]; then
    echo "Usage: $0 ORIGIN DEST DEPARTURE_DATE [RETURN_DATE]"
    echo ""
    echo "Example:"
    echo "  $0 CNF BKK 2026-12-15"
    echo "  $0 CNF BKK 2026-12-15 2027-01-10"
    exit 1
fi

ORIGIN="$1"
DESTINATION="$2"
DEPARTURE="$3"
RETURN="${4:-}"

# Execute (safe - no eval, direct argument passing)
echo "🔍 Searching flights: $ORIGIN → $DESTINATION" >&2
echo "📅 Departure: $DEPARTURE" >&2
[ -n "$RETURN" ] && echo "📅 Return: $RETURN" >&2
echo "" >&2

if [ -n "$RETURN" ]; then
    python3 "$PYTHON_SCRIPT" \
        --origin "$ORIGIN" \
        --destination "$DESTINATION" \
        --departure "$DEPARTURE" \
        --return "$RETURN"
else
    python3 "$PYTHON_SCRIPT" \
        --origin "$ORIGIN" \
        --destination "$DESTINATION" \
        --departure "$DEPARTURE"
fi


path: templates/alert.md
content: # 🔔 PRICE ALERT!

**Route:** {{ORIGIN}} → {{DESTINATION}}
**Dates:** {{DEPARTURE_DATE}}{{#RETURN_DATE}} / {{RETURN_DATE}}{{/RETURN_DATE}}

---

## 📉 PRICE DROPPED!

- **Previous Price:** {{OLD_PRICE}} {{CURRENCY}}
- **Current Price:** {{NEW_PRICE}} {{CURRENCY}}
- **Savings:** {{SAVINGS}} {{CURRENCY}} ({{SAVINGS_PERCENT}}%)

---

## ✈️ Flight Details

**Airline:** {{AIRLINE}}
**Outbound:** {{DEPARTURE_DATE}}
{{#RETURN_DATE}}**Return:** {{RETURN_DATE}}{{/RETURN_DATE}}

{{#BOOKING_LINK}}
🔗 **Book Now:** {{BOOKING_LINK}}
{{/BOOKING_LINK}}

---

⚠️ **Price valid as of:** {{TIMESTAMP}}
⏰ **Alert expires in:** 48 hours

---

This is an automated price alert. Prices may change without notice.


path: templates/results.md
content: # 🔍 Flight Search Results

**Route:** {{ORIGIN}} → {{DESTINATION}}
**Dates:** {{DEPARTURE_DATE}}{{#RETURN_DATE}} / {{RETURN_DATE}}{{/RETURN_DATE}}
**Passengers:** {{ADULTS}} adult(s)
**Currency:** {{CURRENCY}}

---

## ✈️ Best Options

{{#FLIGHTS}}
### {{INDEX}}. {{AIRLINE}} - {{PRICE}} {{CURRENCY}}

**Outbound:** {{DEPARTURE_DATE}}
- {{#SEGMENTS_OUTBOUND}}
  - {{FROM}} → {{TO}} | {{DEPARTURE_TIME}} - {{ARRIVAL_TIME}} | {{CARRIER}} {{FLIGHT_NUMBER}}
  - Duration: {{DURATION}}
{{/SEGMENTS_OUTBOUND}}

{{#HAS_RETURN}}
**Return:** {{RETURN_DATE}}
- {{#SEGMENTS_RETURN}}
  - {{FROM}} → {{TO}} | {{DEPARTURE_TIME}} - {{ARRIVAL_TIME}} | {{CARRIER}} {{FLIGHT_NUMBER}}
  - Duration: {{DURATION}}
{{/SEGMENTS_RETURN}}
{{/HAS_RETURN}}

**Total Duration:** {{TOTAL_DURATION}}
**Class:** {{TRAVEL_CLASS}}

---
{{/FLIGHTS}}

## 📊 Summary

- **Lowest Price:** {{MIN_PRICE}} {{CURRENCY}} ({{MIN_AIRLINE}})
- **Shortest Duration:** {{MIN_DURATION}} ({{MIN_DURATION_AIRLINE}})
- **Best Value:** {{BEST_VALUE_AIRLINE}}

---

🔔 **Want to monitor prices?** Say "monitor this route"


path: templates/status.md
content: # ✈️ Flight Status

**Flight:** {{FLIGHT_NUMBER}}
**Airline:** {{AIRLINE_NAME}}
**Route:** {{DEPARTURE_AIRPORT}} ({{DEPARTURE_IATA}}) → {{ARRIVAL_AIRPORT}} ({{ARRIVAL_IATA}})
**Date:** {{FLIGHT_DATE}}

---

## {{STATUS_EMOJI}} Status: {{STATUS_TEXT}}

---

### 📍 Departure

- **Airport:** {{DEPARTURE_AIRPORT}}
- **Terminal:** {{DEPARTURE_TERMINAL}}
- **Gate:** {{DEPARTURE_GATE}}
- **Scheduled:** {{DEPARTURE_SCHEDULED}}
- **Estimated:** {{DEPARTURE_ESTIMATED}}
{{#DEPARTURE_DELAY}}
- ⚠️ **Delay:** {{DEPARTURE_DELAY}} minutes
{{/DEPARTURE_DELAY}}

---

### 🛬 Arrival

- **Airport:** {{ARRIVAL_AIRPORT}}
- **Terminal:** {{ARRIVAL_TERMINAL}}
- **Gate:** {{ARRIVAL_GATE}}
- **Baggage:** {{ARRIVAL_BAGGAGE}}
- **Scheduled:** {{ARRIVAL_SCHEDULED}}
- **Estimated:** {{ARRIVAL_ESTIMATED}}
{{#ARRIVAL_DELAY}}
- ⚠️ **Delay:** {{ARRIVAL_DELAY}} minutes
{{/ARRIVAL_DELAY}}

---

### 🛩️ Aircraft

- **Type:** {{AIRCRAFT_TYPE}}
- **Registration:** {{AIRCRAFT_REGISTRATION}}

---

{{#LIVE_TRACKING}}
### 📡 Live Tracking

- **Altitude:** {{ALTITUDE}} m
- **Speed:** {{SPEED}} km/h
- **Direction:** {{DIRECTION}}°
- **Location:** {{LATITUDE}}, {{LONGITUDE}}
- **Last Updated:** {{LIVE_UPDATED}}
{{/LIVE_TRACKING}}

---

🔔 **Want to track this flight?** Say "track {{FLIGHT_NUMBER}}"

_Data from AviationStack. Updates every 30-60 seconds._


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)