@mailmodo/cli 0.0.26-beta.pr28.45 → 0.0.26
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/dist/commands/billing/index.d.ts +12 -11
- package/dist/commands/billing/index.js +40 -150
- package/dist/commands/contacts/index.js +5 -6
- package/dist/commands/domain/index.d.ts +0 -1
- package/dist/commands/domain/index.js +29 -32
- package/dist/lib/api-client.d.ts +0 -4
- package/dist/lib/api-client.js +1 -7
- package/dist/lib/constants.d.ts +0 -2
- package/dist/lib/constants.js +0 -2
- package/dist/lib/fetch-file.d.ts +0 -4
- package/dist/lib/fetch-file.js +25 -46
- package/oclif.manifest.json +15 -38
- package/package.json +1 -1
|
@@ -3,23 +3,24 @@ export default class Billing extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
-
'auto-charge-block-count': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
6
|
cap: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
checkout: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
-
purchase: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
7
|
status: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
8
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
9
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
10
|
};
|
|
14
11
|
run(): Promise<void>;
|
|
15
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves and displays the current billing status including card info,
|
|
14
|
+
* block usage, spending total, AI generation limits, and optionally
|
|
15
|
+
* opens Stripe Checkout if no card is on file.
|
|
16
|
+
*/
|
|
16
17
|
private showStatus;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Updates the monthly block spending cap. This limits how many 10k-email
|
|
20
|
+
* blocks will be auto-charged per billing cycle.
|
|
21
|
+
*
|
|
22
|
+
* @param {number} cap - The maximum number of blocks per month.
|
|
23
|
+
* @param {boolean} jsonOutput - Whether to output JSON instead of formatted text.
|
|
24
|
+
*/
|
|
24
25
|
private setCap;
|
|
25
26
|
}
|
|
@@ -4,29 +4,16 @@ import open from 'open';
|
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
5
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
6
6
|
export default class Billing extends BaseCommand {
|
|
7
|
-
static description = 'View billing status,
|
|
7
|
+
static description = 'View billing status, manage payment, and set spending cap';
|
|
8
8
|
static examples = [
|
|
9
9
|
'<%= config.bin %> billing',
|
|
10
10
|
'<%= config.bin %> billing --status',
|
|
11
11
|
'<%= config.bin %> billing --cap 5',
|
|
12
|
-
'<%= config.bin %> billing --cap 5 --auto-charge-block-count 2',
|
|
13
|
-
'<%= config.bin %> billing --purchase 3',
|
|
14
|
-
'<%= config.bin %> billing --checkout',
|
|
15
12
|
];
|
|
16
13
|
static flags = {
|
|
17
14
|
...BaseCommand.baseFlags,
|
|
18
|
-
'auto-charge-block-count': Flags.integer({
|
|
19
|
-
description: 'Blocks to auto-purchase when the quota runs low (use with --cap)',
|
|
20
|
-
}),
|
|
21
15
|
cap: Flags.integer({
|
|
22
|
-
description: 'Set monthly
|
|
23
|
-
}),
|
|
24
|
-
checkout: Flags.boolean({
|
|
25
|
-
default: false,
|
|
26
|
-
description: 'Open Stripe checkout to add or update a payment method',
|
|
27
|
-
}),
|
|
28
|
-
purchase: Flags.integer({
|
|
29
|
-
description: 'Manually purchase this many 10,000-email blocks',
|
|
16
|
+
description: 'Set monthly block cap (max blocks to auto-charge)',
|
|
30
17
|
}),
|
|
31
18
|
status: Flags.boolean({
|
|
32
19
|
default: false,
|
|
@@ -36,44 +23,17 @@ export default class Billing extends BaseCommand {
|
|
|
36
23
|
async run() {
|
|
37
24
|
const { flags } = await this.parse(Billing);
|
|
38
25
|
await this.ensureAuth();
|
|
39
|
-
const autoChargeBlockCount = flags['auto-charge-block-count'];
|
|
40
|
-
if (autoChargeBlockCount !== undefined && flags.cap === undefined) {
|
|
41
|
-
this.error(`Use ${chalk.cyan('--auto-charge-block-count')} together with ${chalk.cyan('--cap')}.`);
|
|
42
|
-
}
|
|
43
|
-
if (flags.checkout) {
|
|
44
|
-
await this.startCheckout(flags.json);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (flags.purchase !== undefined) {
|
|
48
|
-
await this.purchaseBlocks(flags.purchase, flags.json);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
26
|
if (flags.cap !== undefined) {
|
|
52
|
-
await this.setCap(flags.cap,
|
|
27
|
+
await this.setCap(flags.cap, flags.json);
|
|
53
28
|
return;
|
|
54
29
|
}
|
|
55
30
|
await this.showStatus(flags.json);
|
|
56
31
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const { checkoutUrl } = response.data;
|
|
63
|
-
if (jsonOutput) {
|
|
64
|
-
this.log(JSON.stringify({ checkoutUrl }, null, 2));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
this.log(`\n ${chalk.bold('Stripe Checkout')} — add or update your payment method.`);
|
|
68
|
-
this.log(` ${chalk.dim(checkoutUrl)}\n`);
|
|
69
|
-
try {
|
|
70
|
-
await open(checkoutUrl);
|
|
71
|
-
this.log(` Opening in browser...\n`);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
this.log(` ${chalk.dim('Could not open browser. Visit the URL above manually.')}\n`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves and displays the current billing status including card info,
|
|
34
|
+
* block usage, spending total, AI generation limits, and optionally
|
|
35
|
+
* opens Stripe Checkout if no card is on file.
|
|
36
|
+
*/
|
|
77
37
|
async showStatus(jsonOutput) {
|
|
78
38
|
const response = await this.withApiSpinner({ json: jsonOutput, text: ' Loading billing status...' }, () => this.apiClient.get(API_ENDPOINTS.BILLING_STATUS));
|
|
79
39
|
if (!response.ok) {
|
|
@@ -85,125 +45,55 @@ export default class Billing extends BaseCommand {
|
|
|
85
45
|
return;
|
|
86
46
|
}
|
|
87
47
|
this.log('');
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.log(` Auto-charge: ${this.formatAutoCharge(data)}`);
|
|
91
|
-
this.log(` Monthly cap: ${this.formatCap(data.cap)}`);
|
|
92
|
-
this.log(` Total spent: ${this.formatCurrency(data.totalSpent, data.spentCurrency)}`);
|
|
93
|
-
if (data.activeBlocks.length === 0) {
|
|
94
|
-
this.log(' Active blocks: none');
|
|
95
|
-
this.log('');
|
|
96
|
-
return;
|
|
48
|
+
if (data.cardOnFile) {
|
|
49
|
+
this.log(` Card: ${data.cardSummary || 'Card on file'}`);
|
|
97
50
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.log(` - ${this.formatUsageBlock(block)}`);
|
|
51
|
+
else {
|
|
52
|
+
this.log(` Card: ${chalk.yellow('No card on file')}`);
|
|
101
53
|
}
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
formatAutoCharge(data) {
|
|
105
|
-
if (!data.autoChargeEnabled) {
|
|
106
|
-
return 'disabled';
|
|
54
|
+
if (data.freeExhausted) {
|
|
55
|
+
this.log(` Free tier: ${chalk.dim('exhausted')}`);
|
|
107
56
|
}
|
|
108
|
-
|
|
109
|
-
|
|
57
|
+
else {
|
|
58
|
+
this.log(` Free tier: active`);
|
|
110
59
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
60
|
+
this.log(` Current block: ${data.currentBlockUsed ?? 0} / 10,000 used`);
|
|
61
|
+
this.log(` Blocks used: ${data.blocksUsed ?? 0} ($${data.totalSpent || '0'} total)`);
|
|
62
|
+
this.log(` Monthly cap: ${data.cap ?? 'not set'} blocks${data.cap ? ` ($${data.cap * 19} max/month)` : ''}`);
|
|
63
|
+
if (data.aiRemaining) {
|
|
64
|
+
this.log(`\n AI generation:`);
|
|
65
|
+
this.log(` Init uses remaining: ${data.aiRemaining.inits}/5 this month`);
|
|
66
|
+
this.log(` Edit uses remaining: ${data.aiRemaining.edits}/50 this month`);
|
|
117
67
|
}
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
return parts.length > 0 ? parts.join(' / ') : 'not set';
|
|
122
|
-
}
|
|
123
|
-
formatCurrency(amount, currency) {
|
|
124
|
-
const numericAmount = typeof amount === 'number' ? amount : Number.parseFloat(amount);
|
|
125
|
-
const normalizedCurrency = currency.toUpperCase();
|
|
126
|
-
if (Number.isFinite(numericAmount)) {
|
|
68
|
+
if (!data.cardOnFile && data.freeExhausted && data.checkoutUrl) {
|
|
69
|
+
this.log(`\n ${chalk.yellow('Free tier exhausted.')} Opening payment page...\n`);
|
|
127
70
|
try {
|
|
128
|
-
|
|
129
|
-
currency: normalizedCurrency,
|
|
130
|
-
style: 'currency',
|
|
131
|
-
}).format(numericAmount);
|
|
71
|
+
await open(data.checkoutUrl);
|
|
132
72
|
}
|
|
133
73
|
catch {
|
|
134
|
-
|
|
74
|
+
this.log(` Visit: ${chalk.cyan(data.checkoutUrl)}`);
|
|
135
75
|
}
|
|
136
76
|
}
|
|
137
|
-
return `${String(amount)} ${normalizedCurrency}`;
|
|
138
|
-
}
|
|
139
|
-
formatPaymentMethod(data) {
|
|
140
|
-
if (!data.hasPaymentMethod) {
|
|
141
|
-
return 'No payment method on file';
|
|
142
|
-
}
|
|
143
|
-
const primaryMethod = data.paymentMethod[0];
|
|
144
|
-
if (!primaryMethod) {
|
|
145
|
-
return 'Payment method on file';
|
|
146
|
-
}
|
|
147
|
-
const brand = primaryMethod.brand || 'Card';
|
|
148
|
-
return primaryMethod.last4
|
|
149
|
-
? `${brand} ending ${primaryMethod.last4}`
|
|
150
|
-
: `${brand} on file`;
|
|
151
|
-
}
|
|
152
|
-
formatUsageBlock(block) {
|
|
153
|
-
const allowance = block.blockAllowance ?? Math.max(block.blocksCount * block.blockSize, 0);
|
|
154
|
-
const used = Math.max(block.emailsSent ?? 0, 0);
|
|
155
|
-
const activationSuffix = block.activatedAt
|
|
156
|
-
? `, activated ${new Date(block.activatedAt).toLocaleDateString('en-US')}`
|
|
157
|
-
: '';
|
|
158
|
-
return `${block.type} block: ${used.toLocaleString()} / ${allowance.toLocaleString()} used (${block.status}${activationSuffix})`;
|
|
159
|
-
}
|
|
160
|
-
pluralize(word, count) {
|
|
161
|
-
return count === 1 ? word : `${word}s`;
|
|
162
|
-
}
|
|
163
|
-
async purchaseBlocks(blocksCount, jsonOutput) {
|
|
164
|
-
if (blocksCount < 1) {
|
|
165
|
-
this.error('Purchase block count must be at least 1 block.');
|
|
166
|
-
}
|
|
167
|
-
const response = await this.withApiSpinner({ json: jsonOutput, text: ' Initiating block purchase...' }, () => this.apiClient.post(API_ENDPOINTS.BILLING_PURCHASE, {
|
|
168
|
-
blocksCount,
|
|
169
|
-
}));
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
this.handleApiError(response);
|
|
172
|
-
}
|
|
173
|
-
if (jsonOutput) {
|
|
174
|
-
this.log(JSON.stringify(response.data, null, 2));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
this.log(`\n ${chalk.green('✓')} ${response.data.message}`);
|
|
178
|
-
this.log(` Blocks: ${blocksCount} ${this.pluralize('block', blocksCount)}`);
|
|
179
|
-
this.log(` Payment intent:${` ${chalk.dim(response.data.paymentIntentId)}`}`);
|
|
180
77
|
this.log('');
|
|
181
78
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return this.apiClient.post(API_ENDPOINTS.BILLING_CAP, payload);
|
|
194
|
-
});
|
|
79
|
+
/**
|
|
80
|
+
* Updates the monthly block spending cap. This limits how many 10k-email
|
|
81
|
+
* blocks will be auto-charged per billing cycle.
|
|
82
|
+
*
|
|
83
|
+
* @param {number} cap - The maximum number of blocks per month.
|
|
84
|
+
* @param {boolean} jsonOutput - Whether to output JSON instead of formatted text.
|
|
85
|
+
*/
|
|
86
|
+
async setCap(cap, jsonOutput) {
|
|
87
|
+
const response = await this.withApiSpinner({ json: jsonOutput, text: ' Updating spending cap...' }, () => this.apiClient.patch(API_ENDPOINTS.BILLING_CAP, {
|
|
88
|
+
cap,
|
|
89
|
+
}));
|
|
195
90
|
if (!response.ok) {
|
|
196
91
|
this.handleApiError(response);
|
|
197
92
|
}
|
|
198
93
|
if (jsonOutput) {
|
|
199
|
-
this.log(JSON.stringify(
|
|
94
|
+
this.log(JSON.stringify({ cap, maxMonthlySpend: `$${cap * 19}`, status: 'updated' }, null, 2));
|
|
200
95
|
return;
|
|
201
96
|
}
|
|
202
|
-
this.log(`\n ${chalk.green('✓')} ${
|
|
203
|
-
this.log(` Monthly cap: ${chalk.bold(String(response.data.capBlocks))} ${this.pluralize('block', response.data.capBlocks)} (${response.data.capEmails.toLocaleString()} emails)`);
|
|
204
|
-
if (response.data.autoChargeBlockCount !== undefined) {
|
|
205
|
-
this.log(` Auto-charge: ${response.data.autoChargeBlockCount} ${this.pluralize('block', response.data.autoChargeBlockCount)}`);
|
|
206
|
-
}
|
|
207
|
-
this.log('');
|
|
97
|
+
this.log(`\n ${chalk.green('✓')} Cap set to ${chalk.bold(String(cap))} blocks ($${cap * 19} max/month).\n`);
|
|
208
98
|
}
|
|
209
99
|
}
|
|
@@ -2,7 +2,6 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { confirm } from '@inquirer/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { writeFile } from 'node:fs/promises';
|
|
5
|
-
import { resolve } from 'node:path';
|
|
6
5
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
7
6
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
8
7
|
export default class Contacts extends BaseCommand {
|
|
@@ -10,7 +9,7 @@ export default class Contacts extends BaseCommand {
|
|
|
10
9
|
static examples = [
|
|
11
10
|
'<%= config.bin %> contacts',
|
|
12
11
|
'<%= config.bin %> contacts --search sarah@example.com',
|
|
13
|
-
|
|
12
|
+
"<%= config.bin %> contacts --export # GDPR CSV → contacts.csv",
|
|
14
13
|
'<%= config.bin %> contacts --delete sarah@example.com',
|
|
15
14
|
];
|
|
16
15
|
static flags = {
|
|
@@ -113,14 +112,14 @@ export default class Contacts extends BaseCommand {
|
|
|
113
112
|
this.log(`\n Export status: ${status ?? 'unknown'}. No download URL yet.\n`);
|
|
114
113
|
return;
|
|
115
114
|
}
|
|
116
|
-
const
|
|
115
|
+
const url = `https://${downloadUrl}`;
|
|
116
|
+
const fileResult = await this.apiClient.getFile(url);
|
|
117
117
|
if (!fileResult.ok) {
|
|
118
118
|
this.error(`Download failed: ${fileResult.status} ${fileResult.error ?? ''}\n` +
|
|
119
119
|
` URL: ${fileResult.debug.fullUrl}`);
|
|
120
120
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.log(`\n ${chalk.green('✓')} Contact export saved to ${chalk.cyan(outputPath)}\n`);
|
|
121
|
+
await writeFile('contacts.csv', Buffer.from(fileResult.body));
|
|
122
|
+
this.log(`\n ${chalk.green('✓')} Contact export saved to ${chalk.cyan('contacts.csv')}\n`);
|
|
124
123
|
}
|
|
125
124
|
/**
|
|
126
125
|
* Performs a GDPR-compliant hard delete of a contact and all their
|
|
@@ -45,7 +45,34 @@ export default class Domain extends BaseCommand {
|
|
|
45
45
|
this.log(`\n ${'─'.repeat(53)}`);
|
|
46
46
|
this.log(` ${chalk.bold('DOMAIN SETUP')}`);
|
|
47
47
|
this.log(` ${'─'.repeat(53)}\n`);
|
|
48
|
-
|
|
48
|
+
let domain;
|
|
49
|
+
let senderEmail;
|
|
50
|
+
let address;
|
|
51
|
+
if (flags.yes) {
|
|
52
|
+
domain = yamlConfig.project?.domain || '';
|
|
53
|
+
senderEmail = yamlConfig.project?.fromEmail || '';
|
|
54
|
+
address = yamlConfig.project?.address || '';
|
|
55
|
+
if (!domain) {
|
|
56
|
+
this.error('Domain is required. Set it in mailmodo.yaml or use interactive mode.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
domain = await input({
|
|
61
|
+
default: yamlConfig.project?.domain,
|
|
62
|
+
message: 'What domain will you send from?',
|
|
63
|
+
validate: (v) => (v?.trim() ? true : 'Domain is required'),
|
|
64
|
+
});
|
|
65
|
+
senderEmail = await input({
|
|
66
|
+
default: yamlConfig.project?.fromEmail,
|
|
67
|
+
message: 'Sender email address:',
|
|
68
|
+
validate: (v) => v?.includes('@') ? true : 'Please enter a valid email',
|
|
69
|
+
});
|
|
70
|
+
address = await input({
|
|
71
|
+
default: yamlConfig.project?.address,
|
|
72
|
+
message: 'Business address (required by law):',
|
|
73
|
+
validate: (v) => (v?.trim() ? true : 'Address is required'),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
49
76
|
const response = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, {
|
|
50
77
|
address,
|
|
51
78
|
domain,
|
|
@@ -60,7 +87,6 @@ export default class Domain extends BaseCommand {
|
|
|
60
87
|
await saveYaml(yamlConfig);
|
|
61
88
|
await saveConfig({ ...config, domain });
|
|
62
89
|
const records = response.data?.dnsRecords || [];
|
|
63
|
-
const guideUrl = response.data?.dnsGuideUrl ?? DNS_GUIDE_URL;
|
|
64
90
|
if (flags.json) {
|
|
65
91
|
this.log(JSON.stringify({ dnsRecords: records, domain }, null, 2));
|
|
66
92
|
return;
|
|
@@ -73,7 +99,7 @@ export default class Domain extends BaseCommand {
|
|
|
73
99
|
this.log(` Value: ${record.value}\n`);
|
|
74
100
|
}
|
|
75
101
|
this.log(` DNS changes take 5–30 minutes to propagate.`);
|
|
76
|
-
this.log(` Full guide: ${chalk.cyan(
|
|
102
|
+
this.log(` Full guide: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
|
|
77
103
|
if (!flags.yes) {
|
|
78
104
|
const action = await input({
|
|
79
105
|
default: '',
|
|
@@ -152,35 +178,6 @@ export default class Domain extends BaseCommand {
|
|
|
152
178
|
this.log(` Bounce rate: ${data.bounceRate ?? 'N/A'}%`);
|
|
153
179
|
this.log(` Spam rate: ${data.spamRate ?? 'N/A'}%\n`);
|
|
154
180
|
}
|
|
155
|
-
async collectDomainInputs(skipPrompts, yamlConfig) {
|
|
156
|
-
if (skipPrompts) {
|
|
157
|
-
const domain = yamlConfig.project?.domain || '';
|
|
158
|
-
if (!domain) {
|
|
159
|
-
this.error('Domain is required. Set it in mailmodo.yaml or use interactive mode.');
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
address: yamlConfig.project?.address || '',
|
|
163
|
-
domain,
|
|
164
|
-
senderEmail: yamlConfig.project?.fromEmail || '',
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
const domain = await input({
|
|
168
|
-
default: yamlConfig.project?.domain,
|
|
169
|
-
message: 'What domain will you send from?',
|
|
170
|
-
validate: (v) => (v?.trim() ? true : 'Domain is required'),
|
|
171
|
-
});
|
|
172
|
-
const senderEmail = await input({
|
|
173
|
-
default: yamlConfig.project?.fromEmail,
|
|
174
|
-
message: 'Sender email address:',
|
|
175
|
-
validate: (v) => (v?.includes('@') ? true : 'Please enter a valid email'),
|
|
176
|
-
});
|
|
177
|
-
const address = await input({
|
|
178
|
-
default: yamlConfig.project?.address,
|
|
179
|
-
message: 'Business address (required by law):',
|
|
180
|
-
validate: (v) => (v?.trim() ? true : 'Address is required'),
|
|
181
|
-
});
|
|
182
|
-
return { address, domain, senderEmail };
|
|
183
|
-
}
|
|
184
181
|
recordLabel(index) {
|
|
185
182
|
const labels = ['DKIM', 'DMARC', 'Return Path'];
|
|
186
183
|
return labels[index] || `Record ${index + 1}`;
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -44,10 +44,6 @@ export declare class ApiClient {
|
|
|
44
44
|
* Bearer auth as other requests; does not parse JSON.
|
|
45
45
|
*/
|
|
46
46
|
getFile(url: string): Promise<FileFetchResult>;
|
|
47
|
-
/**
|
|
48
|
-
* GET an external URL (e.g. blob storage) without auth headers.
|
|
49
|
-
*/
|
|
50
|
-
getPublicFile(url: string): Promise<FileFetchResult>;
|
|
51
47
|
patch<T = Record<string, unknown>>(path: string, body?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
52
48
|
post<T = Record<string, unknown>>(path: string, body?: Record<string, unknown> | unknown): Promise<ApiResponse<T>>;
|
|
53
49
|
postFormData<T = Record<string, unknown>>(path: string, formData: FormData): Promise<ApiResponse<T>>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { API_BASE_URL } from './constants.js';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchFileWithBearerAuth, } from './fetch-file.js';
|
|
3
3
|
/**
|
|
4
4
|
* HTTP client for the Mailmodo CLI API.
|
|
5
5
|
* Wraps the native fetch API with Bearer token authentication,
|
|
@@ -123,12 +123,6 @@ export class ApiClient {
|
|
|
123
123
|
async getFile(url) {
|
|
124
124
|
return fetchFileWithBearerAuth(url, this.apiKey);
|
|
125
125
|
}
|
|
126
|
-
/**
|
|
127
|
-
* GET an external URL (e.g. blob storage) without auth headers.
|
|
128
|
-
*/
|
|
129
|
-
async getPublicFile(url) {
|
|
130
|
-
return fetchFileNoAuth(url);
|
|
131
|
-
}
|
|
132
126
|
async patch(path, body) {
|
|
133
127
|
return this.request('PATCH', path, body);
|
|
134
128
|
}
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -5,8 +5,6 @@ export declare const API_ENDPOINTS: Readonly<{
|
|
|
5
5
|
ASSETS_LOGO: "/assets/logo";
|
|
6
6
|
AUTH_VALIDATE: "/auth/validate";
|
|
7
7
|
BILLING_CAP: "/billing/cap";
|
|
8
|
-
BILLING_CHECKOUT: "/billing/checkout";
|
|
9
|
-
BILLING_PURCHASE: "/billing/purchase";
|
|
10
8
|
BILLING_STATUS: "/billing/status";
|
|
11
9
|
CONTACTS: "/contacts";
|
|
12
10
|
CONTACTS_EXPORT: "/contacts/export";
|
package/dist/lib/constants.js
CHANGED
|
@@ -11,8 +11,6 @@ export const API_ENDPOINTS = Object.freeze({
|
|
|
11
11
|
ASSETS_LOGO: '/assets/logo',
|
|
12
12
|
AUTH_VALIDATE: '/auth/validate',
|
|
13
13
|
BILLING_CAP: '/billing/cap',
|
|
14
|
-
BILLING_CHECKOUT: '/billing/checkout',
|
|
15
|
-
BILLING_PURCHASE: '/billing/purchase',
|
|
16
14
|
BILLING_STATUS: '/billing/status',
|
|
17
15
|
CONTACTS: '/contacts',
|
|
18
16
|
CONTACTS_EXPORT: '/contacts/export',
|
package/dist/lib/fetch-file.d.ts
CHANGED
|
@@ -12,7 +12,3 @@ export interface FileFetchResult {
|
|
|
12
12
|
* does not parse JSON.
|
|
13
13
|
*/
|
|
14
14
|
export declare function fetchFileWithBearerAuth(url: string, apiKey: string): Promise<FileFetchResult>;
|
|
15
|
-
/**
|
|
16
|
-
* GET an absolute external URL (e.g. blob storage) without auth headers.
|
|
17
|
-
*/
|
|
18
|
-
export declare function fetchFileNoAuth(url: string): Promise<FileFetchResult>;
|
package/dist/lib/fetch-file.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
const USER_AGENT = '@mailmodo/cli';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* GET an absolute URL and return the raw body (e.g. CSV). Uses Bearer auth;
|
|
4
|
+
* does not parse JSON.
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchFileWithBearerAuth(url, apiKey) {
|
|
7
|
+
let href;
|
|
8
|
+
try {
|
|
9
|
+
href = new URL(url.trim()).toString();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return {
|
|
13
|
+
body: new ArrayBuffer(0),
|
|
14
|
+
debug: { fullUrl: url.trim() },
|
|
15
|
+
error: 'Invalid URL',
|
|
16
|
+
ok: false,
|
|
17
|
+
status: 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
3
20
|
const debug = { fullUrl: href };
|
|
4
21
|
try {
|
|
5
|
-
const response = await fetch(href, {
|
|
22
|
+
const response = await fetch(href, {
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
'User-Agent': USER_AGENT,
|
|
26
|
+
},
|
|
27
|
+
method: 'GET',
|
|
28
|
+
});
|
|
6
29
|
const body = await response.arrayBuffer();
|
|
7
30
|
if (!response.ok) {
|
|
8
31
|
return {
|
|
@@ -26,47 +49,3 @@ async function doFetch(href, headers) {
|
|
|
26
49
|
};
|
|
27
50
|
}
|
|
28
51
|
}
|
|
29
|
-
function parseUrl(url) {
|
|
30
|
-
try {
|
|
31
|
-
return new URL(url.trim()).toString();
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* GET an absolute URL and return the raw body (e.g. CSV). Uses Bearer auth;
|
|
39
|
-
* does not parse JSON.
|
|
40
|
-
*/
|
|
41
|
-
export async function fetchFileWithBearerAuth(url, apiKey) {
|
|
42
|
-
const href = parseUrl(url);
|
|
43
|
-
if (!href) {
|
|
44
|
-
return {
|
|
45
|
-
body: new ArrayBuffer(0),
|
|
46
|
-
debug: { fullUrl: url.trim() },
|
|
47
|
-
error: 'Invalid URL',
|
|
48
|
-
ok: false,
|
|
49
|
-
status: 0,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return doFetch(href, {
|
|
53
|
-
Authorization: `Bearer ${apiKey}`,
|
|
54
|
-
'User-Agent': USER_AGENT,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* GET an absolute external URL (e.g. blob storage) without auth headers.
|
|
59
|
-
*/
|
|
60
|
-
export async function fetchFileNoAuth(url) {
|
|
61
|
-
const href = parseUrl(url);
|
|
62
|
-
if (!href) {
|
|
63
|
-
return {
|
|
64
|
-
body: new ArrayBuffer(0),
|
|
65
|
-
debug: { fullUrl: url.trim() },
|
|
66
|
-
error: 'Invalid URL',
|
|
67
|
-
ok: false,
|
|
68
|
-
status: 0,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return doFetch(href, { 'User-Agent': USER_AGENT });
|
|
72
|
-
}
|
package/oclif.manifest.json
CHANGED
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
"billing": {
|
|
4
4
|
"aliases": [],
|
|
5
5
|
"args": {},
|
|
6
|
-
"description": "View billing status,
|
|
6
|
+
"description": "View billing status, manage payment, and set spending cap",
|
|
7
7
|
"examples": [
|
|
8
8
|
"<%= config.bin %> billing",
|
|
9
9
|
"<%= config.bin %> billing --status",
|
|
10
|
-
"<%= config.bin %> billing --cap 5"
|
|
11
|
-
"<%= config.bin %> billing --cap 5 --auto-charge-block-count 2",
|
|
12
|
-
"<%= config.bin %> billing --purchase 3",
|
|
13
|
-
"<%= config.bin %> billing --checkout"
|
|
10
|
+
"<%= config.bin %> billing --cap 5"
|
|
14
11
|
],
|
|
15
12
|
"flags": {
|
|
16
13
|
"json": {
|
|
@@ -26,33 +23,13 @@
|
|
|
26
23
|
"allowNo": false,
|
|
27
24
|
"type": "boolean"
|
|
28
25
|
},
|
|
29
|
-
"auto-charge-block-count": {
|
|
30
|
-
"description": "Blocks to auto-purchase when the quota runs low (use with --cap)",
|
|
31
|
-
"name": "auto-charge-block-count",
|
|
32
|
-
"hasDynamicHelp": false,
|
|
33
|
-
"multiple": false,
|
|
34
|
-
"type": "option"
|
|
35
|
-
},
|
|
36
26
|
"cap": {
|
|
37
|
-
"description": "Set monthly
|
|
27
|
+
"description": "Set monthly block cap (max blocks to auto-charge)",
|
|
38
28
|
"name": "cap",
|
|
39
29
|
"hasDynamicHelp": false,
|
|
40
30
|
"multiple": false,
|
|
41
31
|
"type": "option"
|
|
42
32
|
},
|
|
43
|
-
"checkout": {
|
|
44
|
-
"description": "Open Stripe checkout to add or update a payment method",
|
|
45
|
-
"name": "checkout",
|
|
46
|
-
"allowNo": false,
|
|
47
|
-
"type": "boolean"
|
|
48
|
-
},
|
|
49
|
-
"purchase": {
|
|
50
|
-
"description": "Manually purchase this many 10,000-email blocks",
|
|
51
|
-
"name": "purchase",
|
|
52
|
-
"hasDynamicHelp": false,
|
|
53
|
-
"multiple": false,
|
|
54
|
-
"type": "option"
|
|
55
|
-
},
|
|
56
33
|
"status": {
|
|
57
34
|
"description": "Show billing status only",
|
|
58
35
|
"name": "status",
|
|
@@ -365,13 +342,12 @@
|
|
|
365
342
|
"index.js"
|
|
366
343
|
]
|
|
367
344
|
},
|
|
368
|
-
"
|
|
345
|
+
"logout": {
|
|
369
346
|
"aliases": [],
|
|
370
347
|
"args": {},
|
|
371
|
-
"description": "
|
|
348
|
+
"description": "Sign out by removing saved credentials from this machine",
|
|
372
349
|
"examples": [
|
|
373
|
-
"<%= config.bin %>
|
|
374
|
-
"MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
|
|
350
|
+
"<%= config.bin %> logout"
|
|
375
351
|
],
|
|
376
352
|
"flags": {
|
|
377
353
|
"json": {
|
|
@@ -390,7 +366,7 @@
|
|
|
390
366
|
},
|
|
391
367
|
"hasDynamicHelp": false,
|
|
392
368
|
"hiddenAliases": [],
|
|
393
|
-
"id": "
|
|
369
|
+
"id": "logout",
|
|
394
370
|
"pluginAlias": "@mailmodo/cli",
|
|
395
371
|
"pluginName": "@mailmodo/cli",
|
|
396
372
|
"pluginType": "core",
|
|
@@ -400,16 +376,17 @@
|
|
|
400
376
|
"relativePath": [
|
|
401
377
|
"dist",
|
|
402
378
|
"commands",
|
|
403
|
-
"
|
|
379
|
+
"logout",
|
|
404
380
|
"index.js"
|
|
405
381
|
]
|
|
406
382
|
},
|
|
407
|
-
"
|
|
383
|
+
"login": {
|
|
408
384
|
"aliases": [],
|
|
409
385
|
"args": {},
|
|
410
|
-
"description": "
|
|
386
|
+
"description": "Authenticate with Mailmodo using your API key",
|
|
411
387
|
"examples": [
|
|
412
|
-
"<%= config.bin %>
|
|
388
|
+
"<%= config.bin %> login",
|
|
389
|
+
"MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
|
|
413
390
|
],
|
|
414
391
|
"flags": {
|
|
415
392
|
"json": {
|
|
@@ -428,7 +405,7 @@
|
|
|
428
405
|
},
|
|
429
406
|
"hasDynamicHelp": false,
|
|
430
407
|
"hiddenAliases": [],
|
|
431
|
-
"id": "
|
|
408
|
+
"id": "login",
|
|
432
409
|
"pluginAlias": "@mailmodo/cli",
|
|
433
410
|
"pluginName": "@mailmodo/cli",
|
|
434
411
|
"pluginType": "core",
|
|
@@ -438,7 +415,7 @@
|
|
|
438
415
|
"relativePath": [
|
|
439
416
|
"dist",
|
|
440
417
|
"commands",
|
|
441
|
-
"
|
|
418
|
+
"login",
|
|
442
419
|
"index.js"
|
|
443
420
|
]
|
|
444
421
|
},
|
|
@@ -657,5 +634,5 @@
|
|
|
657
634
|
]
|
|
658
635
|
}
|
|
659
636
|
},
|
|
660
|
-
"version": "0.0.26
|
|
637
|
+
"version": "0.0.26"
|
|
661
638
|
}
|