@sendblue/cli 0.1.0 → 0.4.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/login.d.ts +1 -0
- package/dist/commands/login.js +84 -0
- package/dist/commands/send.js +9 -5
- package/dist/commands/setup.js +60 -28
- package/dist/commands/status.js +5 -2
- package/dist/commands/whoami.js +11 -2
- package/dist/index.js +20 -2
- package/dist/lib/api.d.ts +17 -1
- package/dist/lib/api.js +56 -2
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +18 -1
- package/package.json +2 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getCredentials } from '../lib/config.js';
|
|
4
|
+
import { addContact, getSharedContacts } from '../lib/api.js';
|
|
5
|
+
import { formatPhoneNumber, normalizeNumber, printError } from '../lib/format.js';
|
|
6
|
+
export async function addContactCommand(number) {
|
|
7
|
+
const creds = getCredentials();
|
|
8
|
+
if (!creds) {
|
|
9
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const normalized = normalizeNumber(number);
|
|
13
|
+
const spinner = ora({ text: `Adding contact ${formatPhoneNumber(normalized)}...`, indent: 2 }).start();
|
|
14
|
+
try {
|
|
15
|
+
const result = await addContact(creds.apiKey, creds.apiSecret, normalized);
|
|
16
|
+
if (result.verified) {
|
|
17
|
+
spinner.succeed(`Contact ${formatPhoneNumber(normalized)} is already verified!`);
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(chalk.bold(' You can now send messages:'));
|
|
20
|
+
console.log(chalk.cyan(` sendblue send ${normalized} "Hello!"`));
|
|
21
|
+
console.log();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
spinner.succeed('Contact added!');
|
|
25
|
+
console.log();
|
|
26
|
+
// Get the shared number
|
|
27
|
+
const contacts = await getSharedContacts(creds.apiKey, creds.apiSecret);
|
|
28
|
+
const sharedNumber = contacts.sharedNumber;
|
|
29
|
+
console.log(chalk.bold(' To verify this contact:'));
|
|
30
|
+
console.log();
|
|
31
|
+
if (sharedNumber) {
|
|
32
|
+
console.log(` Have ${formatPhoneNumber(normalized)} send a text to:`);
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(chalk.cyan.bold(` ${formatPhoneNumber(sharedNumber)}`));
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(chalk.dim(` (any message works — "hi" is fine)`));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(` Have ${formatPhoneNumber(normalized)} text your Sendblue number.`);
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(chalk.dim(' Once they text in, the contact is verified and you can send messages.'));
|
|
43
|
+
console.log(chalk.dim(` Check status with: sendblue contacts`));
|
|
44
|
+
console.log();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
spinner.fail(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function contactsCommand() {
|
|
52
|
+
const creds = getCredentials();
|
|
53
|
+
if (!creds) {
|
|
54
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const spinner = ora({ text: 'Fetching contacts...', indent: 2 }).start();
|
|
58
|
+
try {
|
|
59
|
+
const result = await getSharedContacts(creds.apiKey, creds.apiSecret);
|
|
60
|
+
spinner.stop();
|
|
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
|
+
spinner.fail(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loginCommand(): Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getCredentials, saveCredentials, credentialsPath } from '../lib/config.js';
|
|
5
|
+
import { sendCode, verifyLogin } from '../lib/api.js';
|
|
6
|
+
import { printError, formatPhoneNumber } from '../lib/format.js';
|
|
7
|
+
const onCancel = () => {
|
|
8
|
+
console.log();
|
|
9
|
+
printError('Login cancelled.');
|
|
10
|
+
process.exit(0);
|
|
11
|
+
};
|
|
12
|
+
export async function loginCommand() {
|
|
13
|
+
console.log();
|
|
14
|
+
console.log(chalk.bold(' sendblue login'));
|
|
15
|
+
console.log(chalk.dim(' Log in to an existing Sendblue account'));
|
|
16
|
+
console.log();
|
|
17
|
+
// Check for existing credentials
|
|
18
|
+
const existing = getCredentials();
|
|
19
|
+
if (existing) {
|
|
20
|
+
const { overwrite } = await prompts({
|
|
21
|
+
type: 'confirm',
|
|
22
|
+
name: 'overwrite',
|
|
23
|
+
message: `You already have an account configured (${existing.email}). Overwrite?`,
|
|
24
|
+
initial: false
|
|
25
|
+
}, { onCancel });
|
|
26
|
+
if (!overwrite) {
|
|
27
|
+
console.log(chalk.dim(' Login cancelled.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Step 1: Collect email
|
|
32
|
+
const { email } = await prompts({
|
|
33
|
+
type: 'text',
|
|
34
|
+
name: 'email',
|
|
35
|
+
message: 'Email',
|
|
36
|
+
validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'Enter a valid email'
|
|
37
|
+
}, { onCancel });
|
|
38
|
+
// Step 2: Send verification code
|
|
39
|
+
const sendSpinner = ora({ text: 'Sending verification code...', indent: 2 }).start();
|
|
40
|
+
try {
|
|
41
|
+
await sendCode(email);
|
|
42
|
+
sendSpinner.succeed(`Code sent to ${email}`);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
sendSpinner.fail(`Failed to send code: ${err instanceof Error ? err.message : String(err)}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
console.log();
|
|
49
|
+
// Step 3: Enter code
|
|
50
|
+
const { code } = await prompts({
|
|
51
|
+
type: 'text',
|
|
52
|
+
name: 'code',
|
|
53
|
+
message: 'Verification code',
|
|
54
|
+
validate: (v) => /^\d{8}$/.test(v) || 'Enter the 8-digit code from your email'
|
|
55
|
+
}, { onCancel });
|
|
56
|
+
// Step 4: Verify code + look up existing account
|
|
57
|
+
const loginSpinner = ora({ text: 'Logging in...', indent: 2 }).start();
|
|
58
|
+
try {
|
|
59
|
+
const result = await verifyLogin(email, code);
|
|
60
|
+
loginSpinner.succeed('Logged in!');
|
|
61
|
+
saveCredentials({
|
|
62
|
+
apiKey: result.apiKey,
|
|
63
|
+
apiSecret: result.apiSecret,
|
|
64
|
+
email: result.email,
|
|
65
|
+
assignedNumber: result.assignedNumber,
|
|
66
|
+
plan: result.plan,
|
|
67
|
+
createdAt: new Date().toISOString()
|
|
68
|
+
});
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(` ${chalk.bold('Email')}: ${result.email}`);
|
|
71
|
+
console.log(` ${chalk.bold('Company')}: ${result.companyName}`);
|
|
72
|
+
if (result.assignedNumber) {
|
|
73
|
+
console.log(` ${chalk.bold('Phone Number')}: ${formatPhoneNumber(result.assignedNumber)}`);
|
|
74
|
+
}
|
|
75
|
+
console.log(` ${chalk.bold('Plan')}: ${result.plan}`);
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(chalk.dim(` Credentials saved to ${credentialsPath()}`));
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
loginSpinner.fail(`Login failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/commands/send.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
2
3
|
import { getCredentials } from '../lib/config.js';
|
|
3
4
|
import { sendMessage } from '../lib/api.js';
|
|
4
|
-
import {
|
|
5
|
+
import { normalizeNumber } from '../lib/format.js';
|
|
6
|
+
import { printError } from '../lib/format.js';
|
|
5
7
|
export async function sendCommand(number, message) {
|
|
6
8
|
const creds = getCredentials();
|
|
7
9
|
if (!creds) {
|
|
8
|
-
printError('No credentials found. Run `sendblue
|
|
10
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
9
11
|
process.exit(1);
|
|
10
12
|
}
|
|
13
|
+
const normalized = normalizeNumber(number);
|
|
14
|
+
const spinner = ora({ text: `Sending to ${normalized}...`, indent: 2 }).start();
|
|
11
15
|
try {
|
|
12
|
-
const result = await sendMessage(creds.apiKey, creds.apiSecret,
|
|
13
|
-
|
|
16
|
+
const result = await sendMessage(creds.apiKey, creds.apiSecret, normalized, message);
|
|
17
|
+
spinner.succeed(`Message sent to ${normalized}`);
|
|
14
18
|
if (result.messageId) {
|
|
15
19
|
console.log(chalk.dim(` Message ID: ${result.messageId}`));
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
catch (err) {
|
|
19
|
-
|
|
23
|
+
spinner.fail(`Send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20
24
|
process.exit(1);
|
|
21
25
|
}
|
|
22
26
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
3
4
|
import { getCredentials, saveCredentials, credentialsPath } from '../lib/config.js';
|
|
4
|
-
import {
|
|
5
|
-
import { printCredentials, printError,
|
|
5
|
+
import { sendCode, verifySetup } from '../lib/api.js';
|
|
6
|
+
import { printCredentials, printError, formatPhoneNumber } from '../lib/format.js';
|
|
7
|
+
const onCancel = () => {
|
|
8
|
+
console.log();
|
|
9
|
+
printError('Setup cancelled.');
|
|
10
|
+
process.exit(0);
|
|
11
|
+
};
|
|
6
12
|
export async function setupCommand() {
|
|
7
13
|
console.log();
|
|
8
14
|
console.log(chalk.bold(' sendblue setup'));
|
|
9
|
-
console.log(chalk.dim('
|
|
15
|
+
console.log(chalk.dim(' Create a new Sendblue account'));
|
|
10
16
|
console.log();
|
|
11
17
|
// Check for existing credentials
|
|
12
18
|
const existing = getCredentials();
|
|
@@ -16,35 +22,57 @@ export async function setupCommand() {
|
|
|
16
22
|
name: 'overwrite',
|
|
17
23
|
message: `You already have an account configured (${existing.email}). Overwrite?`,
|
|
18
24
|
initial: false
|
|
19
|
-
});
|
|
25
|
+
}, { onCancel });
|
|
20
26
|
if (!overwrite) {
|
|
21
|
-
|
|
27
|
+
console.log(chalk.dim(' Setup cancelled.'));
|
|
22
28
|
return;
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
|
-
// Collect
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
printError('Setup cancelled.');
|
|
42
|
-
return;
|
|
31
|
+
// Step 1: Collect email
|
|
32
|
+
const { email } = await prompts({
|
|
33
|
+
type: 'text',
|
|
34
|
+
name: 'email',
|
|
35
|
+
message: 'Email',
|
|
36
|
+
validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'Enter a valid email'
|
|
37
|
+
}, { onCancel });
|
|
38
|
+
// Step 2: Send verification code
|
|
39
|
+
const sendSpinner = ora({ text: 'Sending verification code...', indent: 2 }).start();
|
|
40
|
+
try {
|
|
41
|
+
await sendCode(email);
|
|
42
|
+
sendSpinner.succeed(`Code sent to ${email}`);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
sendSpinner.fail(`Failed to send code: ${err instanceof Error ? err.message : String(err)}`);
|
|
46
|
+
process.exit(1);
|
|
43
47
|
}
|
|
44
48
|
console.log();
|
|
45
|
-
|
|
49
|
+
// Step 3: Enter code
|
|
50
|
+
const { code } = await prompts({
|
|
51
|
+
type: 'text',
|
|
52
|
+
name: 'code',
|
|
53
|
+
message: 'Verification code',
|
|
54
|
+
validate: (v) => /^\d{8}$/.test(v) || 'Enter the 8-digit code from your email'
|
|
55
|
+
}, { onCancel });
|
|
56
|
+
// Step 4: Company name (required for setup)
|
|
57
|
+
const { companyName } = await prompts({
|
|
58
|
+
type: 'text',
|
|
59
|
+
name: 'companyName',
|
|
60
|
+
message: 'Company name (lowercase, hyphens/underscores only)',
|
|
61
|
+
validate: (v) => {
|
|
62
|
+
if (!v)
|
|
63
|
+
return 'Company name is required';
|
|
64
|
+
if (!/^[a-z0-9_-]+$/.test(v))
|
|
65
|
+
return 'Only lowercase letters, numbers, hyphens, and underscores';
|
|
66
|
+
if (v.length < 3 || v.length > 64)
|
|
67
|
+
return 'Must be 3-64 characters';
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}, { onCancel });
|
|
71
|
+
// Step 5: Verify code + create account
|
|
72
|
+
const setupSpinner = ora({ text: 'Setting up your account...', indent: 2 }).start();
|
|
46
73
|
try {
|
|
47
|
-
const result = await
|
|
74
|
+
const result = await verifySetup(email, code, companyName);
|
|
75
|
+
setupSpinner.succeed('Account created!');
|
|
48
76
|
saveCredentials({
|
|
49
77
|
apiKey: result.apiKey,
|
|
50
78
|
apiSecret: result.apiSecret,
|
|
@@ -56,12 +84,16 @@ export async function setupCommand() {
|
|
|
56
84
|
printCredentials(result);
|
|
57
85
|
console.log(chalk.dim(` Credentials saved to ${credentialsPath()}`));
|
|
58
86
|
console.log();
|
|
59
|
-
console.log(chalk.bold('
|
|
60
|
-
console.log(chalk.cyan(` sendblue
|
|
87
|
+
console.log(chalk.bold(' Next step — add a contact:'));
|
|
88
|
+
console.log(chalk.cyan(` sendblue add-contact +15551234567`));
|
|
61
89
|
console.log();
|
|
90
|
+
if (result.assignedNumber) {
|
|
91
|
+
console.log(chalk.dim(` Your contact will need to text ${formatPhoneNumber(result.assignedNumber)} to verify.`));
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
62
94
|
}
|
|
63
95
|
catch (err) {
|
|
64
|
-
|
|
96
|
+
setupSpinner.fail(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
97
|
process.exit(1);
|
|
66
98
|
}
|
|
67
99
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
2
3
|
import { getCredentials } from '../lib/config.js';
|
|
3
4
|
import { getAccount } from '../lib/api.js';
|
|
4
5
|
import { formatPhoneNumber, printError } from '../lib/format.js';
|
|
5
6
|
export async function statusCommand() {
|
|
6
7
|
const creds = getCredentials();
|
|
7
8
|
if (!creds) {
|
|
8
|
-
printError('No credentials found. Run `sendblue
|
|
9
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
9
10
|
process.exit(1);
|
|
10
11
|
}
|
|
12
|
+
const spinner = ora({ text: 'Fetching account status...', indent: 2 }).start();
|
|
11
13
|
try {
|
|
12
14
|
const account = await getAccount(creds.apiKey, creds.apiSecret);
|
|
15
|
+
spinner.stop();
|
|
13
16
|
console.log();
|
|
14
17
|
console.log(chalk.bold(' Account Status'));
|
|
15
18
|
console.log();
|
|
@@ -19,7 +22,7 @@ export async function statusCommand() {
|
|
|
19
22
|
console.log();
|
|
20
23
|
}
|
|
21
24
|
catch (err) {
|
|
22
|
-
|
|
25
|
+
spinner.fail(`Failed to fetch status: ${err instanceof Error ? err.message : String(err)}`);
|
|
23
26
|
process.exit(1);
|
|
24
27
|
}
|
|
25
28
|
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
2
3
|
import { getCredentials, credentialsPath } from '../lib/config.js';
|
|
3
4
|
import { testKeys } from '../lib/api.js';
|
|
4
5
|
import { formatPhoneNumber, printError } from '../lib/format.js';
|
|
5
6
|
export async function whoamiCommand() {
|
|
6
7
|
const creds = getCredentials();
|
|
7
8
|
if (!creds) {
|
|
8
|
-
printError('No credentials found. Run `sendblue
|
|
9
|
+
printError('No credentials found. Run `sendblue login` first.');
|
|
9
10
|
process.exit(1);
|
|
10
11
|
}
|
|
11
|
-
const
|
|
12
|
+
const spinner = ora({ text: 'Validating keys...', indent: 2 }).start();
|
|
13
|
+
let valid = false;
|
|
14
|
+
try {
|
|
15
|
+
valid = await testKeys(creds.apiKey, creds.apiSecret);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Network error — keys may still be valid
|
|
19
|
+
}
|
|
20
|
+
spinner.stop();
|
|
12
21
|
console.log();
|
|
13
22
|
console.log(chalk.bold(' Current Account'));
|
|
14
23
|
console.log();
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
import { setupCommand } from './commands/setup.js';
|
|
5
|
+
import { loginCommand } from './commands/login.js';
|
|
4
6
|
import { sendCommand } from './commands/send.js';
|
|
5
7
|
import { statusCommand } from './commands/status.js';
|
|
6
8
|
import { whoamiCommand } from './commands/whoami.js';
|
|
9
|
+
import { addContactCommand, contactsCommand } from './commands/add-contact.js';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version } = require('../package.json');
|
|
7
12
|
const program = new Command();
|
|
8
13
|
program
|
|
9
14
|
.name('sendblue')
|
|
10
15
|
.description('Sendblue CLI — iMessage numbers for agents')
|
|
11
|
-
.version(
|
|
16
|
+
.version(version);
|
|
12
17
|
program
|
|
13
18
|
.command('setup')
|
|
14
|
-
.description('Create
|
|
19
|
+
.description('Create a new Sendblue account and get an iMessage number')
|
|
15
20
|
.action(setupCommand);
|
|
21
|
+
program
|
|
22
|
+
.command('login')
|
|
23
|
+
.description('Log in to an existing Sendblue account')
|
|
24
|
+
.action(loginCommand);
|
|
25
|
+
program
|
|
26
|
+
.command('add-contact')
|
|
27
|
+
.description('Add a contact to your account')
|
|
28
|
+
.argument('<number>', 'Contact phone number (E.164 format)')
|
|
29
|
+
.action(addContactCommand);
|
|
30
|
+
program
|
|
31
|
+
.command('contacts')
|
|
32
|
+
.description('List contacts and verification status')
|
|
33
|
+
.action(contactsCommand);
|
|
16
34
|
program
|
|
17
35
|
.command('send')
|
|
18
36
|
.description('Send a message')
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
interface SetupResponse {
|
|
2
2
|
status: string;
|
|
3
3
|
email: string;
|
|
4
|
+
companyName: string;
|
|
4
5
|
apiKey: string;
|
|
5
6
|
apiSecret: string;
|
|
6
7
|
assignedNumber: string;
|
|
@@ -18,8 +19,23 @@ interface AccountResponse {
|
|
|
18
19
|
plan?: string;
|
|
19
20
|
[key: string]: unknown;
|
|
20
21
|
}
|
|
21
|
-
export declare function
|
|
22
|
+
export declare function sendCode(email: string): Promise<void>;
|
|
23
|
+
export declare function verifySetup(email: string, code: string, companyName: string): Promise<SetupResponse>;
|
|
24
|
+
export declare function verifyLogin(email: string, code: string): Promise<SetupResponse>;
|
|
22
25
|
export declare function sendMessage(apiKey: string, apiSecret: string, number: string, content: string): Promise<SendMessageResponse>;
|
|
23
26
|
export declare function getAccount(apiKey: string, apiSecret: string): Promise<AccountResponse>;
|
|
27
|
+
interface ContactRoute {
|
|
28
|
+
id: number;
|
|
29
|
+
number: string;
|
|
30
|
+
verified: boolean;
|
|
31
|
+
createdAt?: string;
|
|
32
|
+
}
|
|
33
|
+
interface SharedContactsResponse {
|
|
34
|
+
status: string;
|
|
35
|
+
sharedNumber: string | null;
|
|
36
|
+
contacts: ContactRoute[];
|
|
37
|
+
}
|
|
38
|
+
export declare function addContact(apiKey: string, apiSecret: string, recipient: string): Promise<ContactRoute>;
|
|
39
|
+
export declare function getSharedContacts(apiKey: string, apiSecret: string): Promise<SharedContactsResponse>;
|
|
24
40
|
export declare function testKeys(apiKey: string, apiSecret: string): Promise<boolean>;
|
|
25
41
|
export {};
|
package/dist/lib/api.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
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
|
+
});
|
|
9
|
+
if (!res.ok) {
|
|
10
|
+
const body = await res.json().catch(() => ({}));
|
|
11
|
+
throw new Error(body.error || body.message || `Failed to send code (${res.status})`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function verifySetup(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-setup' })
|
|
8
19
|
});
|
|
9
20
|
if (!res.ok) {
|
|
10
21
|
const body = await res.json().catch(() => ({}));
|
|
@@ -12,6 +23,18 @@ export async function setupAccount(email, companyName) {
|
|
|
12
23
|
}
|
|
13
24
|
return res.json();
|
|
14
25
|
}
|
|
26
|
+
export async function verifyLogin(email, code) {
|
|
27
|
+
const res = await fetch(`${SETUP_BASE}/api/v3/cli/setup`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify({ email, code, action: 'verify-login' })
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
const body = await res.json().catch(() => ({}));
|
|
34
|
+
throw new Error(body.error || body.message || `Login failed (${res.status})`);
|
|
35
|
+
}
|
|
36
|
+
return res.json();
|
|
37
|
+
}
|
|
15
38
|
export async function sendMessage(apiKey, apiSecret, number, content) {
|
|
16
39
|
const res = await fetch(`${API_BASE}/api/send-message`, {
|
|
17
40
|
method: 'POST',
|
|
@@ -42,6 +65,37 @@ export async function getAccount(apiKey, apiSecret) {
|
|
|
42
65
|
}
|
|
43
66
|
return res.json();
|
|
44
67
|
}
|
|
68
|
+
export async function addContact(apiKey, apiSecret, recipient) {
|
|
69
|
+
const res = await fetch(`${API_BASE}/accounts/verify-contact-on-shared-account`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
'sb-api-key-id': apiKey,
|
|
74
|
+
'sb-api-secret-key': apiSecret
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify({ recipient })
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const body = await res.json().catch(() => ({}));
|
|
80
|
+
throw new Error(body.message || body.error || `Failed to add contact (${res.status})`);
|
|
81
|
+
}
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
return data.route;
|
|
84
|
+
}
|
|
85
|
+
export async function getSharedContacts(apiKey, apiSecret) {
|
|
86
|
+
const res = await fetch(`${API_BASE}/accounts/shared-contacts`, {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
headers: {
|
|
89
|
+
'sb-api-key-id': apiKey,
|
|
90
|
+
'sb-api-secret-key': apiSecret
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const body = await res.json().catch(() => ({}));
|
|
95
|
+
throw new Error(body.message || body.error || `Failed to get contacts (${res.status})`);
|
|
96
|
+
}
|
|
97
|
+
return res.json();
|
|
98
|
+
}
|
|
45
99
|
export async function testKeys(apiKey, apiSecret) {
|
|
46
100
|
try {
|
|
47
101
|
const res = await fetch(`${API_BASE}/account`, {
|
package/dist/lib/format.d.ts
CHANGED
package/dist/lib/format.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
export function normalizeNumber(input) {
|
|
3
|
+
// Strip non-digit chars except leading +
|
|
4
|
+
let num = input.replace(/[^\d+]/g, '');
|
|
5
|
+
// 10-digit US number: prepend +1
|
|
6
|
+
if (/^\d{10}$/.test(num)) {
|
|
7
|
+
num = `+1${num}`;
|
|
8
|
+
}
|
|
9
|
+
// 11-digit starting with 1: prepend +
|
|
10
|
+
else if (/^1\d{10}$/.test(num)) {
|
|
11
|
+
num = `+${num}`;
|
|
12
|
+
}
|
|
13
|
+
// Already has +: keep as-is
|
|
14
|
+
else if (!num.startsWith('+')) {
|
|
15
|
+
num = `+${num}`;
|
|
16
|
+
}
|
|
17
|
+
return num;
|
|
18
|
+
}
|
|
2
19
|
export function formatPhoneNumber(e164) {
|
|
3
20
|
// +15551234567 -> +1 (555) 123-4567
|
|
4
21
|
const match = e164.match(/^\+1(\d{3})(\d{3})(\d{4})$/);
|
|
@@ -22,7 +39,7 @@ export function printCredentials(data) {
|
|
|
22
39
|
console.log();
|
|
23
40
|
console.log(` ${chalk.bold('Phone Number')}: ${formatPhoneNumber(data.assignedNumber)}`);
|
|
24
41
|
console.log(` ${chalk.bold('API Key')}: ${data.apiKey}`);
|
|
25
|
-
console.log(` ${chalk.bold('API Secret')}: ${data.apiSecret}`);
|
|
42
|
+
console.log(` ${chalk.bold('API Secret')}: ${'*'.repeat(data.apiSecret.length - 4)}${data.apiSecret.slice(-4)}`);
|
|
26
43
|
console.log(` ${chalk.bold('Plan')}: ${data.plan}`);
|
|
27
44
|
console.log();
|
|
28
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendblue/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Sendblue CLI — iMessage numbers for agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"chalk": "^5.3.0",
|
|
31
31
|
"commander": "^12.1.0",
|
|
32
|
+
"ora": "^9.3.0",
|
|
32
33
|
"prompts": "^2.4.2"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|