@cakemail-org/cakemail-cli 1.7.0 → 2.0.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.
Files changed (198) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +40 -0
  3. package/.env.test.example +45 -0
  4. package/CHANGELOG.md +1031 -0
  5. package/README.md +41 -37
  6. package/audit-formats.js +128 -0
  7. package/cakemail.rb +20 -0
  8. package/dist/client.js +1 -1
  9. package/dist/client.js.map +1 -1
  10. package/dist/commands/account.js +1 -1
  11. package/dist/commands/account.js.map +1 -1
  12. package/dist/commands/attributes.js +1 -1
  13. package/dist/commands/attributes.js.map +1 -1
  14. package/dist/commands/campaigns.js +1 -1
  15. package/dist/commands/campaigns.js.map +1 -1
  16. package/dist/commands/contacts.js +1 -1
  17. package/dist/commands/contacts.js.map +1 -1
  18. package/dist/commands/emails.js +1 -1
  19. package/dist/commands/emails.js.map +1 -1
  20. package/dist/commands/interests.js +1 -1
  21. package/dist/commands/interests.js.map +1 -1
  22. package/dist/commands/lists.js +1 -1
  23. package/dist/commands/lists.js.map +1 -1
  24. package/dist/commands/logs.js +1 -1
  25. package/dist/commands/logs.js.map +1 -1
  26. package/dist/commands/reports.js +1 -1
  27. package/dist/commands/reports.js.map +1 -1
  28. package/dist/commands/segments.js +1 -1
  29. package/dist/commands/segments.js.map +1 -1
  30. package/dist/commands/senders.js +1 -1
  31. package/dist/commands/senders.js.map +1 -1
  32. package/dist/commands/suppressed.js +1 -1
  33. package/dist/commands/suppressed.js.map +1 -1
  34. package/dist/commands/tags.js +1 -1
  35. package/dist/commands/tags.js.map +1 -1
  36. package/dist/commands/templates.js +1 -1
  37. package/dist/commands/templates.js.map +1 -1
  38. package/dist/commands/transactional-templates.js +1 -1
  39. package/dist/commands/transactional-templates.js.map +1 -1
  40. package/dist/commands/webhooks.js +1 -1
  41. package/dist/commands/webhooks.js.map +1 -1
  42. package/dist/utils/config.js +2 -2
  43. package/dist/utils/config.js.map +1 -1
  44. package/dist/utils/errors.js +1 -1
  45. package/dist/utils/errors.js.map +1 -1
  46. package/dist/utils/progress.d.ts.map +1 -1
  47. package/dist/utils/progress.js +32 -4
  48. package/dist/utils/progress.js.map +1 -1
  49. package/dist/utils/spinner.d.ts +17 -0
  50. package/dist/utils/spinner.d.ts.map +1 -0
  51. package/dist/utils/spinner.js +43 -0
  52. package/dist/utils/spinner.js.map +1 -0
  53. package/docs/DOCUMENTATION-STANDARD.md +1068 -0
  54. package/docs/README.md +161 -0
  55. package/docs/developer/ARCHITECTURE.md +516 -0
  56. package/docs/developer/AUTH.md +204 -0
  57. package/docs/developer/CONTRIBUTING.md +227 -0
  58. package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
  59. package/docs/developer/PROJECT_INDEX.md +365 -0
  60. package/docs/planning/API_COVERAGE.md +1045 -0
  61. package/docs/planning/BACKLOG.md +1159 -0
  62. package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
  63. package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
  64. package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
  65. package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
  66. package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
  67. package/docs/planning/cakemail-profile-system-plan.md +1121 -0
  68. package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
  69. package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
  70. package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
  71. package/docs/user-manual/.obsidian/app.json +1 -0
  72. package/docs/user-manual/.obsidian/appearance.json +1 -0
  73. package/docs/user-manual/.obsidian/core-plugins.json +33 -0
  74. package/docs/user-manual/.obsidian/workspace.json +167 -0
  75. package/docs/user-manual/01-getting-started/01-installation.md +214 -0
  76. package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
  77. package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
  78. package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
  79. package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
  80. package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
  81. package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
  82. package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
  83. package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
  84. package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
  85. package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
  86. package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
  87. package/docs/user-manual/03-email-operations/01-senders.md +490 -0
  88. package/docs/user-manual/03-email-operations/02-templates.md +444 -0
  89. package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
  90. package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
  91. package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
  92. package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
  93. package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
  94. package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
  95. package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
  96. package/docs/user-manual/05-contact-management/01-lists.md +836 -0
  97. package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
  98. package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
  99. package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
  100. package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
  101. package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
  102. package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
  103. package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
  104. package/docs/user-manual/07-integrations/02-automation.md +326 -0
  105. package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
  106. package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
  107. package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
  108. package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
  109. package/docs/user-manual/09-command-reference/01-config.md +776 -0
  110. package/docs/user-manual/09-command-reference/02-account.md +652 -0
  111. package/docs/user-manual/09-command-reference/03-lists.md +958 -0
  112. package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
  113. package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
  114. package/docs/user-manual/09-command-reference/06-segments.md +894 -0
  115. package/docs/user-manual/09-command-reference/07-senders.md +803 -0
  116. package/docs/user-manual/09-command-reference/08-templates.md +818 -0
  117. package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
  118. package/docs/user-manual/09-command-reference/10-emails.md +807 -0
  119. package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
  120. package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
  121. package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
  122. package/docs/user-manual/09-command-reference/14-interests.md +630 -0
  123. package/docs/user-manual/09-command-reference/15-tags.md +584 -0
  124. package/docs/user-manual/09-command-reference/16-logs.md +656 -0
  125. package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
  126. package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
  127. package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
  128. package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
  129. package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
  130. package/docs/user-manual/11-appendix/04-faq.md +484 -0
  131. package/docs/user-manual/11-appendix/05-glossary.md +250 -0
  132. package/docs/user-manual/README.md +0 -0
  133. package/package.json +13 -61
  134. package/src/cli.ts +125 -0
  135. package/src/client.ts +16 -0
  136. package/src/commands/account.ts +267 -0
  137. package/src/commands/accounts.ts +78 -0
  138. package/src/commands/actions.ts +249 -0
  139. package/src/commands/attributes.ts +139 -0
  140. package/src/commands/campaign-blueprints.ts +106 -0
  141. package/src/commands/campaigns.ts +469 -0
  142. package/src/commands/config.ts +77 -0
  143. package/src/commands/contacts.ts +612 -0
  144. package/src/commands/custom-attributes.ts +127 -0
  145. package/src/commands/dkims.ts +117 -0
  146. package/src/commands/domains.ts +82 -0
  147. package/src/commands/email-apis.ts +569 -0
  148. package/src/commands/emails.ts +197 -0
  149. package/src/commands/forms.ts +283 -0
  150. package/src/commands/interests.ts +155 -0
  151. package/src/commands/links.ts +38 -0
  152. package/src/commands/lists.ts +406 -0
  153. package/src/commands/logos.ts +71 -0
  154. package/src/commands/logs.ts +386 -0
  155. package/src/commands/reports.ts +306 -0
  156. package/src/commands/segments.ts +158 -0
  157. package/src/commands/senders.ts +204 -0
  158. package/src/commands/sub-accounts.ts +271 -0
  159. package/src/commands/suppressed-emails.ts +234 -0
  160. package/src/commands/suppressed.ts +198 -0
  161. package/src/commands/system-emails.ts +85 -0
  162. package/src/commands/tags.ts +146 -0
  163. package/src/commands/tasks.ts +116 -0
  164. package/src/commands/templates.ts +189 -0
  165. package/src/commands/tokens.ts +83 -0
  166. package/src/commands/transactional-emails.ts +374 -0
  167. package/src/commands/transactional-templates.ts +385 -0
  168. package/src/commands/users.ts +506 -0
  169. package/src/commands/webhooks.ts +172 -0
  170. package/src/commands/workflow-blueprints.ts +123 -0
  171. package/src/commands/workflows.ts +265 -0
  172. package/src/types/profile.ts +93 -0
  173. package/src/utils/auth.ts +272 -0
  174. package/src/utils/config-file.ts +96 -0
  175. package/src/utils/config.ts +134 -0
  176. package/src/utils/confirm.ts +32 -0
  177. package/src/utils/defaults.ts +99 -0
  178. package/src/utils/errors.ts +116 -0
  179. package/src/utils/interactive.ts +91 -0
  180. package/src/utils/list-defaults.ts +74 -0
  181. package/src/utils/output.ts +190 -0
  182. package/src/utils/progress.ts +320 -0
  183. package/src/utils/spinner.ts +22 -0
  184. package/tests/IMPLEMENTATION_STATUS.md +258 -0
  185. package/tests/PTY_SETUP.md +118 -0
  186. package/tests/PTY_TESTING_GUIDE.md +507 -0
  187. package/tests/README.md +244 -0
  188. package/tests/fixtures/api-responses/campaigns.json +34 -0
  189. package/tests/fixtures/test-config.json +13 -0
  190. package/tests/helpers/cli-runner.ts +128 -0
  191. package/tests/helpers/mock-server.ts +301 -0
  192. package/tests/helpers/pty-runner.ts +181 -0
  193. package/tests/integration/campaigns-real-api.test.ts +196 -0
  194. package/tests/integration/setup-integration.ts +50 -0
  195. package/tests/pty/campaigns.test.ts +241 -0
  196. package/tests/setup.ts +34 -0
  197. package/tsconfig.json +15 -0
  198. package/vitest.config.ts +28 -0
@@ -0,0 +1,127 @@
1
+ // ABOUTME: CLI commands for Custom Attribute.
2
+ // ABOUTME: Generated by api-kit from the OpenAPI spec.
3
+
4
+ import { Command } from 'commander';
5
+ import { createSpinner } from '../utils/spinner.js';
6
+ import { confirmDelete } from '../utils/confirm.js';
7
+
8
+ export function createCustomAttributesCommand(
9
+ client: any,
10
+ formatter: any
11
+ ): Command {
12
+ const cmd = new Command('custom-attributes')
13
+ .description('Define custom data fields for contacts. Custom attributes extend the contact model with account-specific fields (e.g. company name, purchase date) that can be used in segmentation and merge tags.');
14
+
15
+
16
+ cmd
17
+ .command('list-custom-attributes <list-id>')
18
+ .description('Show all custom attributes')
19
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
20
+ .option('--page <value>', 'page')
21
+ .option('--per-page <value>', 'per_page')
22
+ .option('--with-count <value>', 'Include count in the response')
23
+ .action(async (list_id, options) => {
24
+ const spinner = createSpinner('Fetching custom attribute...').start();
25
+ try {
26
+ const data = await client.listCustomAttributes({
27
+ list_id: Number(list_id),
28
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
29
+ page: options.page != null ? Number(options.page) : undefined,
30
+ perPage: options.perPage != null ? Number(options.perPage) : undefined,
31
+ withCount: options.withCount,
32
+ });
33
+
34
+ spinner.stop();
35
+ formatter.output(data);
36
+ } catch (error: any) {
37
+ spinner.stop();
38
+ formatter.error(error.message);
39
+ process.exit(1);
40
+ }
41
+ });
42
+
43
+
44
+ cmd
45
+ .command('create-custom-attribute <list-id>')
46
+ .description('Create a custom attribute')
47
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
48
+ .requiredOption('--name <value>', 'Cannot be one of the reserved terms: id, email, status, registered, bounce_type, bounce_count, tags, _tags')
49
+ .requiredOption('--type <value>', 'type')
50
+ .action(async (list_id, options) => {
51
+ const spinner = createSpinner('Creating custom attribute...').start();
52
+ try {
53
+ const data = await client.createCustomAttribute({
54
+ list_id: Number(list_id),
55
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
56
+ name: options.name,
57
+ type: options.type,
58
+ });
59
+
60
+ spinner.stop();
61
+ formatter.success('Custom Attribute created successfully');
62
+ formatter.output(data);
63
+ } catch (error: any) {
64
+ spinner.stop();
65
+ formatter.error(error.message);
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+
71
+ cmd
72
+ .command('get-custom-attribute <list-id> <name>')
73
+ .description('Show a custom attribute')
74
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
75
+ .action(async (list_id, name, options) => {
76
+ const spinner = createSpinner('Fetching custom attribute...').start();
77
+ try {
78
+ const data = await client.getCustomAttribute({
79
+ list_id: Number(list_id),
80
+ name,
81
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
82
+ });
83
+
84
+ spinner.stop();
85
+ formatter.output(data);
86
+ } catch (error: any) {
87
+ spinner.stop();
88
+ formatter.error(error.message);
89
+ process.exit(1);
90
+ }
91
+ });
92
+
93
+
94
+ cmd
95
+ .command('delete-custom-attribute <list-id> <name>')
96
+ .description('Delete a custom attribute')
97
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
98
+ .option('-f, --force', 'Skip confirmation prompt')
99
+ .action(async (list_id, name, options) => {
100
+ if (!options.force) {
101
+ const confirmed = await confirmDelete('custom attribute', list_id);
102
+ if (!confirmed) {
103
+ formatter.info('Deletion cancelled');
104
+ return;
105
+ }
106
+ }
107
+
108
+ const spinner = createSpinner('Deleting custom attribute...').start();
109
+ try {
110
+ const data = await client.deleteCustomAttribute({
111
+ list_id: Number(list_id),
112
+ name,
113
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
114
+ });
115
+
116
+ spinner.stop();
117
+ formatter.success('Custom Attribute deleted successfully');
118
+ } catch (error: any) {
119
+ spinner.stop();
120
+ formatter.error(error.message);
121
+ process.exit(1);
122
+ }
123
+ });
124
+
125
+
126
+ return cmd;
127
+ }
@@ -0,0 +1,117 @@
1
+ // ABOUTME: CLI commands for DKIM.
2
+ // ABOUTME: Generated by api-kit from the OpenAPI spec.
3
+
4
+ import { Command } from 'commander';
5
+ import { createSpinner } from '../utils/spinner.js';
6
+ import { confirmDelete } from '../utils/confirm.js';
7
+
8
+ export function createDkimsCommand(
9
+ client: any,
10
+ formatter: any
11
+ ): Command {
12
+ const cmd = new Command('dkims')
13
+ .description('Manage DKIM (DomainKeys Identified Mail) keys for email authentication. DKIM signing improves deliverability and prevents email spoofing.');
14
+
15
+
16
+ cmd
17
+ .command('list-dkim-keys-brands-default-dkim-get')
18
+ .description('List Dkim Keys')
19
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
20
+ .action(async (options) => {
21
+ const spinner = createSpinner('Fetching dkim...').start();
22
+ try {
23
+ const data = await client.list_dkim_keys_brands_default_dkim_get({
24
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
25
+ });
26
+
27
+ spinner.stop();
28
+ formatter.output(data);
29
+ } catch (error: any) {
30
+ spinner.stop();
31
+ formatter.error(error.message);
32
+ process.exit(1);
33
+ }
34
+ });
35
+
36
+
37
+ cmd
38
+ .command('create-dkim-key-brands-default-dkim-post')
39
+ .description('Create Dkim Key')
40
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
41
+ .option('--selector <value>', 'selector')
42
+ .requiredOption('--domain <value>', 'domain')
43
+ .action(async (options) => {
44
+ const spinner = createSpinner('Creating dkim...').start();
45
+ try {
46
+ const data = await client.create_dkim_key_brands_default_dkim_post({
47
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
48
+ selector: options.selector,
49
+ domain: options.domain,
50
+ });
51
+
52
+ spinner.stop();
53
+ formatter.success('DKIM created successfully');
54
+ formatter.output(data);
55
+ } catch (error: any) {
56
+ spinner.stop();
57
+ formatter.error(error.message);
58
+ process.exit(1);
59
+ }
60
+ });
61
+
62
+
63
+ cmd
64
+ .command('get-dkim-key-brands-default-dkim-id-get <id>')
65
+ .description('Get Dkim Key')
66
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
67
+ .action(async (id, options) => {
68
+ const spinner = createSpinner('Fetching dkim...').start();
69
+ try {
70
+ const data = await client.get_dkim_key_brands_default_dkim__id__get({
71
+ id: Number(id),
72
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
73
+ });
74
+
75
+ spinner.stop();
76
+ formatter.output(data);
77
+ } catch (error: any) {
78
+ spinner.stop();
79
+ formatter.error(error.message);
80
+ process.exit(1);
81
+ }
82
+ });
83
+
84
+
85
+ cmd
86
+ .command('delete-dkim-key-brands-default-dkim-id-delete <id>')
87
+ .description('Delete Dkim Key')
88
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
89
+ .option('-f, --force', 'Skip confirmation prompt')
90
+ .action(async (id, options) => {
91
+ if (!options.force) {
92
+ const confirmed = await confirmDelete('dkim', id);
93
+ if (!confirmed) {
94
+ formatter.info('Deletion cancelled');
95
+ return;
96
+ }
97
+ }
98
+
99
+ const spinner = createSpinner('Deleting dkim...').start();
100
+ try {
101
+ const data = await client.delete_dkim_key_brands_default_dkim__id__delete({
102
+ id: Number(id),
103
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
104
+ });
105
+
106
+ spinner.stop();
107
+ formatter.success('DKIM deleted successfully');
108
+ } catch (error: any) {
109
+ spinner.stop();
110
+ formatter.error(error.message);
111
+ process.exit(1);
112
+ }
113
+ });
114
+
115
+
116
+ return cmd;
117
+ }
@@ -0,0 +1,82 @@
1
+ // ABOUTME: CLI commands for Domain.
2
+ // ABOUTME: Generated by api-kit from the OpenAPI spec.
3
+
4
+ import { Command } from 'commander';
5
+ import { createSpinner } from '../utils/spinner.js';
6
+
7
+ export function createDomainsCommand(
8
+ client: any,
9
+ formatter: any
10
+ ): Command {
11
+ const cmd = new Command('domains')
12
+ .description('Configure custom tracking and bounce domains. Custom domains replace default platform URLs in email links and bounce handling with your own branded domains.');
13
+
14
+
15
+ cmd
16
+ .command('show')
17
+ .description('Show domains')
18
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
19
+ .action(async (options) => {
20
+ const spinner = createSpinner('Fetching domain...').start();
21
+ try {
22
+ const data = await client.showDomains({
23
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
24
+ });
25
+
26
+ spinner.stop();
27
+ formatter.output(data);
28
+ } catch (error: any) {
29
+ spinner.stop();
30
+ formatter.error(error.message);
31
+ process.exit(1);
32
+ }
33
+ });
34
+
35
+
36
+ cmd
37
+ .command('patch')
38
+ .description('Change domains')
39
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
40
+ .requiredOption('--domains <value>', 'Custom domain configuration')
41
+ .action(async (options) => {
42
+ const spinner = createSpinner('Updating domain...').start();
43
+ try {
44
+ const data = await client.patchDomains({
45
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
46
+ domains: options.domains,
47
+ });
48
+
49
+ spinner.stop();
50
+ formatter.success('Domain updated successfully');
51
+ formatter.output(data);
52
+ } catch (error: any) {
53
+ spinner.stop();
54
+ formatter.error(error.message);
55
+ process.exit(1);
56
+ }
57
+ });
58
+
59
+
60
+ cmd
61
+ .command('validate')
62
+ .description('Validate domains')
63
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
64
+ .action(async (options) => {
65
+ const spinner = createSpinner('Fetching domain...').start();
66
+ try {
67
+ const data = await client.validateDomains({
68
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
69
+ });
70
+
71
+ spinner.stop();
72
+ formatter.output(data);
73
+ } catch (error: any) {
74
+ spinner.stop();
75
+ formatter.error(error.message);
76
+ process.exit(1);
77
+ }
78
+ });
79
+
80
+
81
+ return cmd;
82
+ }