@sendblue/cli 0.1.0 → 0.2.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/add-contact.d.ts +2 -0
- package/dist/commands/add-contact.js +90 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +45 -22
- package/dist/index.js +14 -4
- package/dist/lib/api.d.ts +15 -1
- package/dist/lib/api.js +45 -3
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getCredentials } from '../lib/config.js';
|
|
3
|
+
import { addContact, getSharedContacts } from '../lib/api.js';
|
|
4
|
+
import { formatPhoneNumber, printError, printInfo, printSuccess } from '../lib/format.js';
|
|
5
|
+
export async function addContactCommand(number) {
|
|
6
|
+
const creds = getCredentials();
|
|
7
|
+
if (!creds) {
|
|
8
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
// Normalize number
|
|
12
|
+
const normalized = number.startsWith('+') ? number : `+${number}`;
|
|
13
|
+
console.log();
|
|
14
|
+
printInfo(` Adding contact ${formatPhoneNumber(normalized)}...`);
|
|
15
|
+
try {
|
|
16
|
+
const result = await addContact(creds.apiKey, creds.apiSecret, normalized);
|
|
17
|
+
if (result.verified) {
|
|
18
|
+
printSuccess(` Contact ${formatPhoneNumber(normalized)} is already verified!`);
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(chalk.bold(' You can now send messages:'));
|
|
21
|
+
console.log(chalk.cyan(` sendblue send ${normalized} "Hello!"`));
|
|
22
|
+
console.log();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Contact added but needs verification
|
|
26
|
+
console.log(chalk.green(` Contact added!`));
|
|
27
|
+
console.log();
|
|
28
|
+
// Get the shared number
|
|
29
|
+
const contacts = await getSharedContacts(creds.apiKey, creds.apiSecret);
|
|
30
|
+
const sharedNumber = contacts.sharedNumber;
|
|
31
|
+
console.log(chalk.bold(' To verify this contact:'));
|
|
32
|
+
console.log();
|
|
33
|
+
if (sharedNumber) {
|
|
34
|
+
console.log(` Have ${formatPhoneNumber(normalized)} send a text to:`);
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(chalk.cyan.bold(` ${formatPhoneNumber(sharedNumber)}`));
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(chalk.dim(` (any message works — "hi" is fine)`));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(` Have ${formatPhoneNumber(normalized)} text your Sendblue number.`);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(chalk.dim(' Once they text in, the contact is verified and you can send messages.'));
|
|
45
|
+
console.log(chalk.dim(` Check status with: sendblue contacts`));
|
|
46
|
+
console.log();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
printError(` Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function contactsCommand() {
|
|
54
|
+
const creds = getCredentials();
|
|
55
|
+
if (!creds) {
|
|
56
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await getSharedContacts(creds.apiKey, creds.apiSecret);
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.bold(' Contacts'));
|
|
63
|
+
if (result.sharedNumber) {
|
|
64
|
+
console.log(chalk.dim(` Shared number: ${formatPhoneNumber(result.sharedNumber)}`));
|
|
65
|
+
}
|
|
66
|
+
console.log();
|
|
67
|
+
if (result.contacts.length === 0) {
|
|
68
|
+
console.log(chalk.dim(' No contacts yet. Add one with:'));
|
|
69
|
+
console.log(chalk.cyan(' sendblue add-contact +15551234567'));
|
|
70
|
+
console.log();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
for (const contact of result.contacts) {
|
|
74
|
+
const status = contact.verified
|
|
75
|
+
? chalk.green('verified')
|
|
76
|
+
: chalk.yellow('awaiting text');
|
|
77
|
+
console.log(` ${formatPhoneNumber(contact.number)} ${status}`);
|
|
78
|
+
}
|
|
79
|
+
const pending = result.contacts.filter((c) => !c.verified);
|
|
80
|
+
if (pending.length > 0 && result.sharedNumber) {
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(chalk.dim(` Pending contacts need to text ${formatPhoneNumber(result.sharedNumber)} to verify.`));
|
|
83
|
+
}
|
|
84
|
+
console.log();
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
printError(` Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function loginCommand(): Promise<void>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { getCredentials, saveCredentials, credentialsPath } from '../lib/config.js';
|
|
4
|
-
import {
|
|
4
|
+
import { sendCode, verifyCode } from '../lib/api.js';
|
|
5
5
|
import { printCredentials, printError, printInfo } from '../lib/format.js';
|
|
6
|
-
export async function
|
|
6
|
+
export async function loginCommand() {
|
|
7
7
|
console.log();
|
|
8
|
-
console.log(chalk.bold(' sendblue
|
|
8
|
+
console.log(chalk.bold(' sendblue login'));
|
|
9
9
|
console.log(chalk.dim(' iMessage numbers for agents'));
|
|
10
10
|
console.log();
|
|
11
11
|
// Check for existing credentials
|
|
@@ -18,33 +18,56 @@ export async function setupCommand() {
|
|
|
18
18
|
initial: false
|
|
19
19
|
});
|
|
20
20
|
if (!overwrite) {
|
|
21
|
-
printInfo('
|
|
21
|
+
printInfo('Login cancelled.');
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
// Collect
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
// Step 1: Collect email
|
|
26
|
+
const { email } = await prompts({
|
|
27
|
+
type: 'text',
|
|
28
|
+
name: 'email',
|
|
29
|
+
message: 'Email',
|
|
30
|
+
validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'Enter a valid email'
|
|
31
|
+
});
|
|
32
|
+
if (!email) {
|
|
33
|
+
printError('Login cancelled.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Step 2: Send verification code
|
|
37
|
+
console.log();
|
|
38
|
+
printInfo(' Sending verification code...');
|
|
39
|
+
try {
|
|
40
|
+
await sendCode(email);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
printError(` Failed to send code: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk.green(` Code sent to ${email}`));
|
|
47
|
+
console.log();
|
|
48
|
+
// Step 3: Enter code
|
|
49
|
+
const { code } = await prompts({
|
|
50
|
+
type: 'text',
|
|
51
|
+
name: 'code',
|
|
52
|
+
message: 'Verification code',
|
|
53
|
+
validate: (v) => /^\d{6}$/.test(v) || 'Enter the 6-digit code from your email'
|
|
54
|
+
});
|
|
55
|
+
if (!code) {
|
|
56
|
+
printError('Login cancelled.');
|
|
42
57
|
return;
|
|
43
58
|
}
|
|
59
|
+
// Step 4: Optional company name
|
|
60
|
+
const { companyName } = await prompts({
|
|
61
|
+
type: 'text',
|
|
62
|
+
name: 'companyName',
|
|
63
|
+
message: 'Company name (optional)',
|
|
64
|
+
initial: ''
|
|
65
|
+
});
|
|
44
66
|
console.log();
|
|
45
67
|
printInfo(' Setting up your account...');
|
|
68
|
+
// Step 5: Verify code + create account
|
|
46
69
|
try {
|
|
47
|
-
const result = await
|
|
70
|
+
const result = await verifyCode(email, code, companyName || undefined);
|
|
48
71
|
saveCredentials({
|
|
49
72
|
apiKey: result.apiKey,
|
|
50
73
|
apiSecret: result.apiSecret,
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { loginCommand } from './commands/setup.js';
|
|
4
4
|
import { sendCommand } from './commands/send.js';
|
|
5
5
|
import { statusCommand } from './commands/status.js';
|
|
6
6
|
import { whoamiCommand } from './commands/whoami.js';
|
|
7
|
+
import { addContactCommand, contactsCommand } from './commands/add-contact.js';
|
|
7
8
|
const program = new Command();
|
|
8
9
|
program
|
|
9
10
|
.name('sendblue')
|
|
10
11
|
.description('Sendblue CLI — iMessage numbers for agents')
|
|
11
12
|
.version('0.1.0');
|
|
12
13
|
program
|
|
13
|
-
.command('
|
|
14
|
-
.description('Create an account and get
|
|
15
|
-
.action(
|
|
14
|
+
.command('login')
|
|
15
|
+
.description('Create an account and get an iMessage number')
|
|
16
|
+
.action(loginCommand);
|
|
17
|
+
program
|
|
18
|
+
.command('add-contact')
|
|
19
|
+
.description('Add a contact to your account')
|
|
20
|
+
.argument('<number>', 'Contact phone number (E.164 format)')
|
|
21
|
+
.action(addContactCommand);
|
|
22
|
+
program
|
|
23
|
+
.command('contacts')
|
|
24
|
+
.description('List contacts and verification status')
|
|
25
|
+
.action(contactsCommand);
|
|
16
26
|
program
|
|
17
27
|
.command('send')
|
|
18
28
|
.description('Send a message')
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -18,8 +18,22 @@ interface AccountResponse {
|
|
|
18
18
|
plan?: string;
|
|
19
19
|
[key: string]: unknown;
|
|
20
20
|
}
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function sendCode(email: string): Promise<void>;
|
|
22
|
+
export declare function verifyCode(email: string, code: string, companyName?: string): Promise<SetupResponse>;
|
|
22
23
|
export declare function sendMessage(apiKey: string, apiSecret: string, number: string, content: string): Promise<SendMessageResponse>;
|
|
23
24
|
export declare function getAccount(apiKey: string, apiSecret: string): Promise<AccountResponse>;
|
|
25
|
+
interface ContactRoute {
|
|
26
|
+
id: number;
|
|
27
|
+
number: string;
|
|
28
|
+
verified: boolean;
|
|
29
|
+
createdAt?: string;
|
|
30
|
+
}
|
|
31
|
+
interface SharedContactsResponse {
|
|
32
|
+
status: string;
|
|
33
|
+
sharedNumber: string | null;
|
|
34
|
+
contacts: ContactRoute[];
|
|
35
|
+
}
|
|
36
|
+
export declare function addContact(apiKey: string, apiSecret: string, recipient: string): Promise<ContactRoute>;
|
|
37
|
+
export declare function getSharedContacts(apiKey: string, apiSecret: string): Promise<SharedContactsResponse>;
|
|
24
38
|
export declare function testKeys(apiKey: string, apiSecret: string): Promise<boolean>;
|
|
25
39
|
export {};
|
package/dist/lib/api.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
const API_BASE = 'https://api.sendblue.co';
|
|
2
2
|
const SETUP_BASE = process.env.SENDBLUE_SETUP_URL || 'https://app.sendblue.co';
|
|
3
|
-
export async function
|
|
3
|
+
export async function sendCode(email) {
|
|
4
4
|
const res = await fetch(`${SETUP_BASE}/api/v3/cli/setup`, {
|
|
5
5
|
method: 'POST',
|
|
6
6
|
headers: { 'Content-Type': 'application/json' },
|
|
7
|
-
body: JSON.stringify({ email
|
|
7
|
+
body: JSON.stringify({ email })
|
|
8
8
|
});
|
|
9
9
|
if (!res.ok) {
|
|
10
10
|
const body = await res.json().catch(() => ({}));
|
|
11
|
-
throw new Error(body.error || body.message || `
|
|
11
|
+
throw new Error(body.error || body.message || `Failed to send code (${res.status})`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function verifyCode(email, code, companyName) {
|
|
15
|
+
const res = await fetch(`${SETUP_BASE}/api/v3/cli/setup`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({ email, code, companyName, action: 'verify' })
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const body = await res.json().catch(() => ({}));
|
|
22
|
+
throw new Error(body.error || body.message || `Verification failed (${res.status})`);
|
|
12
23
|
}
|
|
13
24
|
return res.json();
|
|
14
25
|
}
|
|
@@ -42,6 +53,37 @@ export async function getAccount(apiKey, apiSecret) {
|
|
|
42
53
|
}
|
|
43
54
|
return res.json();
|
|
44
55
|
}
|
|
56
|
+
export async function addContact(apiKey, apiSecret, recipient) {
|
|
57
|
+
const res = await fetch(`${API_BASE}/accounts/verify-contact-on-shared-account`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'sb-api-key-id': apiKey,
|
|
62
|
+
'sb-api-secret-key': apiSecret
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({ recipient })
|
|
65
|
+
});
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
const body = await res.json().catch(() => ({}));
|
|
68
|
+
throw new Error(body.message || body.error || `Failed to add contact (${res.status})`);
|
|
69
|
+
}
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
return data.route;
|
|
72
|
+
}
|
|
73
|
+
export async function getSharedContacts(apiKey, apiSecret) {
|
|
74
|
+
const res = await fetch(`${API_BASE}/accounts/shared-contacts`, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: {
|
|
77
|
+
'sb-api-key-id': apiKey,
|
|
78
|
+
'sb-api-secret-key': apiSecret
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const body = await res.json().catch(() => ({}));
|
|
83
|
+
throw new Error(body.message || body.error || `Failed to get contacts (${res.status})`);
|
|
84
|
+
}
|
|
85
|
+
return res.json();
|
|
86
|
+
}
|
|
45
87
|
export async function testKeys(apiKey, apiSecret) {
|
|
46
88
|
try {
|
|
47
89
|
const res = await fetch(`${API_BASE}/account`, {
|