@ktmcp-cli/airportweb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +78 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/bin/airportweb.js +9 -0
- package/package.json +27 -0
- package/src/api.js +140 -0
- package/src/config.js +22 -0
- package/src/index.js +440 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# AGENT.md — Airport Web CLI for AI Agents
|
|
2
|
+
|
|
3
|
+
This document explains how to use the Airport Web CLI as an AI agent.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `airportweb` CLI provides access to the Airport Web API. No authentication is required — it is a public API.
|
|
8
|
+
|
|
9
|
+
## All Commands
|
|
10
|
+
|
|
11
|
+
### Config
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
airportweb config get <key>
|
|
15
|
+
airportweb config set <key> <value>
|
|
16
|
+
airportweb config list
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Airports
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Search airports
|
|
23
|
+
airportweb airports search "London"
|
|
24
|
+
airportweb airports search "JFK"
|
|
25
|
+
|
|
26
|
+
# Get specific airport
|
|
27
|
+
airportweb airports get JFK
|
|
28
|
+
airportweb airports get LHR
|
|
29
|
+
|
|
30
|
+
# List airports
|
|
31
|
+
airportweb airports list
|
|
32
|
+
airportweb airports list --limit 100
|
|
33
|
+
|
|
34
|
+
# By country code
|
|
35
|
+
airportweb airports by-country US
|
|
36
|
+
airportweb airports by-country GB
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Airlines
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# List airlines
|
|
43
|
+
airportweb airlines list
|
|
44
|
+
|
|
45
|
+
# Get specific airline
|
|
46
|
+
airportweb airlines get AA
|
|
47
|
+
airportweb airlines get BA
|
|
48
|
+
|
|
49
|
+
# Search airlines
|
|
50
|
+
airportweb airlines search "American"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Routes
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Get routes
|
|
57
|
+
airportweb routes get --from JFK
|
|
58
|
+
airportweb routes get --to LHR
|
|
59
|
+
airportweb routes get --from JFK --to LHR
|
|
60
|
+
|
|
61
|
+
# Search routes with filters
|
|
62
|
+
airportweb routes search --from JFK --airline AA
|
|
63
|
+
airportweb routes search --from LAX --to SFO
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## JSON Output
|
|
67
|
+
|
|
68
|
+
All commands support `--json`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
airportweb airports get JFK --json
|
|
72
|
+
airportweb airlines list --json
|
|
73
|
+
airportweb routes get --from JFK --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Error Handling
|
|
77
|
+
|
|
78
|
+
The CLI exits with code 1 on error and prints to stderr.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KTMCP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
> "Six months ago, everyone was talking about MCPs. And I was like, screw MCPs. Every MCP would be better as a CLI."
|
|
2
|
+
>
|
|
3
|
+
> — [Peter Steinberger](https://twitter.com/steipete), Founder of OpenClaw
|
|
4
|
+
> [Watch on YouTube (~2:39:00)](https://www.youtube.com/@lexfridman) | [Lex Fridman Podcast #491](https://lexfridman.com/peter-steinberger/)
|
|
5
|
+
|
|
6
|
+
# Airport Web CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for the Airport Web API. Search airports, airlines, and routes directly from your terminal.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @ktmcp-cli/airportweb
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
No authentication required — the Airport Web API is public.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
airportweb config list
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Airports
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Search airports by name or city
|
|
30
|
+
airportweb airports search "London"
|
|
31
|
+
airportweb airports search "JFK"
|
|
32
|
+
|
|
33
|
+
# Get airport by IATA code
|
|
34
|
+
airportweb airports get JFK
|
|
35
|
+
airportweb airports get LHR
|
|
36
|
+
|
|
37
|
+
# List airports
|
|
38
|
+
airportweb airports list
|
|
39
|
+
airportweb airports list --limit 20
|
|
40
|
+
|
|
41
|
+
# List airports by country
|
|
42
|
+
airportweb airports by-country US
|
|
43
|
+
airportweb airports by-country DE
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Airlines
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# List airlines
|
|
50
|
+
airportweb airlines list
|
|
51
|
+
airportweb airlines list --limit 30
|
|
52
|
+
|
|
53
|
+
# Get airline by IATA code
|
|
54
|
+
airportweb airlines get AA
|
|
55
|
+
airportweb airlines get BA
|
|
56
|
+
|
|
57
|
+
# Search airlines
|
|
58
|
+
airportweb airlines search "Delta"
|
|
59
|
+
airportweb airlines search "Lufthansa"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Routes
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Get routes from an airport
|
|
66
|
+
airportweb routes get --from JFK
|
|
67
|
+
|
|
68
|
+
# Get routes to an airport
|
|
69
|
+
airportweb routes get --to LHR
|
|
70
|
+
|
|
71
|
+
# Get routes between airports
|
|
72
|
+
airportweb routes get --from JFK --to LHR
|
|
73
|
+
|
|
74
|
+
# Search routes with filters
|
|
75
|
+
airportweb routes search --from JFK --airline AA
|
|
76
|
+
airportweb routes search --from LAX --to SFO
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### JSON Output
|
|
80
|
+
|
|
81
|
+
All commands support `--json` for machine-readable output:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
airportweb airports get JFK --json
|
|
85
|
+
airportweb airports search "London" --json | jq '.[].iata_code'
|
|
86
|
+
airportweb routes get --from JFK --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Examples
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Find all airports in Germany
|
|
93
|
+
airportweb airports by-country DE
|
|
94
|
+
|
|
95
|
+
# Search for Delta airlines
|
|
96
|
+
airportweb airlines search "Delta" --json
|
|
97
|
+
|
|
98
|
+
# Get all routes from LAX
|
|
99
|
+
airportweb routes get --from LAX --json | jq '.[].arr_iata' | sort | uniq
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/airportweb",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for Airport Web API - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"airportweb": "bin/airportweb.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["airportweb", "airports", "airlines", "routes", "cli", "api", "ktmcp"],
|
|
11
|
+
"author": "KTMCP",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"axios": "^1.6.7",
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"ora": "^8.0.1",
|
|
18
|
+
"conf": "^12.0.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": { "node": ">=18.0.0" },
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/ktmcp-cli/airportweb.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/airportweb-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/airportweb/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://airport-web.appspot.com';
|
|
4
|
+
|
|
5
|
+
const client = axios.create({
|
|
6
|
+
baseURL: BASE_URL,
|
|
7
|
+
headers: {
|
|
8
|
+
'Accept': 'application/json',
|
|
9
|
+
'Content-Type': 'application/json'
|
|
10
|
+
},
|
|
11
|
+
timeout: 15000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function handleApiError(error) {
|
|
15
|
+
if (error.response) {
|
|
16
|
+
const status = error.response.status;
|
|
17
|
+
const data = error.response.data;
|
|
18
|
+
if (status === 404) {
|
|
19
|
+
throw new Error('Resource not found.');
|
|
20
|
+
} else if (status === 429) {
|
|
21
|
+
throw new Error('Rate limit exceeded. Please wait before retrying.');
|
|
22
|
+
} else {
|
|
23
|
+
const message = data?.message || data?.error || JSON.stringify(data);
|
|
24
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
25
|
+
}
|
|
26
|
+
} else if (error.request) {
|
|
27
|
+
throw new Error('No response from Airport Web API. Check your internet connection.');
|
|
28
|
+
} else {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================
|
|
34
|
+
// AIRPORTS
|
|
35
|
+
// ============================================================
|
|
36
|
+
|
|
37
|
+
export async function listAirports({ search, limit = 50 } = {}) {
|
|
38
|
+
try {
|
|
39
|
+
const params = {};
|
|
40
|
+
if (search) params.q = search;
|
|
41
|
+
if (limit) params.limit = limit;
|
|
42
|
+
const response = await client.get('/api/airports', { params });
|
|
43
|
+
return response.data || [];
|
|
44
|
+
} catch (error) {
|
|
45
|
+
handleApiError(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function getAirportByIata(iata) {
|
|
50
|
+
try {
|
|
51
|
+
const response = await client.get(`/api/airports/${iata.toUpperCase()}`);
|
|
52
|
+
return response.data || null;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
handleApiError(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function listAirportsByCountry(country) {
|
|
59
|
+
try {
|
|
60
|
+
const params = { country };
|
|
61
|
+
const response = await client.get('/api/airports', { params });
|
|
62
|
+
return response.data || [];
|
|
63
|
+
} catch (error) {
|
|
64
|
+
handleApiError(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function searchAirports(query) {
|
|
69
|
+
try {
|
|
70
|
+
const params = { q: query };
|
|
71
|
+
const response = await client.get('/api/airports', { params });
|
|
72
|
+
return response.data || [];
|
|
73
|
+
} catch (error) {
|
|
74
|
+
handleApiError(error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================
|
|
79
|
+
// AIRLINES
|
|
80
|
+
// ============================================================
|
|
81
|
+
|
|
82
|
+
export async function listAirlines({ search, limit = 50 } = {}) {
|
|
83
|
+
try {
|
|
84
|
+
const params = {};
|
|
85
|
+
if (search) params.q = search;
|
|
86
|
+
if (limit) params.limit = limit;
|
|
87
|
+
const response = await client.get('/api/airlines', { params });
|
|
88
|
+
return response.data || [];
|
|
89
|
+
} catch (error) {
|
|
90
|
+
handleApiError(error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function getAirline(iata) {
|
|
95
|
+
try {
|
|
96
|
+
const response = await client.get(`/api/airlines/${iata.toUpperCase()}`);
|
|
97
|
+
return response.data || null;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
handleApiError(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function searchAirlines(query) {
|
|
104
|
+
try {
|
|
105
|
+
const params = { q: query };
|
|
106
|
+
const response = await client.get('/api/airlines', { params });
|
|
107
|
+
return response.data || [];
|
|
108
|
+
} catch (error) {
|
|
109
|
+
handleApiError(error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================================
|
|
114
|
+
// ROUTES
|
|
115
|
+
// ============================================================
|
|
116
|
+
|
|
117
|
+
export async function getRoutes({ from, to } = {}) {
|
|
118
|
+
try {
|
|
119
|
+
const params = {};
|
|
120
|
+
if (from) params.dep_iata = from.toUpperCase();
|
|
121
|
+
if (to) params.arr_iata = to.toUpperCase();
|
|
122
|
+
const response = await client.get('/api/routes', { params });
|
|
123
|
+
return response.data || [];
|
|
124
|
+
} catch (error) {
|
|
125
|
+
handleApiError(error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function searchRoutes({ from, to, airline } = {}) {
|
|
130
|
+
try {
|
|
131
|
+
const params = {};
|
|
132
|
+
if (from) params.dep_iata = from.toUpperCase();
|
|
133
|
+
if (to) params.arr_iata = to.toUpperCase();
|
|
134
|
+
if (airline) params.airline_iata = airline.toUpperCase();
|
|
135
|
+
const response = await client.get('/api/routes', { params });
|
|
136
|
+
return response.data || [];
|
|
137
|
+
} catch (error) {
|
|
138
|
+
handleApiError(error);
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/airportweb' });
|
|
4
|
+
|
|
5
|
+
export function getConfig(key) {
|
|
6
|
+
return config.get(key);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setConfig(key, value) {
|
|
10
|
+
config.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isConfigured() {
|
|
14
|
+
// Airport Web API is public — no auth required
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getAllConfig() {
|
|
19
|
+
return config.store;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default config;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, getAllConfig } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
listAirports,
|
|
7
|
+
getAirportByIata,
|
|
8
|
+
listAirportsByCountry,
|
|
9
|
+
searchAirports,
|
|
10
|
+
listAirlines,
|
|
11
|
+
getAirline,
|
|
12
|
+
searchAirlines,
|
|
13
|
+
getRoutes,
|
|
14
|
+
searchRoutes
|
|
15
|
+
} from './api.js';
|
|
16
|
+
|
|
17
|
+
const program = new Command();
|
|
18
|
+
|
|
19
|
+
// ============================================================
|
|
20
|
+
// Helpers
|
|
21
|
+
// ============================================================
|
|
22
|
+
|
|
23
|
+
function printSuccess(message) {
|
|
24
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printError(message) {
|
|
28
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function printTable(data, columns) {
|
|
32
|
+
if (!data || data.length === 0) {
|
|
33
|
+
console.log(chalk.yellow('No results found.'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const widths = {};
|
|
38
|
+
columns.forEach(col => {
|
|
39
|
+
widths[col.key] = col.label.length;
|
|
40
|
+
data.forEach(row => {
|
|
41
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
42
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
43
|
+
});
|
|
44
|
+
widths[col.key] = Math.min(widths[col.key], 40);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
48
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
49
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
50
|
+
|
|
51
|
+
data.forEach(row => {
|
|
52
|
+
const line = columns.map(col => {
|
|
53
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
54
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
55
|
+
}).join(' ');
|
|
56
|
+
console.log(line);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function printJson(data) {
|
|
63
|
+
console.log(JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function withSpinner(message, fn) {
|
|
67
|
+
const spinner = ora(message).start();
|
|
68
|
+
try {
|
|
69
|
+
const result = await fn();
|
|
70
|
+
spinner.stop();
|
|
71
|
+
return result;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
spinner.stop();
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================
|
|
79
|
+
// Program metadata
|
|
80
|
+
// ============================================================
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.name('airportweb')
|
|
84
|
+
.description(chalk.bold('Airport Web CLI') + ' - Airports, airlines, and routes from your terminal')
|
|
85
|
+
.version('1.0.0');
|
|
86
|
+
|
|
87
|
+
// ============================================================
|
|
88
|
+
// CONFIG
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
92
|
+
|
|
93
|
+
configCmd
|
|
94
|
+
.command('get <key>')
|
|
95
|
+
.description('Get a configuration value')
|
|
96
|
+
.action((key) => {
|
|
97
|
+
const value = getConfig(key);
|
|
98
|
+
if (value === undefined) {
|
|
99
|
+
printError(`Key '${key}' not found`);
|
|
100
|
+
} else {
|
|
101
|
+
console.log(value);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
configCmd
|
|
106
|
+
.command('set <key> <value>')
|
|
107
|
+
.description('Set a configuration value')
|
|
108
|
+
.action((key, value) => {
|
|
109
|
+
setConfig(key, value);
|
|
110
|
+
printSuccess(`Config '${key}' set`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
configCmd
|
|
114
|
+
.command('list')
|
|
115
|
+
.description('List all configuration values')
|
|
116
|
+
.action(() => {
|
|
117
|
+
const all = getAllConfig();
|
|
118
|
+
if (Object.keys(all).length === 0) {
|
|
119
|
+
console.log(chalk.yellow('No configuration set. Airport Web API is public — no auth required.'));
|
|
120
|
+
} else {
|
|
121
|
+
console.log(chalk.bold('\nAirport Web CLI Configuration\n'));
|
|
122
|
+
Object.entries(all).forEach(([k, v]) => {
|
|
123
|
+
console.log(`${k}: ${chalk.cyan(String(v))}`);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ============================================================
|
|
129
|
+
// AIRPORTS
|
|
130
|
+
// ============================================================
|
|
131
|
+
|
|
132
|
+
const airportsCmd = program.command('airports').description('Search and browse airports');
|
|
133
|
+
|
|
134
|
+
airportsCmd
|
|
135
|
+
.command('search <query>')
|
|
136
|
+
.description('Search airports by name, city, or IATA code')
|
|
137
|
+
.option('--limit <n>', 'Maximum number of results', '50')
|
|
138
|
+
.option('--json', 'Output as JSON')
|
|
139
|
+
.action(async (query, options) => {
|
|
140
|
+
try {
|
|
141
|
+
const airports = await withSpinner(`Searching airports for "${query}"...`, () =>
|
|
142
|
+
searchAirports(query)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (options.json) {
|
|
146
|
+
printJson(airports);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const data = Array.isArray(airports) ? airports.slice(0, parseInt(options.limit)) : [];
|
|
151
|
+
printTable(data, [
|
|
152
|
+
{ key: 'iata_code', label: 'IATA' },
|
|
153
|
+
{ key: 'icao_code', label: 'ICAO' },
|
|
154
|
+
{ key: 'name', label: 'Name' },
|
|
155
|
+
{ key: 'city', label: 'City' },
|
|
156
|
+
{ key: 'country', label: 'Country' },
|
|
157
|
+
{ key: 'latitude', label: 'Lat', format: (v) => v ? String(parseFloat(v).toFixed(4)) : '' },
|
|
158
|
+
{ key: 'longitude', label: 'Lon', format: (v) => v ? String(parseFloat(v).toFixed(4)) : '' }
|
|
159
|
+
]);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
printError(error.message);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
airportsCmd
|
|
167
|
+
.command('get <iata>')
|
|
168
|
+
.description('Get airport details by IATA code')
|
|
169
|
+
.option('--json', 'Output as JSON')
|
|
170
|
+
.action(async (iata, options) => {
|
|
171
|
+
try {
|
|
172
|
+
const airport = await withSpinner(`Fetching airport ${iata.toUpperCase()}...`, () =>
|
|
173
|
+
getAirportByIata(iata)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!airport) {
|
|
177
|
+
printError('Airport not found');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (options.json) {
|
|
182
|
+
printJson(airport);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(chalk.bold('\nAirport Details\n'));
|
|
187
|
+
console.log('IATA Code: ', chalk.cyan(airport.iata_code || iata.toUpperCase()));
|
|
188
|
+
console.log('ICAO Code: ', airport.icao_code || 'N/A');
|
|
189
|
+
console.log('Name: ', chalk.bold(airport.name || 'N/A'));
|
|
190
|
+
console.log('City: ', airport.city || 'N/A');
|
|
191
|
+
console.log('Country: ', airport.country || 'N/A');
|
|
192
|
+
console.log('Latitude: ', airport.latitude || 'N/A');
|
|
193
|
+
console.log('Longitude: ', airport.longitude || 'N/A');
|
|
194
|
+
console.log('Timezone: ', airport.timezone || 'N/A');
|
|
195
|
+
console.log('Elevation: ', airport.altitude ? `${airport.altitude} ft` : 'N/A');
|
|
196
|
+
console.log('');
|
|
197
|
+
} catch (error) {
|
|
198
|
+
printError(error.message);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
airportsCmd
|
|
204
|
+
.command('list')
|
|
205
|
+
.description('List airports')
|
|
206
|
+
.option('--limit <n>', 'Maximum number of results', '50')
|
|
207
|
+
.option('--json', 'Output as JSON')
|
|
208
|
+
.action(async (options) => {
|
|
209
|
+
try {
|
|
210
|
+
const airports = await withSpinner('Fetching airports...', () =>
|
|
211
|
+
listAirports({ limit: parseInt(options.limit) })
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (options.json) {
|
|
215
|
+
printJson(airports);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const data = Array.isArray(airports) ? airports : [];
|
|
220
|
+
printTable(data, [
|
|
221
|
+
{ key: 'iata_code', label: 'IATA' },
|
|
222
|
+
{ key: 'icao_code', label: 'ICAO' },
|
|
223
|
+
{ key: 'name', label: 'Name' },
|
|
224
|
+
{ key: 'city', label: 'City' },
|
|
225
|
+
{ key: 'country', label: 'Country' }
|
|
226
|
+
]);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
printError(error.message);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
airportsCmd
|
|
234
|
+
.command('by-country <country>')
|
|
235
|
+
.description('List airports by country code (e.g. US, DE, GB)')
|
|
236
|
+
.option('--json', 'Output as JSON')
|
|
237
|
+
.action(async (country, options) => {
|
|
238
|
+
try {
|
|
239
|
+
const airports = await withSpinner(`Fetching airports in ${country}...`, () =>
|
|
240
|
+
listAirportsByCountry(country)
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (options.json) {
|
|
244
|
+
printJson(airports);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const data = Array.isArray(airports) ? airports : [];
|
|
249
|
+
printTable(data, [
|
|
250
|
+
{ key: 'iata_code', label: 'IATA' },
|
|
251
|
+
{ key: 'icao_code', label: 'ICAO' },
|
|
252
|
+
{ key: 'name', label: 'Name' },
|
|
253
|
+
{ key: 'city', label: 'City' },
|
|
254
|
+
{ key: 'country', label: 'Country' }
|
|
255
|
+
]);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
printError(error.message);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ============================================================
|
|
263
|
+
// AIRLINES
|
|
264
|
+
// ============================================================
|
|
265
|
+
|
|
266
|
+
const airlinesCmd = program.command('airlines').description('Search and browse airlines');
|
|
267
|
+
|
|
268
|
+
airlinesCmd
|
|
269
|
+
.command('list')
|
|
270
|
+
.description('List airlines')
|
|
271
|
+
.option('--limit <n>', 'Maximum number of results', '50')
|
|
272
|
+
.option('--json', 'Output as JSON')
|
|
273
|
+
.action(async (options) => {
|
|
274
|
+
try {
|
|
275
|
+
const airlines = await withSpinner('Fetching airlines...', () =>
|
|
276
|
+
listAirlines({ limit: parseInt(options.limit) })
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (options.json) {
|
|
280
|
+
printJson(airlines);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const data = Array.isArray(airlines) ? airlines : [];
|
|
285
|
+
printTable(data, [
|
|
286
|
+
{ key: 'iata_code', label: 'IATA' },
|
|
287
|
+
{ key: 'icao_code', label: 'ICAO' },
|
|
288
|
+
{ key: 'name', label: 'Name' },
|
|
289
|
+
{ key: 'country', label: 'Country' },
|
|
290
|
+
{ key: 'active', label: 'Active', format: (v) => v ? 'Yes' : 'No' }
|
|
291
|
+
]);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
printError(error.message);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
airlinesCmd
|
|
299
|
+
.command('get <iata>')
|
|
300
|
+
.description('Get airline details by IATA code')
|
|
301
|
+
.option('--json', 'Output as JSON')
|
|
302
|
+
.action(async (iata, options) => {
|
|
303
|
+
try {
|
|
304
|
+
const airline = await withSpinner(`Fetching airline ${iata.toUpperCase()}...`, () =>
|
|
305
|
+
getAirline(iata)
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (!airline) {
|
|
309
|
+
printError('Airline not found');
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (options.json) {
|
|
314
|
+
printJson(airline);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log(chalk.bold('\nAirline Details\n'));
|
|
319
|
+
console.log('IATA Code: ', chalk.cyan(airline.iata_code || iata.toUpperCase()));
|
|
320
|
+
console.log('ICAO Code: ', airline.icao_code || 'N/A');
|
|
321
|
+
console.log('Name: ', chalk.bold(airline.name || 'N/A'));
|
|
322
|
+
console.log('Country: ', airline.country || 'N/A');
|
|
323
|
+
console.log('Active: ', airline.active ? chalk.green('Yes') : chalk.red('No'));
|
|
324
|
+
console.log('');
|
|
325
|
+
} catch (error) {
|
|
326
|
+
printError(error.message);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
airlinesCmd
|
|
332
|
+
.command('search <query>')
|
|
333
|
+
.description('Search airlines by name or code')
|
|
334
|
+
.option('--json', 'Output as JSON')
|
|
335
|
+
.action(async (query, options) => {
|
|
336
|
+
try {
|
|
337
|
+
const airlines = await withSpinner(`Searching airlines for "${query}"...`, () =>
|
|
338
|
+
searchAirlines(query)
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
if (options.json) {
|
|
342
|
+
printJson(airlines);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const data = Array.isArray(airlines) ? airlines : [];
|
|
347
|
+
printTable(data, [
|
|
348
|
+
{ key: 'iata_code', label: 'IATA' },
|
|
349
|
+
{ key: 'icao_code', label: 'ICAO' },
|
|
350
|
+
{ key: 'name', label: 'Name' },
|
|
351
|
+
{ key: 'country', label: 'Country' },
|
|
352
|
+
{ key: 'active', label: 'Active', format: (v) => v ? 'Yes' : 'No' }
|
|
353
|
+
]);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
printError(error.message);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// ============================================================
|
|
361
|
+
// ROUTES
|
|
362
|
+
// ============================================================
|
|
363
|
+
|
|
364
|
+
const routesCmd = program.command('routes').description('Search and browse flight routes');
|
|
365
|
+
|
|
366
|
+
routesCmd
|
|
367
|
+
.command('get')
|
|
368
|
+
.description('Get routes between airports')
|
|
369
|
+
.option('--from <iata>', 'Departure airport IATA code')
|
|
370
|
+
.option('--to <iata>', 'Arrival airport IATA code')
|
|
371
|
+
.option('--json', 'Output as JSON')
|
|
372
|
+
.action(async (options) => {
|
|
373
|
+
if (!options.from && !options.to) {
|
|
374
|
+
printError('Please specify at least --from or --to');
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const routes = await withSpinner('Fetching routes...', () =>
|
|
380
|
+
getRoutes({ from: options.from, to: options.to })
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (options.json) {
|
|
384
|
+
printJson(routes);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const data = Array.isArray(routes) ? routes : [];
|
|
389
|
+
printTable(data, [
|
|
390
|
+
{ key: 'dep_iata', label: 'From' },
|
|
391
|
+
{ key: 'arr_iata', label: 'To' },
|
|
392
|
+
{ key: 'airline_iata', label: 'Airline' },
|
|
393
|
+
{ key: 'codeshare', label: 'Codeshare', format: (v) => v ? 'Yes' : 'No' }
|
|
394
|
+
]);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
printError(error.message);
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
routesCmd
|
|
402
|
+
.command('search')
|
|
403
|
+
.description('Search routes with filters')
|
|
404
|
+
.option('--from <iata>', 'Departure airport IATA code')
|
|
405
|
+
.option('--to <iata>', 'Arrival airport IATA code')
|
|
406
|
+
.option('--airline <iata>', 'Filter by airline IATA code')
|
|
407
|
+
.option('--json', 'Output as JSON')
|
|
408
|
+
.action(async (options) => {
|
|
409
|
+
try {
|
|
410
|
+
const routes = await withSpinner('Searching routes...', () =>
|
|
411
|
+
searchRoutes({ from: options.from, to: options.to, airline: options.airline })
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
if (options.json) {
|
|
415
|
+
printJson(routes);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const data = Array.isArray(routes) ? routes : [];
|
|
420
|
+
printTable(data, [
|
|
421
|
+
{ key: 'dep_iata', label: 'From' },
|
|
422
|
+
{ key: 'arr_iata', label: 'To' },
|
|
423
|
+
{ key: 'airline_iata', label: 'Airline' },
|
|
424
|
+
{ key: 'codeshare', label: 'Codeshare', format: (v) => v ? 'Yes' : 'No' }
|
|
425
|
+
]);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
printError(error.message);
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// ============================================================
|
|
433
|
+
// Parse
|
|
434
|
+
// ============================================================
|
|
435
|
+
|
|
436
|
+
program.parse(process.argv);
|
|
437
|
+
|
|
438
|
+
if (process.argv.length <= 2) {
|
|
439
|
+
program.help();
|
|
440
|
+
}
|