@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.
@@ -0,0 +1,6 @@
1
+ export declare function messagesCommand(opts: {
2
+ number?: string;
3
+ limit?: string;
4
+ outbound?: boolean;
5
+ inbound?: boolean;
6
+ }): Promise<void>;
@@ -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
+ }
@@ -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('Sendblue CLI — iMessage numbers for agents')
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({ number, content })
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`, {
@@ -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;
@@ -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
- ` ${blue('██')} `,
6
- ` ${blue('██████')} `,
7
- ` ${blue('██████████')} `,
8
- ` ${blue('████████████')}`,
9
- ` ${blue('██████████████')}`,
10
- ` ${blue('██████████████')}`,
11
- ` ${blue('████████████')}`,
12
- ` ${blue('██████████')} `,
13
- ` ${blue('████████')} `,
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, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendblue/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Sendblue CLI — iMessage numbers for agents",
5
5
  "type": "module",
6
6
  "bin": {