@mailmodo/cli 0.0.36-beta.pr38.61 → 0.0.36
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 +0 -1
- package/dist/commands/billing/index.js +33 -43
- package/dist/commands/settings/index.d.ts +0 -1
- package/dist/commands/settings/index.js +4 -28
- package/dist/lib/base-command.d.ts +0 -17
- package/dist/lib/base-command.js +0 -26
- package/oclif.manifest.json +44 -44
- package/package.json +1 -1
|
@@ -4,7 +4,6 @@ import open from 'open';
|
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
5
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
6
6
|
import { INFO } from '../../lib/messages.js';
|
|
7
|
-
import { loadYaml, saveYaml } from '../../lib/yaml-config.js';
|
|
8
7
|
export default class Billing extends BaseCommand {
|
|
9
8
|
static description = 'View billing status, purchase blocks, set cap, or add a payment method';
|
|
10
9
|
static examples = [
|
|
@@ -90,23 +89,18 @@ export default class Billing extends BaseCommand {
|
|
|
90
89
|
this.log(` Tier: ${data.tier}`);
|
|
91
90
|
this.log(` Payment: ${this.formatPaymentMethod(data)}`);
|
|
92
91
|
this.log(` Auto-charge: ${this.formatAutoCharge(data)}`);
|
|
93
|
-
|
|
94
|
-
this.log(` Monthly cap: ${this.formatCap(data.cap)}`);
|
|
95
|
-
}
|
|
92
|
+
this.log(` Monthly cap: ${this.formatCap(data.cap)}`);
|
|
96
93
|
this.log(` Total spent: ${this.formatCurrency(data.totalSpent, data.spentCurrency)}`);
|
|
97
94
|
if (data.activeBlocks.length === 0) {
|
|
98
95
|
this.log(' Active blocks: none');
|
|
96
|
+
this.log('');
|
|
97
|
+
return;
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.log(` - ${this.formatUsageBlock(block)}`);
|
|
104
|
-
}
|
|
99
|
+
this.log(' Active blocks:');
|
|
100
|
+
for (const block of data.activeBlocks) {
|
|
101
|
+
this.log(` - ${this.formatUsageBlock(block)}`);
|
|
105
102
|
}
|
|
106
103
|
this.log('');
|
|
107
|
-
if (!data.hasPaymentMethod) {
|
|
108
|
-
await this.startCheckout(jsonOutput);
|
|
109
|
-
}
|
|
110
104
|
}
|
|
111
105
|
formatAutoCharge(data) {
|
|
112
106
|
if (!data.autoChargeEnabled) {
|
|
@@ -118,18 +112,14 @@ export default class Billing extends BaseCommand {
|
|
|
118
112
|
return `enabled (${data.autoChargeBlockCount} ${this.pluralize('block', data.autoChargeBlockCount)})`;
|
|
119
113
|
}
|
|
120
114
|
formatCap(cap) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return `${cap.inBlocks} ${this.pluralize('block', cap.inBlocks)} (${cap.inEmails.toLocaleString()} ${this.pluralize('email', cap.inEmails)})`;
|
|
115
|
+
const parts = [];
|
|
116
|
+
if (typeof cap.inBlocks === 'number') {
|
|
117
|
+
parts.push(`${cap.inBlocks} ${this.pluralize('block', cap.inBlocks)}`);
|
|
125
118
|
}
|
|
126
|
-
if (
|
|
127
|
-
|
|
119
|
+
if (typeof cap.inEmails === 'number') {
|
|
120
|
+
parts.push(`${cap.inEmails.toLocaleString()} emails`);
|
|
128
121
|
}
|
|
129
|
-
|
|
130
|
-
return `${cap.inEmails.toLocaleString()} ${this.pluralize('email', cap.inEmails)}`;
|
|
131
|
-
}
|
|
132
|
-
return 'not set';
|
|
122
|
+
return parts.length > 0 ? parts.join(' / ') : 'not set';
|
|
133
123
|
}
|
|
134
124
|
formatCurrency(amount, currency) {
|
|
135
125
|
const numericAmount = typeof amount === 'number' ? amount : Number.parseFloat(amount);
|
|
@@ -164,18 +154,9 @@ export default class Billing extends BaseCommand {
|
|
|
164
154
|
const allowance = block.blockAllowance ?? Math.max(block.blocksCount * block.blockSize, 0);
|
|
165
155
|
const used = Math.max(block.emailsSent ?? 0, 0);
|
|
166
156
|
const activationSuffix = block.activatedAt
|
|
167
|
-
? `, activated
|
|
157
|
+
? `, activated ${new Date(block.activatedAt).toLocaleDateString('en-US')}`
|
|
168
158
|
: '';
|
|
169
|
-
return `${block.type} block
|
|
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);
|
|
159
|
+
return `${block.type} block: ${used.toLocaleString()} / ${allowance.toLocaleString()} used (${block.status}${activationSuffix})`;
|
|
179
160
|
}
|
|
180
161
|
pluralize(word, count) {
|
|
181
162
|
return count === 1 ? word : `${word}s`;
|
|
@@ -200,20 +181,29 @@ export default class Billing extends BaseCommand {
|
|
|
200
181
|
this.log('');
|
|
201
182
|
}
|
|
202
183
|
async setCap(cap, autoChargeBlockCount, jsonOutput) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
184
|
+
if (cap < 1) {
|
|
185
|
+
this.error('Cap must be at least 1 block.');
|
|
186
|
+
}
|
|
187
|
+
if (autoChargeBlockCount !== undefined && autoChargeBlockCount < 1) {
|
|
188
|
+
this.error('Auto-charge block count must be at least 1 block.');
|
|
189
|
+
}
|
|
190
|
+
const response = await this.withApiSpinner({ json: jsonOutput, text: ' Updating spending cap...' }, () => {
|
|
191
|
+
const payload = autoChargeBlockCount === undefined
|
|
192
|
+
? { cap }
|
|
193
|
+
: { autoChargeBlockCount, cap };
|
|
194
|
+
return this.apiClient.post(API_ENDPOINTS.BILLING_CAP, payload);
|
|
207
195
|
});
|
|
208
|
-
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
this.handleApiError(response);
|
|
198
|
+
}
|
|
209
199
|
if (jsonOutput) {
|
|
210
|
-
this.log(JSON.stringify(data, null, 2));
|
|
200
|
+
this.log(JSON.stringify(response.data, null, 2));
|
|
211
201
|
return;
|
|
212
202
|
}
|
|
213
|
-
this.log(`\n ${chalk.green('✓')} ${data.message}`);
|
|
214
|
-
this.log(` Monthly cap: ${chalk.bold(String(data.capBlocks))} ${this.pluralize('block', data.capBlocks)} (${data.capEmails.toLocaleString()} emails)`);
|
|
215
|
-
if (data.autoChargeBlockCount !== undefined) {
|
|
216
|
-
this.log(` Auto-charge: ${data.autoChargeBlockCount} ${this.pluralize('block', data.autoChargeBlockCount)}`);
|
|
203
|
+
this.log(`\n ${chalk.green('✓')} ${response.data.message}`);
|
|
204
|
+
this.log(` Monthly cap: ${chalk.bold(String(response.data.capBlocks))} ${this.pluralize('block', response.data.capBlocks)} (${response.data.capEmails.toLocaleString()} emails)`);
|
|
205
|
+
if (response.data.autoChargeBlockCount !== undefined) {
|
|
206
|
+
this.log(` Auto-charge: ${response.data.autoChargeBlockCount} ${this.pluralize('block', response.data.autoChargeBlockCount)}`);
|
|
217
207
|
}
|
|
218
208
|
this.log('');
|
|
219
209
|
}
|
|
@@ -73,11 +73,8 @@ export default class Settings extends BaseCommand {
|
|
|
73
73
|
if (!(propKey in project) && key !== 'logo_file') {
|
|
74
74
|
this.error(`Unknown setting: ${key}`);
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
project[propKey] = value;
|
|
76
|
+
project[propKey] =
|
|
77
|
+
propKey === 'monthlyCap' ? Number(value) : value;
|
|
81
78
|
await saveYaml(yamlConfig);
|
|
82
79
|
if (isJson) {
|
|
83
80
|
this.log(JSON.stringify({ [propKey]: value, status: 'updated' }, null, 2));
|
|
@@ -86,21 +83,6 @@ export default class Settings extends BaseCommand {
|
|
|
86
83
|
this.log(`\n ${chalk.green('✓')} ${key} updated to ${chalk.cyan(value)}`);
|
|
87
84
|
this.log(` ${INFO.DEPLOY_TO_APPLY}\n`);
|
|
88
85
|
}
|
|
89
|
-
async applyMonthlyCapChange(yamlConfig, rawValue, isJson) {
|
|
90
|
-
const parsed = Number(rawValue);
|
|
91
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
92
|
-
this.error('monthly_cap must be a positive integer (blocks).');
|
|
93
|
-
}
|
|
94
|
-
await this.ensureAuth();
|
|
95
|
-
const data = await this.applyBillingCap({ cap: parsed, json: isJson });
|
|
96
|
-
yamlConfig.project.monthlyCap = data.capBlocks;
|
|
97
|
-
await saveYaml(yamlConfig);
|
|
98
|
-
if (isJson) {
|
|
99
|
-
this.log(JSON.stringify({ monthlyCap: data.capBlocks, status: 'updated' }, null, 2));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
this.log(`\n ${chalk.green('✓')} monthly_cap updated to ${chalk.cyan(String(data.capBlocks))} (${data.capEmails.toLocaleString()} emails)\n`);
|
|
103
|
-
}
|
|
104
86
|
displaySettingsGroup(group, keys, project, domainVerified) {
|
|
105
87
|
const availableKeys = keys.filter((key) => {
|
|
106
88
|
if (group === 'brand' && key === 'logo_file')
|
|
@@ -177,13 +159,6 @@ export default class Settings extends BaseCommand {
|
|
|
177
159
|
await this.handleDomainChange(yamlConfig);
|
|
178
160
|
return;
|
|
179
161
|
}
|
|
180
|
-
if (editKey === 'monthly_cap') {
|
|
181
|
-
const newValue = await input({
|
|
182
|
-
message: 'New monthly cap (blocks):',
|
|
183
|
-
});
|
|
184
|
-
await this.applyMonthlyCapChange(yamlConfig, newValue, false);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
162
|
if (editKey === 'email_style') {
|
|
188
163
|
const style = await select({
|
|
189
164
|
choices: [
|
|
@@ -201,7 +176,8 @@ export default class Settings extends BaseCommand {
|
|
|
201
176
|
const newValue = await input({
|
|
202
177
|
message: `New value for ${editKey}:`,
|
|
203
178
|
});
|
|
204
|
-
project[editPropKey] =
|
|
179
|
+
project[editPropKey] =
|
|
180
|
+
editPropKey === 'monthlyCap' ? Number(newValue) : newValue;
|
|
205
181
|
await saveYaml(yamlConfig);
|
|
206
182
|
this.log(`\n ${chalk.green('✓')} Updated. ${INFO.DEPLOY_TO_APPLY}\n`);
|
|
207
183
|
}
|
|
@@ -2,12 +2,6 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { ApiClient, type ApiRequestDebugInfo } from './api-client.js';
|
|
3
3
|
import { type MailmodoConfig } from './config.js';
|
|
4
4
|
import { type MailmodoYaml } from './yaml-config.js';
|
|
5
|
-
export interface BillingCapUpdateResult {
|
|
6
|
-
autoChargeBlockCount?: number;
|
|
7
|
-
capBlocks: number;
|
|
8
|
-
capEmails: number;
|
|
9
|
-
message: string;
|
|
10
|
-
}
|
|
11
5
|
/**
|
|
12
6
|
* Abstract base command providing shared functionality for all Mailmodo CLI commands.
|
|
13
7
|
* Subclasses inherit --json and --yes base flags, authentication enforcement,
|
|
@@ -74,17 +68,6 @@ export declare abstract class BaseCommand extends Command {
|
|
|
74
68
|
fromName: string;
|
|
75
69
|
replyTo: string;
|
|
76
70
|
}>;
|
|
77
|
-
/**
|
|
78
|
-
* Updates the account's monthly sending cap on the server.
|
|
79
|
-
* Validates inputs, wraps the POST /billing/cap call in a spinner, and
|
|
80
|
-
* surfaces errors via handleApiError. The caller is responsible for any
|
|
81
|
-
* local persistence (e.g. writing `monthlyCap` to mailmodo.yaml).
|
|
82
|
-
*/
|
|
83
|
-
protected applyBillingCap(options: {
|
|
84
|
-
autoChargeBlockCount?: number;
|
|
85
|
-
cap: number;
|
|
86
|
-
json: boolean;
|
|
87
|
-
}): Promise<BillingCapUpdateResult>;
|
|
88
71
|
protected registerDomain(yamlConfig: MailmodoYaml, inputs: {
|
|
89
72
|
address: string;
|
|
90
73
|
domain: string;
|
package/dist/lib/base-command.js
CHANGED
|
@@ -145,32 +145,6 @@ export class BaseCommand extends Command {
|
|
|
145
145
|
});
|
|
146
146
|
return { address, domain, fromEmail, fromName, replyTo };
|
|
147
147
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Updates the account's monthly sending cap on the server.
|
|
150
|
-
* Validates inputs, wraps the POST /billing/cap call in a spinner, and
|
|
151
|
-
* surfaces errors via handleApiError. The caller is responsible for any
|
|
152
|
-
* local persistence (e.g. writing `monthlyCap` to mailmodo.yaml).
|
|
153
|
-
*/
|
|
154
|
-
async applyBillingCap(options) {
|
|
155
|
-
if (options.cap < 1) {
|
|
156
|
-
this.error('Cap must be at least 1 block.');
|
|
157
|
-
}
|
|
158
|
-
if (options.autoChargeBlockCount !== undefined &&
|
|
159
|
-
options.autoChargeBlockCount < 1) {
|
|
160
|
-
this.error('Auto-charge block count must be at least 1 block.');
|
|
161
|
-
}
|
|
162
|
-
const payload = options.autoChargeBlockCount === undefined
|
|
163
|
-
? { cap: options.cap }
|
|
164
|
-
: {
|
|
165
|
-
autoChargeBlockCount: options.autoChargeBlockCount,
|
|
166
|
-
cap: options.cap,
|
|
167
|
-
};
|
|
168
|
-
const response = await this.withApiSpinner({ json: options.json, text: ' Updating spending cap...' }, () => this.apiClient.post(API_ENDPOINTS.BILLING_CAP, payload));
|
|
169
|
-
if (!response.ok) {
|
|
170
|
-
this.handleApiError(response);
|
|
171
|
-
}
|
|
172
|
-
return response.data;
|
|
173
|
-
}
|
|
174
148
|
async registerDomain(yamlConfig, inputs, json) {
|
|
175
149
|
const apiPayload = {
|
|
176
150
|
address: inputs.address,
|
package/oclif.manifest.json
CHANGED
|
@@ -512,19 +512,14 @@
|
|
|
512
512
|
"index.js"
|
|
513
513
|
]
|
|
514
514
|
},
|
|
515
|
-
"
|
|
515
|
+
"settings": {
|
|
516
516
|
"aliases": [],
|
|
517
|
-
"args": {
|
|
518
|
-
|
|
519
|
-
"description": "Email template ID to preview",
|
|
520
|
-
"name": "id"
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
"description": "Preview an email in browser, as text, or send a test",
|
|
517
|
+
"args": {},
|
|
518
|
+
"description": "View and update project settings",
|
|
524
519
|
"examples": [
|
|
525
|
-
"<%= config.bin %>
|
|
526
|
-
"<%= config.bin %>
|
|
527
|
-
"<%= config.bin %>
|
|
520
|
+
"<%= config.bin %> settings",
|
|
521
|
+
"<%= config.bin %> settings --set brand_color=#0F3460",
|
|
522
|
+
"<%= config.bin %> settings --json"
|
|
528
523
|
],
|
|
529
524
|
"flags": {
|
|
530
525
|
"json": {
|
|
@@ -540,23 +535,17 @@
|
|
|
540
535
|
"allowNo": false,
|
|
541
536
|
"type": "boolean"
|
|
542
537
|
},
|
|
543
|
-
"
|
|
544
|
-
"description": "
|
|
545
|
-
"name": "
|
|
538
|
+
"set": {
|
|
539
|
+
"description": "Set a setting (format: key=value)",
|
|
540
|
+
"name": "set",
|
|
546
541
|
"hasDynamicHelp": false,
|
|
547
542
|
"multiple": false,
|
|
548
543
|
"type": "option"
|
|
549
|
-
},
|
|
550
|
-
"text": {
|
|
551
|
-
"description": "Output plain text version (for AI agents)",
|
|
552
|
-
"name": "text",
|
|
553
|
-
"allowNo": false,
|
|
554
|
-
"type": "boolean"
|
|
555
544
|
}
|
|
556
545
|
},
|
|
557
546
|
"hasDynamicHelp": false,
|
|
558
547
|
"hiddenAliases": [],
|
|
559
|
-
"id": "
|
|
548
|
+
"id": "settings",
|
|
560
549
|
"pluginAlias": "@mailmodo/cli",
|
|
561
550
|
"pluginName": "@mailmodo/cli",
|
|
562
551
|
"pluginType": "core",
|
|
@@ -566,18 +555,17 @@
|
|
|
566
555
|
"relativePath": [
|
|
567
556
|
"dist",
|
|
568
557
|
"commands",
|
|
569
|
-
"
|
|
558
|
+
"settings",
|
|
570
559
|
"index.js"
|
|
571
560
|
]
|
|
572
561
|
},
|
|
573
|
-
"
|
|
562
|
+
"status": {
|
|
574
563
|
"aliases": [],
|
|
575
564
|
"args": {},
|
|
576
|
-
"description": "View and
|
|
565
|
+
"description": "View email performance metrics and quota usage",
|
|
577
566
|
"examples": [
|
|
578
|
-
"<%= config.bin %>
|
|
579
|
-
"<%= config.bin %>
|
|
580
|
-
"<%= config.bin %> settings --json"
|
|
567
|
+
"<%= config.bin %> status",
|
|
568
|
+
"<%= config.bin %> status --json"
|
|
581
569
|
],
|
|
582
570
|
"flags": {
|
|
583
571
|
"json": {
|
|
@@ -592,18 +580,11 @@
|
|
|
592
580
|
"name": "yes",
|
|
593
581
|
"allowNo": false,
|
|
594
582
|
"type": "boolean"
|
|
595
|
-
},
|
|
596
|
-
"set": {
|
|
597
|
-
"description": "Set a setting (format: key=value)",
|
|
598
|
-
"name": "set",
|
|
599
|
-
"hasDynamicHelp": false,
|
|
600
|
-
"multiple": false,
|
|
601
|
-
"type": "option"
|
|
602
583
|
}
|
|
603
584
|
},
|
|
604
585
|
"hasDynamicHelp": false,
|
|
605
586
|
"hiddenAliases": [],
|
|
606
|
-
"id": "
|
|
587
|
+
"id": "status",
|
|
607
588
|
"pluginAlias": "@mailmodo/cli",
|
|
608
589
|
"pluginName": "@mailmodo/cli",
|
|
609
590
|
"pluginType": "core",
|
|
@@ -613,17 +594,23 @@
|
|
|
613
594
|
"relativePath": [
|
|
614
595
|
"dist",
|
|
615
596
|
"commands",
|
|
616
|
-
"
|
|
597
|
+
"status",
|
|
617
598
|
"index.js"
|
|
618
599
|
]
|
|
619
600
|
},
|
|
620
|
-
"
|
|
601
|
+
"preview": {
|
|
621
602
|
"aliases": [],
|
|
622
|
-
"args": {
|
|
623
|
-
|
|
603
|
+
"args": {
|
|
604
|
+
"id": {
|
|
605
|
+
"description": "Email template ID to preview",
|
|
606
|
+
"name": "id"
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
"description": "Preview an email in browser, as text, or send a test",
|
|
624
610
|
"examples": [
|
|
625
|
-
"<%= config.bin %>
|
|
626
|
-
"<%= config.bin %>
|
|
611
|
+
"<%= config.bin %> preview welcome",
|
|
612
|
+
"<%= config.bin %> preview welcome --text",
|
|
613
|
+
"<%= config.bin %> preview welcome --send me@example.com"
|
|
627
614
|
],
|
|
628
615
|
"flags": {
|
|
629
616
|
"json": {
|
|
@@ -638,11 +625,24 @@
|
|
|
638
625
|
"name": "yes",
|
|
639
626
|
"allowNo": false,
|
|
640
627
|
"type": "boolean"
|
|
628
|
+
},
|
|
629
|
+
"send": {
|
|
630
|
+
"description": "Send test email to this address",
|
|
631
|
+
"name": "send",
|
|
632
|
+
"hasDynamicHelp": false,
|
|
633
|
+
"multiple": false,
|
|
634
|
+
"type": "option"
|
|
635
|
+
},
|
|
636
|
+
"text": {
|
|
637
|
+
"description": "Output plain text version (for AI agents)",
|
|
638
|
+
"name": "text",
|
|
639
|
+
"allowNo": false,
|
|
640
|
+
"type": "boolean"
|
|
641
641
|
}
|
|
642
642
|
},
|
|
643
643
|
"hasDynamicHelp": false,
|
|
644
644
|
"hiddenAliases": [],
|
|
645
|
-
"id": "
|
|
645
|
+
"id": "preview",
|
|
646
646
|
"pluginAlias": "@mailmodo/cli",
|
|
647
647
|
"pluginName": "@mailmodo/cli",
|
|
648
648
|
"pluginType": "core",
|
|
@@ -652,10 +652,10 @@
|
|
|
652
652
|
"relativePath": [
|
|
653
653
|
"dist",
|
|
654
654
|
"commands",
|
|
655
|
-
"
|
|
655
|
+
"preview",
|
|
656
656
|
"index.js"
|
|
657
657
|
]
|
|
658
658
|
}
|
|
659
659
|
},
|
|
660
|
-
"version": "0.0.36
|
|
660
|
+
"version": "0.0.36"
|
|
661
661
|
}
|