@sendblue/cli 0.4.1 → 0.5.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.
@@ -1,8 +1,16 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
+ import qrcode from 'qrcode-terminal';
3
4
  import { getCredentials } from '../lib/config.js';
4
5
  import { addContact, getSharedContacts } from '../lib/api.js';
5
6
  import { formatPhoneNumber, normalizeNumber, printError } from '../lib/format.js';
7
+ function generateSmsQr(phoneNumber) {
8
+ return new Promise((resolve) => {
9
+ qrcode.generate(`sms:${phoneNumber}`, { small: true }, (code) => {
10
+ resolve(code);
11
+ });
12
+ });
13
+ }
6
14
  export async function addContactCommand(number) {
7
15
  const creds = getCredentials();
8
16
  if (!creds) {
@@ -29,10 +37,18 @@ export async function addContactCommand(number) {
29
37
  console.log(chalk.bold(' To verify this contact:'));
30
38
  console.log();
31
39
  if (sharedNumber) {
32
- console.log(` Have ${formatPhoneNumber(normalized)} send a text to:`);
40
+ console.log(` Have ${formatPhoneNumber(normalized)} send any text to:`);
33
41
  console.log();
34
42
  console.log(chalk.cyan.bold(` ${formatPhoneNumber(sharedNumber)}`));
35
43
  console.log();
44
+ // Show QR code for easy texting
45
+ const qr = await generateSmsQr(sharedNumber);
46
+ console.log(chalk.dim(' Or scan to open a text:'));
47
+ console.log();
48
+ for (const line of qr.split('\n')) {
49
+ console.log(` ${line}`);
50
+ }
51
+ console.log();
36
52
  console.log(chalk.dim(` (any message works — "hi" is fine)`));
37
53
  }
38
54
  else {
@@ -3,7 +3,7 @@ import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import { getCredentials, saveCredentials, credentialsPath } from '../lib/config.js';
5
5
  import { sendCode, verifyLogin } from '../lib/api.js';
6
- import { printError, formatPhoneNumber } from '../lib/format.js';
6
+ import { printError, printLogo, formatPhoneNumber } from '../lib/format.js';
7
7
  const onCancel = () => {
8
8
  console.log();
9
9
  printError('Login cancelled.');
@@ -11,6 +11,7 @@ const onCancel = () => {
11
11
  };
12
12
  export async function loginCommand() {
13
13
  console.log();
14
+ printLogo();
14
15
  console.log(chalk.bold(' sendblue login'));
15
16
  console.log(chalk.dim(' Log in to an existing Sendblue account'));
16
17
  console.log();
@@ -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}`));
@@ -1,9 +1,17 @@
1
1
  import prompts from 'prompts';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
+ import qrcode from 'qrcode-terminal';
4
5
  import { getCredentials, saveCredentials, credentialsPath } from '../lib/config.js';
5
- import { sendCode, verifySetup } from '../lib/api.js';
6
- import { printCredentials, printError, formatPhoneNumber } from '../lib/format.js';
6
+ import { sendCode, verifySetup, addContact, getSharedContacts } from '../lib/api.js';
7
+ import { printCredentials, printError, printLogo, formatPhoneNumber, normalizeNumber } from '../lib/format.js';
8
+ function generateSmsQr(phoneNumber) {
9
+ return new Promise((resolve) => {
10
+ qrcode.generate(`sms:${phoneNumber}`, { small: true }, (code) => {
11
+ resolve(code);
12
+ });
13
+ });
14
+ }
7
15
  const onCancel = () => {
8
16
  console.log();
9
17
  printError('Setup cancelled.');
@@ -11,6 +19,7 @@ const onCancel = () => {
11
19
  };
12
20
  export async function setupCommand() {
13
21
  console.log();
22
+ printLogo();
14
23
  console.log(chalk.bold(' sendblue setup'));
15
24
  console.log(chalk.dim(' Create a new Sendblue account'));
16
25
  console.log();
@@ -70,8 +79,9 @@ export async function setupCommand() {
70
79
  }, { onCancel });
71
80
  // Step 5: Verify code + create account
72
81
  const setupSpinner = ora({ text: 'Setting up your account...', indent: 2 }).start();
82
+ let result;
73
83
  try {
74
- const result = await verifySetup(email, code, companyName);
84
+ result = await verifySetup(email, code, companyName);
75
85
  setupSpinner.succeed('Account created!');
76
86
  saveCredentials({
77
87
  apiKey: result.apiKey,
@@ -84,16 +94,67 @@ export async function setupCommand() {
84
94
  printCredentials(result);
85
95
  console.log(chalk.dim(` Credentials saved to ${credentialsPath()}`));
86
96
  console.log();
87
- console.log(chalk.bold(' Next step — add a contact:'));
88
- console.log(chalk.cyan(` sendblue add-contact +15551234567`));
97
+ }
98
+ catch (err) {
99
+ setupSpinner.fail(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
100
+ process.exit(1);
101
+ }
102
+ // Step 6: Add first contact
103
+ console.log(chalk.bold(' Add your first contact'));
104
+ console.log(chalk.dim(' Enter the phone number you want to message via iMessage.'));
105
+ console.log();
106
+ const { contactNumber } = await prompts({
107
+ type: 'text',
108
+ name: 'contactNumber',
109
+ message: 'Contact phone number',
110
+ validate: (v) => {
111
+ const n = normalizeNumber(v);
112
+ return /^\+\d{10,15}$/.test(n) || 'Enter a valid phone number (e.g. +15551234567)';
113
+ }
114
+ }, { onCancel });
115
+ const normalized = normalizeNumber(contactNumber);
116
+ const contactSpinner = ora({ text: `Adding contact ${formatPhoneNumber(normalized)}...`, indent: 2 }).start();
117
+ try {
118
+ const contact = await addContact(result.apiKey, result.apiSecret, normalized);
119
+ if (contact.verified) {
120
+ contactSpinner.succeed(`Contact ${formatPhoneNumber(normalized)} is already verified!`);
121
+ console.log();
122
+ console.log(chalk.bold(' You\'re all set! Send a message:'));
123
+ console.log(chalk.cyan(` sendblue send ${normalized} "Hello from Sendblue!"`));
124
+ console.log();
125
+ return;
126
+ }
127
+ contactSpinner.succeed('Contact added!');
89
128
  console.log();
90
- if (result.assignedNumber) {
91
- console.log(chalk.dim(` Your contact will need to text ${formatPhoneNumber(result.assignedNumber)} to verify.`));
129
+ // Get the shared number to show QR code
130
+ const contacts = await getSharedContacts(result.apiKey, result.apiSecret);
131
+ const sharedNumber = contacts.sharedNumber || result.assignedNumber;
132
+ if (sharedNumber) {
133
+ console.log(chalk.bold(' One last step — verify the contact'));
134
+ console.log();
135
+ console.log(` Have ${chalk.cyan(formatPhoneNumber(normalized))} send any text to:`);
136
+ console.log();
137
+ console.log(chalk.cyan.bold(` ${formatPhoneNumber(sharedNumber)}`));
138
+ console.log();
139
+ // Show QR code for easy texting
140
+ const qr = await generateSmsQr(sharedNumber);
141
+ console.log(chalk.dim(' Or scan this QR code to open a text:'));
92
142
  console.log();
143
+ // Indent each line of the QR code
144
+ for (const line of qr.split('\n')) {
145
+ console.log(` ${line}`);
146
+ }
147
+ console.log();
148
+ console.log(chalk.dim(' Once they text in, run:'));
149
+ console.log(chalk.cyan(` sendblue send ${normalized} "Hello from Sendblue!"`));
93
150
  }
151
+ console.log();
94
152
  }
95
153
  catch (err) {
96
- setupSpinner.fail(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
97
- process.exit(1);
154
+ contactSpinner.fail(`Failed to add contact: ${err instanceof Error ? err.message : String(err)}`);
155
+ console.log();
156
+ console.log(chalk.dim(' You can add contacts later with:'));
157
+ console.log(chalk.cyan(` sendblue add-contact +15551234567`));
158
+ console.log();
98
159
  }
99
160
  }
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;
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(() => ({}));
@@ -1,3 +1,4 @@
1
+ export declare function printLogo(): void;
1
2
  export declare function normalizeNumber(input: string): string;
2
3
  export declare function formatPhoneNumber(e164: string): string;
3
4
  export declare function printSuccess(message: string): void;
@@ -1,4 +1,25 @@
1
1
  import chalk from 'chalk';
2
+ const blue = chalk.hex('#0088FF');
3
+ export function printLogo() {
4
+ const logo = [
5
+ ` ${blue('██')} `,
6
+ ` ${blue('██████')} `,
7
+ ` ${blue('██████████')} `,
8
+ ` ${blue('████████████')}`,
9
+ ` ${blue('██████████████')}`,
10
+ ` ${blue('██████████████')}`,
11
+ ` ${blue('████████████')}`,
12
+ ` ${blue('██████████')} `,
13
+ ` ${blue('████████')} `,
14
+ ];
15
+ for (const line of logo) {
16
+ console.log(` ${line}`);
17
+ }
18
+ console.log();
19
+ console.log(blue.bold(' sendblue'));
20
+ console.log(chalk.dim(' iMessage for agents'));
21
+ console.log();
22
+ }
2
23
  export function normalizeNumber(input) {
3
24
  // Strip non-digit chars except leading +
4
25
  let num = input.replace(/[^\d+]/g, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendblue/cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Sendblue CLI — iMessage numbers for agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,11 +30,13 @@
30
30
  "chalk": "^5.3.0",
31
31
  "commander": "^12.1.0",
32
32
  "ora": "^9.3.0",
33
- "prompts": "^2.4.2"
33
+ "prompts": "^2.4.2",
34
+ "qrcode-terminal": "^0.12.0"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/node": "^20.0.0",
37
38
  "@types/prompts": "^2.4.9",
39
+ "@types/qrcode-terminal": "^0.12.2",
38
40
  "typescript": "^5.5.0"
39
41
  }
40
42
  }