@ktmcp-cli/telnyx 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 +82 -0
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/banner.svg +17 -0
- package/bin/telnyx.js +9 -0
- package/package.json +40 -0
- package/src/api.js +157 -0
- package/src/config.js +33 -0
- package/src/index.js +683 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# AGENT.md — Telnyx CLI for AI Agents
|
|
2
|
+
|
|
3
|
+
This document explains how to use the Telnyx CLI as an AI agent.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `telnyx` CLI provides access to Telnyx's communications platform API. Use it to send SMS, make calls, manage phone numbers, and verify users.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
telnyx config set --api-key <key>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Get API key from: https://portal.telnyx.com/#/app/api-keys
|
|
16
|
+
|
|
17
|
+
## All Commands
|
|
18
|
+
|
|
19
|
+
### Config
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
telnyx config set --api-key <key>
|
|
23
|
+
telnyx config show
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Messages
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
telnyx messages send --from +1XXX --to +1YYY --text "Hello"
|
|
30
|
+
telnyx messages list
|
|
31
|
+
telnyx messages list --json
|
|
32
|
+
telnyx messages get <id>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Numbers
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
telnyx numbers list
|
|
39
|
+
telnyx numbers list --json
|
|
40
|
+
telnyx numbers get <id>
|
|
41
|
+
telnyx numbers search --country US
|
|
42
|
+
telnyx numbers order +12125551234
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Calls
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
telnyx calls create --to +1YYY --from +1XXX
|
|
49
|
+
telnyx calls hangup <call-control-id>
|
|
50
|
+
telnyx calls speak <call-control-id> --text "Hello"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Verify
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
telnyx verify send --to +1XXX # Send SMS code
|
|
57
|
+
telnyx verify send --to +1XXX --type call # Voice call code
|
|
58
|
+
telnyx verify check <id> --code 123456 # Verify the code
|
|
59
|
+
telnyx verify list
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Connections
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
telnyx connections list
|
|
66
|
+
telnyx connections get <id>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Profiles
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
telnyx profiles list
|
|
73
|
+
telnyx profiles get <id>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Tips for Agents
|
|
77
|
+
|
|
78
|
+
1. Always use `--json` when parsing results programmatically
|
|
79
|
+
2. Phone numbers must be in E.164 format: `+12025551234`
|
|
80
|
+
3. The `call-control-id` from `calls create` is used for call actions
|
|
81
|
+
4. Verification IDs from `verify send` are needed for `verify check`
|
|
82
|
+
5. Use `numbers search` before `numbers order` to find available numbers
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 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,131 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
> "Six months ago, everyone was talking about MCPs. And I was like, screw MCPs. Every MCP would be better as a CLI."
|
|
4
|
+
>
|
|
5
|
+
> — [Peter Steinberger](https://twitter.com/steipete), Founder of OpenClaw
|
|
6
|
+
> [Watch on YouTube (~2:39:00)](https://www.youtube.com/@lexfridman) | [Lex Fridman Podcast #491](https://lexfridman.com/peter-steinberger/)
|
|
7
|
+
|
|
8
|
+
# Telnyx CLI
|
|
9
|
+
|
|
10
|
+
> **⚠️ Unofficial CLI** - Not officially sponsored or affiliated with Telnyx.
|
|
11
|
+
|
|
12
|
+
A production-ready command-line interface for the [Telnyx API](https://developers.telnyx.com/) — programmable communications platform. Send SMS, make calls, manage phone numbers, and verify users directly from your terminal.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Messages** — Send and receive SMS/MMS messages
|
|
17
|
+
- **Phone Numbers** — List, search, and order phone numbers
|
|
18
|
+
- **Calls** — Initiate and control phone calls
|
|
19
|
+
- **Verify** — Send and check 2FA verification codes
|
|
20
|
+
- **Connections** — Manage SIP trunks and connections
|
|
21
|
+
- **Messaging Profiles** — Configure messaging settings
|
|
22
|
+
- **JSON output** — All commands support `--json` for scripting
|
|
23
|
+
- **Colorized output** — Clean terminal output with chalk
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @ktmcp-cli/telnyx
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Get your API key from https://portal.telnyx.com/#/app/api-keys
|
|
35
|
+
telnyx config set --api-key YOUR_API_KEY
|
|
36
|
+
|
|
37
|
+
# Send an SMS
|
|
38
|
+
telnyx messages send --from +12025551234 --to +19175559876 --text "Hello from Telnyx CLI!"
|
|
39
|
+
|
|
40
|
+
# List your phone numbers
|
|
41
|
+
telnyx numbers list
|
|
42
|
+
|
|
43
|
+
# Search for available numbers
|
|
44
|
+
telnyx numbers search --country US --area-code 212
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
### Config
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
telnyx config set --api-key <key>
|
|
53
|
+
telnyx config show
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Messages
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
telnyx messages send --from +1XXX --to +1YYY --text "Hello"
|
|
60
|
+
telnyx messages send --from +1XXX --to +1YYY --text "Photo" --media-urls https://example.com/photo.jpg
|
|
61
|
+
telnyx messages list
|
|
62
|
+
telnyx messages list --json
|
|
63
|
+
telnyx messages get <message-id>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Phone Numbers
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
telnyx numbers list
|
|
70
|
+
telnyx numbers list --json
|
|
71
|
+
telnyx numbers get <number-id>
|
|
72
|
+
telnyx numbers search --country US
|
|
73
|
+
telnyx numbers search --country US --area-code 212
|
|
74
|
+
telnyx numbers order +12125551234
|
|
75
|
+
telnyx numbers order +12125551234 --connection-id <id>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Calls
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
telnyx calls create --to +1YYY --from +1XXX
|
|
82
|
+
telnyx calls create --to +1YYY --from +1XXX --connection-id <id>
|
|
83
|
+
telnyx calls hangup <call-control-id>
|
|
84
|
+
telnyx calls speak <call-control-id> --text "Hello, this is an automated message"
|
|
85
|
+
telnyx calls speak <call-control-id> --text "Bonjour" --language fr-FR --voice female
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Verify (2FA)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
telnyx verify send --to +1XXX
|
|
92
|
+
telnyx verify send --to +1XXX --type sms
|
|
93
|
+
telnyx verify send --to +1XXX --type call
|
|
94
|
+
telnyx verify check <verification-id> --code 123456
|
|
95
|
+
telnyx verify list
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Connections
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
telnyx connections list
|
|
102
|
+
telnyx connections get <connection-id>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Messaging Profiles
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
telnyx profiles list
|
|
109
|
+
telnyx profiles get <profile-id>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## JSON Output
|
|
113
|
+
|
|
114
|
+
All commands support `--json` for structured output:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
telnyx numbers list --json | jq '.[].phone_number'
|
|
118
|
+
telnyx messages list --json | jq '.[0]'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Why CLI > MCP?
|
|
122
|
+
|
|
123
|
+
No server to run. No protocol overhead. Just install and go.
|
|
124
|
+
|
|
125
|
+
- **Simpler** — Just a binary you call directly
|
|
126
|
+
- **Composable** — Pipe to `jq`, `grep`, `awk`
|
|
127
|
+
- **Scriptable** — Works in cron jobs, CI/CD, shell scripts
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT — Part of the [Kill The MCP](https://killthemcp.com) project.
|
package/banner.svg
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="800" height="200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#0a0a0a;stop-opacity:1" />
|
|
5
|
+
<stop offset="100%" style="stop-color:#1a1a1a;stop-opacity:1" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="textGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
8
|
+
<stop offset="0%" style="stop-color:#ff6b00;stop-opacity:1" />
|
|
9
|
+
<stop offset="100%" style="stop-color:#ff9a00;stop-opacity:1" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<rect width="800" height="200" fill="url(#bgGrad)" rx="12" />
|
|
13
|
+
<text x="40" y="90" font-family="monospace" font-size="52" font-weight="bold" fill="url(#textGrad)">telnyx</text>
|
|
14
|
+
<text x="40" y="130" font-family="monospace" font-size="18" fill="#666">Communications Platform CLI</text>
|
|
15
|
+
<text x="40" y="165" font-family="monospace" font-size="14" fill="#444">npm install -g @ktmcp-cli/telnyx</text>
|
|
16
|
+
<text x="720" y="180" font-family="monospace" font-size="12" fill="#333">killthemcp.com</text>
|
|
17
|
+
</svg>
|
package/bin/telnyx.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/telnyx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for Telnyx API - SMS, calls, and phone numbers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"telnyx": "bin/telnyx.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"telnyx",
|
|
12
|
+
"sms",
|
|
13
|
+
"telephony",
|
|
14
|
+
"voice",
|
|
15
|
+
"phone",
|
|
16
|
+
"cli",
|
|
17
|
+
"api",
|
|
18
|
+
"ktmcp"
|
|
19
|
+
],
|
|
20
|
+
"author": "KTMCP",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"commander": "^12.0.0",
|
|
24
|
+
"axios": "^1.6.7",
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"ora": "^8.0.1",
|
|
27
|
+
"conf": "^12.0.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/ktmcp-cli/telnyx.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://killthemcp.com/telnyx-cli",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/ktmcp-cli/telnyx/issues"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const BASE_URL = 'https://api.telnyx.com/v2';
|
|
5
|
+
|
|
6
|
+
function getClient() {
|
|
7
|
+
const apiKey = getConfig('apiKey');
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error('API key not configured. Run: telnyx config set --api-key YOUR_KEY');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return axios.create({
|
|
13
|
+
baseURL: BASE_URL,
|
|
14
|
+
headers: {
|
|
15
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
'Accept': 'application/json'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function request(method, path, data = null, params = null) {
|
|
23
|
+
const client = getClient();
|
|
24
|
+
try {
|
|
25
|
+
const response = await client.request({ method, url: path, data, params });
|
|
26
|
+
return response.data;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const errData = error.response?.data;
|
|
29
|
+
const msg = errData?.errors?.[0]?.detail ||
|
|
30
|
+
errData?.errors?.[0]?.title ||
|
|
31
|
+
errData?.error?.message ||
|
|
32
|
+
error.message;
|
|
33
|
+
const code = error.response?.status;
|
|
34
|
+
throw new Error(`API Error ${code}: ${msg}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================
|
|
39
|
+
// Messaging
|
|
40
|
+
// ============================================================
|
|
41
|
+
|
|
42
|
+
export async function sendMessage({ from, to, text, subject, mediaUrls }) {
|
|
43
|
+
const body = { from, to, text };
|
|
44
|
+
if (subject) body.subject = subject;
|
|
45
|
+
if (mediaUrls) body.media_urls = mediaUrls;
|
|
46
|
+
return await request('POST', '/messages', body);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function listMessages(params = {}) {
|
|
50
|
+
const data = await request('GET', '/messages', null, params);
|
|
51
|
+
return data.data || [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getMessage(id) {
|
|
55
|
+
const data = await request('GET', `/messages/${id}`);
|
|
56
|
+
return data.data || null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================
|
|
60
|
+
// Phone Numbers
|
|
61
|
+
// ============================================================
|
|
62
|
+
|
|
63
|
+
export async function listPhoneNumbers(params = {}) {
|
|
64
|
+
const data = await request('GET', '/phone_numbers', null, params);
|
|
65
|
+
return data.data || [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function getPhoneNumber(id) {
|
|
69
|
+
const data = await request('GET', `/phone_numbers/${id}`);
|
|
70
|
+
return data.data || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function searchAvailableNumbers(params = {}) {
|
|
74
|
+
const data = await request('GET', '/available_phone_numbers', null, params);
|
|
75
|
+
return data.data || [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function orderPhoneNumber({ phoneNumber, connectionId }) {
|
|
79
|
+
const body = { phone_numbers: [{ phone_number: phoneNumber }] };
|
|
80
|
+
if (connectionId) body.connection_id = connectionId;
|
|
81
|
+
return await request('POST', '/number_orders', body);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================
|
|
85
|
+
// Calls
|
|
86
|
+
// ============================================================
|
|
87
|
+
|
|
88
|
+
export async function createCall({ to, from, connectionId, webhookUrl }) {
|
|
89
|
+
const body = { to, from };
|
|
90
|
+
if (connectionId) body.connection_id = connectionId;
|
|
91
|
+
if (webhookUrl) body.webhook_url = webhookUrl;
|
|
92
|
+
return await request('POST', '/calls', body);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function getCall(callControlId) {
|
|
96
|
+
const data = await request('GET', `/calls/${callControlId}`);
|
|
97
|
+
return data.data || null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function hangupCall(callControlId) {
|
|
101
|
+
return await request('POST', `/calls/${callControlId}/actions/hangup`, {});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function speakText(callControlId, { payload, voice, language }) {
|
|
105
|
+
return await request('POST', `/calls/${callControlId}/actions/speak`, {
|
|
106
|
+
payload,
|
|
107
|
+
voice: voice || 'female',
|
|
108
|
+
language: language || 'en-US'
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================
|
|
113
|
+
// Connections (SIP Trunks)
|
|
114
|
+
// ============================================================
|
|
115
|
+
|
|
116
|
+
export async function listConnections(params = {}) {
|
|
117
|
+
const data = await request('GET', '/connections', null, params);
|
|
118
|
+
return data.data || [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getConnection(id) {
|
|
122
|
+
const data = await request('GET', `/connections/${id}`);
|
|
123
|
+
return data.data || null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================================
|
|
127
|
+
// Verify (2FA)
|
|
128
|
+
// ============================================================
|
|
129
|
+
|
|
130
|
+
export async function sendVerification({ phoneNumber, type, verifyProfileId }) {
|
|
131
|
+
const body = { phone_number: phoneNumber, type: type || 'sms' };
|
|
132
|
+
if (verifyProfileId) body.verify_profile_id = verifyProfileId;
|
|
133
|
+
return await request('POST', '/verifications', body);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function verifyCode({ verificationId, code }) {
|
|
137
|
+
return await request('POST', `/verifications/${verificationId}/actions/verify`, { code });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function listVerifications(params = {}) {
|
|
141
|
+
const data = await request('GET', '/verifications', null, params);
|
|
142
|
+
return data.data || [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================
|
|
146
|
+
// Messaging Profiles
|
|
147
|
+
// ============================================================
|
|
148
|
+
|
|
149
|
+
export async function listMessagingProfiles(params = {}) {
|
|
150
|
+
const data = await request('GET', '/messaging_profiles', null, params);
|
|
151
|
+
return data.data || [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function getMessagingProfile(id) {
|
|
155
|
+
const data = await request('GET', `/messaging_profiles/${id}`);
|
|
156
|
+
return data.data || null;
|
|
157
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({
|
|
4
|
+
projectName: 'ktmcp-telnyx',
|
|
5
|
+
schema: {
|
|
6
|
+
apiKey: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
default: ''
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export function getConfig(key) {
|
|
14
|
+
return config.get(key);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setConfig(key, value) {
|
|
18
|
+
config.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getAllConfig() {
|
|
22
|
+
return config.store;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function clearConfig() {
|
|
26
|
+
config.clear();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isConfigured() {
|
|
30
|
+
return !!config.get('apiKey');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default config;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, isConfigured } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
sendMessage,
|
|
7
|
+
listMessages,
|
|
8
|
+
getMessage,
|
|
9
|
+
listPhoneNumbers,
|
|
10
|
+
getPhoneNumber,
|
|
11
|
+
searchAvailableNumbers,
|
|
12
|
+
orderPhoneNumber,
|
|
13
|
+
createCall,
|
|
14
|
+
getCall,
|
|
15
|
+
hangupCall,
|
|
16
|
+
speakText,
|
|
17
|
+
listConnections,
|
|
18
|
+
getConnection,
|
|
19
|
+
sendVerification,
|
|
20
|
+
verifyCode,
|
|
21
|
+
listVerifications,
|
|
22
|
+
listMessagingProfiles,
|
|
23
|
+
getMessagingProfile
|
|
24
|
+
} from './api.js';
|
|
25
|
+
|
|
26
|
+
const program = new Command();
|
|
27
|
+
|
|
28
|
+
// ============================================================
|
|
29
|
+
// Helpers
|
|
30
|
+
// ============================================================
|
|
31
|
+
|
|
32
|
+
function printSuccess(message) {
|
|
33
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function printError(message) {
|
|
37
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function printTable(data, columns) {
|
|
41
|
+
if (!data || data.length === 0) {
|
|
42
|
+
console.log(chalk.yellow('No results found.'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const widths = {};
|
|
47
|
+
columns.forEach(col => {
|
|
48
|
+
widths[col.key] = col.label.length;
|
|
49
|
+
data.forEach(row => {
|
|
50
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
51
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
52
|
+
});
|
|
53
|
+
widths[col.key] = Math.min(widths[col.key], 40);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
57
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
58
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
59
|
+
|
|
60
|
+
data.forEach(row => {
|
|
61
|
+
const line = columns.map(col => {
|
|
62
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
63
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
64
|
+
}).join(' ');
|
|
65
|
+
console.log(line);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function printJson(data) {
|
|
72
|
+
console.log(JSON.stringify(data, null, 2));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function withSpinner(message, fn) {
|
|
76
|
+
const spinner = ora(message).start();
|
|
77
|
+
try {
|
|
78
|
+
const result = await fn();
|
|
79
|
+
spinner.stop();
|
|
80
|
+
return result;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
spinner.stop();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function requireAuth() {
|
|
88
|
+
if (!isConfigured()) {
|
|
89
|
+
printError('Telnyx API key not configured.');
|
|
90
|
+
console.log('\nRun the following to configure:');
|
|
91
|
+
console.log(chalk.cyan(' telnyx config set --api-key YOUR_API_KEY'));
|
|
92
|
+
console.log('\nGet your API key from: https://portal.telnyx.com/#/app/api-keys');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================
|
|
98
|
+
// Program metadata
|
|
99
|
+
// ============================================================
|
|
100
|
+
|
|
101
|
+
program
|
|
102
|
+
.name('telnyx')
|
|
103
|
+
.description(chalk.bold('Telnyx CLI') + ' - Communications platform from your terminal')
|
|
104
|
+
.version('1.0.0');
|
|
105
|
+
|
|
106
|
+
// ============================================================
|
|
107
|
+
// CONFIG
|
|
108
|
+
// ============================================================
|
|
109
|
+
|
|
110
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
111
|
+
|
|
112
|
+
configCmd
|
|
113
|
+
.command('set')
|
|
114
|
+
.description('Set configuration values')
|
|
115
|
+
.option('--api-key <key>', 'Telnyx API key')
|
|
116
|
+
.action((options) => {
|
|
117
|
+
if (options.apiKey) {
|
|
118
|
+
setConfig('apiKey', options.apiKey);
|
|
119
|
+
printSuccess('API key set');
|
|
120
|
+
} else {
|
|
121
|
+
printError('No options provided. Use --api-key');
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
configCmd
|
|
126
|
+
.command('show')
|
|
127
|
+
.description('Show current configuration')
|
|
128
|
+
.action(() => {
|
|
129
|
+
const apiKey = getConfig('apiKey');
|
|
130
|
+
console.log(chalk.bold('\nTelnyx CLI Configuration\n'));
|
|
131
|
+
console.log('API Key: ', apiKey ? chalk.green(apiKey.substring(0, 8) + '...') : chalk.red('not set'));
|
|
132
|
+
console.log('');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ============================================================
|
|
136
|
+
// MESSAGES
|
|
137
|
+
// ============================================================
|
|
138
|
+
|
|
139
|
+
const messagesCmd = program.command('messages').description('Send and manage SMS/MMS messages');
|
|
140
|
+
|
|
141
|
+
messagesCmd
|
|
142
|
+
.command('send')
|
|
143
|
+
.description('Send an SMS or MMS message')
|
|
144
|
+
.requiredOption('--from <number>', 'From phone number (E.164 format, e.g. +12025551234)')
|
|
145
|
+
.requiredOption('--to <number>', 'To phone number (E.164 format)')
|
|
146
|
+
.requiredOption('--text <message>', 'Message text')
|
|
147
|
+
.option('--media-urls <urls>', 'Comma-separated media URLs (for MMS)')
|
|
148
|
+
.option('--json', 'Output as JSON')
|
|
149
|
+
.action(async (options) => {
|
|
150
|
+
requireAuth();
|
|
151
|
+
try {
|
|
152
|
+
const params = { from: options.from, to: options.to, text: options.text };
|
|
153
|
+
if (options.mediaUrls) params.mediaUrls = options.mediaUrls.split(',');
|
|
154
|
+
|
|
155
|
+
const result = await withSpinner('Sending message...', () => sendMessage(params));
|
|
156
|
+
|
|
157
|
+
if (options.json) {
|
|
158
|
+
printJson(result);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
printSuccess(`Message sent!`);
|
|
163
|
+
console.log('Message ID: ', chalk.cyan(result.data?.id || 'N/A'));
|
|
164
|
+
console.log('From: ', result.data?.from?.phone_number || options.from);
|
|
165
|
+
console.log('To: ', result.data?.to?.[0]?.phone_number || options.to);
|
|
166
|
+
console.log('Status: ', result.data?.to?.[0]?.status || 'queued');
|
|
167
|
+
console.log('');
|
|
168
|
+
} catch (error) {
|
|
169
|
+
printError(error.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
messagesCmd
|
|
175
|
+
.command('list')
|
|
176
|
+
.description('List recent messages')
|
|
177
|
+
.option('--page-size <n>', 'Results per page', '20')
|
|
178
|
+
.option('--json', 'Output as JSON')
|
|
179
|
+
.action(async (options) => {
|
|
180
|
+
requireAuth();
|
|
181
|
+
try {
|
|
182
|
+
const messages = await withSpinner('Fetching messages...', () =>
|
|
183
|
+
listMessages({ page_size: options.pageSize })
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (options.json) {
|
|
187
|
+
printJson(messages);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
printTable(messages, [
|
|
192
|
+
{ key: 'id', label: 'ID', format: (v) => (v || '').substring(0, 12) + '...' },
|
|
193
|
+
{ key: 'from', label: 'From', format: (v) => typeof v === 'object' ? v.phone_number : v },
|
|
194
|
+
{ key: 'to', label: 'To', format: (v) => Array.isArray(v) ? v[0]?.phone_number : v },
|
|
195
|
+
{ key: 'text', label: 'Text', format: (v) => (v || '').substring(0, 30) },
|
|
196
|
+
{ key: 'direction', label: 'Direction' },
|
|
197
|
+
{ key: 'created_at', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' }
|
|
198
|
+
]);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
printError(error.message);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
messagesCmd
|
|
206
|
+
.command('get <message-id>')
|
|
207
|
+
.description('Get a specific message')
|
|
208
|
+
.option('--json', 'Output as JSON')
|
|
209
|
+
.action(async (messageId, options) => {
|
|
210
|
+
requireAuth();
|
|
211
|
+
try {
|
|
212
|
+
const message = await withSpinner('Fetching message...', () => getMessage(messageId));
|
|
213
|
+
|
|
214
|
+
if (!message) {
|
|
215
|
+
printError('Message not found');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (options.json) {
|
|
220
|
+
printJson(message);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(chalk.bold('\nMessage Details\n'));
|
|
225
|
+
console.log('ID: ', chalk.cyan(message.id));
|
|
226
|
+
console.log('From: ', typeof message.from === 'object' ? message.from?.phone_number : message.from);
|
|
227
|
+
console.log('To: ', Array.isArray(message.to) ? message.to[0]?.phone_number : message.to);
|
|
228
|
+
console.log('Text: ', message.text || 'N/A');
|
|
229
|
+
console.log('Direction: ', message.direction);
|
|
230
|
+
console.log('Status: ', message.to?.[0]?.status || 'N/A');
|
|
231
|
+
console.log('Created: ', message.created_at ? new Date(message.created_at).toLocaleString() : 'N/A');
|
|
232
|
+
console.log('');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
printError(error.message);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ============================================================
|
|
240
|
+
// PHONE NUMBERS
|
|
241
|
+
// ============================================================
|
|
242
|
+
|
|
243
|
+
const numbersCmd = program.command('numbers').description('Manage phone numbers');
|
|
244
|
+
|
|
245
|
+
numbersCmd
|
|
246
|
+
.command('list')
|
|
247
|
+
.description('List your phone numbers')
|
|
248
|
+
.option('--page-size <n>', 'Results per page', '20')
|
|
249
|
+
.option('--json', 'Output as JSON')
|
|
250
|
+
.action(async (options) => {
|
|
251
|
+
requireAuth();
|
|
252
|
+
try {
|
|
253
|
+
const numbers = await withSpinner('Fetching phone numbers...', () =>
|
|
254
|
+
listPhoneNumbers({ page_size: options.pageSize })
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if (options.json) {
|
|
258
|
+
printJson(numbers);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
printTable(numbers, [
|
|
263
|
+
{ key: 'id', label: 'ID', format: (v) => (v || '').substring(0, 12) + '...' },
|
|
264
|
+
{ key: 'phone_number', label: 'Phone Number' },
|
|
265
|
+
{ key: 'status', label: 'Status' },
|
|
266
|
+
{ key: 'country_code', label: 'Country' },
|
|
267
|
+
{ key: 'type', label: 'Type' },
|
|
268
|
+
{ key: 'created_at', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' }
|
|
269
|
+
]);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
printError(error.message);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
numbersCmd
|
|
277
|
+
.command('get <number-id>')
|
|
278
|
+
.description('Get details for a specific phone number')
|
|
279
|
+
.option('--json', 'Output as JSON')
|
|
280
|
+
.action(async (numberId, options) => {
|
|
281
|
+
requireAuth();
|
|
282
|
+
try {
|
|
283
|
+
const number = await withSpinner('Fetching phone number...', () => getPhoneNumber(numberId));
|
|
284
|
+
|
|
285
|
+
if (!number) {
|
|
286
|
+
printError('Phone number not found');
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (options.json) {
|
|
291
|
+
printJson(number);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log(chalk.bold('\nPhone Number Details\n'));
|
|
296
|
+
console.log('ID: ', chalk.cyan(number.id));
|
|
297
|
+
console.log('Number: ', chalk.bold(number.phone_number));
|
|
298
|
+
console.log('Status: ', number.status);
|
|
299
|
+
console.log('Country: ', number.country_code || 'N/A');
|
|
300
|
+
console.log('Type: ', number.type || 'N/A');
|
|
301
|
+
console.log('Features: ', (number.features || []).join(', ') || 'N/A');
|
|
302
|
+
console.log('');
|
|
303
|
+
} catch (error) {
|
|
304
|
+
printError(error.message);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
numbersCmd
|
|
310
|
+
.command('search')
|
|
311
|
+
.description('Search available phone numbers to buy')
|
|
312
|
+
.option('--country <code>', 'Country code (e.g. US)', 'US')
|
|
313
|
+
.option('--area-code <code>', 'Area code to search in')
|
|
314
|
+
.option('--contains <digits>', 'Filter numbers containing these digits')
|
|
315
|
+
.option('--limit <n>', 'Max results', '10')
|
|
316
|
+
.option('--json', 'Output as JSON')
|
|
317
|
+
.action(async (options) => {
|
|
318
|
+
requireAuth();
|
|
319
|
+
try {
|
|
320
|
+
const params = {
|
|
321
|
+
country_code: options.country,
|
|
322
|
+
limit: options.limit
|
|
323
|
+
};
|
|
324
|
+
if (options.areaCode) params.national_destination_code = options.areaCode;
|
|
325
|
+
if (options.contains) params.phone_number_type = 'local';
|
|
326
|
+
|
|
327
|
+
const numbers = await withSpinner('Searching available numbers...', () =>
|
|
328
|
+
searchAvailableNumbers(params)
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
if (options.json) {
|
|
332
|
+
printJson(numbers);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
printTable(numbers, [
|
|
337
|
+
{ key: 'phone_number', label: 'Phone Number' },
|
|
338
|
+
{ key: 'region_name', label: 'Region' },
|
|
339
|
+
{ key: 'phone_number_type', label: 'Type' },
|
|
340
|
+
{ key: 'cost', label: 'Monthly Cost', format: (v, row) => row.cost?.monthly?.amount ? `$${row.cost.monthly.amount} ${row.cost.monthly.currency}` : 'N/A' }
|
|
341
|
+
]);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
printError(error.message);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
numbersCmd
|
|
349
|
+
.command('order <phone-number>')
|
|
350
|
+
.description('Order a phone number')
|
|
351
|
+
.option('--connection-id <id>', 'Connection/trunk ID to assign to')
|
|
352
|
+
.option('--json', 'Output as JSON')
|
|
353
|
+
.action(async (phoneNumber, options) => {
|
|
354
|
+
requireAuth();
|
|
355
|
+
try {
|
|
356
|
+
const result = await withSpinner(`Ordering ${phoneNumber}...`, () =>
|
|
357
|
+
orderPhoneNumber({ phoneNumber, connectionId: options.connectionId })
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
if (options.json) {
|
|
361
|
+
printJson(result);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
printSuccess(`Order placed for ${chalk.bold(phoneNumber)}`);
|
|
366
|
+
console.log('Order ID: ', result.data?.id || 'N/A');
|
|
367
|
+
console.log('Status: ', result.data?.status || 'N/A');
|
|
368
|
+
console.log('');
|
|
369
|
+
} catch (error) {
|
|
370
|
+
printError(error.message);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ============================================================
|
|
376
|
+
// CALLS
|
|
377
|
+
// ============================================================
|
|
378
|
+
|
|
379
|
+
const callsCmd = program.command('calls').description('Make and manage phone calls');
|
|
380
|
+
|
|
381
|
+
callsCmd
|
|
382
|
+
.command('create')
|
|
383
|
+
.description('Initiate a new call')
|
|
384
|
+
.requiredOption('--to <number>', 'Destination number (E.164 format)')
|
|
385
|
+
.requiredOption('--from <number>', 'Caller ID (E.164 format)')
|
|
386
|
+
.option('--connection-id <id>', 'Telnyx connection ID')
|
|
387
|
+
.option('--webhook-url <url>', 'Webhook URL for call events')
|
|
388
|
+
.option('--json', 'Output as JSON')
|
|
389
|
+
.action(async (options) => {
|
|
390
|
+
requireAuth();
|
|
391
|
+
try {
|
|
392
|
+
const result = await withSpinner(`Calling ${options.to}...`, () =>
|
|
393
|
+
createCall({ to: options.to, from: options.from, connectionId: options.connectionId, webhookUrl: options.webhookUrl })
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
if (options.json) {
|
|
397
|
+
printJson(result);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
printSuccess('Call initiated!');
|
|
402
|
+
console.log('Call Control ID: ', chalk.cyan(result.data?.call_control_id || 'N/A'));
|
|
403
|
+
console.log('Call Session ID: ', result.data?.call_session_id || 'N/A');
|
|
404
|
+
console.log('State: ', result.data?.state || 'N/A');
|
|
405
|
+
console.log('');
|
|
406
|
+
} catch (error) {
|
|
407
|
+
printError(error.message);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
callsCmd
|
|
413
|
+
.command('hangup <call-control-id>')
|
|
414
|
+
.description('Hang up an active call')
|
|
415
|
+
.option('--json', 'Output as JSON')
|
|
416
|
+
.action(async (callControlId, options) => {
|
|
417
|
+
requireAuth();
|
|
418
|
+
try {
|
|
419
|
+
const result = await withSpinner('Hanging up...', () => hangupCall(callControlId));
|
|
420
|
+
if (options.json) {
|
|
421
|
+
printJson(result);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
printSuccess('Call hung up');
|
|
425
|
+
} catch (error) {
|
|
426
|
+
printError(error.message);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
callsCmd
|
|
432
|
+
.command('speak <call-control-id>')
|
|
433
|
+
.description('Speak text on an active call (text-to-speech)')
|
|
434
|
+
.requiredOption('--text <message>', 'Text to speak')
|
|
435
|
+
.option('--voice <voice>', 'Voice (female|male)', 'female')
|
|
436
|
+
.option('--language <lang>', 'Language code', 'en-US')
|
|
437
|
+
.option('--json', 'Output as JSON')
|
|
438
|
+
.action(async (callControlId, options) => {
|
|
439
|
+
requireAuth();
|
|
440
|
+
try {
|
|
441
|
+
const result = await withSpinner('Speaking text...', () =>
|
|
442
|
+
speakText(callControlId, { payload: options.text, voice: options.voice, language: options.language })
|
|
443
|
+
);
|
|
444
|
+
if (options.json) {
|
|
445
|
+
printJson(result);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
printSuccess('Text-to-speech started');
|
|
449
|
+
} catch (error) {
|
|
450
|
+
printError(error.message);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// ============================================================
|
|
456
|
+
// VERIFY
|
|
457
|
+
// ============================================================
|
|
458
|
+
|
|
459
|
+
const verifyCmd = program.command('verify').description('Send and check 2FA verification codes');
|
|
460
|
+
|
|
461
|
+
verifyCmd
|
|
462
|
+
.command('send')
|
|
463
|
+
.description('Send a verification code')
|
|
464
|
+
.requiredOption('--to <number>', 'Phone number to verify (E.164 format)')
|
|
465
|
+
.option('--type <type>', 'Delivery type (sms|call|flash)', 'sms')
|
|
466
|
+
.option('--profile-id <id>', 'Verify profile ID')
|
|
467
|
+
.option('--json', 'Output as JSON')
|
|
468
|
+
.action(async (options) => {
|
|
469
|
+
requireAuth();
|
|
470
|
+
try {
|
|
471
|
+
const result = await withSpinner(`Sending ${options.type.toUpperCase()} code to ${options.to}...`, () =>
|
|
472
|
+
sendVerification({ phoneNumber: options.to, type: options.type, verifyProfileId: options.profileId })
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
if (options.json) {
|
|
476
|
+
printJson(result);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
printSuccess('Verification code sent!');
|
|
481
|
+
console.log('Verification ID: ', chalk.cyan(result.data?.id || 'N/A'));
|
|
482
|
+
console.log('Phone: ', options.to);
|
|
483
|
+
console.log('Type: ', options.type.toUpperCase());
|
|
484
|
+
console.log('');
|
|
485
|
+
console.log(chalk.dim('Use: telnyx verify check <verification-id> --code <code>'));
|
|
486
|
+
} catch (error) {
|
|
487
|
+
printError(error.message);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
verifyCmd
|
|
493
|
+
.command('check <verification-id>')
|
|
494
|
+
.description('Check a verification code')
|
|
495
|
+
.requiredOption('--code <code>', 'The verification code to check')
|
|
496
|
+
.option('--json', 'Output as JSON')
|
|
497
|
+
.action(async (verificationId, options) => {
|
|
498
|
+
requireAuth();
|
|
499
|
+
try {
|
|
500
|
+
const result = await withSpinner('Verifying code...', () =>
|
|
501
|
+
verifyCode({ verificationId, code: options.code })
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if (options.json) {
|
|
505
|
+
printJson(result);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const valid = result.data?.response_code === 'accepted';
|
|
510
|
+
if (valid) {
|
|
511
|
+
printSuccess('Code verified successfully!');
|
|
512
|
+
} else {
|
|
513
|
+
printError('Code verification failed: ' + (result.data?.response_code || 'invalid'));
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
} catch (error) {
|
|
517
|
+
printError(error.message);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
verifyCmd
|
|
523
|
+
.command('list')
|
|
524
|
+
.description('List recent verifications')
|
|
525
|
+
.option('--json', 'Output as JSON')
|
|
526
|
+
.action(async (options) => {
|
|
527
|
+
requireAuth();
|
|
528
|
+
try {
|
|
529
|
+
const verifications = await withSpinner('Fetching verifications...', () => listVerifications());
|
|
530
|
+
|
|
531
|
+
if (options.json) {
|
|
532
|
+
printJson(verifications);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
printTable(verifications, [
|
|
537
|
+
{ key: 'id', label: 'ID', format: (v) => (v || '').substring(0, 12) + '...' },
|
|
538
|
+
{ key: 'phone_number', label: 'Phone Number' },
|
|
539
|
+
{ key: 'type', label: 'Type' },
|
|
540
|
+
{ key: 'status', label: 'Status' },
|
|
541
|
+
{ key: 'created_at', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' }
|
|
542
|
+
]);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
printError(error.message);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// ============================================================
|
|
550
|
+
// CONNECTIONS
|
|
551
|
+
// ============================================================
|
|
552
|
+
|
|
553
|
+
const connectionsCmd = program.command('connections').description('Manage SIP connections and trunks');
|
|
554
|
+
|
|
555
|
+
connectionsCmd
|
|
556
|
+
.command('list')
|
|
557
|
+
.description('List all connections')
|
|
558
|
+
.option('--json', 'Output as JSON')
|
|
559
|
+
.action(async (options) => {
|
|
560
|
+
requireAuth();
|
|
561
|
+
try {
|
|
562
|
+
const connections = await withSpinner('Fetching connections...', () => listConnections());
|
|
563
|
+
|
|
564
|
+
if (options.json) {
|
|
565
|
+
printJson(connections);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
printTable(connections, [
|
|
570
|
+
{ key: 'id', label: 'ID', format: (v) => (v || '').substring(0, 12) + '...' },
|
|
571
|
+
{ key: 'connection_name', label: 'Name' },
|
|
572
|
+
{ key: 'record_type', label: 'Type' },
|
|
573
|
+
{ key: 'active', label: 'Active', format: (v) => v ? chalk.green('Yes') : 'No' },
|
|
574
|
+
{ key: 'created_at', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' }
|
|
575
|
+
]);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
printError(error.message);
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
connectionsCmd
|
|
583
|
+
.command('get <connection-id>')
|
|
584
|
+
.description('Get a specific connection')
|
|
585
|
+
.option('--json', 'Output as JSON')
|
|
586
|
+
.action(async (connectionId, options) => {
|
|
587
|
+
requireAuth();
|
|
588
|
+
try {
|
|
589
|
+
const connection = await withSpinner('Fetching connection...', () => getConnection(connectionId));
|
|
590
|
+
|
|
591
|
+
if (!connection) {
|
|
592
|
+
printError('Connection not found');
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (options.json) {
|
|
597
|
+
printJson(connection);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
console.log(chalk.bold('\nConnection Details\n'));
|
|
602
|
+
console.log('ID: ', chalk.cyan(connection.id));
|
|
603
|
+
console.log('Name: ', chalk.bold(connection.connection_name || 'N/A'));
|
|
604
|
+
console.log('Type: ', connection.record_type || 'N/A');
|
|
605
|
+
console.log('Active: ', connection.active ? chalk.green('Yes') : 'No');
|
|
606
|
+
console.log('');
|
|
607
|
+
} catch (error) {
|
|
608
|
+
printError(error.message);
|
|
609
|
+
process.exit(1);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// ============================================================
|
|
614
|
+
// MESSAGING PROFILES
|
|
615
|
+
// ============================================================
|
|
616
|
+
|
|
617
|
+
const profilesCmd = program.command('profiles').description('Manage messaging profiles');
|
|
618
|
+
|
|
619
|
+
profilesCmd
|
|
620
|
+
.command('list')
|
|
621
|
+
.description('List messaging profiles')
|
|
622
|
+
.option('--json', 'Output as JSON')
|
|
623
|
+
.action(async (options) => {
|
|
624
|
+
requireAuth();
|
|
625
|
+
try {
|
|
626
|
+
const profiles = await withSpinner('Fetching messaging profiles...', () => listMessagingProfiles());
|
|
627
|
+
|
|
628
|
+
if (options.json) {
|
|
629
|
+
printJson(profiles);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
printTable(profiles, [
|
|
634
|
+
{ key: 'id', label: 'ID', format: (v) => (v || '').substring(0, 12) + '...' },
|
|
635
|
+
{ key: 'name', label: 'Name' },
|
|
636
|
+
{ key: 'enabled', label: 'Enabled', format: (v) => v ? chalk.green('Yes') : 'No' },
|
|
637
|
+
{ key: 'created_at', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' }
|
|
638
|
+
]);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
printError(error.message);
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
profilesCmd
|
|
646
|
+
.command('get <profile-id>')
|
|
647
|
+
.description('Get a specific messaging profile')
|
|
648
|
+
.option('--json', 'Output as JSON')
|
|
649
|
+
.action(async (profileId, options) => {
|
|
650
|
+
requireAuth();
|
|
651
|
+
try {
|
|
652
|
+
const profile = await withSpinner('Fetching messaging profile...', () => getMessagingProfile(profileId));
|
|
653
|
+
|
|
654
|
+
if (!profile) {
|
|
655
|
+
printError('Messaging profile not found');
|
|
656
|
+
process.exit(1);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (options.json) {
|
|
660
|
+
printJson(profile);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
console.log(chalk.bold('\nMessaging Profile Details\n'));
|
|
665
|
+
console.log('ID: ', chalk.cyan(profile.id));
|
|
666
|
+
console.log('Name: ', chalk.bold(profile.name || 'N/A'));
|
|
667
|
+
console.log('Enabled: ', profile.enabled ? chalk.green('Yes') : 'No');
|
|
668
|
+
console.log('');
|
|
669
|
+
} catch (error) {
|
|
670
|
+
printError(error.message);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// ============================================================
|
|
676
|
+
// Parse
|
|
677
|
+
// ============================================================
|
|
678
|
+
|
|
679
|
+
program.parse(process.argv);
|
|
680
|
+
|
|
681
|
+
if (process.argv.length <= 2) {
|
|
682
|
+
program.help();
|
|
683
|
+
}
|