@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.
@@ -0,0 +1,2 @@
1
+ export declare function addContactCommand(number: string): Promise<void>;
2
+ export declare function contactsCommand(): Promise<void>;
@@ -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
+ }
@@ -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 { printError, printSuccess } from '../lib/format.js';
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 setup` first.');
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, number, message);
13
- printSuccess(`Message sent to ${number}`);
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
- printError(`Send failed: ${err instanceof Error ? err.message : String(err)}`);
23
+ spinner.fail(`Send failed: ${err instanceof Error ? err.message : String(err)}`);
20
24
  process.exit(1);
21
25
  }
22
26
  }
@@ -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 { setupAccount } from '../lib/api.js';
5
- import { printCredentials, printError, printInfo } from '../lib/format.js';
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(' iMessage numbers for agents'));
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
- printInfo('Setup cancelled.');
27
+ console.log(chalk.dim(' Setup cancelled.'));
22
28
  return;
23
29
  }
24
30
  }
25
- // Collect info
26
- const response = await prompts([
27
- {
28
- type: 'text',
29
- name: 'email',
30
- message: 'Email',
31
- validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'Enter a valid email'
32
- },
33
- {
34
- type: 'text',
35
- name: 'companyName',
36
- message: 'Company name (optional)',
37
- initial: ''
38
- }
39
- ]);
40
- if (!response.email) {
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
- printInfo(' Setting up your account...');
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 setupAccount(response.email, response.companyName || undefined);
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(' Quick start:'));
60
- console.log(chalk.cyan(` sendblue send +15551234567 "Hello from 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
- printError(` Setup failed: ${err instanceof Error ? err.message : String(err)}`);
96
+ setupSpinner.fail(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
65
97
  process.exit(1);
66
98
  }
67
99
  }
@@ -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 setup` first.');
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
- printError(`Failed to fetch status: ${err instanceof Error ? err.message : String(err)}`);
25
+ spinner.fail(`Failed to fetch status: ${err instanceof Error ? err.message : String(err)}`);
23
26
  process.exit(1);
24
27
  }
25
28
  }
@@ -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 setup` first.');
9
+ printError('No credentials found. Run `sendblue login` first.');
9
10
  process.exit(1);
10
11
  }
11
- const valid = await testKeys(creds.apiKey, creds.apiSecret);
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('0.1.0');
16
+ .version(version);
12
17
  program
13
18
  .command('setup')
14
- .description('Create an account and get a phone number')
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 setupAccount(email: string, companyName?: string): Promise<SetupResponse>;
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 setupAccount(email, companyName) {
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, companyName })
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`, {
@@ -1,3 +1,4 @@
1
+ export declare function normalizeNumber(input: string): string;
1
2
  export declare function formatPhoneNumber(e164: string): string;
2
3
  export declare function printSuccess(message: string): void;
3
4
  export declare function printError(message: string): void;
@@ -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.1.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": {