@sendblue/cli 0.5.0 → 0.6.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/dist/commands/messages.d.ts +6 -0
- package/dist/commands/messages.js +66 -0
- package/dist/commands/send.js +1 -1
- package/dist/index.js +11 -1
- package/dist/lib/api.d.ts +31 -1
- package/dist/lib/api.js +27 -2
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +35 -9
- package/package.json +1 -1
|
@@ -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/send.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function sendCommand(number, message) {
|
|
|
13
13
|
const normalized = normalizeNumber(number);
|
|
14
14
|
const spinner = ora({ text: `Sending to ${normalized}...`, indent: 2 }).start();
|
|
15
15
|
try {
|
|
16
|
-
const result = await sendMessage(creds.apiKey, creds.apiSecret, normalized, message);
|
|
16
|
+
const result = await sendMessage(creds.apiKey, creds.apiSecret, normalized, message, creds.assignedNumber);
|
|
17
17
|
spinner.succeed(`Message sent to ${normalized}`);
|
|
18
18
|
if (result.messageId) {
|
|
19
19
|
console.log(chalk.dim(` Message ID: ${result.messageId}`));
|
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
|
@@ -22,7 +22,7 @@ interface AccountResponse {
|
|
|
22
22
|
export declare function sendCode(email: string): Promise<void>;
|
|
23
23
|
export declare function verifySetup(email: string, code: string, companyName: string): Promise<SetupResponse>;
|
|
24
24
|
export declare function verifyLogin(email: string, code: string): Promise<SetupResponse>;
|
|
25
|
-
export declare function sendMessage(apiKey: string, apiSecret: string, number: string, content: string): Promise<SendMessageResponse>;
|
|
25
|
+
export declare function sendMessage(apiKey: string, apiSecret: string, number: string, content: string, fromNumber?: string): Promise<SendMessageResponse>;
|
|
26
26
|
export declare function getAccount(apiKey: string, apiSecret: string): Promise<AccountResponse>;
|
|
27
27
|
interface ContactRoute {
|
|
28
28
|
id: number;
|
|
@@ -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
|
@@ -35,7 +35,10 @@ export async function verifyLogin(email, code) {
|
|
|
35
35
|
}
|
|
36
36
|
return res.json();
|
|
37
37
|
}
|
|
38
|
-
export async function sendMessage(apiKey, apiSecret, number, content) {
|
|
38
|
+
export async function sendMessage(apiKey, apiSecret, number, content, fromNumber) {
|
|
39
|
+
const body = { number, content };
|
|
40
|
+
if (fromNumber)
|
|
41
|
+
body.from_number = fromNumber;
|
|
39
42
|
const res = await fetch(`${API_BASE}/api/send-message`, {
|
|
40
43
|
method: 'POST',
|
|
41
44
|
headers: {
|
|
@@ -43,7 +46,7 @@ export async function sendMessage(apiKey, apiSecret, number, content) {
|
|
|
43
46
|
'sb-api-key-id': apiKey,
|
|
44
47
|
'sb-api-secret-key': apiSecret
|
|
45
48
|
},
|
|
46
|
-
body: JSON.stringify(
|
|
49
|
+
body: JSON.stringify(body)
|
|
47
50
|
});
|
|
48
51
|
if (!res.ok) {
|
|
49
52
|
const body = await res.json().catch(() => ({}));
|
|
@@ -96,6 +99,28 @@ export async function getSharedContacts(apiKey, apiSecret) {
|
|
|
96
99
|
}
|
|
97
100
|
return res.json();
|
|
98
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
|
+
}
|
|
99
124
|
export async function testKeys(apiKey, apiSecret) {
|
|
100
125
|
try {
|
|
101
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,16 +1,21 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
const blue = chalk.hex('#0088FF');
|
|
3
3
|
export function printLogo() {
|
|
4
|
+
// Cloud/bubble shape with small circle top-right, matching Sendblue logo
|
|
5
|
+
const b = blue('█');
|
|
6
|
+
const h = blue('▀');
|
|
7
|
+
const l = blue('▄');
|
|
4
8
|
const logo = [
|
|
5
|
-
`
|
|
6
|
-
` ${
|
|
7
|
-
`
|
|
8
|
-
` ${
|
|
9
|
-
`
|
|
10
|
-
`
|
|
11
|
-
` ${
|
|
12
|
-
`
|
|
13
|
-
`
|
|
9
|
+
` ${l}${l}${l} `,
|
|
10
|
+
` ${l}${l}${l}${l} ${b}${b}${b} `,
|
|
11
|
+
` ${l}${b}${b}${b}${b}${b}${l} ${h}${h}${h} `,
|
|
12
|
+
` ${l}${b}${b}${b}${b}${b}${b}${b}${b}${l} `,
|
|
13
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${l} `,
|
|
14
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${l} `,
|
|
15
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} `,
|
|
16
|
+
` ${h}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${h} `,
|
|
17
|
+
` ${h}${b}${b}${b}${b}${b}${b}${b}${b}${h} `,
|
|
18
|
+
` ${h}${h}${h}${h}${h}${h}${h} `,
|
|
14
19
|
];
|
|
15
20
|
for (const line of logo) {
|
|
16
21
|
console.log(` ${line}`);
|
|
@@ -20,6 +25,27 @@ export function printLogo() {
|
|
|
20
25
|
console.log(chalk.dim(' iMessage for agents'));
|
|
21
26
|
console.log();
|
|
22
27
|
}
|
|
28
|
+
export function getLogo() {
|
|
29
|
+
const b = blue('█');
|
|
30
|
+
const h = blue('▀');
|
|
31
|
+
const l = blue('▄');
|
|
32
|
+
const lines = [
|
|
33
|
+
` ${l}${l}${l} `,
|
|
34
|
+
` ${l}${l}${l}${l} ${b}${b}${b} `,
|
|
35
|
+
` ${l}${b}${b}${b}${b}${b}${l} ${h}${h}${h} `,
|
|
36
|
+
` ${l}${b}${b}${b}${b}${b}${b}${b}${b}${l} `,
|
|
37
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${l} `,
|
|
38
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${l} `,
|
|
39
|
+
` ${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b} `,
|
|
40
|
+
` ${h}${b}${b}${b}${b}${b}${b}${b}${b}${b}${b}${h} `,
|
|
41
|
+
` ${h}${b}${b}${b}${b}${b}${b}${b}${b}${h} `,
|
|
42
|
+
` ${h}${h}${h}${h}${h}${h}${h} `,
|
|
43
|
+
``,
|
|
44
|
+
blue.bold(' sendblue'),
|
|
45
|
+
chalk.dim(' iMessage for agents'),
|
|
46
|
+
];
|
|
47
|
+
return '\n' + lines.map(l => ` ${l}`).join('\n') + '\n';
|
|
48
|
+
}
|
|
23
49
|
export function normalizeNumber(input) {
|
|
24
50
|
// Strip non-digit chars except leading +
|
|
25
51
|
let num = input.replace(/[^\d+]/g, '');
|