@sendblue/cli 0.5.1 → 0.6.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/dist/commands/login.js +1 -1
- package/dist/commands/messages.d.ts +6 -0
- package/dist/commands/messages.js +66 -0
- package/dist/commands/setup.js +1 -1
- package/dist/index.js +11 -1
- package/dist/lib/api.d.ts +30 -0
- package/dist/lib/api.js +22 -0
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +95 -14
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function loginCommand() {
|
|
|
34
34
|
type: 'text',
|
|
35
35
|
name: 'email',
|
|
36
36
|
message: 'Email',
|
|
37
|
-
validate: (v) => /^[^\s@]+@[^\s@]+\.[
|
|
37
|
+
validate: (v) => /^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(v) || 'Enter a valid email'
|
|
38
38
|
}, { onCancel });
|
|
39
39
|
// Step 2: Send verification code
|
|
40
40
|
const sendSpinner = ora({ text: 'Sending verification code...', indent: 2 }).start();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getCredentials } from '../lib/config.js';
|
|
4
|
+
import { getMessages } from '../lib/api.js';
|
|
5
|
+
import { formatPhoneNumber, normalizeNumber, printError } from '../lib/format.js';
|
|
6
|
+
export async function messagesCommand(opts) {
|
|
7
|
+
const creds = getCredentials();
|
|
8
|
+
if (!creds) {
|
|
9
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const spinner = ora({ text: 'Fetching messages...', indent: 2 }).start();
|
|
13
|
+
try {
|
|
14
|
+
const isOutbound = opts.outbound ? true : opts.inbound ? false : undefined;
|
|
15
|
+
const number = opts.number ? normalizeNumber(opts.number) : undefined;
|
|
16
|
+
const limit = Math.min(parseInt(opts.limit || '10', 10) || 10, 100);
|
|
17
|
+
const result = await getMessages(creds.apiKey, creds.apiSecret, {
|
|
18
|
+
number,
|
|
19
|
+
limit,
|
|
20
|
+
isOutbound
|
|
21
|
+
});
|
|
22
|
+
spinner.stop();
|
|
23
|
+
console.log();
|
|
24
|
+
if (result.data.length === 0) {
|
|
25
|
+
console.log(chalk.dim(' No messages found.'));
|
|
26
|
+
console.log();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(chalk.bold(` Messages`) + chalk.dim(` (${result.data.length} of ${result.pagination.total})`));
|
|
30
|
+
console.log();
|
|
31
|
+
for (const msg of result.data) {
|
|
32
|
+
const direction = msg.is_outbound
|
|
33
|
+
? chalk.cyan('OUT')
|
|
34
|
+
: chalk.green(' IN');
|
|
35
|
+
const otherNumber = msg.is_outbound ? msg.to_number : msg.from_number;
|
|
36
|
+
const formatted = otherNumber && typeof otherNumber === 'string' ? formatPhoneNumber(otherNumber) : '?';
|
|
37
|
+
const date = new Date(msg.date_sent);
|
|
38
|
+
const timestamp = date.toLocaleString('en-US', {
|
|
39
|
+
month: 'short',
|
|
40
|
+
day: 'numeric',
|
|
41
|
+
hour: 'numeric',
|
|
42
|
+
minute: '2-digit',
|
|
43
|
+
hour12: true
|
|
44
|
+
});
|
|
45
|
+
const status = msg.status === 'SENT' || msg.status === 'DELIVERED'
|
|
46
|
+
? ''
|
|
47
|
+
: ` ${chalk.yellow(`[${msg.status}]`)}`;
|
|
48
|
+
const content = msg.content
|
|
49
|
+
? msg.content.length > 80
|
|
50
|
+
? msg.content.slice(0, 77) + '...'
|
|
51
|
+
: msg.content
|
|
52
|
+
: chalk.dim('(media)');
|
|
53
|
+
console.log(` ${direction} ${chalk.dim(timestamp)} ${chalk.bold(formatted)}${status}`);
|
|
54
|
+
console.log(` ${content}`);
|
|
55
|
+
console.log();
|
|
56
|
+
}
|
|
57
|
+
if (result.pagination.hasMore) {
|
|
58
|
+
console.log(chalk.dim(` Showing ${result.data.length} of ${result.pagination.total}. Use --limit to see more.`));
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
spinner.fail(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function setupCommand() {
|
|
|
42
42
|
type: 'text',
|
|
43
43
|
name: 'email',
|
|
44
44
|
message: 'Email',
|
|
45
|
-
validate: (v) => /^[^\s@]+@[^\s@]+\.[
|
|
45
|
+
validate: (v) => /^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(v) || 'Enter a valid email'
|
|
46
46
|
}, { onCancel });
|
|
47
47
|
// Step 2: Send verification code
|
|
48
48
|
const sendSpinner = ora({ text: 'Sending verification code...', indent: 2 }).start();
|
package/dist/index.js
CHANGED
|
@@ -4,15 +4,17 @@ import { Command } from 'commander';
|
|
|
4
4
|
import { setupCommand } from './commands/setup.js';
|
|
5
5
|
import { loginCommand } from './commands/login.js';
|
|
6
6
|
import { sendCommand } from './commands/send.js';
|
|
7
|
+
import { messagesCommand } from './commands/messages.js';
|
|
7
8
|
import { statusCommand } from './commands/status.js';
|
|
8
9
|
import { whoamiCommand } from './commands/whoami.js';
|
|
9
10
|
import { addContactCommand, contactsCommand } from './commands/add-contact.js';
|
|
11
|
+
import { getLogo } from './lib/format.js';
|
|
10
12
|
const require = createRequire(import.meta.url);
|
|
11
13
|
const { version } = require('../package.json');
|
|
12
14
|
const program = new Command();
|
|
13
15
|
program
|
|
14
16
|
.name('sendblue')
|
|
15
|
-
.description(
|
|
17
|
+
.description(getLogo())
|
|
16
18
|
.version(version);
|
|
17
19
|
program
|
|
18
20
|
.command('setup')
|
|
@@ -37,6 +39,14 @@ program
|
|
|
37
39
|
.argument('<number>', 'Recipient phone number (E.164 format)')
|
|
38
40
|
.argument('<message>', 'Message content')
|
|
39
41
|
.action(sendCommand);
|
|
42
|
+
program
|
|
43
|
+
.command('messages')
|
|
44
|
+
.description('View recent messages')
|
|
45
|
+
.option('-n, --number <number>', 'Filter by contact phone number')
|
|
46
|
+
.option('-l, --limit <count>', 'Number of messages to show', '10')
|
|
47
|
+
.option('--outbound', 'Show only outbound messages')
|
|
48
|
+
.option('--inbound', 'Show only inbound messages')
|
|
49
|
+
.action(messagesCommand);
|
|
40
50
|
program
|
|
41
51
|
.command('status')
|
|
42
52
|
.description('Check your account status')
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -37,5 +37,35 @@ interface SharedContactsResponse {
|
|
|
37
37
|
}
|
|
38
38
|
export declare function addContact(apiKey: string, apiSecret: string, recipient: string): Promise<ContactRoute>;
|
|
39
39
|
export declare function getSharedContacts(apiKey: string, apiSecret: string): Promise<SharedContactsResponse>;
|
|
40
|
+
export interface Message {
|
|
41
|
+
content: string;
|
|
42
|
+
number: string;
|
|
43
|
+
from_number: string;
|
|
44
|
+
to_number: string;
|
|
45
|
+
is_outbound: boolean;
|
|
46
|
+
status: string;
|
|
47
|
+
date_sent: string;
|
|
48
|
+
date_updated: string;
|
|
49
|
+
sendblue_number: string;
|
|
50
|
+
media_url?: string;
|
|
51
|
+
message_handle: string;
|
|
52
|
+
row_id: string;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
interface MessagesResponse {
|
|
56
|
+
status: string;
|
|
57
|
+
data: Message[];
|
|
58
|
+
pagination: {
|
|
59
|
+
total: number;
|
|
60
|
+
limit: number;
|
|
61
|
+
offset: number;
|
|
62
|
+
hasMore: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export declare function getMessages(apiKey: string, apiSecret: string, opts: {
|
|
66
|
+
number?: string;
|
|
67
|
+
limit?: number;
|
|
68
|
+
isOutbound?: boolean;
|
|
69
|
+
}): Promise<MessagesResponse>;
|
|
40
70
|
export declare function testKeys(apiKey: string, apiSecret: string): Promise<boolean>;
|
|
41
71
|
export {};
|
package/dist/lib/api.js
CHANGED
|
@@ -99,6 +99,28 @@ export async function getSharedContacts(apiKey, apiSecret) {
|
|
|
99
99
|
}
|
|
100
100
|
return res.json();
|
|
101
101
|
}
|
|
102
|
+
export async function getMessages(apiKey, apiSecret, opts) {
|
|
103
|
+
const params = new URLSearchParams();
|
|
104
|
+
params.set('limit', String(opts.limit || 10));
|
|
105
|
+
params.set('order_by', 'createdAt');
|
|
106
|
+
params.set('order_direction', 'desc');
|
|
107
|
+
if (opts.number)
|
|
108
|
+
params.set('number', opts.number);
|
|
109
|
+
if (opts.isOutbound !== undefined)
|
|
110
|
+
params.set('is_outbound', String(opts.isOutbound));
|
|
111
|
+
const res = await fetch(`${API_BASE}/api/v2/messages?${params}`, {
|
|
112
|
+
method: 'GET',
|
|
113
|
+
headers: {
|
|
114
|
+
'sb-api-key-id': apiKey,
|
|
115
|
+
'sb-api-secret-key': apiSecret
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
const body = await res.json().catch(() => ({}));
|
|
120
|
+
throw new Error(body.error || body.message || `Failed to get messages (${res.status})`);
|
|
121
|
+
}
|
|
122
|
+
return res.json();
|
|
123
|
+
}
|
|
102
124
|
export async function testKeys(apiKey, apiSecret) {
|
|
103
125
|
try {
|
|
104
126
|
const res = await fetch(`${API_BASE}/account`, {
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function printLogo(): void;
|
|
2
|
+
export declare function getLogo(): string;
|
|
2
3
|
export declare function normalizeNumber(input: string): string;
|
|
3
4
|
export declare function formatPhoneNumber(e164: string): string;
|
|
4
5
|
export declare function printSuccess(message: string): void;
|
package/dist/lib/format.js
CHANGED
|
@@ -1,25 +1,106 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
const blue = chalk.hex('#0088FF');
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
|
+
export function printLogo(): void {
|
|
5
|
+
// Chat bubble with small satellite circle, matching Sendblue logo
|
|
6
|
+
const b = blue('█')
|
|
7
|
+
const h = blue('▀')
|
|
8
|
+
const l = blue('▄')
|
|
4
9
|
const logo = [
|
|
5
|
-
`
|
|
6
|
-
`
|
|
7
|
-
`
|
|
8
|
-
`
|
|
9
|
-
` ${
|
|
10
|
-
`
|
|
11
|
-
`
|
|
12
|
-
`
|
|
13
|
-
`
|
|
14
|
-
|
|
10
|
+
` ${l}${l}`,
|
|
11
|
+
` ${l}${l}${l}${l}${l}${l}${l} ${l}${l}${l}`,
|
|
12
|
+
` ${l}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} ${b}${b}${b}${b}${b}${b}`,
|
|
13
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} ${b}${b}${b}${b}${b}${b}`,
|
|
14
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} ${h}${h}${h}`,
|
|
15
|
+
` ${l}${l}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
16
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
17
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
18
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
19
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
20
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
21
|
+
` ${h}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${h}`,
|
|
22
|
+
` ${h}${h}${h} ${h}${h}${h}${h}${h}${h}${h} ${h}${h}`,
|
|
23
|
+
` ${h}${h}${h}${h}${h}`,
|
|
24
|
+
]
|
|
15
25
|
for (const line of logo) {
|
|
16
|
-
console.log(` ${line}`)
|
|
26
|
+
console.log(` ${line}`)
|
|
27
|
+
}
|
|
28
|
+
console.log()
|
|
29
|
+
console.log(blue.bold(' sendblue'))
|
|
30
|
+
console.log(chalk.dim(' iMessage for agents'))
|
|
31
|
+
console.log()
|
|
32
|
+
}
|
|
33
|
+
*/
|
|
34
|
+
export function printLogo() {
|
|
35
|
+
const lines = [
|
|
36
|
+
'███████╗███████╗███╗ ██╗██████╗',
|
|
37
|
+
'██╔════╝██╔════╝████╗ ██║██╔══██╗',
|
|
38
|
+
'███████╗█████╗ ██╔██╗ ██║██║ ██║',
|
|
39
|
+
'╚════██║██╔══╝ ██║╚██╗██║██║ ██║',
|
|
40
|
+
'███████║███████╗██║ ╚████║██████╔╝',
|
|
41
|
+
'╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝',
|
|
42
|
+
'',
|
|
43
|
+
'██████╗ ██╗ ██╗ ██╗███████╗',
|
|
44
|
+
'██╔══██╗██║ ██║ ██║██╔════╝',
|
|
45
|
+
'██████╔╝██║ ██║ ██║█████╗',
|
|
46
|
+
'██╔══██╗██║ ██║ ██║██╔══╝',
|
|
47
|
+
'██████╔╝███████╗╚██████╔╝███████╗',
|
|
48
|
+
'╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝',
|
|
49
|
+
];
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
console.log(blue(line));
|
|
17
52
|
}
|
|
18
53
|
console.log();
|
|
19
|
-
console.log(
|
|
20
|
-
console.log(chalk.dim(' iMessage for agents'));
|
|
54
|
+
console.log(chalk.dim(' iMessage for agents'));
|
|
21
55
|
console.log();
|
|
22
56
|
}
|
|
57
|
+
/*
|
|
58
|
+
export function getLogo(): string {
|
|
59
|
+
const b = blue('█')
|
|
60
|
+
const h = blue('▀')
|
|
61
|
+
const l = blue('▄')
|
|
62
|
+
const lines = [
|
|
63
|
+
` ${l}${l}`,
|
|
64
|
+
` ${l}${l}${l}${l}${l}${l}${l} ${l}${l}${l}`,
|
|
65
|
+
` ${l}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} ${b}${b}${b}${b}${b}${b}`,
|
|
66
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} ${b}${b}${b}${b}${b}${b}`,
|
|
67
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} ${h}${h}${h}`,
|
|
68
|
+
` ${l}${l}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
69
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
70
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
71
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
72
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
73
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}`,
|
|
74
|
+
` ${h}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${h}`,
|
|
75
|
+
` ${h}${h}${h} ${h}${h}${h}${h}${h}${h}${h} ${h}${h}`,
|
|
76
|
+
` ${h}${h}${h}${h}${h}`,
|
|
77
|
+
``,
|
|
78
|
+
blue.bold(' Sendblue'),
|
|
79
|
+
chalk.dim(' iMessage for agents'),
|
|
80
|
+
]
|
|
81
|
+
return '\n' + lines.map(l => ` ${l}`).join('\n') + '\n'
|
|
82
|
+
}
|
|
83
|
+
*/
|
|
84
|
+
export function getLogo() {
|
|
85
|
+
const lines = [
|
|
86
|
+
'███████╗███████╗███╗ ██╗██████╗',
|
|
87
|
+
'██╔════╝██╔════╝████╗ ██║██╔══██╗',
|
|
88
|
+
'███████╗█████╗ ██╔██╗ ██║██║ ██║',
|
|
89
|
+
'╚════██║██╔══╝ ██║╚██╗██║██║ ██║',
|
|
90
|
+
'███████║███████╗██║ ╚████║██████╔╝',
|
|
91
|
+
'╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝',
|
|
92
|
+
'',
|
|
93
|
+
'██████╗ ██╗ ██╗ ██╗███████╗',
|
|
94
|
+
'██╔══██╗██║ ██║ ██║██╔════╝',
|
|
95
|
+
'██████╔╝██║ ██║ ██║█████╗',
|
|
96
|
+
'██╔══██╗██║ ██║ ██║██╔══╝',
|
|
97
|
+
'██████╔╝███████╗╚██████╔╝███████╗',
|
|
98
|
+
'╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝',
|
|
99
|
+
'',
|
|
100
|
+
chalk.dim(' iMessage for agents'),
|
|
101
|
+
];
|
|
102
|
+
return '\n' + lines.map(l => blue(l)).join('\n') + '\n';
|
|
103
|
+
}
|
|
23
104
|
export function normalizeNumber(input) {
|
|
24
105
|
// Strip non-digit chars except leading +
|
|
25
106
|
let num = input.replace(/[^\d+]/g, '');
|