@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.
@@ -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 { 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
+ }
@@ -1 +1 @@
1
- export declare function setupCommand(): Promise<void>;
1
+ export declare function loginCommand(): Promise<void>;
@@ -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 { setupAccount } from '../lib/api.js';
4
+ import { sendCode, verifyCode } from '../lib/api.js';
5
5
  import { printCredentials, printError, printInfo } from '../lib/format.js';
6
- export async function setupCommand() {
6
+ export async function loginCommand() {
7
7
  console.log();
8
- console.log(chalk.bold(' sendblue setup'));
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('Setup cancelled.');
21
+ printInfo('Login cancelled.');
22
22
  return;
23
23
  }
24
24
  }
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.');
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 setupAccount(response.email, response.companyName || undefined);
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 { setupCommand } from './commands/setup.js';
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('setup')
14
- .description('Create an account and get a phone number')
15
- .action(setupCommand);
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 setupAccount(email: string, companyName?: string): Promise<SetupResponse>;
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 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
8
  });
9
9
  if (!res.ok) {
10
10
  const body = await res.json().catch(() => ({}));
11
- throw new Error(body.error || body.message || `Setup failed (${res.status})`);
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`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendblue/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Sendblue CLI — iMessage numbers for agents",
5
5
  "type": "module",
6
6
  "bin": {