@sesender/cli 1.0.0 → 1.0.3

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/bin/ses-cli.mjs CHANGED
@@ -3,11 +3,18 @@
3
3
  import { configSet, configShow, configClear } from "../src/commands/config.mjs";
4
4
  import { smsSend, smsBulk, smsHistory } from "../src/commands/sms.mjs";
5
5
  import { emailSend, emailBulk, emailHistory } from "../src/commands/email.mjs";
6
- import { contactsList, contactsImport, contactsGroups } from "../src/commands/contacts.mjs";
7
- import { campaignsList, analyticsOverview } from "../src/commands/campaigns.mjs";
8
- import { validatePhone, validateEmail } from "../src/commands/validate.mjs";
6
+ import { contactsList, contactsImport, contactsGroups, contactsExport, contactsDelete } from "../src/commands/contacts.mjs";
7
+ import { campaignsList, campaignsStats, campaignsPause, campaignsResume, campaignsCancel, analyticsOverview } from "../src/commands/campaigns.mjs";
8
+ import { validatePhone, validateEmail, validateBulk } from "../src/commands/validate.mjs";
9
+ import { providersList } from "../src/commands/providers.mjs";
10
+ import { webhooksList, webhooksCreate, webhooksDelete, webhooksTest } from "../src/commands/webhooks.mjs";
11
+ import { seedCheck, seedBalance } from "../src/commands/seedcheck.mjs";
12
+ import { templatesList, templatesGet } from "../src/commands/templates.mjs";
13
+ import { accountInfo, accountRateLimit } from "../src/commands/account.mjs";
14
+ import { trackingDomainsList, trackingStats } from "../src/commands/tracking.mjs";
15
+ import { scheduleList, scheduleCancel } from "../src/commands/schedule.mjs";
9
16
 
10
- const VERSION = "1.0.0";
17
+ const VERSION = "1.0.1";
11
18
 
12
19
  function parseArgs(argv) {
13
20
  const args = { _positional: [] };
@@ -33,55 +40,114 @@ function parseArgs(argv) {
33
40
  function showHelp() {
34
41
  console.log(`
35
42
  SESender CLI v${VERSION}
36
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
43
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
37
44
 
38
45
  USAGE
39
46
  ses-cli <command> <subcommand> [options]
40
47
 
41
- COMMANDS
42
- config set Configure API key and base URL
43
- config show Show current configuration
44
- config clear Remove saved configuration
48
+ CONFIGURATION
49
+ config set Configure API key and base URL
50
+ config show Show current configuration
51
+ config clear Remove saved configuration
52
+
53
+ MESSAGING
54
+ sms send Send a single SMS
55
+ sms bulk Send bulk SMS from CSV file
56
+ sms history View SMS message history
45
57
 
46
- sms send Send a single SMS
47
- sms bulk Send bulk SMS from CSV file
48
- sms history View SMS message history
58
+ email send Send a single email
59
+ email bulk Send bulk emails from CSV file
60
+ email history View email message history
49
61
 
50
- email send Send a single email
51
- email bulk Send bulk emails from CSV file
52
- email history View email message history
62
+ CAMPAIGNS
63
+ campaigns list List campaigns with stats
64
+ campaigns stats View detailed campaign statistics
65
+ campaigns pause Pause a running campaign
66
+ campaigns resume Resume a paused campaign
67
+ campaigns cancel Cancel a campaign permanently
53
68
 
54
- contacts list List contacts
69
+ CONTACTS
70
+ contacts list List contacts (with search/filter)
55
71
  contacts import Import contacts from CSV
56
72
  contacts groups List contact groups
73
+ contacts export Export contacts to CSV
74
+ contacts delete Delete a contact
57
75
 
58
- campaigns list List campaigns with stats
59
- analytics View analytics overview
76
+ PROVIDERS
77
+ providers list List available SMS/email providers
78
+
79
+ TEMPLATES
80
+ templates list List saved templates
81
+ templates get View a specific template
82
+
83
+ WEBHOOKS
84
+ webhooks list List configured webhooks
85
+ webhooks create Create a new webhook
86
+ webhooks delete Delete a webhook
87
+ webhooks test Send a test event to a webhook
60
88
 
89
+ TRACKING
90
+ tracking list List tracking domains
91
+ tracking stats View tracking/click statistics
92
+
93
+ SCHEDULING
94
+ schedule list List scheduled messages
95
+ schedule cancel Cancel a scheduled message
96
+
97
+ VALIDATION
61
98
  validate phone Validate a phone number
62
99
  validate email Validate an email address
100
+ validate bulk Bulk validate from file
101
+
102
+ SEED CHECK
103
+ seed-check Check emails against seed database
104
+ seed-balance Check seed check credit balance
105
+
106
+ ANALYTICS
107
+ analytics View analytics overview
108
+
109
+ ACCOUNT
110
+ account info View account information
111
+ account rate View API rate limit status
63
112
 
64
113
  GLOBAL OPTIONS
65
- --json Output in JSON format (machine-readable)
66
- --help Show this help message
67
- --version Show version number
114
+ --json Output in JSON format (machine-readable)
115
+ --help Show this help message
116
+ --version Show version number
117
+
118
+ FLAGS (for sms/email send)
119
+ --provider Choose specific provider (e.g., ses, mailgun, twilio)
120
+ --route Routing mode (e.g., priority, round-robin)
121
+ --schedule Schedule send time (ISO 8601 format)
122
+ --timezone Timezone for scheduling (e.g., America/New_York)
123
+ --track-clicks Enable click tracking
124
+ --track-opens Enable open tracking
68
125
 
69
126
  EXAMPLES
70
127
  ses-cli config set --api-key sk_live_abc123 --base-url https://sesender.com
71
- ses-cli sms send --to +1234567890 --body "Hello from CLI!"
72
- ses-cli email send --to user@example.com --subject "Test" --body "Hello"
73
- ses-cli sms bulk --file numbers.csv --body "Promo: 20% off!"
128
+ ses-cli sms send --to +1234567890 --body "Hello!" --provider twilio
129
+ ses-cli email send --to user@example.com --subject "Test" --html "<h1>Hi</h1>"
130
+ ses-cli sms bulk --file numbers.csv --body "Promo: 20% off!" --provider ses
131
+ ses-cli campaigns list --status sending --type email
132
+ ses-cli campaigns stats --id c-abc123
133
+ ses-cli campaigns pause --id c-abc123
74
134
  ses-cli contacts import --file contacts.csv --group "Newsletter"
135
+ ses-cli contacts list --search "gmail" --limit 50
136
+ ses-cli webhooks create --url https://myapp.com/hook --events "email.delivered,sms.sent"
75
137
  ses-cli validate phone +1234567890
76
- ses-cli analytics --json
138
+ ses-cli validate bulk --file emails.csv --type email
139
+ ses-cli seed-check --emails "user@test.com" --words "trap,seed"
140
+ ses-cli analytics --period 30d --json
141
+ ses-cli schedule list --status pending
77
142
 
78
143
  ENVIRONMENT VARIABLES
79
- SES_API_KEY API key (overrides config file)
80
- SES_BASE_URL Base URL (overrides config file)
144
+ SES_API_KEY API key (overrides config file)
145
+ SES_BASE_URL Base URL (overrides config file)
81
146
 
82
147
  CONFIGURATION
83
148
  Config file: ~/.sesenderrc
84
149
  Create API keys at: Settings > API Keys in your SESender dashboard
150
+ Docs: https://sesender.com/api-docs
85
151
  `);
86
152
  }
87
153
 
@@ -144,8 +210,10 @@ async function main() {
144
210
  case "list": await contactsList(args, jsonMode); break;
145
211
  case "import": await contactsImport(args, jsonMode); break;
146
212
  case "groups": await contactsGroups(args, jsonMode); break;
213
+ case "export": await contactsExport(args, jsonMode); break;
214
+ case "delete": await contactsDelete(args, jsonMode); break;
147
215
  default:
148
- console.error("Usage: ses-cli contacts <list|import|groups>");
216
+ console.error("Usage: ses-cli contacts <list|import|groups|export|delete>");
149
217
  process.exit(1);
150
218
  }
151
219
  break;
@@ -153,8 +221,12 @@ async function main() {
153
221
  case "campaigns":
154
222
  switch (subcommand) {
155
223
  case "list": await campaignsList(args, jsonMode); break;
224
+ case "stats": await campaignsStats(args, jsonMode); break;
225
+ case "pause": await campaignsPause(args, jsonMode); break;
226
+ case "resume": await campaignsResume(args, jsonMode); break;
227
+ case "cancel": await campaignsCancel(args, jsonMode); break;
156
228
  default:
157
- console.error("Usage: ses-cli campaigns <list>");
229
+ console.error("Usage: ses-cli campaigns <list|stats|pause|resume|cancel>");
158
230
  process.exit(1);
159
231
  }
160
232
  break;
@@ -163,12 +235,82 @@ async function main() {
163
235
  await analyticsOverview(args, jsonMode);
164
236
  break;
165
237
 
238
+ case "providers":
239
+ switch (subcommand) {
240
+ case "list": await providersList(args, jsonMode); break;
241
+ default:
242
+ console.error("Usage: ses-cli providers <list>");
243
+ process.exit(1);
244
+ }
245
+ break;
246
+
247
+ case "templates":
248
+ switch (subcommand) {
249
+ case "list": await templatesList(args, jsonMode); break;
250
+ case "get": await templatesGet(args, jsonMode); break;
251
+ default:
252
+ console.error("Usage: ses-cli templates <list|get>");
253
+ process.exit(1);
254
+ }
255
+ break;
256
+
257
+ case "webhooks":
258
+ switch (subcommand) {
259
+ case "list": await webhooksList(args, jsonMode); break;
260
+ case "create": await webhooksCreate(args, jsonMode); break;
261
+ case "delete": await webhooksDelete(args, jsonMode); break;
262
+ case "test": await webhooksTest(args, jsonMode); break;
263
+ default:
264
+ console.error("Usage: ses-cli webhooks <list|create|delete|test>");
265
+ process.exit(1);
266
+ }
267
+ break;
268
+
269
+ case "tracking":
270
+ switch (subcommand) {
271
+ case "list": await trackingDomainsList(args, jsonMode); break;
272
+ case "stats": await trackingStats(args, jsonMode); break;
273
+ default:
274
+ console.error("Usage: ses-cli tracking <list|stats>");
275
+ process.exit(1);
276
+ }
277
+ break;
278
+
279
+ case "schedule":
280
+ switch (subcommand) {
281
+ case "list": await scheduleList(args, jsonMode); break;
282
+ case "cancel": await scheduleCancel(args, jsonMode); break;
283
+ default:
284
+ console.error("Usage: ses-cli schedule <list|cancel>");
285
+ process.exit(1);
286
+ }
287
+ break;
288
+
166
289
  case "validate":
167
290
  switch (subcommand) {
168
291
  case "phone": await validatePhone(args, jsonMode); break;
169
292
  case "email": await validateEmail(args, jsonMode); break;
293
+ case "bulk": await validateBulk(args, jsonMode); break;
294
+ default:
295
+ console.error("Usage: ses-cli validate <phone|email|bulk>");
296
+ process.exit(1);
297
+ }
298
+ break;
299
+
300
+ case "seed-check":
301
+ await seedCheck(args, jsonMode);
302
+ break;
303
+
304
+ case "seed-balance":
305
+ await seedBalance(args, jsonMode);
306
+ break;
307
+
308
+ case "account":
309
+ switch (subcommand) {
310
+ case "info": await accountInfo(args, jsonMode); break;
311
+ case "rate": await accountRateLimit(args, jsonMode); break;
170
312
  default:
171
- console.error("Usage: ses-cli validate <phone|email>");
313
+ console.error("Usage: ses-cli account <info|rate>");
172
314
  process.exit(1);
173
315
  }
174
316
  break;
@@ -180,7 +322,11 @@ async function main() {
180
322
  }
181
323
  } catch (error) {
182
324
  if (error.message !== "process.exit") {
183
- console.error(`Unexpected error: ${error.message}`);
325
+ if (jsonMode) {
326
+ console.log(JSON.stringify({ error: error.message }, null, 2));
327
+ } else {
328
+ console.error(`Error: ${error.message}`);
329
+ }
184
330
  process.exit(1);
185
331
  }
186
332
  }
package/package.json CHANGED
@@ -1,25 +1,35 @@
1
1
  {
2
2
  "name": "@sesender/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "SESender CLI - Send SMS/emails, manage contacts, and view analytics from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
7
- "ses-cli": "./bin/ses-cli.mjs"
7
+ "ses-cli": "bin/ses-cli.mjs"
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
12
  "README.md"
13
13
  ],
14
- "keywords": ["sesender", "sms", "email", "marketing", "cli", "bulk-sms", "bulk-email"],
14
+ "keywords": [
15
+ "sesender",
16
+ "sms",
17
+ "email",
18
+ "marketing",
19
+ "cli",
20
+ "bulk-sms",
21
+ "bulk-email"
22
+ ],
15
23
  "author": "SESender Team",
16
24
  "license": "MIT",
17
25
  "repository": {
18
26
  "type": "git",
19
- "url": "https://github.com/sesender/cli"
27
+ "url": "https://github.com/ilan7251/ses-messaging-platform.git",
28
+ "directory": "cli"
20
29
  },
21
30
  "engines": {
22
31
  "node": ">=18.0.0"
23
32
  },
33
+ "homepage": "https://sesender.com/api-docs",
24
34
  "dependencies": {}
25
35
  }
@@ -0,0 +1,48 @@
1
+ import { apiRequest } from "../api.mjs";
2
+ import { outputResult, success } from "../output.mjs";
3
+
4
+ export async function accountInfo(args, jsonMode) {
5
+ const result = await apiRequest("GET", "/account");
6
+
7
+ if (jsonMode) {
8
+ outputResult(result, true);
9
+ } else {
10
+ const data = result.data || result;
11
+ console.log(`\n Account Information`);
12
+ console.log(` ─────────────────────────`);
13
+ console.log(` Name: ${data.name || "N/A"}`);
14
+ console.log(` Email: ${data.email || "N/A"}`);
15
+ console.log(` User ID: ${data.id || "N/A"}`);
16
+ if (data.apiKey) {
17
+ console.log(`\n API Key Details`);
18
+ console.log(` ─────────────────────────`);
19
+ console.log(` Plan: ${data.apiKey.plan || "N/A"}`);
20
+ console.log(` Rate Limit: ${data.apiKey.rateLimit || "N/A"} req/hour`);
21
+ console.log(` Used: ${data.apiKey.requestsThisHour || 0} this hour`);
22
+ console.log(` Permissions: ${(data.apiKey.permissions || []).join(", ")}`);
23
+ }
24
+ console.log("");
25
+ }
26
+ }
27
+
28
+ export async function accountRateLimit(args, jsonMode) {
29
+ const result = await apiRequest("GET", "/rate-limit");
30
+
31
+ if (jsonMode) {
32
+ outputResult(result, true);
33
+ } else {
34
+ const data = result.data || result;
35
+ const pct = data.limit > 0 ? Math.round((data.used / data.limit) * 100) : 0;
36
+ const bar = "█".repeat(Math.round(pct / 5)) + "░".repeat(20 - Math.round(pct / 5));
37
+
38
+ console.log(`\n Rate Limit Status`);
39
+ console.log(` ─────────────────────────`);
40
+ console.log(` Plan: ${data.plan || "N/A"}`);
41
+ console.log(` Limit: ${data.limit} requests/hour`);
42
+ console.log(` Used: ${data.used}`);
43
+ console.log(` Remaining: ${data.remaining}`);
44
+ console.log(` Usage: [${bar}] ${pct}%`);
45
+ if (data.resetAt) console.log(` Resets at: ${new Date(data.resetAt).toLocaleString()}`);
46
+ console.log("");
47
+ }
48
+ }
@@ -1,69 +1,185 @@
1
1
  import { apiRequest } from "../api.mjs";
2
- import { formatTable, outputResult, success } from "../output.mjs";
2
+ import { formatTable, outputResult, success, info } from "../output.mjs";
3
3
 
4
4
  export async function campaignsList(args, jsonMode) {
5
5
  const limit = args["--limit"] || "20";
6
- const result = await apiRequest("GET", "/analytics/campaigns", null, { limit });
6
+ const page = args["--page"] || "1";
7
+ const status = args["--status"];
8
+ const type = args["--type"];
9
+
10
+ const params = { limit, page };
11
+ if (status) params.status = status;
12
+ if (type) params.type = type;
13
+
14
+ const result = await apiRequest("GET", "/analytics/campaigns", null, params);
7
15
 
8
16
  if (jsonMode) {
9
17
  outputResult(result, true);
10
18
  } else {
11
- const campaigns = result.campaigns || result.data || [];
19
+ const campaigns = result.data?.campaigns || result.campaigns || result.data || [];
12
20
  if (campaigns.length === 0) {
13
21
  console.log("No campaigns found.");
14
22
  return;
15
23
  }
16
24
  formatTable(
17
- ["ID", "Name", "Type", "Status", "Sent", "Opens", "Clicks"],
25
+ ["ID", "Name", "Type", "Status", "Sent", "Opens", "Clicks", "Date"],
18
26
  campaigns.map((c) => [
19
27
  c.id || c.campaignId,
20
- (c.name || c.campaignName || "").slice(0, 25),
28
+ (c.name || c.campaignName || c.subject || "").slice(0, 20),
21
29
  c.type || "email",
22
30
  c.status || "-",
23
31
  c.sent || c.totalSent || 0,
24
32
  c.opens || c.totalOpens || 0,
25
33
  c.clicks || c.totalClicks || 0,
34
+ c.createdAt ? new Date(c.createdAt).toLocaleDateString() : "-",
26
35
  ])
27
36
  );
28
- console.log(`\nShowing ${campaigns.length} campaigns`);
37
+ console.log(`\nShowing ${campaigns.length} campaigns (page ${page})`);
38
+ }
39
+ }
40
+
41
+ export async function campaignsStats(args, jsonMode) {
42
+ const id = args["--id"];
43
+
44
+ if (!id) {
45
+ console.error(`
46
+ Usage: ses-cli campaigns stats --id CAMPAIGN_ID
47
+
48
+ Shows detailed statistics for a specific campaign including:
49
+ - Delivery rates, open rates, click rates
50
+ - Bounce and complaint rates
51
+ - Per-link click breakdown
52
+
53
+ Examples:
54
+ ses-cli campaigns stats --id c-abc123
55
+ ses-cli campaigns stats --id mv-campaign-456 --json
56
+ `);
57
+ process.exit(1);
58
+ }
59
+
60
+ const result = await apiRequest("GET", `/analytics/campaigns/${id}`);
61
+
62
+ if (jsonMode) {
63
+ outputResult(result, true);
64
+ } else {
65
+ const data = result.data || result;
66
+ console.log(`\n Campaign Statistics: ${data.name || id}`);
67
+ console.log(` ─────────────────────────────────`);
68
+ console.log(` Status: ${data.status || "N/A"}`);
69
+ console.log(` Total Sent: ${data.totalSent || data.sent || 0}`);
70
+ console.log(` Delivered: ${data.delivered || 0} (${data.deliveryRate || "N/A"}%)`);
71
+ console.log(` Opens: ${data.opens || data.totalOpens || 0} (${data.openRate || "N/A"}%)`);
72
+ console.log(` Clicks: ${data.clicks || data.totalClicks || 0} (${data.clickRate || "N/A"}%)`);
73
+ console.log(` Bounces: ${data.bounces || 0} (${data.bounceRate || "N/A"}%)`);
74
+ console.log(` Complaints: ${data.complaints || 0}`);
75
+ console.log(` Unsubs: ${data.unsubscribes || 0}`);
76
+ console.log("");
77
+ }
78
+ }
79
+
80
+ export async function campaignsPause(args, jsonMode) {
81
+ const id = args["--id"];
82
+
83
+ if (!id) {
84
+ console.error(`
85
+ Usage: ses-cli campaigns pause --id CAMPAIGN_ID
86
+
87
+ Pauses a running campaign. Messages already in queue will still be sent.
88
+ Resume with: ses-cli campaigns resume --id CAMPAIGN_ID
89
+ `);
90
+ process.exit(1);
91
+ }
92
+
93
+ const result = await apiRequest("POST", `/campaigns/${id}/pause`);
94
+
95
+ if (jsonMode) {
96
+ outputResult(result, true);
97
+ } else {
98
+ success(`Campaign ${id} paused`);
99
+ }
100
+ }
101
+
102
+ export async function campaignsResume(args, jsonMode) {
103
+ const id = args["--id"];
104
+
105
+ if (!id) {
106
+ console.error("Usage: ses-cli campaigns resume --id CAMPAIGN_ID");
107
+ process.exit(1);
108
+ }
109
+
110
+ const result = await apiRequest("POST", `/campaigns/${id}/resume`);
111
+
112
+ if (jsonMode) {
113
+ outputResult(result, true);
114
+ } else {
115
+ success(`Campaign ${id} resumed`);
116
+ }
117
+ }
118
+
119
+ export async function campaignsCancel(args, jsonMode) {
120
+ const id = args["--id"];
121
+
122
+ if (!id) {
123
+ console.error(`
124
+ Usage: ses-cli campaigns cancel --id CAMPAIGN_ID
125
+
126
+ Permanently cancels a campaign. This cannot be undone.
127
+ Unsent messages will not be delivered.
128
+ `);
129
+ process.exit(1);
130
+ }
131
+
132
+ const result = await apiRequest("POST", `/campaigns/${id}/cancel`);
133
+
134
+ if (jsonMode) {
135
+ outputResult(result, true);
136
+ } else {
137
+ success(`Campaign ${id} cancelled`);
29
138
  }
30
139
  }
31
140
 
32
141
  export async function analyticsOverview(args, jsonMode) {
33
- const result = await apiRequest("GET", "/analytics/overview");
142
+ const period = args["--period"] || "7d";
143
+
144
+ const result = await apiRequest("GET", "/analytics/overview", null, { period });
34
145
 
35
146
  if (jsonMode) {
36
147
  outputResult(result, true);
37
148
  } else {
38
- console.log("═══════════════════════════════════════");
39
- console.log(" SESender Analytics Overview");
40
- console.log("═══════════════════════════════════════");
149
+ const data = result.data || result;
150
+ console.log("");
151
+ console.log(` ═══════════════════════════════════════`);
152
+ console.log(` SESender Analytics Overview (${period})`);
153
+ console.log(` ═══════════════════════════════════════`);
41
154
  console.log("");
42
155
 
43
- if (result.sms) {
156
+ if (data.sms || data.smsSent !== undefined) {
44
157
  console.log(" SMS:");
45
- console.log(` Total Sent: ${result.sms.totalSent || 0}`);
46
- console.log(` Delivered: ${result.sms.delivered || 0}`);
47
- console.log(` Failed: ${result.sms.failed || 0}`);
158
+ console.log(` Total Sent: ${data.sms?.totalSent || data.smsSent || 0}`);
159
+ console.log(` Delivered: ${data.sms?.delivered || 0}`);
160
+ console.log(` Failed: ${data.sms?.failed || 0}`);
48
161
  console.log("");
49
162
  }
50
163
 
51
- if (result.email) {
164
+ if (data.email || data.emailsSent !== undefined) {
52
165
  console.log(" Email:");
53
- console.log(` Total Sent: ${result.email.totalSent || 0}`);
54
- console.log(` Opens: ${result.email.totalOpens || 0}`);
55
- console.log(` Clicks: ${result.email.totalClicks || 0}`);
56
- console.log(` Bounces: ${result.email.bounces || 0}`);
166
+ console.log(` Total Sent: ${data.email?.totalSent || data.emailsSent || 0}`);
167
+ console.log(` Opens: ${data.email?.totalOpens || data.opens || 0}`);
168
+ console.log(` Clicks: ${data.email?.totalClicks || data.clicks || 0}`);
169
+ console.log(` Bounces: ${data.email?.bounces || data.bounces || 0}`);
170
+ console.log(` Open Rate: ${data.email?.openRate || data.openRate || "N/A"}%`);
171
+ console.log(` Click Rate: ${data.email?.clickRate || data.clickRate || "N/A"}%`);
57
172
  console.log("");
58
173
  }
59
174
 
60
- if (result.contacts) {
175
+ if (data.contacts) {
61
176
  console.log(" Contacts:");
62
- console.log(` Total: ${result.contacts.total || 0}`);
63
- console.log(` Groups: ${result.contacts.groups || 0}`);
177
+ console.log(` Total: ${data.contacts.total || 0}`);
178
+ console.log(` Groups: ${data.contacts.groups || 0}`);
64
179
  console.log("");
65
180
  }
66
181
 
67
- console.log("═══════════════════════════════════════");
182
+ console.log(` ═══════════════════════════════════════`);
183
+ console.log("");
68
184
  }
69
185
  }