@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.
@@ -19,7 +19,6 @@ export default class Billing extends BaseCommand {
19
19
  private formatCurrency;
20
20
  private formatPaymentMethod;
21
21
  private formatUsageBlock;
22
- private persistMonthlyCap;
23
22
  private pluralize;
24
23
  private purchaseBlocks;
25
24
  private setCap;
@@ -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
- if (data.tier !== 'free') {
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
- else {
101
- this.log(' Active blocks:');
102
- for (const block of data.activeBlocks) {
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 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)})`;
115
+ const parts = [];
116
+ if (typeof cap.inBlocks === 'number') {
117
+ parts.push(`${cap.inBlocks} ${this.pluralize('block', cap.inBlocks)}`);
125
118
  }
126
- if (hasBlocks) {
127
- return `${cap.inBlocks} ${this.pluralize('block', cap.inBlocks)}`;
119
+ if (typeof cap.inEmails === 'number') {
120
+ parts.push(`${cap.inEmails.toLocaleString()} emails`);
128
121
  }
129
- if (hasEmails) {
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 at ${new Date(block.activatedAt).toLocaleDateString('en-US')}`
157
+ ? `, activated ${new Date(block.activatedAt).toLocaleDateString('en-US')}`
168
158
  : '';
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);
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
- const data = await this.applyBillingCap({
204
- autoChargeBlockCount,
205
- cap,
206
- json: jsonOutput,
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
- await this.persistMonthlyCap(data.capBlocks);
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
  }
@@ -9,7 +9,6 @@ export default class Settings extends BaseCommand {
9
9
  };
10
10
  run(): Promise<void>;
11
11
  private applySetFlag;
12
- private applyMonthlyCapChange;
13
12
  private displaySettingsGroup;
14
13
  /**
15
14
  * Prompts the user to pick a setting key to edit and dispatches
@@ -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
- if (propKey === 'monthlyCap') {
77
- await this.applyMonthlyCapChange(yamlConfig, value, isJson);
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] = newValue;
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;
@@ -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,
@@ -512,19 +512,14 @@
512
512
  "index.js"
513
513
  ]
514
514
  },
515
- "preview": {
515
+ "settings": {
516
516
  "aliases": [],
517
- "args": {
518
- "id": {
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 %> preview welcome",
526
- "<%= config.bin %> preview welcome --text",
527
- "<%= config.bin %> preview welcome --send me@example.com"
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
- "send": {
544
- "description": "Send test email to this address",
545
- "name": "send",
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": "preview",
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
- "preview",
558
+ "settings",
570
559
  "index.js"
571
560
  ]
572
561
  },
573
- "settings": {
562
+ "status": {
574
563
  "aliases": [],
575
564
  "args": {},
576
- "description": "View and update project settings",
565
+ "description": "View email performance metrics and quota usage",
577
566
  "examples": [
578
- "<%= config.bin %> settings",
579
- "<%= config.bin %> settings --set brand_color=#0F3460",
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": "settings",
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
- "settings",
597
+ "status",
617
598
  "index.js"
618
599
  ]
619
600
  },
620
- "status": {
601
+ "preview": {
621
602
  "aliases": [],
622
- "args": {},
623
- "description": "View email performance metrics and quota usage",
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 %> status",
626
- "<%= config.bin %> status --json"
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": "status",
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
- "status",
655
+ "preview",
656
656
  "index.js"
657
657
  ]
658
658
  }
659
659
  },
660
- "version": "0.0.36-beta.pr38.61"
660
+ "version": "0.0.36"
661
661
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mailmodo/cli",
3
3
  "description": "Email lifecycle automation for the AI-native builder generation.",
4
- "version": "0.0.36-beta.pr38.61",
4
+ "version": "0.0.36",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"