@ktmcp-cli/abstractapi 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 +36 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/bin/abstractapi.js +2 -0
- package/package.json +27 -0
- package/src/api.js +81 -0
- package/src/config.js +19 -0
- package/src/index.js +251 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# AbstractAPI CLI - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
This CLI provides programmatic access to the AbstractAPI Geolocation API.
|
|
4
|
+
|
|
5
|
+
## Quick Start for AI Agents
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
abstractapi config set --api-key YOUR_KEY
|
|
9
|
+
abstractapi geolocate current
|
|
10
|
+
abstractapi geolocate lookup 8.8.8.8
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Commands
|
|
14
|
+
|
|
15
|
+
### config
|
|
16
|
+
- `abstractapi config set --api-key <key>` - Set API key
|
|
17
|
+
- `abstractapi config get <key>` - Get config value
|
|
18
|
+
- `abstractapi config list` - Show all config
|
|
19
|
+
|
|
20
|
+
### geolocate
|
|
21
|
+
- `abstractapi geolocate lookup <ip>` - Look up geolocation for an IP address
|
|
22
|
+
- `abstractapi geolocate current` - Look up your own current IP's geolocation
|
|
23
|
+
|
|
24
|
+
### vpn
|
|
25
|
+
- `abstractapi vpn detect [ip]` - Detect if IP is using VPN/proxy (omit IP for your own)
|
|
26
|
+
|
|
27
|
+
### timezone
|
|
28
|
+
- `abstractapi timezone get [--ip <ip>] [--location <location>]` - Get timezone info
|
|
29
|
+
- `abstractapi timezone current` - Get timezone for your current IP
|
|
30
|
+
|
|
31
|
+
## Tips for Agents
|
|
32
|
+
|
|
33
|
+
- All commands support `--json` for machine-readable output
|
|
34
|
+
- Pipe to `jq`: `abstractapi geolocate lookup 8.8.8.8 --json | jq '{city, country, timezone}'`
|
|
35
|
+
- Omit IP in vpn detect to check your own IP
|
|
36
|
+
- Location strings work for timezone: "London", "New York", "51.5,-0.09"
|
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,116 @@
|
|
|
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
|
+
# AbstractAPI CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for the [AbstractAPI](https://abstractapi.com) Geolocation API. Look up IP addresses, detect VPNs, and get timezone information directly from your terminal.
|
|
9
|
+
|
|
10
|
+
> **Disclaimer**: This is an unofficial CLI tool and is not affiliated with, endorsed by, or supported by Abstract API.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @ktmcp-cli/abstractapi
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
abstractapi config set --api-key YOUR_API_KEY
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Get your API key at [abstractapi.com](https://abstractapi.com).
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Configuration
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Set API key
|
|
32
|
+
abstractapi config set --api-key YOUR_API_KEY
|
|
33
|
+
|
|
34
|
+
# Show configuration
|
|
35
|
+
abstractapi config list
|
|
36
|
+
|
|
37
|
+
# Get a specific config value
|
|
38
|
+
abstractapi config get apiKey
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Geolocation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Look up an IP address
|
|
45
|
+
abstractapi geolocate lookup 8.8.8.8
|
|
46
|
+
abstractapi geolocate lookup 1.1.1.1
|
|
47
|
+
|
|
48
|
+
# Look up your own current IP
|
|
49
|
+
abstractapi geolocate current
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### VPN Detection
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Detect if an IP is using a VPN or proxy
|
|
56
|
+
abstractapi vpn detect 8.8.8.8
|
|
57
|
+
|
|
58
|
+
# Check your own IP
|
|
59
|
+
abstractapi vpn detect
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Timezone
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Get timezone for an IP
|
|
66
|
+
abstractapi timezone get --ip 8.8.8.8
|
|
67
|
+
|
|
68
|
+
# Get timezone by location name
|
|
69
|
+
abstractapi timezone get --location "London"
|
|
70
|
+
abstractapi timezone get --location "New York"
|
|
71
|
+
|
|
72
|
+
# Get timezone for your current IP
|
|
73
|
+
abstractapi timezone current
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### JSON Output
|
|
77
|
+
|
|
78
|
+
All commands support `--json` for machine-readable output:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Look up IP as JSON
|
|
82
|
+
abstractapi geolocate lookup 8.8.8.8 --json
|
|
83
|
+
|
|
84
|
+
# Pipe to jq
|
|
85
|
+
abstractapi geolocate lookup 1.1.1.1 --json | jq '{city, country, timezone}'
|
|
86
|
+
|
|
87
|
+
# Get your timezone as JSON
|
|
88
|
+
abstractapi timezone current --json | jq '.timezone'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Examples
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Find where a suspicious IP is located
|
|
95
|
+
abstractapi geolocate lookup 192.168.1.1
|
|
96
|
+
|
|
97
|
+
# Check if your VPN is working
|
|
98
|
+
abstractapi vpn detect
|
|
99
|
+
|
|
100
|
+
# Find timezone for a remote meeting participant
|
|
101
|
+
abstractapi timezone get --ip 123.456.789.0
|
|
102
|
+
|
|
103
|
+
# Script: Get country code for multiple IPs
|
|
104
|
+
for ip in 8.8.8.8 1.1.1.1 9.9.9.9; do
|
|
105
|
+
echo -n "$ip: "
|
|
106
|
+
abstractapi geolocate lookup $ip --json | jq -r '.country_code'
|
|
107
|
+
done
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
Part of the [KTMCP CLI](https://killthemcp.com) project — replacing MCPs with simple, composable CLIs.
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/abstractapi",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for Abstract API Geolocation - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"abstractapi": "bin/abstractapi.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["abstractapi", "geolocation", "ip", "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/abstractapi.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/abstractapi-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/abstractapi/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const BASE_URL = 'https://ipgeolocation.abstractapi.com/v1';
|
|
5
|
+
|
|
6
|
+
function getApiKey() {
|
|
7
|
+
const apiKey = getConfig('apiKey');
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error('API key not configured. Run: abstractapi config set apiKey YOUR_API_KEY');
|
|
10
|
+
}
|
|
11
|
+
return apiKey;
|
|
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 === 401 || status === 403) {
|
|
19
|
+
throw new Error('Authentication failed. Check your API key: abstractapi config set apiKey YOUR_KEY');
|
|
20
|
+
} else if (status === 429) {
|
|
21
|
+
throw new Error('Rate limit exceeded. Please wait before retrying.');
|
|
22
|
+
} else if (status === 404) {
|
|
23
|
+
throw new Error('Resource not found.');
|
|
24
|
+
} else {
|
|
25
|
+
const message = data?.error?.message || data?.message || JSON.stringify(data);
|
|
26
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
27
|
+
}
|
|
28
|
+
} else if (error.request) {
|
|
29
|
+
throw new Error('No response from AbstractAPI. Check your internet connection.');
|
|
30
|
+
} else {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function apiGet(url, params = {}) {
|
|
36
|
+
const apiKey = getApiKey();
|
|
37
|
+
try {
|
|
38
|
+
const response = await axios.get(url, {
|
|
39
|
+
params: { api_key: apiKey, ...params }
|
|
40
|
+
});
|
|
41
|
+
return response.data;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
handleApiError(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================
|
|
48
|
+
// GEOLOCATE
|
|
49
|
+
// ============================================================
|
|
50
|
+
|
|
51
|
+
export async function lookupIp(ipAddress) {
|
|
52
|
+
return await apiGet(BASE_URL + '/', { ip_address: ipAddress });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function lookupCurrent() {
|
|
56
|
+
return await apiGet(BASE_URL + '/');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================
|
|
60
|
+
// VPN DETECTION
|
|
61
|
+
// ============================================================
|
|
62
|
+
|
|
63
|
+
export async function detectVpn(ipAddress) {
|
|
64
|
+
const params = ipAddress ? { ip_address: ipAddress } : {};
|
|
65
|
+
return await apiGet('https://vpndetection.abstractapi.com/v1/', params);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================
|
|
69
|
+
// TIMEZONE
|
|
70
|
+
// ============================================================
|
|
71
|
+
|
|
72
|
+
export async function getTimezone({ ipAddress, location }) {
|
|
73
|
+
const params = {};
|
|
74
|
+
if (ipAddress) params.ip_address = ipAddress;
|
|
75
|
+
if (location) params.location = location;
|
|
76
|
+
return await apiGet('https://timezone.abstractapi.com/v1/', params);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function getCurrentTimezone() {
|
|
80
|
+
return await apiGet('https://timezone.abstractapi.com/v1/');
|
|
81
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/abstractapi' });
|
|
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
|
+
return !!config.get('apiKey');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAllConfig() {
|
|
18
|
+
return config.store;
|
|
19
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, isConfigured, getAllConfig } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
lookupIp,
|
|
7
|
+
lookupCurrent,
|
|
8
|
+
detectVpn,
|
|
9
|
+
getTimezone,
|
|
10
|
+
getCurrentTimezone
|
|
11
|
+
} from './api.js';
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
// ============================================================
|
|
16
|
+
// Helpers
|
|
17
|
+
// ============================================================
|
|
18
|
+
|
|
19
|
+
function printSuccess(message) {
|
|
20
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function printError(message) {
|
|
24
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printJson(data) {
|
|
28
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function withSpinner(message, fn) {
|
|
32
|
+
const spinner = ora(message).start();
|
|
33
|
+
try {
|
|
34
|
+
const result = await fn();
|
|
35
|
+
spinner.stop();
|
|
36
|
+
return result;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
spinner.stop();
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function requireAuth() {
|
|
44
|
+
if (!isConfigured()) {
|
|
45
|
+
printError('AbstractAPI key not configured.');
|
|
46
|
+
console.log('\nRun the following to configure:');
|
|
47
|
+
console.log(chalk.cyan(' abstractapi config set apiKey YOUR_API_KEY'));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function printGeoResult(data) {
|
|
53
|
+
console.log(chalk.bold('\nGeolocation Result\n'));
|
|
54
|
+
console.log('IP Address: ', chalk.cyan(data.ip_address || 'N/A'));
|
|
55
|
+
console.log('City: ', data.city || 'N/A');
|
|
56
|
+
console.log('Region: ', data.region || 'N/A');
|
|
57
|
+
console.log('Country: ', `${data.country || 'N/A'} (${data.country_code || '??'})`);
|
|
58
|
+
console.log('Continent: ', data.continent || 'N/A');
|
|
59
|
+
console.log('Latitude: ', data.latitude ?? 'N/A');
|
|
60
|
+
console.log('Longitude: ', data.longitude ?? 'N/A');
|
|
61
|
+
console.log('Timezone: ', data.timezone?.name || 'N/A');
|
|
62
|
+
console.log('Currency: ', data.currency?.currency_name ? `${data.currency.currency_name} (${data.currency.currency_code})` : 'N/A');
|
|
63
|
+
console.log('ISP: ', data.connection?.isp_name || data.connection?.autonomous_system_organization || 'N/A');
|
|
64
|
+
console.log('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// Program metadata
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.name('abstractapi')
|
|
73
|
+
.description(chalk.bold('AbstractAPI CLI') + ' - IP geolocation & detection from your terminal')
|
|
74
|
+
.version('1.0.0');
|
|
75
|
+
|
|
76
|
+
// ============================================================
|
|
77
|
+
// CONFIG
|
|
78
|
+
// ============================================================
|
|
79
|
+
|
|
80
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
81
|
+
|
|
82
|
+
configCmd
|
|
83
|
+
.command('set')
|
|
84
|
+
.description('Set configuration values')
|
|
85
|
+
.option('--api-key <key>', 'AbstractAPI API key')
|
|
86
|
+
.action((options) => {
|
|
87
|
+
if (options.apiKey) {
|
|
88
|
+
setConfig('apiKey', options.apiKey);
|
|
89
|
+
printSuccess('API key set');
|
|
90
|
+
} else {
|
|
91
|
+
printError('No options provided. Use --api-key');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
configCmd
|
|
96
|
+
.command('get')
|
|
97
|
+
.description('Get a configuration value')
|
|
98
|
+
.argument('<key>', 'Config key to retrieve')
|
|
99
|
+
.action((key) => {
|
|
100
|
+
const value = getConfig(key);
|
|
101
|
+
if (value === undefined || value === null || value === '') {
|
|
102
|
+
console.log(chalk.yellow(`${key}: not set`));
|
|
103
|
+
} else {
|
|
104
|
+
const masked = key.toLowerCase().includes('key') ? '*'.repeat(8) : value;
|
|
105
|
+
console.log(`${key}: ${chalk.green(masked)}`);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
configCmd
|
|
110
|
+
.command('list')
|
|
111
|
+
.description('List all configuration values')
|
|
112
|
+
.action(() => {
|
|
113
|
+
const all = getAllConfig();
|
|
114
|
+
console.log(chalk.bold('\nAbstractAPI CLI Configuration\n'));
|
|
115
|
+
console.log('API Key:', all.apiKey ? chalk.green('*'.repeat(8)) : chalk.red('not set'));
|
|
116
|
+
console.log('');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ============================================================
|
|
120
|
+
// GEOLOCATE
|
|
121
|
+
// ============================================================
|
|
122
|
+
|
|
123
|
+
const geoCmd = program.command('geolocate').description('IP geolocation lookup');
|
|
124
|
+
|
|
125
|
+
geoCmd
|
|
126
|
+
.command('lookup <ip>')
|
|
127
|
+
.description('Look up geolocation data for an IP address')
|
|
128
|
+
.option('--json', 'Output as JSON')
|
|
129
|
+
.action(async (ip, options) => {
|
|
130
|
+
requireAuth();
|
|
131
|
+
try {
|
|
132
|
+
const data = await withSpinner(`Looking up IP ${ip}...`, () => lookupIp(ip));
|
|
133
|
+
if (options.json) { printJson(data); return; }
|
|
134
|
+
printGeoResult(data);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
printError(error.message);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
geoCmd
|
|
142
|
+
.command('current')
|
|
143
|
+
.description('Look up geolocation data for your current IP address')
|
|
144
|
+
.option('--json', 'Output as JSON')
|
|
145
|
+
.action(async (options) => {
|
|
146
|
+
requireAuth();
|
|
147
|
+
try {
|
|
148
|
+
const data = await withSpinner('Looking up your IP address...', () => lookupCurrent());
|
|
149
|
+
if (options.json) { printJson(data); return; }
|
|
150
|
+
printGeoResult(data);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
printError(error.message);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ============================================================
|
|
158
|
+
// VPN DETECTION
|
|
159
|
+
// ============================================================
|
|
160
|
+
|
|
161
|
+
const vpnCmd = program.command('vpn').description('VPN and proxy detection');
|
|
162
|
+
|
|
163
|
+
vpnCmd
|
|
164
|
+
.command('detect')
|
|
165
|
+
.description('Detect if an IP address is using a VPN or proxy')
|
|
166
|
+
.argument('[ip]', 'IP address to check (omit for your current IP)')
|
|
167
|
+
.option('--json', 'Output as JSON')
|
|
168
|
+
.action(async (ip, options) => {
|
|
169
|
+
requireAuth();
|
|
170
|
+
try {
|
|
171
|
+
const data = await withSpinner(`Checking VPN status${ip ? ` for ${ip}` : ''}...`, () => detectVpn(ip));
|
|
172
|
+
if (options.json) { printJson(data); return; }
|
|
173
|
+
console.log(chalk.bold('\nVPN Detection Result\n'));
|
|
174
|
+
console.log('IP Address: ', chalk.cyan(data.ip_address || ip || 'N/A'));
|
|
175
|
+
const isVpn = data.is_vpn ?? data.vpn ?? false;
|
|
176
|
+
const isProxy = data.is_proxy ?? data.proxy ?? false;
|
|
177
|
+
const isTor = data.is_tor ?? data.tor ?? false;
|
|
178
|
+
console.log('VPN: ', isVpn ? chalk.red('Yes') : chalk.green('No'));
|
|
179
|
+
console.log('Proxy: ', isProxy ? chalk.red('Yes') : chalk.green('No'));
|
|
180
|
+
console.log('Tor: ', isTor ? chalk.red('Yes') : chalk.green('No'));
|
|
181
|
+
if (data.service) console.log('Service: ', data.service);
|
|
182
|
+
console.log('');
|
|
183
|
+
} catch (error) {
|
|
184
|
+
printError(error.message);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ============================================================
|
|
190
|
+
// TIMEZONE
|
|
191
|
+
// ============================================================
|
|
192
|
+
|
|
193
|
+
const tzCmd = program.command('timezone').description('Timezone lookup');
|
|
194
|
+
|
|
195
|
+
tzCmd
|
|
196
|
+
.command('get')
|
|
197
|
+
.description('Get timezone information for an IP address or location')
|
|
198
|
+
.option('--ip <ip>', 'IP address to look up')
|
|
199
|
+
.option('--location <location>', 'Location name or coordinates (e.g. "London" or "51.5,-0.09")')
|
|
200
|
+
.option('--json', 'Output as JSON')
|
|
201
|
+
.action(async (options) => {
|
|
202
|
+
requireAuth();
|
|
203
|
+
try {
|
|
204
|
+
const data = await withSpinner('Fetching timezone data...', () =>
|
|
205
|
+
getTimezone({ ipAddress: options.ip, location: options.location })
|
|
206
|
+
);
|
|
207
|
+
if (options.json) { printJson(data); return; }
|
|
208
|
+
console.log(chalk.bold('\nTimezone Information\n'));
|
|
209
|
+
console.log('Timezone: ', chalk.cyan(data.timezone || 'N/A'));
|
|
210
|
+
console.log('Abbreviation: ', data.abbreviation || data.timezone_abbreviation || 'N/A');
|
|
211
|
+
console.log('GMT Offset: ', data.gmt_offset ?? 'N/A');
|
|
212
|
+
console.log('Current Time: ', data.datetime || data.current_time || 'N/A');
|
|
213
|
+
console.log('Is DST: ', data.is_dst !== undefined ? (data.is_dst ? chalk.yellow('Yes') : 'No') : 'N/A');
|
|
214
|
+
console.log('');
|
|
215
|
+
} catch (error) {
|
|
216
|
+
printError(error.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
tzCmd
|
|
222
|
+
.command('current')
|
|
223
|
+
.description('Get timezone information for your current IP address')
|
|
224
|
+
.option('--json', 'Output as JSON')
|
|
225
|
+
.action(async (options) => {
|
|
226
|
+
requireAuth();
|
|
227
|
+
try {
|
|
228
|
+
const data = await withSpinner('Fetching your timezone...', () => getCurrentTimezone());
|
|
229
|
+
if (options.json) { printJson(data); return; }
|
|
230
|
+
console.log(chalk.bold('\nYour Timezone Information\n'));
|
|
231
|
+
console.log('Timezone: ', chalk.cyan(data.timezone || 'N/A'));
|
|
232
|
+
console.log('Abbreviation: ', data.abbreviation || data.timezone_abbreviation || 'N/A');
|
|
233
|
+
console.log('GMT Offset: ', data.gmt_offset ?? 'N/A');
|
|
234
|
+
console.log('Current Time: ', data.datetime || data.current_time || 'N/A');
|
|
235
|
+
console.log('Is DST: ', data.is_dst !== undefined ? (data.is_dst ? chalk.yellow('Yes') : 'No') : 'N/A');
|
|
236
|
+
console.log('');
|
|
237
|
+
} catch (error) {
|
|
238
|
+
printError(error.message);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ============================================================
|
|
244
|
+
// Parse
|
|
245
|
+
// ============================================================
|
|
246
|
+
|
|
247
|
+
program.parse(process.argv);
|
|
248
|
+
|
|
249
|
+
if (process.argv.length <= 2) {
|
|
250
|
+
program.help();
|
|
251
|
+
}
|