@mailmodo/cli 0.0.54 → 0.0.55-beta.pr57.93

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 (136) hide show
  1. package/dist/commands/billing/index.d.ts +1 -11
  2. package/dist/commands/billing/index.js +28 -181
  3. package/dist/commands/contacts/index.d.ts +1 -19
  4. package/dist/commands/contacts/index.js +21 -114
  5. package/dist/commands/deploy/index.d.ts +1 -32
  6. package/dist/commands/deploy/index.js +52 -303
  7. package/dist/commands/deployments/index.d.ts +1 -4
  8. package/dist/commands/deployments/index.js +11 -52
  9. package/dist/commands/domain/index.d.ts +1 -14
  10. package/dist/commands/domain/index.js +19 -100
  11. package/dist/commands/edit/index.d.ts +2 -20
  12. package/dist/commands/edit/index.js +35 -244
  13. package/dist/commands/emails/index.d.ts +1 -2
  14. package/dist/commands/emails/index.js +26 -91
  15. package/dist/commands/init/index.d.ts +1 -2
  16. package/dist/commands/init/index.js +43 -179
  17. package/dist/commands/login/index.d.ts +2 -0
  18. package/dist/commands/login/index.js +35 -64
  19. package/dist/commands/logs/index.d.ts +1 -8
  20. package/dist/commands/logs/index.js +12 -55
  21. package/dist/commands/preview/index.d.ts +1 -19
  22. package/dist/commands/preview/index.js +40 -210
  23. package/dist/commands/sdk/index.d.ts +1 -3
  24. package/dist/commands/sdk/index.js +14 -46
  25. package/dist/commands/settings/index.d.ts +1 -22
  26. package/dist/commands/settings/index.js +35 -241
  27. package/dist/commands/status/index.d.ts +1 -0
  28. package/dist/commands/status/index.js +13 -39
  29. package/dist/lib/api-client.d.ts +5 -0
  30. package/dist/lib/api-client.js +45 -0
  31. package/dist/lib/base-command.d.ts +25 -1
  32. package/dist/lib/base-command.js +91 -5
  33. package/dist/lib/commands/billing/checkout-status.d.ts +3 -0
  34. package/dist/lib/commands/billing/checkout-status.js +63 -0
  35. package/dist/lib/commands/billing/format.d.ts +7 -0
  36. package/dist/lib/commands/billing/format.js +63 -0
  37. package/dist/lib/commands/billing/purchase-cap.d.ts +7 -0
  38. package/dist/lib/commands/billing/purchase-cap.js +57 -0
  39. package/dist/lib/commands/billing/types.d.ts +72 -0
  40. package/dist/lib/commands/billing/types.js +1 -0
  41. package/dist/lib/commands/contacts/actions.d.ts +3 -0
  42. package/dist/lib/commands/contacts/actions.js +49 -0
  43. package/dist/lib/commands/contacts/export-delete.d.ts +9 -0
  44. package/dist/lib/commands/contacts/export-delete.js +51 -0
  45. package/dist/lib/commands/contacts/types.d.ts +35 -0
  46. package/dist/lib/commands/contacts/types.js +1 -0
  47. package/dist/lib/commands/deploy/domain-setup.d.ts +8 -0
  48. package/dist/lib/commands/deploy/domain-setup.js +82 -0
  49. package/dist/lib/commands/deploy/output.d.ts +5 -0
  50. package/dist/lib/commands/deploy/output.js +61 -0
  51. package/dist/lib/commands/deploy/payload.d.ts +41 -0
  52. package/dist/lib/commands/deploy/payload.js +95 -0
  53. package/dist/lib/commands/deploy/sequence-status.d.ts +3 -0
  54. package/dist/lib/commands/deploy/sequence-status.js +56 -0
  55. package/dist/lib/commands/deploy/types.d.ts +88 -0
  56. package/dist/lib/commands/deploy/types.js +1 -0
  57. package/dist/lib/commands/deployments/output.d.ts +2 -0
  58. package/dist/lib/commands/deployments/output.js +68 -0
  59. package/dist/lib/commands/deployments/types.d.ts +24 -0
  60. package/dist/lib/commands/deployments/types.js +1 -0
  61. package/dist/lib/commands/domain/setup.d.ts +8 -0
  62. package/dist/lib/commands/domain/setup.js +53 -0
  63. package/dist/lib/commands/domain/types.d.ts +56 -0
  64. package/dist/lib/commands/domain/types.js +1 -0
  65. package/dist/lib/commands/domain/verify.d.ts +5 -0
  66. package/dist/lib/commands/domain/verify.js +50 -0
  67. package/dist/lib/commands/edit/diff.d.ts +7 -0
  68. package/dist/lib/commands/edit/diff.js +65 -0
  69. package/dist/lib/commands/edit/display.d.ts +5 -0
  70. package/dist/lib/commands/edit/display.js +53 -0
  71. package/dist/lib/commands/edit/flow.d.ts +8 -0
  72. package/dist/lib/commands/edit/flow.js +70 -0
  73. package/dist/lib/commands/edit/persist.d.ts +5 -0
  74. package/dist/lib/commands/edit/persist.js +65 -0
  75. package/dist/lib/commands/edit/types.d.ts +37 -0
  76. package/dist/lib/commands/edit/types.js +1 -0
  77. package/dist/lib/commands/emails/editor.d.ts +2 -0
  78. package/dist/lib/commands/emails/editor.js +43 -0
  79. package/dist/lib/commands/emails/output.d.ts +4 -0
  80. package/dist/lib/commands/emails/output.js +36 -0
  81. package/dist/lib/commands/emails/types.d.ts +3 -0
  82. package/dist/lib/commands/emails/types.js +1 -0
  83. package/dist/lib/commands/init/analysis.d.ts +3 -0
  84. package/dist/lib/commands/init/analysis.js +69 -0
  85. package/dist/lib/commands/init/output.d.ts +12 -0
  86. package/dist/lib/commands/init/output.js +39 -0
  87. package/dist/lib/commands/init/payload.d.ts +8 -0
  88. package/dist/lib/commands/init/payload.js +78 -0
  89. package/dist/lib/commands/init/types.d.ts +57 -0
  90. package/dist/lib/commands/init/types.js +1 -0
  91. package/dist/lib/commands/login/output.d.ts +8 -0
  92. package/dist/lib/commands/login/output.js +53 -0
  93. package/dist/lib/commands/login/types.d.ts +19 -0
  94. package/dist/lib/commands/login/types.js +1 -0
  95. package/dist/lib/commands/logs/output.d.ts +2 -0
  96. package/dist/lib/commands/logs/output.js +52 -0
  97. package/dist/lib/commands/logs/types.d.ts +23 -0
  98. package/dist/lib/commands/logs/types.js +1 -0
  99. package/dist/lib/commands/preview/actions.d.ts +11 -0
  100. package/dist/lib/commands/preview/actions.js +43 -0
  101. package/dist/lib/commands/preview/render.d.ts +3 -0
  102. package/dist/lib/commands/preview/render.js +30 -0
  103. package/dist/lib/commands/preview/server.d.ts +8 -0
  104. package/dist/lib/commands/preview/server.js +63 -0
  105. package/dist/lib/commands/preview/types.d.ts +19 -0
  106. package/dist/lib/commands/preview/types.js +1 -0
  107. package/dist/lib/commands/preview/wrapper-html.d.ts +2 -0
  108. package/dist/lib/commands/preview/wrapper-html.js +35 -0
  109. package/dist/lib/commands/sdk/output.d.ts +2 -0
  110. package/dist/lib/commands/sdk/output.js +42 -0
  111. package/dist/lib/commands/sdk/types.d.ts +21 -0
  112. package/dist/lib/commands/sdk/types.js +1 -0
  113. package/dist/lib/commands/settings/actions.d.ts +10 -0
  114. package/dist/lib/commands/settings/actions.js +56 -0
  115. package/dist/lib/commands/settings/display.d.ts +15 -0
  116. package/dist/lib/commands/settings/display.js +69 -0
  117. package/dist/lib/commands/settings/logo-domain.d.ts +3 -0
  118. package/dist/lib/commands/settings/logo-domain.js +47 -0
  119. package/dist/lib/commands/settings/prompt.d.ts +2 -0
  120. package/dist/lib/commands/settings/prompt.js +82 -0
  121. package/dist/lib/commands/settings/types.d.ts +65 -0
  122. package/dist/lib/commands/settings/types.js +1 -0
  123. package/dist/lib/commands/status/output.d.ts +2 -0
  124. package/dist/lib/commands/status/output.js +49 -0
  125. package/dist/lib/commands/status/types.d.ts +28 -0
  126. package/dist/lib/commands/status/types.js +1 -0
  127. package/dist/lib/constants.d.ts +1 -0
  128. package/dist/lib/constants.js +1 -0
  129. package/dist/lib/messages.d.ts +22 -0
  130. package/dist/lib/messages.js +22 -0
  131. package/dist/lib/templates/missing-templates.d.ts +5 -0
  132. package/dist/lib/templates/missing-templates.js +61 -0
  133. package/dist/lib/templates/types.d.ts +13 -0
  134. package/dist/lib/templates/types.js +1 -0
  135. package/oclif.manifest.json +40 -40
  136. package/package.json +1 -1
@@ -12,15 +12,5 @@ export default class Billing extends BaseCommand {
12
12
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
13
  };
14
14
  run(): Promise<void>;
15
- private startCheckout;
16
- private showStatus;
17
- private formatAutoCharge;
18
- private formatCap;
19
- private formatCurrency;
20
- private formatPaymentMethod;
21
- private formatUsageBlock;
22
- private persistMonthlyCap;
23
- private pluralize;
24
- private purchaseBlocks;
25
- private setCap;
15
+ private makeCtx;
26
16
  }
@@ -1,10 +1,8 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
- import open from 'open';
4
- import { BaseCommand, FREE_TIER } from '../../lib/base-command.js';
5
- import { API_ENDPOINTS } from '../../lib/constants.js';
6
- import { INFO } from '../../lib/messages.js';
7
- import { loadYaml, saveYaml } from '../../lib/yaml-config.js';
3
+ import { BaseCommand } from '../../lib/base-command.js';
4
+ import { showStatus, startCheckout, } from '../../lib/commands/billing/checkout-status.js';
5
+ import { purchaseBlocks, setCap, } from '../../lib/commands/billing/purchase-cap.js';
8
6
  export default class Billing extends BaseCommand {
9
7
  static description = 'View billing status, purchase blocks, set cap, or add a payment method';
10
8
  static examples = [
@@ -20,16 +18,12 @@ export default class Billing extends BaseCommand {
20
18
  'auto-charge-block-count': Flags.integer({
21
19
  description: 'Blocks to auto-purchase when the quota runs low (use with --cap)',
22
20
  }),
23
- cap: Flags.integer({
24
- description: 'Set monthly sending cap in blocks',
25
- }),
21
+ cap: Flags.integer({ description: 'Set monthly sending cap in blocks' }),
26
22
  checkout: Flags.boolean({
27
23
  default: false,
28
24
  description: 'Open Stripe checkout to add or update a payment method',
29
25
  }),
30
- purchase: Flags.integer({
31
- description: 'Manually purchase email blocks',
32
- }),
26
+ purchase: Flags.integer({ description: 'Manually purchase email blocks' }),
33
27
  status: Flags.boolean({
34
28
  default: false,
35
29
  description: 'Show billing status only',
@@ -38,189 +32,42 @@ export default class Billing extends BaseCommand {
38
32
  async run() {
39
33
  const { flags } = await this.parse(Billing);
40
34
  await this.ensureAuth();
35
+ const ctx = this.makeCtx();
41
36
  const autoChargeBlockCount = flags['auto-charge-block-count'];
42
37
  if (autoChargeBlockCount !== undefined && flags.cap === undefined) {
43
38
  this.error(`Use ${chalk.cyan('--auto-charge-block-count')} together with ${chalk.cyan('--cap')}.`);
44
39
  }
45
40
  if (flags.checkout) {
46
- await this.startCheckout(flags.json);
41
+ await startCheckout(ctx, flags.json);
47
42
  return;
48
43
  }
49
44
  if (flags.purchase !== undefined) {
50
- await this.purchaseBlocks(flags.purchase, flags.json);
45
+ await purchaseBlocks(ctx, flags.purchase, flags.json);
51
46
  return;
52
47
  }
53
48
  if (flags.cap !== undefined) {
54
- await this.setCap(flags.cap, autoChargeBlockCount, flags.json);
55
- return;
56
- }
57
- await this.showStatus(flags.json, flags.status);
58
- }
59
- async startCheckout(jsonOutput) {
60
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Creating checkout session...' }, () => this.apiClient.post(API_ENDPOINTS.BILLING_CHECKOUT));
61
- if (!response.ok) {
62
- this.handleApiError(response);
63
- }
64
- const { checkoutUrl } = response.data;
65
- if (jsonOutput) {
66
- this.log(JSON.stringify({ checkoutUrl }, null, 2));
67
- return;
68
- }
69
- this.log(`\n ${chalk.bold('Stripe Checkout')} — add or update your payment method.`);
70
- this.log(` ${chalk.dim(checkoutUrl)}\n`);
71
- try {
72
- await open(checkoutUrl);
73
- this.log(` ${INFO.BROWSER_OPENING}\n`);
74
- }
75
- catch {
76
- this.log(` ${INFO.BROWSER_OPEN_FAILED}\n`);
77
- }
78
- }
79
- async showStatus(jsonOutput, statusOnly) {
80
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Loading billing status...' }, () => this.apiClient.get(API_ENDPOINTS.BILLING_STATUS));
81
- if (!response.ok) {
82
- this.handleApiError(response);
83
- }
84
- const { data } = response;
85
- if (jsonOutput) {
86
- this.log(JSON.stringify(data, null, 2));
49
+ await setCap(ctx, {
50
+ autoChargeBlockCount,
51
+ cap: flags.cap,
52
+ json: flags.json,
53
+ });
87
54
  return;
88
55
  }
89
- this.log('');
90
- this.log(` Tier: ${data.tier}`);
91
- this.log(` Payment: ${this.formatPaymentMethod(data)}`);
92
- this.log(` Auto-charge: ${this.formatAutoCharge(data)}`);
93
- if (data.tier !== 'free') {
94
- this.log(` Monthly cap: ${this.formatCap(data.cap)}`);
95
- }
96
- this.log(` Total spent: ${this.formatCurrency(data.totalSpent, data.spentCurrency)}`);
97
- if (data.activeBlocks.length === 0) {
98
- this.log(' Active blocks: none');
99
- }
100
- else {
101
- this.log(' Active blocks:');
102
- for (const block of data.activeBlocks) {
103
- this.log(` - ${this.formatUsageBlock(block)}`);
104
- }
105
- }
106
- this.log('');
107
- if (!data.hasPaymentMethod && !statusOnly) {
108
- await this.startCheckout(jsonOutput);
109
- }
56
+ await showStatus(ctx, flags.json, flags.status);
110
57
  }
111
- formatAutoCharge(data) {
112
- if (!data.autoChargeEnabled) {
113
- return 'disabled';
114
- }
115
- if (typeof data.autoChargeBlockCount !== 'number') {
116
- return 'enabled';
117
- }
118
- return `enabled (${data.autoChargeBlockCount} ${this.pluralize('block', data.autoChargeBlockCount)})`;
119
- }
120
- formatCap(cap) {
121
- const hasBlocks = typeof cap.inBlocks === 'number';
122
- const hasEmails = typeof cap.inEmails === 'number';
123
- if (hasBlocks && hasEmails) {
124
- return `${cap.inBlocks} ${this.pluralize('block', cap.inBlocks)} (${cap.inEmails.toLocaleString()} ${this.pluralize('email', cap.inEmails)})`;
125
- }
126
- if (hasBlocks) {
127
- return `${cap.inBlocks} ${this.pluralize('block', cap.inBlocks)}`;
128
- }
129
- if (hasEmails) {
130
- return `${cap.inEmails.toLocaleString()} ${this.pluralize('email', cap.inEmails)}`;
131
- }
132
- return 'not set';
133
- }
134
- formatCurrency(amount, currency) {
135
- const numericAmount = typeof amount === 'number' ? amount : Number.parseFloat(amount);
136
- const normalizedCurrency = currency.toUpperCase();
137
- if (Number.isFinite(numericAmount)) {
138
- try {
139
- return new Intl.NumberFormat('en-US', {
140
- currency: normalizedCurrency,
141
- style: 'currency',
142
- }).format(numericAmount);
143
- }
144
- catch {
145
- return `${numericAmount.toFixed(2)} ${normalizedCurrency}`;
146
- }
147
- }
148
- return `${String(amount)} ${normalizedCurrency}`;
149
- }
150
- formatPaymentMethod(data) {
151
- if (!data.hasPaymentMethod) {
152
- return 'No payment method on file';
153
- }
154
- const primaryMethod = data.paymentMethod[0];
155
- if (!primaryMethod) {
156
- return 'Payment method on file';
157
- }
158
- const brand = primaryMethod.brand || 'Card';
159
- return primaryMethod.last4
160
- ? `${brand} ending ${primaryMethod.last4}`
161
- : `${brand} on file`;
162
- }
163
- formatUsageBlock(block) {
164
- const allowance = block.blockAllowance ?? Math.max(block.blocksCount * block.blockSize, 0);
165
- const used = Math.max(block.emailsSent ?? 0, 0);
166
- const activationSuffix = block.activatedAt
167
- ? `, activated at ${new Date(block.activatedAt).toLocaleDateString('en-US')}`
168
- : '';
169
- return `${block.type} block (${used + allowance}): ${used} ${this.pluralize('email', used)} sent, ${allowance} ${this.pluralize('email', allowance)} remaining${activationSuffix}`;
170
- }
171
- async persistMonthlyCap(capBlocks) {
172
- const yamlConfig = await loadYaml();
173
- if (!yamlConfig)
174
- return;
175
- if (yamlConfig.project.monthlyCap === capBlocks)
176
- return;
177
- yamlConfig.project.monthlyCap = capBlocks;
178
- await saveYaml(yamlConfig);
179
- }
180
- pluralize(word, count) {
181
- return count === 1 ? word : `${word}s`;
182
- }
183
- async purchaseBlocks(blocksCount, jsonOutput) {
184
- if (blocksCount < 1) {
185
- this.error('Purchase block count must be at least 1 block.');
186
- }
187
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Initiating block purchase...' }, () => this.apiClient.post(API_ENDPOINTS.BILLING_PURCHASE, {
188
- blocksCount,
189
- }));
190
- if (!response.ok) {
191
- this.handleApiError(response);
192
- }
193
- if (jsonOutput) {
194
- this.log(JSON.stringify(response.data, null, 2));
195
- return;
196
- }
197
- this.log(`\n ${chalk.green('✓')} ${response.data.message}`);
198
- this.log(` Blocks: ${blocksCount} ${this.pluralize('block', blocksCount)}`);
199
- this.log(` Payment intent:${` ${chalk.dim(response.data.paymentIntentId)}`}`);
200
- this.log('');
201
- }
202
- async setCap(cap, autoChargeBlockCount, jsonOutput) {
203
- this.validateBillingCapInputs({ autoChargeBlockCount, cap });
204
- const tier = await this.fetchBillingTier();
205
- if (tier === FREE_TIER) {
206
- this.warnFreeTierCapBlocked(jsonOutput);
207
- return;
208
- }
209
- const data = await this.applyBillingCap({
210
- autoChargeBlockCount,
211
- cap,
212
- json: jsonOutput,
213
- });
214
- await this.persistMonthlyCap(data.capBlocks);
215
- if (jsonOutput) {
216
- this.log(JSON.stringify(data, null, 2));
217
- return;
218
- }
219
- this.log(`\n ${chalk.green('✓')} ${data.message}`);
220
- this.log(` Monthly cap: ${chalk.bold(String(data.capBlocks))} ${this.pluralize('block', data.capBlocks)} (${data.capEmails.toLocaleString()} emails)`);
221
- if (data.autoChargeBlockCount !== undefined) {
222
- this.log(` Auto-charge: ${data.autoChargeBlockCount} ${this.pluralize('block', data.autoChargeBlockCount)}`);
223
- }
224
- this.log('');
58
+ makeCtx() {
59
+ return {
60
+ applyBillingCap: (opts) => this.applyBillingCap(opts),
61
+ error: (msg) => this.error(msg),
62
+ fetchBillingTier: () => this.fetchBillingTier(),
63
+ get: (path, params) => this.apiClient.get(path, params),
64
+ log: (msg) => this.log(msg),
65
+ onApiError: (r) => this.handleApiError(r),
66
+ post: (path, body) => this.apiClient.post(path, body),
67
+ spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
68
+ syncYaml: () => this.syncYamlToServer(),
69
+ validateBillingCapInputs: (opts) => this.validateBillingCapInputs(opts),
70
+ warnFreeTierCapBlocked: (json) => this.warnFreeTierCapBlocked(json),
71
+ };
225
72
  }
226
73
  }
@@ -10,23 +10,5 @@ export default class Contacts extends BaseCommand {
10
10
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
12
  run(): Promise<void>;
13
- /**
14
- * Displays aggregate contact counts: total, active, unsubscribed, bounced.
15
- */
16
- private showSummary;
17
- /**
18
- * Looks up a single contact by email address and displays their
19
- * status, properties, and pending emails.
20
- */
21
- private searchContact;
22
- /**
23
- * Triggers a CSV export of all contacts. The API returns a download URL
24
- * for the generated file.
25
- */
26
- private exportContacts;
27
- /**
28
- * Performs a GDPR-compliant hard delete of a contact and all their
29
- * send history. Requires confirmation unless --yes flag is used.
30
- */
31
- private deleteContact;
13
+ private makeCtx;
32
14
  }
@@ -1,10 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { confirm } from '@inquirer/prompts';
3
- import chalk from 'chalk';
4
- import { writeFile } from 'node:fs/promises';
5
- import { resolve } from 'node:path';
6
2
  import { BaseCommand } from '../../lib/base-command.js';
7
- import { API_ENDPOINTS } from '../../lib/constants.js';
3
+ import { deleteContact, exportContacts, } from '../../lib/commands/contacts/export-delete.js';
4
+ import { searchContact, showSummary, } from '../../lib/commands/contacts/actions.js';
8
5
  export default class Contacts extends BaseCommand {
9
6
  static description = 'Manage contacts — search, export, or delete';
10
7
  static examples = [
@@ -27,124 +24,34 @@ export default class Contacts extends BaseCommand {
27
24
  async run() {
28
25
  const { flags } = await this.parse(Contacts);
29
26
  await this.ensureAuth();
27
+ const ctx = this.makeCtx();
30
28
  if (flags.search) {
31
- await this.searchContact(flags.search, flags.json);
29
+ await searchContact(ctx, flags.search, flags.json);
32
30
  return;
33
31
  }
34
32
  if (flags.export) {
35
- await this.exportContacts(flags.json);
33
+ await exportContacts(ctx, { json: flags.json });
36
34
  return;
37
35
  }
38
36
  if (flags.delete) {
39
- await this.deleteContact(flags.delete, flags.json, flags.yes);
40
- return;
41
- }
42
- await this.showSummary(flags.json);
43
- }
44
- /**
45
- * Displays aggregate contact counts: total, active, unsubscribed, bounced.
46
- */
47
- async showSummary(jsonOutput) {
48
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Loading contacts...' }, () => this.apiClient.get(API_ENDPOINTS.CONTACTS));
49
- if (!response.ok) {
50
- this.handleApiError(response);
51
- }
52
- const { data } = response;
53
- if (jsonOutput) {
54
- this.log(JSON.stringify(data, null, 2));
55
- return;
56
- }
57
- this.log(`\n Total: ${chalk.bold(String(data.total ?? 0))} ` +
58
- `Active: ${chalk.green(String(data.active ?? 0))} ` +
59
- `Unsubscribed: ${chalk.yellow(String(data.unsubscribed ?? 0))} ` +
60
- `Bounced: ${chalk.red(String(data.bounced ?? 0))}\n`);
61
- }
62
- /**
63
- * Looks up a single contact by email address and displays their
64
- * status, properties, and pending emails.
65
- */
66
- async searchContact(email, jsonOutput) {
67
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Looking up contact...' }, () => this.apiClient.get(`${API_ENDPOINTS.CONTACTS}/${encodeURIComponent(email)}`));
68
- if (!response.ok) {
69
- if (response.status === 404) {
70
- if (jsonOutput) {
71
- this.log(JSON.stringify({ email, error: 'Contact not found' }, null, 2));
72
- }
73
- else {
74
- this.log(`\n Contact '${email}' not found.\n`);
75
- }
76
- return;
77
- }
78
- this.handleApiError(response);
79
- }
80
- const contact = response.data;
81
- if (jsonOutput) {
82
- this.log(JSON.stringify(contact, null, 2));
83
- return;
84
- }
85
- this.log(`\n ${chalk.bold('Email:')} ${contact.email}`);
86
- this.log(` ${chalk.bold('Status:')} ${contact.status}`);
87
- this.log(` ${chalk.bold('Added:')} ${contact.added}`);
88
- this.log(` ${chalk.bold('Next email:')} ${contact.nextEmail || 'None'}`);
89
- if (contact.properties && Object.keys(contact.properties).length > 0) {
90
- this.log(` ${chalk.bold('Properties:')}`);
91
- for (const [key, value] of Object.entries(contact.properties)) {
92
- this.log(` ${key}: ${String(value)}`);
93
- }
94
- }
95
- this.log('');
96
- }
97
- /**
98
- * Triggers a CSV export of all contacts. The API returns a download URL
99
- * for the generated file.
100
- */
101
- async exportContacts(jsonOutput) {
102
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Preparing contact export...' }, () => this.apiClient.get(API_ENDPOINTS.CONTACTS_EXPORT));
103
- if (!response.ok) {
104
- this.handleApiError(response);
105
- }
106
- if (jsonOutput) {
107
- this.log(JSON.stringify(response.data, null, 2));
108
- return;
109
- }
110
- this.log(`\n ${chalk.green('✓')} Contact export started.`);
111
- const { downloadUrl, status } = response.data;
112
- if (!downloadUrl) {
113
- this.log(`\n Export status: ${status ?? 'unknown'}. No download URL yet.\n`);
114
- return;
115
- }
116
- const fileResult = await this.withApiSpinner({ json: jsonOutput, text: ' Downloading CSV file...' }, () => this.apiClient.getPublicFile(downloadUrl.trim()));
117
- if (!fileResult.ok) {
118
- this.error(`Download failed: ${fileResult.status} ${fileResult.error ?? ''}\n` +
119
- ` URL: ${fileResult.debug.fullUrl}`);
120
- }
121
- const outputPath = resolve('contacts.csv');
122
- await writeFile(outputPath, Buffer.from(fileResult.body));
123
- this.log(`\n ${chalk.green('✓')} Contact export saved to ${chalk.cyan(outputPath)}\n`);
124
- }
125
- /**
126
- * Performs a GDPR-compliant hard delete of a contact and all their
127
- * send history. Requires confirmation unless --yes flag is used.
128
- */
129
- async deleteContact(email, jsonOutput, skipConfirm) {
130
- if (!skipConfirm) {
131
- const confirmed = await confirm({
132
- default: false,
133
- message: `Permanently delete ${email} and all their data? This cannot be undone.`,
37
+ await deleteContact(ctx, {
38
+ email: flags.delete,
39
+ json: flags.json,
40
+ skipConfirm: flags.yes,
134
41
  });
135
- if (!confirmed) {
136
- this.log('\n Delete cancelled.\n');
137
- return;
138
- }
139
- }
140
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Deleting contact...' }, () => this.apiClient.delete(`${API_ENDPOINTS.CONTACTS}/${encodeURIComponent(email)}`));
141
- if (!response.ok) {
142
- this.handleApiError(response);
143
- }
144
- if (jsonOutput) {
145
- this.log(JSON.stringify({ deleted: true, email }, null, 2));
146
42
  return;
147
43
  }
148
- this.log(`\n ${chalk.green('✓')} Contact ${email} permanently deleted.\n`);
44
+ await showSummary(ctx, flags.json);
45
+ }
46
+ makeCtx() {
47
+ return {
48
+ delete: (path) => this.apiClient.delete(path),
49
+ error: (msg) => this.error(msg),
50
+ get: (path, params) => this.apiClient.get(path, params),
51
+ getPublicFile: (url) => this.apiClient.getPublicFile(url),
52
+ log: (msg) => this.log(msg),
53
+ onApiError: (r) => this.handleApiError(r),
54
+ spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
55
+ };
149
56
  }
150
57
  }
@@ -8,37 +8,6 @@ export default class Deploy extends BaseCommand {
8
8
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  };
11
- private fetchDomainVerifyForDeploy;
12
11
  run(): Promise<void>;
13
- private validateSequence;
14
- /**
15
- * Calls `POST /sequences/{id}/status` with `{ status: "paused" }` and prints
16
- * a confirmation-aware success/no-op message. Skips the prompt entirely when
17
- * `--yes` is set so the command stays scriptable. `--json` always emits the
18
- * raw server payload (sequenceId, status, alreadyInStatus).
19
- */
20
- private pauseSequence;
21
- /**
22
- * Calls `POST /sequences/{id}/status` with `{ status: "active" }`. No prompt
23
- * — resuming is the safe direction (it does not start sends that weren't
24
- * already queued). `--json` always emits the raw server payload (sequenceId,
25
- * status, alreadyInStatus).
26
- */
27
- private resumeSequence;
28
- private updateSequenceStatus;
29
- private sequenceStatusPath;
30
- private buildDeployPayload;
31
- private resolveMonthlyCapForDeploy;
32
- private mapEmailToPayload;
33
- private buildBrandSection;
34
- private buildProductSection;
35
- private buildSenderSection;
36
- private buildProjectPayload;
37
- private confirmDeploy;
38
- private ensureDomainReady;
39
- private logPreDeploySummary;
40
- private logDiff;
41
- private logDeploySuccessInstructions;
42
- private runDomainSetup;
43
- private verifyDomain;
12
+ private makeCtx;
44
13
  }