@mailmodo/cli 0.0.12 → 0.0.13-beta.pr14.22

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.
@@ -3,6 +3,7 @@ import { input } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
5
  import { API_ENDPOINTS, DNS_GUIDE_URL } from '../../lib/constants.js';
6
+ import { saveConfig } from '../../lib/config.js';
6
7
  import { saveYaml } from '../../lib/yaml-config.js';
7
8
  export default class Domain extends BaseCommand {
8
9
  static description = 'Set up and verify your sending domain';
@@ -24,22 +25,22 @@ export default class Domain extends BaseCommand {
24
25
  };
25
26
  async run() {
26
27
  const { flags } = await this.parse(Domain);
27
- await this.ensureAuth();
28
+ const config = await this.ensureAuth();
28
29
  if (flags.verify) {
29
- await this.verifyDomain(flags.json);
30
+ await this.verifyDomain(flags.json, config);
30
31
  return;
31
32
  }
32
33
  if (flags.status) {
33
- await this.showDomainStatus(flags.json);
34
+ await this.showDomainStatus(flags.json, config);
34
35
  return;
35
36
  }
36
- await this.setupDomain(flags);
37
+ await this.setupDomain(flags, config);
37
38
  }
38
39
  /**
39
40
  * Interactive domain setup: collects domain, sender email, and business address,
40
41
  * then calls the API to retrieve the required DNS records.
41
42
  */
42
- async setupDomain(flags) {
43
+ async setupDomain(flags, config) {
43
44
  const yamlConfig = await this.ensureYaml();
44
45
  this.log(`\n ${'─'.repeat(53)}`);
45
46
  this.log(` ${chalk.bold('DOMAIN SETUP')}`);
@@ -84,6 +85,7 @@ export default class Domain extends BaseCommand {
84
85
  yamlConfig.project.fromEmail = senderEmail;
85
86
  yamlConfig.project.address = address;
86
87
  await saveYaml(yamlConfig);
88
+ await saveConfig({ ...config, domain });
87
89
  const records = response.data?.dnsRecords || [];
88
90
  if (flags.json) {
89
91
  this.log(JSON.stringify({ dnsRecords: records, domain }, null, 2));
@@ -104,15 +106,21 @@ export default class Domain extends BaseCommand {
104
106
  message: "Press Enter once you've added the records, or 'skip'.",
105
107
  });
106
108
  if (action.toLowerCase() !== 'skip') {
107
- await this.verifyDomain(false);
109
+ await this.verifyDomain(false, { ...config, domain });
108
110
  }
109
111
  }
110
112
  }
111
113
  /**
112
114
  * Calls the domain verification API and displays pass/fail for each DNS record.
113
115
  */
114
- async verifyDomain(jsonOutput) {
115
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY));
116
+ async verifyDomain(jsonOutput, config) {
117
+ if (!config.domain) {
118
+ this.error(`No domain configured. Run ${chalk.cyan('mailmodo domain')} to set up your sending domain.`);
119
+ }
120
+ const domain = config.domain;
121
+ const response = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
122
+ domain,
123
+ }));
116
124
  if (!response.ok) {
117
125
  this.handleApiError(response);
118
126
  }
@@ -121,16 +129,16 @@ export default class Domain extends BaseCommand {
121
129
  this.log(JSON.stringify({ dkim, dmarc, spf }, null, 2));
122
130
  return;
123
131
  }
124
- this.log(` SPF ${spf === 'pass' ? chalk.green('✓') : chalk.red('✗ Not found')}`);
125
- this.log(` DKIM ${dkim === 'pass' ? chalk.green('✓') : chalk.red('✗ Not found')}`);
126
- this.log(` DMARC ${dmarc === 'pass' ? chalk.green('✓') : chalk.red('✗ Not found')}`);
127
- const allPassed = spf === 'pass' && dkim === 'pass' && dmarc === 'pass';
132
+ this.log(` SPF ${spf ? chalk.green('✓') : chalk.red('✗ Not found')}`);
133
+ this.log(` DKIM ${dkim ? chalk.green('✓') : chalk.red('✗ Not found')}`);
134
+ this.log(` DMARC ${dmarc ? chalk.green('✓') : chalk.red('✗ Not found')}`);
135
+ const allPassed = spf && dkim && dmarc;
128
136
  if (allPassed) {
129
137
  this.log(`\n ${chalk.green('✓')} Domain verified.\n`);
130
138
  }
131
139
  else {
132
140
  this.log(`\n ${chalk.yellow('Some records failed.')}`);
133
- if (dkim !== 'pass') {
141
+ if (!dkim) {
134
142
  this.log(`\n DKIM common mistakes:`);
135
143
  this.log(` - Using TXT instead of CNAME record type`);
136
144
  this.log(` - Including the full domain in the Host field`);
@@ -144,8 +152,14 @@ export default class Domain extends BaseCommand {
144
152
  * Displays domain health metrics including verification status,
145
153
  * bounce rate, and spam complaint rate.
146
154
  */
147
- async showDomainStatus(jsonOutput) {
148
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Loading domain status...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_STATUS));
155
+ async showDomainStatus(jsonOutput, config) {
156
+ if (!config.domain) {
157
+ this.error(`No domain configured. Run ${chalk.cyan('mailmodo domain')} to set up your sending domain.`);
158
+ }
159
+ const domain = config.domain;
160
+ const response = await this.withApiSpinner({ json: jsonOutput, text: ' Loading domain status...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_STATUS, {
161
+ domain,
162
+ }));
149
163
  if (!response.ok) {
150
164
  this.handleApiError(response);
151
165
  }
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
2
2
  import { editor, input, select } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
- import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP } from '../../lib/constants.js';
5
+ import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, } from '../../lib/constants.js';
6
6
  import { saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
7
7
  function isValidUrl(value) {
8
8
  try {
@@ -8,13 +8,30 @@ export default class Settings extends BaseCommand {
8
8
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  };
10
10
  run(): Promise<void>;
11
+ /**
12
+ * Prompts the user to pick a setting key to edit and dispatches
13
+ * to the appropriate handler for that key.
14
+ */
15
+ private promptEditSetting;
16
+ /**
17
+ * Fetches the domain verification status from the API.
18
+ * Returns true/false for verified/unverified, or null if unavailable.
19
+ */
20
+ private fetchDomainVerified;
21
+ /**
22
+ * Handles domain change: collects the new domain, sender email, and
23
+ * business address, calls the API to register them, displays the required
24
+ * DNS records, and saves the updated config. Emails won't send until the
25
+ * domain is re-verified.
26
+ */
27
+ private handleDomainChange;
28
+ private recordLabel;
11
29
  /**
12
30
  * Handles the logo file upload flow: validates the local file exists,
13
31
  * reads it, uploads to Mailmodo CDN via API, and updates both logoFile
14
32
  * and logoUrl in the project config.
15
33
  *
16
34
  * @param {import('../../lib/yaml-config.js').MailmodoYaml} yamlConfig - The full YAML config to update and save.
17
- * @param {boolean} jsonOutput - When true, spinner uses stderr so stdout stays clean.
18
35
  */
19
36
  private handleLogoUpload;
20
37
  }
@@ -5,7 +5,7 @@ import { existsSync } from 'node:fs';
5
5
  import { readFile } from 'node:fs/promises';
6
6
  import { resolve } from 'node:path';
7
7
  import { BaseCommand } from '../../lib/base-command.js';
8
- import { API_ENDPOINTS } from '../../lib/constants.js';
8
+ import { API_ENDPOINTS, DNS_GUIDE_URL } from '../../lib/constants.js';
9
9
  import { saveYaml } from '../../lib/yaml-config.js';
10
10
  const SETTINGS_GROUPS = Object.freeze({
11
11
  billing: ['monthly_cap'],
@@ -14,6 +14,11 @@ const SETTINGS_GROUPS = Object.freeze({
14
14
  identity: ['from_name', 'from_email', 'reply_to'],
15
15
  integrations: ['webhook_url'],
16
16
  });
17
+ const SETUP_HINTS = {
18
+ address: "'mailmodo domain'",
19
+ domain: "'mailmodo domain'",
20
+ monthlyCap: "'mailmodo billing --cap <n>'",
21
+ };
17
22
  /**
18
23
  * Converts a user-facing snake_case YAML setting key to its
19
24
  * corresponding camelCase TypeScript property name.
@@ -65,66 +70,180 @@ export default class Settings extends BaseCommand {
65
70
  this.log(JSON.stringify({ settings: project }, null, 2));
66
71
  return;
67
72
  }
73
+ const domainVerified = await this.fetchDomainVerified(project.domain);
68
74
  this.log(`\n Current settings for ${chalk.bold(project.name || 'project')}:\n`);
69
75
  for (const [group, keys] of Object.entries(SETTINGS_GROUPS)) {
76
+ const availableKeys = keys.filter((key) => settingKeyToProp(key) in project);
77
+ if (availableKeys.length === 0) {
78
+ const hint = SETUP_HINTS[settingKeyToProp(keys[0])];
79
+ if (hint) {
80
+ this.log(` ${chalk.bold(group.charAt(0).toUpperCase() + group.slice(1))}`);
81
+ this.log(` ${'─'.repeat(49)}`);
82
+ this.log(` ${chalk.dim(`Run ${hint} to configure.`)}`);
83
+ this.log('');
84
+ }
85
+ continue;
86
+ }
70
87
  this.log(` ${chalk.bold(group.charAt(0).toUpperCase() + group.slice(1))}`);
71
88
  this.log(` ${'─'.repeat(49)}`);
72
- for (const key of keys) {
89
+ for (const key of availableKeys) {
73
90
  const propKey = settingKeyToProp(key);
74
91
  const value = project[propKey];
75
- const displayValue = value ? String(value) : chalk.dim('(not set)');
92
+ let displayValue = value ? String(value) : chalk.dim('(not set)');
93
+ if (key === 'domain' && value && domainVerified === true) {
94
+ displayValue += ` ${chalk.green('✓ verified')}`;
95
+ }
96
+ else if (key === 'domain' && value && domainVerified === false) {
97
+ displayValue += ` ${chalk.red('✗ not verified')}`;
98
+ }
76
99
  this.log(` ${key.padEnd(16)} ${displayValue}`);
77
100
  }
101
+ const missingKeys = keys.filter((key) => !(settingKeyToProp(key) in project));
102
+ for (const key of missingKeys) {
103
+ const hint = SETUP_HINTS[settingKeyToProp(key)];
104
+ if (hint) {
105
+ this.log(` ${key.padEnd(16)} ${chalk.dim(`(run ${hint} to set up)`)}`);
106
+ }
107
+ }
78
108
  this.log('');
79
109
  }
80
110
  if (!flags.yes) {
81
- const editKey = await input({
82
- default: 'n',
83
- message: "Edit a setting? (key or 'n'):",
111
+ await this.promptEditSetting(yamlConfig);
112
+ }
113
+ }
114
+ /**
115
+ * Prompts the user to pick a setting key to edit and dispatches
116
+ * to the appropriate handler for that key.
117
+ */
118
+ async promptEditSetting(yamlConfig) {
119
+ const { project } = yamlConfig;
120
+ const editKey = await input({
121
+ default: 'n',
122
+ message: "Edit a setting? (key or 'n'):",
123
+ });
124
+ if (editKey === 'n')
125
+ return;
126
+ const editPropKey = settingKeyToProp(editKey);
127
+ if (!(editPropKey in project)) {
128
+ const hint = SETUP_HINTS[editPropKey];
129
+ if (hint) {
130
+ this.log(`\n ${editKey} is not configured yet. Run ${chalk.cyan(hint)} to set it up.\n`);
131
+ }
132
+ else {
133
+ this.log(`\n Unknown setting: ${editKey}\n`);
134
+ }
135
+ return;
136
+ }
137
+ if (editKey === 'logo_file') {
138
+ await this.handleLogoUpload(yamlConfig);
139
+ return;
140
+ }
141
+ if (editKey === 'domain') {
142
+ await this.handleDomainChange(yamlConfig);
143
+ return;
144
+ }
145
+ if (editKey === 'email_style') {
146
+ const style = await select({
147
+ choices: [
148
+ { name: 'plain', value: 'plain' },
149
+ { name: 'branded', value: 'branded' },
150
+ ],
151
+ message: 'Email style:',
84
152
  });
85
- if (editKey !== 'n') {
86
- const editPropKey = settingKeyToProp(editKey);
87
- if (!(editPropKey in project)) {
88
- this.log(`\n Unknown setting: ${editKey}\n`);
89
- return;
90
- }
91
- if (editKey === 'logo_file') {
92
- await this.handleLogoUpload(yamlConfig, flags.json);
93
- return;
94
- }
95
- if (editKey === 'email_style') {
96
- const style = await select({
97
- choices: [
98
- { name: 'plain', value: 'plain' },
99
- { name: 'branded', value: 'branded' },
100
- ],
101
- message: 'Email style:',
102
- });
103
- project.emailStyle = style;
104
- await saveYaml(yamlConfig);
105
- this.log(`\n ${chalk.green('✓')} email_style updated to ${chalk.cyan(style)}`);
106
- this.log(` Run ${chalk.cyan("'mailmodo deploy'")} to apply.\n`);
107
- return;
108
- }
109
- const newValue = await input({
110
- message: `New value for ${editKey}:`,
111
- });
112
- project[editPropKey] =
113
- editPropKey === 'monthlyCap' ? Number(newValue) : newValue;
114
- await saveYaml(yamlConfig);
115
- this.log(`\n ${chalk.green('✓')} Updated. Run ${chalk.cyan("'mailmodo deploy'")} to apply.\n`);
153
+ project.emailStyle = style;
154
+ await saveYaml(yamlConfig);
155
+ this.log(`\n ${chalk.green('✓')} email_style updated to ${chalk.cyan(style)}`);
156
+ this.log(` Run ${chalk.cyan("'mailmodo deploy'")} to apply.\n`);
157
+ return;
158
+ }
159
+ const newValue = await input({
160
+ message: `New value for ${editKey}:`,
161
+ });
162
+ project[editPropKey] =
163
+ editPropKey === 'monthlyCap' ? Number(newValue) : newValue;
164
+ await saveYaml(yamlConfig);
165
+ this.log(`\n ${chalk.green('✓')} Updated. Run ${chalk.cyan("'mailmodo deploy'")} to apply.\n`);
166
+ }
167
+ /**
168
+ * Fetches the domain verification status from the API.
169
+ * Returns true/false for verified/unverified, or null if unavailable.
170
+ */
171
+ async fetchDomainVerified(domain) {
172
+ if (!domain)
173
+ return null;
174
+ try {
175
+ await this.ensureAuth();
176
+ const response = await this.apiClient.get(API_ENDPOINTS.DOMAIN_STATUS, { domain });
177
+ if (!response.ok) {
178
+ this.log(` ${chalk.dim('Could not fetch domain status. Run')} ${chalk.cyan("'mailmodo domain --status'")} ${chalk.dim('to check manually.')}`);
179
+ return null;
116
180
  }
181
+ return response.data?.verified === true;
182
+ }
183
+ catch {
184
+ this.log(` ${chalk.dim('Could not reach API for domain status. Skipping verification check.')}`);
185
+ return null;
117
186
  }
118
187
  }
188
+ /**
189
+ * Handles domain change: collects the new domain, sender email, and
190
+ * business address, calls the API to register them, displays the required
191
+ * DNS records, and saves the updated config. Emails won't send until the
192
+ * domain is re-verified.
193
+ */
194
+ async handleDomainChange(yamlConfig) {
195
+ const newDomain = await input({
196
+ message: 'New domain:',
197
+ validate: (v) => (v?.trim() ? true : 'Domain is required'),
198
+ });
199
+ const newFromEmail = await input({
200
+ default: yamlConfig.project.fromEmail || '',
201
+ message: 'Sender email (from address):',
202
+ validate: (v) => (v?.includes('@') ? true : 'Please enter a valid email'),
203
+ });
204
+ const newAddress = await input({
205
+ default: yamlConfig.project.address || '',
206
+ message: 'Business address (required by law):',
207
+ validate: (v) => (v?.trim() ? true : 'Address is required'),
208
+ });
209
+ await this.ensureAuth();
210
+ const response = await this.apiClient.post(API_ENDPOINTS.DOMAIN, {
211
+ address: newAddress,
212
+ domain: newDomain,
213
+ fromEmail: newFromEmail,
214
+ });
215
+ if (!response.ok) {
216
+ this.handleApiError(response);
217
+ }
218
+ const records = response.data?.dnsRecords || [];
219
+ yamlConfig.project.domain = newDomain;
220
+ yamlConfig.project.fromEmail = newFromEmail;
221
+ yamlConfig.project.address = newAddress;
222
+ await saveYaml(yamlConfig);
223
+ this.log(`\n Domain, sender email, and business address updated. You will need to re-verify.`);
224
+ this.log(` New DNS records:\n`);
225
+ for (const [i, record] of records.entries()) {
226
+ this.log(` ${chalk.bold(`RECORD ${i + 1} — ${this.recordLabel(i)}`)}`);
227
+ this.log(` Type: ${record.type}`);
228
+ this.log(` Host: ${record.host}`);
229
+ this.log(` Value: ${record.value}\n`);
230
+ }
231
+ this.log(` Run ${chalk.cyan("'mailmodo domain --verify'")} once records are added.`);
232
+ this.log(` Emails will not send until the new domain is verified.`);
233
+ this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
234
+ }
235
+ recordLabel(index) {
236
+ const labels = ['SPF', 'DKIM', 'DMARC'];
237
+ return labels[index] || `Record ${index + 1}`;
238
+ }
119
239
  /**
120
240
  * Handles the logo file upload flow: validates the local file exists,
121
241
  * reads it, uploads to Mailmodo CDN via API, and updates both logoFile
122
242
  * and logoUrl in the project config.
123
243
  *
124
244
  * @param {import('../../lib/yaml-config.js').MailmodoYaml} yamlConfig - The full YAML config to update and save.
125
- * @param {boolean} jsonOutput - When true, spinner uses stderr so stdout stays clean.
126
245
  */
127
- async handleLogoUpload(yamlConfig, jsonOutput) {
246
+ async handleLogoUpload(yamlConfig) {
128
247
  const logoPath = await input({ message: 'Path to logo file:' });
129
248
  const resolvedPath = resolve(logoPath);
130
249
  if (!existsSync(resolvedPath)) {
@@ -135,7 +254,7 @@ export default class Settings extends BaseCommand {
135
254
  const fileBuffer = await readFile(resolvedPath);
136
255
  const formData = new FormData();
137
256
  formData.append('logo', new Blob([new Uint8Array(fileBuffer)]), logoPath.split(/[/\\]/).pop() || 'logo.png');
138
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Uploading logo...' }, () => this.apiClient.postFormData(API_ENDPOINTS.ASSETS_LOGO, formData));
257
+ const response = await this.apiClient.postFormData(API_ENDPOINTS.ASSETS_LOGO, formData);
139
258
  if (!response.ok) {
140
259
  this.handleApiError(response);
141
260
  }
@@ -1,6 +1,7 @@
1
1
  export interface MailmodoConfig {
2
2
  accountName?: string;
3
3
  apiKey: string;
4
+ domain?: string;
4
5
  email?: string;
5
6
  freeRemaining?: number;
6
7
  }
@@ -1,4 +1,4 @@
1
- export declare const API_BASE_URL: string;
1
+ export declare const API_BASE_URL = "https://app-vertex-debug.azurewebsites.net";
2
2
  export declare const API_ENDPOINTS: Readonly<{
3
3
  ANALYTICS: "/analytics";
4
4
  ANALYZE: "/analyze";
@@ -1,6 +1,7 @@
1
1
  /** Set by `bin/dev.js` when running the CLI locally (tsx bootstrap). */
2
2
  const DEV_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
3
- const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
3
+ // const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
4
+ const PRODUCTION_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
4
5
  export const API_BASE_URL = process.env.MAILMODO_DEV_TSX
5
6
  ? DEV_API_BASE_URL
6
7
  : PRODUCTION_API_BASE_URL;
@@ -473,14 +473,19 @@
473
473
  "index.js"
474
474
  ]
475
475
  },
476
- "settings": {
476
+ "preview": {
477
477
  "aliases": [],
478
- "args": {},
479
- "description": "View and update project settings",
478
+ "args": {
479
+ "id": {
480
+ "description": "Email ID to preview",
481
+ "name": "id"
482
+ }
483
+ },
484
+ "description": "Preview an email in browser, as text, or send a test",
480
485
  "examples": [
481
- "<%= config.bin %> settings",
482
- "<%= config.bin %> settings --set brand_color=#0F3460",
483
- "<%= config.bin %> settings --json"
486
+ "<%= config.bin %> preview welcome",
487
+ "<%= config.bin %> preview welcome --text",
488
+ "<%= config.bin %> preview welcome --send me@example.com"
484
489
  ],
485
490
  "flags": {
486
491
  "json": {
@@ -496,17 +501,23 @@
496
501
  "allowNo": false,
497
502
  "type": "boolean"
498
503
  },
499
- "set": {
500
- "description": "Set a setting (format: key=value)",
501
- "name": "set",
504
+ "send": {
505
+ "description": "Send test email to this address",
506
+ "name": "send",
502
507
  "hasDynamicHelp": false,
503
508
  "multiple": false,
504
509
  "type": "option"
510
+ },
511
+ "text": {
512
+ "description": "Output plain text version (for AI agents)",
513
+ "name": "text",
514
+ "allowNo": false,
515
+ "type": "boolean"
505
516
  }
506
517
  },
507
518
  "hasDynamicHelp": false,
508
519
  "hiddenAliases": [],
509
- "id": "settings",
520
+ "id": "preview",
510
521
  "pluginAlias": "@mailmodo/cli",
511
522
  "pluginName": "@mailmodo/cli",
512
523
  "pluginType": "core",
@@ -516,23 +527,18 @@
516
527
  "relativePath": [
517
528
  "dist",
518
529
  "commands",
519
- "settings",
530
+ "preview",
520
531
  "index.js"
521
532
  ]
522
533
  },
523
- "preview": {
534
+ "settings": {
524
535
  "aliases": [],
525
- "args": {
526
- "id": {
527
- "description": "Email ID to preview",
528
- "name": "id"
529
- }
530
- },
531
- "description": "Preview an email in browser, as text, or send a test",
536
+ "args": {},
537
+ "description": "View and update project settings",
532
538
  "examples": [
533
- "<%= config.bin %> preview welcome",
534
- "<%= config.bin %> preview welcome --text",
535
- "<%= config.bin %> preview welcome --send me@example.com"
539
+ "<%= config.bin %> settings",
540
+ "<%= config.bin %> settings --set brand_color=#0F3460",
541
+ "<%= config.bin %> settings --json"
536
542
  ],
537
543
  "flags": {
538
544
  "json": {
@@ -548,23 +554,17 @@
548
554
  "allowNo": false,
549
555
  "type": "boolean"
550
556
  },
551
- "send": {
552
- "description": "Send test email to this address",
553
- "name": "send",
557
+ "set": {
558
+ "description": "Set a setting (format: key=value)",
559
+ "name": "set",
554
560
  "hasDynamicHelp": false,
555
561
  "multiple": false,
556
562
  "type": "option"
557
- },
558
- "text": {
559
- "description": "Output plain text version (for AI agents)",
560
- "name": "text",
561
- "allowNo": false,
562
- "type": "boolean"
563
563
  }
564
564
  },
565
565
  "hasDynamicHelp": false,
566
566
  "hiddenAliases": [],
567
- "id": "preview",
567
+ "id": "settings",
568
568
  "pluginAlias": "@mailmodo/cli",
569
569
  "pluginName": "@mailmodo/cli",
570
570
  "pluginType": "core",
@@ -574,7 +574,7 @@
574
574
  "relativePath": [
575
575
  "dist",
576
576
  "commands",
577
- "preview",
577
+ "settings",
578
578
  "index.js"
579
579
  ]
580
580
  },
@@ -618,5 +618,5 @@
618
618
  ]
619
619
  }
620
620
  },
621
- "version": "0.0.12"
621
+ "version": "0.0.13-beta.pr14.22"
622
622
  }
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.12",
4
+ "version": "0.0.13-beta.pr14.22",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"
@@ -24,13 +24,17 @@
24
24
  "@types/chai": "^4",
25
25
  "@types/js-yaml": "^4.0.9",
26
26
  "@types/mocha": "^10",
27
- "@types/node": "^18",
27
+ "@types/node": "^18.19.130",
28
28
  "chai": "^4",
29
29
  "eslint": "^9",
30
30
  "eslint-config-oclif": "^6",
31
31
  "eslint-config-prettier": "^10",
32
+ "eslint-plugin-unused-imports": "^4.4.1",
33
+ "husky": "^9.1.7",
34
+ "lint-staged": "^16.4.0",
32
35
  "mocha": "^11",
33
36
  "oclif": "^4",
37
+ "prettier": "^3.8.2",
34
38
  "shx": "^0.3.3",
35
39
  "ts-node": "^10",
36
40
  "tsx": "^4.21.0",
@@ -69,7 +73,15 @@
69
73
  "posttest": "npm run lint",
70
74
  "prepack": "oclif manifest && oclif readme",
71
75
  "test": "mocha --forbid-only \"test/**/*.test.ts\"",
72
- "version": "oclif readme && git add README.md"
76
+ "version": "oclif readme && git add README.md",
77
+ "prepare": "husky"
73
78
  },
74
- "types": "dist/index.d.ts"
79
+ "types": "dist/index.d.ts",
80
+ "lint-staged": {
81
+ "*.{ts,tsx,js,jsx,mjs,cjs}": [
82
+ "prettier --write",
83
+ "eslint --fix"
84
+ ],
85
+ "*.{json,md,yaml,yml}": "prettier --write"
86
+ }
75
87
  }