@ktmcp-cli/points-of-interest 1.1.1

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/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Points of Interest CLI
2
+
3
+ Production-ready command-line interface for the Points of Interest API.
4
+
5
+ > **⚠️ Unofficial CLI** - This tool is an independent project built on the public API.
6
+
7
+ ## Features
8
+
9
+ - Complete coverage of Points of Interest API endpoints
10
+ - Simple, intuitive command structure
11
+ - JSON and pretty-print output formats
12
+ - Persistent configuration storage
13
+ - Progress indicators for async operations
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g @ktmcp-cli/points-of-interest
19
+ ```
20
+
21
+ Or install locally:
22
+
23
+ ```bash
24
+ cd points-of-interest
25
+ npm install
26
+ npm link
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ ### Set API Key
32
+
33
+ ```bash
34
+ points-of-interest config set apiKey YOUR_KEY_HERE
35
+ ```
36
+
37
+ ### Environment Variables
38
+
39
+ ```bash
40
+ export API_KEY=your_key_here
41
+ export API_BASE_URL=https://api.example.com
42
+ ```
43
+
44
+ ### View Configuration
45
+
46
+ ```bash
47
+ points-of-interest config list
48
+ points-of-interest config get apiKey
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ### search
54
+
55
+ ```bash
56
+ # List items
57
+ points-of-interest search list
58
+
59
+ # Get item by ID
60
+ points-of-interest search get <id>
61
+
62
+ # Output as JSON
63
+ points-of-interest search list --json
64
+ ```
65
+
66
+ ## Available Resources
67
+
68
+ - `search`
69
+ - `retrieve`
70
+
71
+ ## JSON Output
72
+
73
+ All commands support `--json` flag for machine-readable output:
74
+
75
+ ```bash
76
+ points-of-interest search list --json
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { readFileSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+ import chalk from 'chalk';
8
+
9
+ import config from '../src/commands/config.js';
10
+ import search from '../src/commands/search.js';
11
+ import retrieve from '../src/commands/retrieve.js';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ const packageJson = JSON.parse(
17
+ readFileSync(join(__dirname, '../package.json'), 'utf-8')
18
+ );
19
+
20
+ const program = new Command();
21
+
22
+ program
23
+ .name('points-of-interest')
24
+ .description(chalk.cyan('
25
+ Before using this API, we recommend you read our **[Authorization Guide](https://developers.amadeus.com/self-service/apis-docs/guides/authorization-262)** for more information on how to generate an access token.
26
+
27
+ Please also be aware that our test environment is based on a subset of the production, this API in test only returns a few selected cities. You can find the list in our **[data collection](https://github.com/amadeus4dev/data-collection)**.
28
+ '))
29
+ .version(packageJson.version, '-v, --version', 'Output the current version')
30
+ .addHelpText('after', `
31
+ ${chalk.bold('Examples:')}
32
+ $ points-of-interest config set apiKey YOUR_KEY
33
+ $ points-of-interest search list
34
+ $ points-of-interest search list --json
35
+
36
+ ${chalk.bold('Environment Variables:')}
37
+ API_KEY API authentication key
38
+ API_BASE_URL API base URL
39
+ API_TIMEOUT Request timeout in ms (default: 30000)
40
+
41
+ ${chalk.bold('Documentation:')}
42
+ GitHub: ${chalk.blue('https://github.com/ktmcp-cli/points-of-interest')}
43
+ `);
44
+
45
+ program.addCommand(config);
46
+ program.addCommand(search);
47
+ program.addCommand(retrieve);
48
+
49
+ process.on('unhandledRejection', (error) => {
50
+ console.error(chalk.red('Unhandled error:'), error);
51
+ process.exit(1);
52
+ });
53
+
54
+ program.parse(process.argv);
55
+
56
+ if (!process.argv.slice(2).length) {
57
+ program.outputHelp();
58
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@ktmcp-cli/points-of-interest",
3
+ "version": "1.1.1",
4
+ "description": "\nBefore using this API, we recommend you read our **[Authorization Guide](https://developers.amadeus.com/self-service/apis-docs/guides/authorization-262)** for more information on how to generate an access token.\n\nPlease also be aware that our test environment is based on a subset of the production, this API in test only returns a few selected cities. You can find the list in our **[data collection](https://github.com/amadeus4dev/data-collection)**.\n",
5
+ "type": "module",
6
+ "main": "bin/points-of-interest.js",
7
+ "bin": {
8
+ "points-of-interest": "./bin/points-of-interest.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/points-of-interest.js",
12
+ "dev": "node bin/points-of-interest.js"
13
+ },
14
+ "keywords": [
15
+ "cli",
16
+ "api",
17
+ "points-of-interest",
18
+ "ktmcp"
19
+ ],
20
+ "author": "KTMCP",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "commander": "^12.0.0",
24
+ "axios": "^1.6.0",
25
+ "chalk": "^5.3.0",
26
+ "conf": "^12.0.0",
27
+ "dotenv": "^16.4.0",
28
+ "ora": "^8.0.1"
29
+ },
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ }
33
+ }
@@ -0,0 +1,42 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { loadConfig, setConfig, getConfig, saveConfig } from '../lib/config.js';
4
+
5
+ const config = new Command('config');
6
+
7
+ config.description('Manage CLI configuration');
8
+
9
+ config
10
+ .command('set <key> <value>')
11
+ .description('Set a configuration value')
12
+ .action((key, value) => {
13
+ if (setConfig(key, value)) {
14
+ console.log(chalk.green(`✓ Set ${key} = ${value}`));
15
+ } else {
16
+ console.error(chalk.red('Failed to save configuration'));
17
+ process.exit(1);
18
+ }
19
+ });
20
+
21
+ config
22
+ .command('get <key>')
23
+ .description('Get a configuration value')
24
+ .action((key) => {
25
+ const value = getConfig(key);
26
+ if (value !== undefined) {
27
+ console.log(chalk.cyan(`${key} = ${value}`));
28
+ } else {
29
+ console.log(chalk.yellow(`${key} is not set`));
30
+ }
31
+ });
32
+
33
+ config
34
+ .command('list')
35
+ .description('List all configuration values')
36
+ .action(() => {
37
+ const all = loadConfig();
38
+ console.log(chalk.cyan('Current configuration:'));
39
+ console.log(JSON.stringify(all, null, 2));
40
+ });
41
+
42
+ export default config;
@@ -0,0 +1,57 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { createClient, handleError } from '../lib/api.js';
5
+
6
+ const retrieve = new Command('retrieve');
7
+
8
+ retrieve
9
+ .description('Manage retrieve');
10
+
11
+ retrieve
12
+ .command('get <pois-id>')
13
+ .description('Retieve one point of interest by its Id.')
14
+ .option('--json', 'Output as JSON')
15
+ .option('-v, --verbose', 'Verbose output')
16
+ .action(async (poisId, options) => {
17
+ const spinner = ora('Retieve one point of interest by its Id....').start();
18
+ try {
19
+ const client = createClient();
20
+ const response = await client.get(`/reference-data/locations/pois/${poisId}`);
21
+ const result = { success: true, status: response.status, data: response.data };
22
+
23
+ spinner.succeed('Success');
24
+
25
+ if (result.success) {
26
+ if (options.json) {
27
+ console.log(JSON.stringify(result.data, null, 2));
28
+ } else {
29
+ // Detail output
30
+ const item = result.data;
31
+ console.log(chalk.bold('Details:'));
32
+ console.log(chalk.gray('─'.repeat(60)));
33
+ Object.entries(item).slice(0, 10).forEach(([key, value]) => {
34
+ console.log(chalk.cyan(key + ':'), String(value).slice(0, 100));
35
+ });
36
+ }
37
+ process.exit(0);
38
+ } else {
39
+ spinner.fail(`Failed (${result.status})`);
40
+ console.error(chalk.red(result.message));
41
+ if (options.verbose && result.data) {
42
+ console.error(chalk.yellow('\nResponse data:'));
43
+ console.error(JSON.stringify(result.data, null, 2));
44
+ }
45
+ process.exit(1);
46
+ }
47
+ } catch (error) {
48
+ spinner.fail('Error occurred');
49
+ console.error(chalk.red('Error:'), error.message);
50
+ if (options.verbose) {
51
+ console.error(error.stack);
52
+ }
53
+ process.exit(1);
54
+ }
55
+ });
56
+
57
+ export default retrieve;
@@ -0,0 +1,121 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { createClient, handleError } from '../lib/api.js';
5
+
6
+ const search = new Command('search');
7
+
8
+ search
9
+ .description('Manage search');
10
+
11
+ search
12
+ .command('get')
13
+ .description('Returns points of interest for a given location and radius.')
14
+ .requiredOption('--latitude <value>', 'Latitude (decimal coordinates)')
15
+ .requiredOption('--longitude <value>', 'Longitude (decimal coordinates)')
16
+ .option('--radius <value>', 'radius of the search in Kilometer. Can be from 0 to 20, default value is 1 Km.')
17
+ .option('--json', 'Output as JSON')
18
+ .option('-v, --verbose', 'Verbose output')
19
+ .action(async (options) => {
20
+ const spinner = ora('Returns points of interest for a given location and radius....').start();
21
+ try {
22
+ const client = createClient();
23
+ const queryParams = {};
24
+ if (options.latitude) queryParams.latitude = options.latitude;
25
+ if (options.longitude) queryParams.longitude = options.longitude;
26
+ if (options.radius) queryParams.radius = options.radius;
27
+
28
+ const response = await client.get(`/reference-data/locations/pois`, { params: queryParams });
29
+ const result = { success: true, status: response.status, data: response.data };
30
+
31
+ spinner.succeed('Success');
32
+
33
+ if (result.success) {
34
+ if (options.json) {
35
+ console.log(JSON.stringify(result.data, null, 2));
36
+ } else {
37
+ // Detail output
38
+ const item = result.data;
39
+ console.log(chalk.bold('Details:'));
40
+ console.log(chalk.gray('─'.repeat(60)));
41
+ Object.entries(item).slice(0, 10).forEach(([key, value]) => {
42
+ console.log(chalk.cyan(key + ':'), String(value).slice(0, 100));
43
+ });
44
+ }
45
+ process.exit(0);
46
+ } else {
47
+ spinner.fail(`Failed (${result.status})`);
48
+ console.error(chalk.red(result.message));
49
+ if (options.verbose && result.data) {
50
+ console.error(chalk.yellow('\nResponse data:'));
51
+ console.error(JSON.stringify(result.data, null, 2));
52
+ }
53
+ process.exit(1);
54
+ }
55
+ } catch (error) {
56
+ spinner.fail('Error occurred');
57
+ console.error(chalk.red('Error:'), error.message);
58
+ if (options.verbose) {
59
+ console.error(error.stack);
60
+ }
61
+ process.exit(1);
62
+ }
63
+ });
64
+
65
+ search
66
+ .command('get')
67
+ .description('Returns points of interest for a given area')
68
+ .requiredOption('--north <value>', 'Latitude north of bounding box (decimal coordinates)')
69
+ .requiredOption('--west <value>', 'Longitude west of bounding box (decimal coordinates)')
70
+ .requiredOption('--south <value>', 'Latitude south of bounding box (decimal coordinates)')
71
+ .requiredOption('--east <value>', 'Longitude east of bounding box (decimal coordinates)')
72
+ .option('--json', 'Output as JSON')
73
+ .option('-v, --verbose', 'Verbose output')
74
+ .action(async (options) => {
75
+ const spinner = ora('Returns points of interest for a given area...').start();
76
+ try {
77
+ const client = createClient();
78
+ const queryParams = {};
79
+ if (options.north) queryParams.north = options.north;
80
+ if (options.west) queryParams.west = options.west;
81
+ if (options.south) queryParams.south = options.south;
82
+ if (options.east) queryParams.east = options.east;
83
+
84
+ const response = await client.get(`/reference-data/locations/pois/by-square`, { params: queryParams });
85
+ const result = { success: true, status: response.status, data: response.data };
86
+
87
+ spinner.succeed('Success');
88
+
89
+ if (result.success) {
90
+ if (options.json) {
91
+ console.log(JSON.stringify(result.data, null, 2));
92
+ } else {
93
+ // Detail output
94
+ const item = result.data;
95
+ console.log(chalk.bold('Details:'));
96
+ console.log(chalk.gray('─'.repeat(60)));
97
+ Object.entries(item).slice(0, 10).forEach(([key, value]) => {
98
+ console.log(chalk.cyan(key + ':'), String(value).slice(0, 100));
99
+ });
100
+ }
101
+ process.exit(0);
102
+ } else {
103
+ spinner.fail(`Failed (${result.status})`);
104
+ console.error(chalk.red(result.message));
105
+ if (options.verbose && result.data) {
106
+ console.error(chalk.yellow('\nResponse data:'));
107
+ console.error(JSON.stringify(result.data, null, 2));
108
+ }
109
+ process.exit(1);
110
+ }
111
+ } catch (error) {
112
+ spinner.fail('Error occurred');
113
+ console.error(chalk.red('Error:'), error.message);
114
+ if (options.verbose) {
115
+ console.error(error.stack);
116
+ }
117
+ process.exit(1);
118
+ }
119
+ });
120
+
121
+ export default search;
package/src/lib/api.js ADDED
@@ -0,0 +1,59 @@
1
+ import axios from 'axios';
2
+ import { loadConfig } from './config.js';
3
+
4
+ /**
5
+ * Create axios instance with configuration
6
+ */
7
+ function createClient(options = {}) {
8
+ const config = loadConfig();
9
+ const baseURL = options.baseUrl || config.baseUrl || 'https://api.example.com';
10
+
11
+ const instance = axios.create({
12
+ baseURL,
13
+ timeout: options.timeout || config.timeout || 30000,
14
+ headers: {
15
+ 'User-Agent': '@ktmcp-cli/points-of-interest/1.0.0',
16
+ 'Content-Type': 'application/json'
17
+ }
18
+ });
19
+
20
+ // Add authentication
21
+ instance.interceptors.request.use((config) => {
22
+ // No authentication configured
23
+ return config;
24
+ });
25
+
26
+ return instance;
27
+ }
28
+
29
+ /**
30
+ * Handle API errors consistently
31
+ */
32
+ function handleError(error) {
33
+ if (error.response) {
34
+ return {
35
+ success: false,
36
+ status: error.response.status,
37
+ statusText: error.response.statusText,
38
+ message: error.response.data?.message ||
39
+ error.response.data?.error ||
40
+ 'API request failed',
41
+ data: error.response.data
42
+ };
43
+ } else if (error.request) {
44
+ return {
45
+ success: false,
46
+ status: 0,
47
+ message: 'No response from server',
48
+ error: error.message
49
+ };
50
+ } else {
51
+ return {
52
+ success: false,
53
+ status: 0,
54
+ message: error.message
55
+ };
56
+ }
57
+ }
58
+
59
+ export { createClient, handleError };
@@ -0,0 +1,58 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import dotenv from 'dotenv';
5
+
6
+ dotenv.config();
7
+
8
+ const CONFIG_DIR = join(homedir(), '.points-of-interest');
9
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
10
+
11
+ const DEFAULT_CONFIG = {
12
+ baseUrl: process.env.API_BASE_URL || 'https://api.example.com',
13
+ apiKey: process.env.API_KEY || '',
14
+ timeout: parseInt(process.env.API_TIMEOUT || '30000', 10)
15
+ };
16
+
17
+ export function loadConfig() {
18
+ if (existsSync(CONFIG_FILE)) {
19
+ try {
20
+ const fileConfig = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
21
+ return { ...DEFAULT_CONFIG, ...fileConfig };
22
+ } catch (error) {
23
+ console.error('Error reading config file:', error.message);
24
+ }
25
+ }
26
+ return DEFAULT_CONFIG;
27
+ }
28
+
29
+ export function saveConfig(config) {
30
+ try {
31
+ if (!existsSync(CONFIG_DIR)) {
32
+ mkdirSync(CONFIG_DIR, { recursive: true });
33
+ }
34
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
35
+ return true;
36
+ } catch (error) {
37
+ console.error('Error saving config:', error.message);
38
+ return false;
39
+ }
40
+ }
41
+
42
+ export function getConfig(key) {
43
+ const config = loadConfig();
44
+ return config[key];
45
+ }
46
+
47
+ export function setConfig(key, value) {
48
+ const config = loadConfig();
49
+ config[key] = value;
50
+ return saveConfig(config);
51
+ }
52
+
53
+ export default {
54
+ loadConfig,
55
+ saveConfig,
56
+ getConfig,
57
+ setConfig
58
+ };