@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,469 @@
1
+ // ABOUTME: CLI commands for Campaign.
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 createCampaignsCommand(
9
+ client: any,
10
+ formatter: any
11
+ ): Command {
12
+ const cmd = new Command('campaigns')
13
+ .description('Create and manage email campaigns. A campaign combines content (from a template or custom HTML), an audience (a list or segment), and a sender identity. Campaigns go through a lifecycle: incomplete → scheduled → delivering → delivered. They can also be active (ready to schedule), suspended, resumed, canceled, archived, or deleted. Send test emails before scheduling to preview the final result.');
14
+
15
+
16
+ cmd
17
+ .command('list')
18
+ .description('Show all campaigns')
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
+ .option('--filter <value>', 'Valid Terms:
24
+ - `status`
25
+ - `name`
26
+ - `type`
27
+ - `list_id`
28
+
29
+ Valid Operators:
30
+ - `==`
31
+
32
+ Query separator:
33
+ - `;`')
34
+ .option('--sort <value>', 'Sort term and direction, using syntax `[-|+]term`.
35
+
36
+ Valid terms:
37
+ - `name`
38
+ - `created_on`
39
+ - `scheduled_for`
40
+ - `scheduled_on`
41
+ - `updated_on`
42
+ - `type`')
43
+ .action(async (options) => {
44
+ const spinner = createSpinner('Fetching campaign...').start();
45
+ try {
46
+ const data = await client.listCampaigns({
47
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
48
+ page: options.page != null ? Number(options.page) : undefined,
49
+ perPage: options.perPage != null ? Number(options.perPage) : undefined,
50
+ withCount: options.withCount,
51
+ filter: options.filter,
52
+ sort: options.sort,
53
+ });
54
+
55
+ spinner.stop();
56
+ formatter.output(data);
57
+ } catch (error: any) {
58
+ spinner.stop();
59
+ formatter.error(error.message);
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+
65
+ cmd
66
+ .command('create')
67
+ .description('Create a campaign')
68
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
69
+ .requiredOption('--data <value>', 'Request body as JSON string')
70
+ .action(async (options) => {
71
+ const spinner = createSpinner('Creating campaign...').start();
72
+ try {
73
+ const body = JSON.parse(options.data);
74
+ const data = await client.createCampaign({
75
+ ...body,
76
+ });
77
+
78
+ spinner.stop();
79
+ formatter.success('Campaign created successfully');
80
+ formatter.output(data);
81
+ } catch (error: any) {
82
+ spinner.stop();
83
+ formatter.error(error.message);
84
+ process.exit(1);
85
+ }
86
+ });
87
+
88
+
89
+ cmd
90
+ .command('get <campaign-id>')
91
+ .description('Show a campaign details')
92
+ .option('--revision-id <value>', 'revision_id')
93
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
94
+ .action(async (campaign_id, options) => {
95
+ const spinner = createSpinner('Fetching campaign...').start();
96
+ try {
97
+ const data = await client.getCampaign({
98
+ campaign_id: Number(campaign_id),
99
+ revisionId: options.revisionId != null ? Number(options.revisionId) : undefined,
100
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
101
+ });
102
+
103
+ spinner.stop();
104
+ formatter.output(data);
105
+ } catch (error: any) {
106
+ spinner.stop();
107
+ formatter.error(error.message);
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+
113
+ cmd
114
+ .command('patch <campaign-id>')
115
+ .description('Update a campaign')
116
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
117
+ .requiredOption('--data <value>', 'Request body as JSON string')
118
+ .action(async (campaign_id, options) => {
119
+ const spinner = createSpinner('Updating campaign...').start();
120
+ try {
121
+ const body = JSON.parse(options.data);
122
+ const data = await client.patchCampaign({
123
+ campaign_id: Number(campaign_id),
124
+ ...body,
125
+ });
126
+
127
+ spinner.stop();
128
+ formatter.success('Campaign updated successfully');
129
+ formatter.output(data);
130
+ } catch (error: any) {
131
+ spinner.stop();
132
+ formatter.error(error.message);
133
+ process.exit(1);
134
+ }
135
+ });
136
+
137
+
138
+ cmd
139
+ .command('delete <campaign-id>')
140
+ .description('Delete a campaign')
141
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
142
+ .option('-f, --force', 'Skip confirmation prompt')
143
+ .action(async (campaign_id, options) => {
144
+ if (!options.force) {
145
+ const confirmed = await confirmDelete('campaign', campaign_id);
146
+ if (!confirmed) {
147
+ formatter.info('Deletion cancelled');
148
+ return;
149
+ }
150
+ }
151
+
152
+ const spinner = createSpinner('Deleting campaign...').start();
153
+ try {
154
+ const data = await client.deleteCampaign({
155
+ campaign_id: Number(campaign_id),
156
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
157
+ });
158
+
159
+ spinner.stop();
160
+ formatter.success('Campaign deleted successfully');
161
+ } catch (error: any) {
162
+ spinner.stop();
163
+ formatter.error(error.message);
164
+ process.exit(1);
165
+ }
166
+ });
167
+
168
+
169
+ cmd
170
+ .command('render <campaign-id>')
171
+ .description('Render a campaign')
172
+ .option('--contact-id <value>', 'contact_id')
173
+ .option('--account-id <value>', 'account_id')
174
+ .action(async (campaign_id, options) => {
175
+ const spinner = createSpinner('Fetching campaign...').start();
176
+ try {
177
+ const data = await client.renderCampaign({
178
+ campaign_id: Number(campaign_id),
179
+ contactId: options.contactId != null ? Number(options.contactId) : undefined,
180
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
181
+ });
182
+
183
+ spinner.stop();
184
+ formatter.output(data);
185
+ } catch (error: any) {
186
+ spinner.stop();
187
+ formatter.error(error.message);
188
+ process.exit(1);
189
+ }
190
+ });
191
+
192
+
193
+ cmd
194
+ .command('send-test-email <campaign-id>')
195
+ .description('Send a test email')
196
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
197
+ .requiredOption('--email <value>', 'email')
198
+ .option('--type <value>', 'type')
199
+ .action(async (campaign_id, options) => {
200
+ const spinner = createSpinner('Creating campaign...').start();
201
+ try {
202
+ const data = await client.sendTestEmail({
203
+ campaign_id: Number(campaign_id),
204
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
205
+ email: options.email,
206
+ type: options.type,
207
+ });
208
+
209
+ spinner.stop();
210
+ formatter.success('Campaign created successfully');
211
+ formatter.output(data);
212
+ } catch (error: any) {
213
+ spinner.stop();
214
+ formatter.error(error.message);
215
+ process.exit(1);
216
+ }
217
+ });
218
+
219
+
220
+ cmd
221
+ .command('schedule <campaign-id>')
222
+ .description('Schedule a campaign')
223
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
224
+ .option('--date <value>', 'A UNIX timestamp in the future')
225
+ .option('--html-empty <value>', 'Required to be true if the html message is empty')
226
+ .action(async (campaign_id, options) => {
227
+ const spinner = createSpinner('Creating campaign...').start();
228
+ try {
229
+ const data = await client.scheduleCampaign({
230
+ campaign_id: Number(campaign_id),
231
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
232
+ date: options.date != null ? Number(options.date) : undefined,
233
+ htmlEmpty: options.htmlEmpty,
234
+ });
235
+
236
+ spinner.stop();
237
+ formatter.success('Campaign created successfully');
238
+ formatter.output(data);
239
+ } catch (error: any) {
240
+ spinner.stop();
241
+ formatter.error(error.message);
242
+ process.exit(1);
243
+ }
244
+ });
245
+
246
+
247
+ cmd
248
+ .command('unschedule <campaign-id>')
249
+ .description('Unschedule a campaign')
250
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
251
+ .action(async (campaign_id, options) => {
252
+ const spinner = createSpinner('Creating campaign...').start();
253
+ try {
254
+ const data = await client.unscheduleCampaign({
255
+ campaign_id: Number(campaign_id),
256
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
257
+ });
258
+
259
+ spinner.stop();
260
+ formatter.success('Campaign created successfully');
261
+ formatter.output(data);
262
+ } catch (error: any) {
263
+ spinner.stop();
264
+ formatter.error(error.message);
265
+ process.exit(1);
266
+ }
267
+ });
268
+
269
+
270
+ cmd
271
+ .command('reschedule <campaign-id>')
272
+ .description('Reschedule a campaign')
273
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
274
+ .option('--date <value>', 'A UNIX timestamp in the future')
275
+ .option('--html-empty <value>', 'Required to be true if the html message is empty')
276
+ .action(async (campaign_id, options) => {
277
+ const spinner = createSpinner('Creating campaign...').start();
278
+ try {
279
+ const data = await client.rescheduleCampaign({
280
+ campaign_id: Number(campaign_id),
281
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
282
+ date: options.date != null ? Number(options.date) : undefined,
283
+ htmlEmpty: options.htmlEmpty,
284
+ });
285
+
286
+ spinner.stop();
287
+ formatter.success('Campaign created successfully');
288
+ formatter.output(data);
289
+ } catch (error: any) {
290
+ spinner.stop();
291
+ formatter.error(error.message);
292
+ process.exit(1);
293
+ }
294
+ });
295
+
296
+
297
+ cmd
298
+ .command('suspend <campaign-id>')
299
+ .description('Suspend a campaign')
300
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
301
+ .action(async (campaign_id, options) => {
302
+ const spinner = createSpinner('Creating campaign...').start();
303
+ try {
304
+ const data = await client.suspendCampaign({
305
+ campaign_id: Number(campaign_id),
306
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
307
+ });
308
+
309
+ spinner.stop();
310
+ formatter.success('Campaign created successfully');
311
+ formatter.output(data);
312
+ } catch (error: any) {
313
+ spinner.stop();
314
+ formatter.error(error.message);
315
+ process.exit(1);
316
+ }
317
+ });
318
+
319
+
320
+ cmd
321
+ .command('resume <campaign-id>')
322
+ .description('Resume a campaign')
323
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
324
+ .action(async (campaign_id, options) => {
325
+ const spinner = createSpinner('Creating campaign...').start();
326
+ try {
327
+ const data = await client.resumeCampaign({
328
+ campaign_id: Number(campaign_id),
329
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
330
+ });
331
+
332
+ spinner.stop();
333
+ formatter.success('Campaign created successfully');
334
+ formatter.output(data);
335
+ } catch (error: any) {
336
+ spinner.stop();
337
+ formatter.error(error.message);
338
+ process.exit(1);
339
+ }
340
+ });
341
+
342
+
343
+ cmd
344
+ .command('cancel <campaign-id>')
345
+ .description('Cancel a campaign')
346
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
347
+ .action(async (campaign_id, options) => {
348
+ const spinner = createSpinner('Creating campaign...').start();
349
+ try {
350
+ const data = await client.cancelCampaign({
351
+ campaign_id: Number(campaign_id),
352
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
353
+ });
354
+
355
+ spinner.stop();
356
+ formatter.success('Campaign created successfully');
357
+ formatter.output(data);
358
+ } catch (error: any) {
359
+ spinner.stop();
360
+ formatter.error(error.message);
361
+ process.exit(1);
362
+ }
363
+ });
364
+
365
+
366
+ cmd
367
+ .command('archive <campaign-id>')
368
+ .description('Archive a campaign')
369
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
370
+ .action(async (campaign_id, options) => {
371
+ const spinner = createSpinner('Creating campaign...').start();
372
+ try {
373
+ const data = await client.archiveCampaign({
374
+ campaign_id: Number(campaign_id),
375
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
376
+ });
377
+
378
+ spinner.stop();
379
+ formatter.success('Campaign created successfully');
380
+ formatter.output(data);
381
+ } catch (error: any) {
382
+ spinner.stop();
383
+ formatter.error(error.message);
384
+ process.exit(1);
385
+ }
386
+ });
387
+
388
+
389
+ cmd
390
+ .command('unarchive <campaign-id>')
391
+ .description('Unarchive a campaign')
392
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
393
+ .action(async (campaign_id, options) => {
394
+ const spinner = createSpinner('Creating campaign...').start();
395
+ try {
396
+ const data = await client.unarchiveCampaign({
397
+ campaign_id: Number(campaign_id),
398
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
399
+ });
400
+
401
+ spinner.stop();
402
+ formatter.success('Campaign created successfully');
403
+ formatter.output(data);
404
+ } catch (error: any) {
405
+ spinner.stop();
406
+ formatter.error(error.message);
407
+ process.exit(1);
408
+ }
409
+ });
410
+
411
+
412
+ cmd
413
+ .command('get-campaign-revisions <campaign-id>')
414
+ .description('Show all campaign revisions')
415
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
416
+ .option('--page <value>', 'page')
417
+ .option('--per-page <value>', 'per_page')
418
+ .option('--with-count <value>', 'Include count in the response')
419
+ .action(async (campaign_id, options) => {
420
+ const spinner = createSpinner('Fetching campaign...').start();
421
+ try {
422
+ const data = await client.getCampaignRevisions({
423
+ campaign_id: Number(campaign_id),
424
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
425
+ page: options.page != null ? Number(options.page) : undefined,
426
+ perPage: options.perPage != null ? Number(options.perPage) : undefined,
427
+ withCount: options.withCount,
428
+ });
429
+
430
+ spinner.stop();
431
+ formatter.output(data);
432
+ } catch (error: any) {
433
+ spinner.stop();
434
+ formatter.error(error.message);
435
+ process.exit(1);
436
+ }
437
+ });
438
+
439
+
440
+ cmd
441
+ .command('list-links <campaign-id>')
442
+ .description('Show a campaign links')
443
+ .option('--account-id <value>', 'Optional Account ID to be used for the request')
444
+ .option('--page <value>', 'page')
445
+ .option('--per-page <value>', 'per_page')
446
+ .option('--with-count <value>', 'Include count in the response')
447
+ .action(async (campaign_id, options) => {
448
+ const spinner = createSpinner('Fetching campaign...').start();
449
+ try {
450
+ const data = await client.listLinks({
451
+ campaign_id: Number(campaign_id),
452
+ accountId: options.accountId != null ? Number(options.accountId) : undefined,
453
+ page: options.page != null ? Number(options.page) : undefined,
454
+ perPage: options.perPage != null ? Number(options.perPage) : undefined,
455
+ withCount: options.withCount,
456
+ });
457
+
458
+ spinner.stop();
459
+ formatter.output(data);
460
+ } catch (error: any) {
461
+ spinner.stop();
462
+ formatter.error(error.message);
463
+ process.exit(1);
464
+ }
465
+ });
466
+
467
+
468
+ return cmd;
469
+ }
@@ -0,0 +1,77 @@
1
+ // ABOUTME: Config management commands for the CLI.
2
+ // ABOUTME: Show, set, and manage profile and default settings.
3
+
4
+ import { Command } from 'commander';
5
+ import { loadConfigFile, updateConfigFile, setCurrentProfile, getCurrentProfile, getDefaults, setDefault } from '../utils/config-file.js';
6
+ import { getProfileConfig, type ProfileType } from '../types/profile.js';
7
+
8
+ export function createConfigCommand(): Command {
9
+ const cmd = new Command('config')
10
+ .description('Manage CLI configuration');
11
+
12
+ cmd
13
+ .command('show')
14
+ .description('Show current configuration')
15
+ .action(() => {
16
+ const config = loadConfigFile();
17
+ if (!config) {
18
+ console.log('No configuration file found. Using defaults.');
19
+ console.log(`Profile: ${getCurrentProfile()}`);
20
+ return;
21
+ }
22
+ console.log(JSON.stringify(config, null, 2));
23
+ });
24
+
25
+ cmd
26
+ .command('set <key> <value>')
27
+ .description('Set a configuration value')
28
+ .action((key: string, value: string) => {
29
+ if (key === 'profile') {
30
+ const valid: ProfileType[] = ['developer', 'marketer', 'balanced'];
31
+ if (!valid.includes(value as ProfileType)) {
32
+ console.error(`Invalid profile: ${value}. Valid: ${valid.join(', ')}`);
33
+ process.exit(1);
34
+ }
35
+ setCurrentProfile(value as ProfileType);
36
+ console.log(`Profile set to: ${value}`);
37
+ } else if (key === 'default.list_id') {
38
+ setDefault('list_id', Number(value));
39
+ console.log(`Default list ID set to: ${value}`);
40
+ } else if (key === 'default.sender_id') {
41
+ setDefault('sender_id', Number(value));
42
+ console.log(`Default sender ID set to: ${value}`);
43
+ } else if (key === 'default.account_id') {
44
+ setDefault('account_id', Number(value));
45
+ console.log(`Default account ID set to: ${value}`);
46
+ } else {
47
+ console.error(`Unknown config key: ${key}`);
48
+ console.error('Valid keys: profile, default.list_id, default.sender_id, default.account_id');
49
+ process.exit(1);
50
+ }
51
+ });
52
+
53
+ cmd
54
+ .command('profile [type]')
55
+ .description('Show or set the active profile')
56
+ .action((type?: string) => {
57
+ if (!type) {
58
+ const current = getCurrentProfile();
59
+ const config = getProfileConfig(current);
60
+ console.log(`Active profile: ${current}`);
61
+ console.log(` Output format: ${config.output.format}`);
62
+ console.log(` Colors: ${config.output.colors}`);
63
+ console.log(` Interactive: ${config.behavior.interactive_prompts}`);
64
+ console.log(` Date format: ${config.display.date_format}`);
65
+ return;
66
+ }
67
+ const valid: ProfileType[] = ['developer', 'marketer', 'balanced'];
68
+ if (!valid.includes(type as ProfileType)) {
69
+ console.error(`Invalid profile: ${type}. Valid: ${valid.join(', ')}`);
70
+ process.exit(1);
71
+ }
72
+ setCurrentProfile(type as ProfileType);
73
+ console.log(`Profile set to: ${type}`);
74
+ });
75
+
76
+ return cmd;
77
+ }