@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.
- package/dist/commands/add-contact.js +17 -1
- package/dist/commands/login.js +2 -1
- package/dist/commands/send.js +1 -1
- package/dist/commands/setup.js +70 -9
- package/dist/lib/api.d.ts +1 -1
- package/dist/lib/api.js +5 -2
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +21 -0
- package/package.json +4 -2
|
@@ -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
|
|
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 {
|
package/dist/commands/login.js
CHANGED
|
@@ -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();
|
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/commands/setup.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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(
|
|
49
|
+
body: JSON.stringify(body)
|
|
47
50
|
});
|
|
48
51
|
if (!res.ok) {
|
|
49
52
|
const body = await res.json().catch(() => ({}));
|
package/dist/lib/format.d.ts
CHANGED
package/dist/lib/format.js
CHANGED
|
@@ -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.
|
|
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
|
}
|