@mailmodo/cli 0.0.28 → 0.0.29-beta.pr31.48

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.
@@ -1,7 +1,7 @@
1
1
  import { confirm, input } from '@inquirer/prompts';
2
2
  import chalk from 'chalk';
3
3
  import { BaseCommand } from '../../lib/base-command.js';
4
- import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, DNS_GUIDE_URL, } from '../../lib/constants.js';
4
+ import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, } from '../../lib/constants.js';
5
5
  import { loadTemplate, saveYaml, } from '../../lib/yaml-config.js';
6
6
  export default class Deploy extends BaseCommand {
7
7
  static description = 'Deploy email sequences and verify sending domain';
@@ -232,7 +232,7 @@ export default class Deploy extends BaseCommand {
232
232
  yamlConfig.project.fromEmail = senderEmail;
233
233
  yamlConfig.project.address = address;
234
234
  await saveYaml(yamlConfig);
235
- this.showDnsRecords(domainResponse.data?.dnsRecords || [], flags.json);
235
+ this.showDnsRecords(domainResponse.data?.dnsRecords || [], flags.json, domainResponse.data?.dnsGuideUrl);
236
236
  if (flags.yes) {
237
237
  return this.verifyDomain(flags.json, domain);
238
238
  }
@@ -273,7 +273,7 @@ export default class Deploy extends BaseCommand {
273
273
  });
274
274
  return { address, domain, senderEmail };
275
275
  }
276
- showDnsRecords(dnsRecords, jsonOutput) {
276
+ showDnsRecords(dnsRecords, jsonOutput, dnsGuideUrl) {
277
277
  if (jsonOutput)
278
278
  return;
279
279
  this.log(`\n Add these ${dnsRecords.length} DNS records to your domain provider:\n`);
@@ -284,7 +284,8 @@ export default class Deploy extends BaseCommand {
284
284
  this.log(` Value: ${record.value}\n`);
285
285
  }
286
286
  this.log(` DNS changes take 5–30 minutes to propagate.`);
287
- this.log(` Full guide: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
287
+ if (dnsGuideUrl)
288
+ this.log(` Full guide: ${chalk.cyan(dnsGuideUrl)}\n`);
288
289
  }
289
290
  async verifyDomain(jsonOutput, domain) {
290
291
  const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
@@ -293,7 +294,7 @@ export default class Deploy extends BaseCommand {
293
294
  if (!verify.ok) {
294
295
  this.handleApiError(verify);
295
296
  }
296
- const { dkim, dmarc, domainStatus, returnPath } = verify.data;
297
+ const { dkim, dmarc, dnsGuideUrl, domainStatus, returnPath } = verify.data;
297
298
  const allPassed = domainStatus === 'VERIFIED';
298
299
  if (!jsonOutput) {
299
300
  this.log(` DKIM ${dkim ? chalk.green('✓') : chalk.red('✗')}`);
@@ -304,7 +305,8 @@ export default class Deploy extends BaseCommand {
304
305
  }
305
306
  else {
306
307
  this.log(`\n ${chalk.yellow('Some records failed.')} Fix them and run ${chalk.cyan('mailmodo domain --verify')}.`);
307
- this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
308
+ if (dnsGuideUrl)
309
+ this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
308
310
  }
309
311
  }
310
312
  return allPassed;
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
2
2
  import { input } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
- import { API_ENDPOINTS, DNS_GUIDE_URL } from '../../lib/constants.js';
5
+ import { API_ENDPOINTS } from '../../lib/constants.js';
6
6
  import { saveConfig } from '../../lib/config.js';
7
7
  import { saveYaml } from '../../lib/yaml-config.js';
8
8
  export default class Domain extends BaseCommand {
@@ -45,22 +45,31 @@ 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
- const { domain, senderEmail, address } = await this.collectDomainInputs(flags.yes, yamlConfig);
49
- const response = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, {
48
+ const { domain, senderEmail, fromName, replyTo, address } = await this.collectDomainInputs(flags.yes, yamlConfig);
49
+ const apiPayload = {
50
50
  address,
51
51
  domain,
52
52
  fromEmail: senderEmail,
53
- }));
53
+ };
54
+ if (fromName)
55
+ apiPayload.fromName = fromName;
56
+ if (replyTo)
57
+ apiPayload.replyTo = replyTo;
58
+ const response = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, apiPayload));
54
59
  if (!response.ok) {
55
60
  this.handleApiError(response);
56
61
  }
57
62
  yamlConfig.project.domain = domain;
58
63
  yamlConfig.project.fromEmail = senderEmail;
59
64
  yamlConfig.project.address = address;
65
+ if (fromName)
66
+ yamlConfig.project.fromName = fromName;
67
+ if (replyTo)
68
+ yamlConfig.project.replyTo = replyTo;
60
69
  await saveYaml(yamlConfig);
61
70
  await saveConfig({ ...config, domain });
62
71
  const records = response.data?.dnsRecords || [];
63
- const guideUrl = response.data?.dnsGuideUrl ?? DNS_GUIDE_URL;
72
+ const guideUrl = response.data?.dnsGuideUrl;
64
73
  if (flags.json) {
65
74
  this.log(JSON.stringify({ dnsRecords: records, domain }, null, 2));
66
75
  return;
@@ -73,7 +82,8 @@ export default class Domain extends BaseCommand {
73
82
  this.log(` Value: ${record.value}\n`);
74
83
  }
75
84
  this.log(` DNS changes take 5–30 minutes to propagate.`);
76
- this.log(` Full guide: ${chalk.cyan(guideUrl)}\n`);
85
+ if (guideUrl)
86
+ this.log(` Full guide: ${chalk.cyan(guideUrl)}\n`);
77
87
  if (!flags.yes) {
78
88
  const action = await input({
79
89
  default: '',
@@ -98,7 +108,7 @@ export default class Domain extends BaseCommand {
98
108
  if (!response.ok) {
99
109
  this.handleApiError(response);
100
110
  }
101
- const { dkim, dmarc, returnPath, domainStatus } = response.data;
111
+ const { dkim, dmarc, dnsGuideUrl, returnPath, domainStatus } = response.data;
102
112
  if (jsonOutput) {
103
113
  this.log(JSON.stringify({ dkim, dmarc, returnPath, domainStatus }, null, 2));
104
114
  return;
@@ -124,7 +134,8 @@ export default class Domain extends BaseCommand {
124
134
  this.log(` - Cloudflare: proxy must be OFF (grey cloud, not orange)`);
125
135
  }
126
136
  this.log(`\n Fix the records and run ${chalk.cyan('mailmodo domain --verify')} again.`);
127
- this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
137
+ if (dnsGuideUrl)
138
+ this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
128
139
  }
129
140
  }
130
141
  /**
@@ -161,6 +172,8 @@ export default class Domain extends BaseCommand {
161
172
  return {
162
173
  address: yamlConfig.project?.address || '',
163
174
  domain,
175
+ fromName: yamlConfig.project?.fromName || '',
176
+ replyTo: yamlConfig.project?.replyTo || '',
164
177
  senderEmail: yamlConfig.project?.fromEmail || '',
165
178
  };
166
179
  }
@@ -174,12 +187,20 @@ export default class Domain extends BaseCommand {
174
187
  message: 'Sender email address:',
175
188
  validate: (v) => (v?.includes('@') ? true : 'Please enter a valid email'),
176
189
  });
190
+ const fromName = await input({
191
+ default: yamlConfig.project?.fromName || '',
192
+ message: 'Display name (optional, shown as sender name):',
193
+ });
194
+ const replyTo = await input({
195
+ default: yamlConfig.project?.replyTo || '',
196
+ message: 'Reply-to address (optional, press Enter to use sender email):',
197
+ });
177
198
  const address = await input({
178
199
  default: yamlConfig.project?.address,
179
200
  message: 'Business address (required by law):',
180
201
  validate: (v) => (v?.trim() ? true : 'Address is required'),
181
202
  });
182
- return { address, domain, senderEmail };
203
+ return { address, domain, fromName, replyTo, senderEmail };
183
204
  }
184
205
  recordLabel(index) {
185
206
  const labels = ['DKIM', 'DMARC', 'Return Path'];
@@ -8,4 +8,5 @@ export default class Init extends BaseCommand {
8
8
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  };
10
10
  run(): Promise<void>;
11
+ private confirmOverwriteIfNeeded;
11
12
  }
@@ -1,9 +1,9 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { editor, input, select } from '@inquirer/prompts';
2
+ import { confirm, editor, input, select } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
5
  import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, } from '../../lib/constants.js';
6
- import { saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
6
+ import { loadYaml, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
7
7
  function isValidUrl(value) {
8
8
  try {
9
9
  return Boolean(new URL(value));
@@ -42,6 +42,8 @@ export default class Init extends BaseCommand {
42
42
  async run() {
43
43
  const { flags } = await this.parse(Init);
44
44
  await this.ensureAuth();
45
+ if (!(await this.confirmOverwriteIfNeeded(flags)))
46
+ return;
45
47
  let productUrl = flags.url;
46
48
  if (!productUrl) {
47
49
  productUrl = await input({
@@ -177,4 +179,21 @@ export default class Init extends BaseCommand {
177
179
  this.log(` Created ${chalk.green('mailmodo.yaml')} + ${chalk.green(String(emailConfigs.length))} email templates in ${chalk.green('/mailmodo')}\n`);
178
180
  this.log(` Run ${chalk.cyan("'mailmodo emails'")} to review.\n`);
179
181
  }
182
+ async confirmOverwriteIfNeeded(flags) {
183
+ const existing = await loadYaml();
184
+ if (!existing)
185
+ return true;
186
+ if (flags.yes)
187
+ return true;
188
+ this.log(`\n ${chalk.yellow('⚠')} ${chalk.bold('mailmodo.yaml already exists.')}`);
189
+ this.log(` Running init will overwrite your current project configuration and all email templates.\n`);
190
+ const proceed = await confirm({
191
+ default: false,
192
+ message: 'Overwrite existing configuration and templates?',
193
+ });
194
+ if (!proceed) {
195
+ this.log(`\n Init cancelled. Run ${chalk.cyan('mailmodo edit')} to modify individual emails.\n`);
196
+ }
197
+ return proceed;
198
+ }
180
199
  }
@@ -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, DNS_GUIDE_URL } from '../../lib/constants.js';
8
+ import { API_ENDPOINTS } 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'],
@@ -232,6 +232,7 @@ export default class Settings extends BaseCommand {
232
232
  this.handleApiError(response);
233
233
  }
234
234
  const records = response.data?.dnsRecords || [];
235
+ const dnsGuideUrl = response.data?.dnsGuideUrl;
235
236
  yamlConfig.project.domain = newDomain;
236
237
  yamlConfig.project.fromEmail = newFromEmail;
237
238
  yamlConfig.project.address = newAddress;
@@ -246,7 +247,8 @@ export default class Settings extends BaseCommand {
246
247
  }
247
248
  this.log(` Run ${chalk.cyan("'mailmodo domain --verify'")} once records are added.`);
248
249
  this.log(` Emails will not send until the new domain is verified.`);
249
- this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
250
+ if (dnsGuideUrl)
251
+ this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
250
252
  }
251
253
  recordLabel(index) {
252
254
  const labels = ['DKIM', 'DMARC', 'Return Path'];
@@ -76,67 +76,6 @@
76
76
  "index.js"
77
77
  ]
78
78
  },
79
- "contacts": {
80
- "aliases": [],
81
- "args": {},
82
- "description": "Manage contacts — search, export, or delete",
83
- "examples": [
84
- "<%= config.bin %> contacts",
85
- "<%= config.bin %> contacts --search sarah@example.com",
86
- "<%= config.bin %> contacts --export # GDPR CSV → contacts.csv",
87
- "<%= config.bin %> contacts --delete sarah@example.com"
88
- ],
89
- "flags": {
90
- "json": {
91
- "description": "Output as JSON",
92
- "name": "json",
93
- "allowNo": false,
94
- "type": "boolean"
95
- },
96
- "yes": {
97
- "char": "y",
98
- "description": "Skip confirmation prompts",
99
- "name": "yes",
100
- "allowNo": false,
101
- "type": "boolean"
102
- },
103
- "delete": {
104
- "description": "GDPR hard delete a contact by email",
105
- "name": "delete",
106
- "hasDynamicHelp": false,
107
- "multiple": false,
108
- "type": "option"
109
- },
110
- "export": {
111
- "description": "Export all contacts as GDPR-compliant CSV (writes contacts.csv in the current directory)",
112
- "name": "export",
113
- "allowNo": false,
114
- "type": "boolean"
115
- },
116
- "search": {
117
- "description": "Search for a contact by email",
118
- "name": "search",
119
- "hasDynamicHelp": false,
120
- "multiple": false,
121
- "type": "option"
122
- }
123
- },
124
- "hasDynamicHelp": false,
125
- "hiddenAliases": [],
126
- "id": "contacts",
127
- "pluginAlias": "@mailmodo/cli",
128
- "pluginName": "@mailmodo/cli",
129
- "pluginType": "core",
130
- "strict": true,
131
- "enableJsonFlag": false,
132
- "isESM": true,
133
- "relativePath": [
134
- "dist",
135
- "commands",
136
- "contacts",
137
- "index.js"
138
- ]
139
- },
140
79
  "deploy": {
141
80
  "aliases": [],
142
81
  "args": {},
@@ -365,6 +304,67 @@
365
304
  "index.js"
366
305
  ]
367
306
  },
307
+ "contacts": {
308
+ "aliases": [],
309
+ "args": {},
310
+ "description": "Manage contacts — search, export, or delete",
311
+ "examples": [
312
+ "<%= config.bin %> contacts",
313
+ "<%= config.bin %> contacts --search sarah@example.com",
314
+ "<%= config.bin %> contacts --export # GDPR CSV → contacts.csv",
315
+ "<%= config.bin %> contacts --delete sarah@example.com"
316
+ ],
317
+ "flags": {
318
+ "json": {
319
+ "description": "Output as JSON",
320
+ "name": "json",
321
+ "allowNo": false,
322
+ "type": "boolean"
323
+ },
324
+ "yes": {
325
+ "char": "y",
326
+ "description": "Skip confirmation prompts",
327
+ "name": "yes",
328
+ "allowNo": false,
329
+ "type": "boolean"
330
+ },
331
+ "delete": {
332
+ "description": "GDPR hard delete a contact by email",
333
+ "name": "delete",
334
+ "hasDynamicHelp": false,
335
+ "multiple": false,
336
+ "type": "option"
337
+ },
338
+ "export": {
339
+ "description": "Export all contacts as GDPR-compliant CSV (writes contacts.csv in the current directory)",
340
+ "name": "export",
341
+ "allowNo": false,
342
+ "type": "boolean"
343
+ },
344
+ "search": {
345
+ "description": "Search for a contact by email",
346
+ "name": "search",
347
+ "hasDynamicHelp": false,
348
+ "multiple": false,
349
+ "type": "option"
350
+ }
351
+ },
352
+ "hasDynamicHelp": false,
353
+ "hiddenAliases": [],
354
+ "id": "contacts",
355
+ "pluginAlias": "@mailmodo/cli",
356
+ "pluginName": "@mailmodo/cli",
357
+ "pluginType": "core",
358
+ "strict": true,
359
+ "enableJsonFlag": false,
360
+ "isESM": true,
361
+ "relativePath": [
362
+ "dist",
363
+ "commands",
364
+ "contacts",
365
+ "index.js"
366
+ ]
367
+ },
368
368
  "login": {
369
369
  "aliases": [],
370
370
  "args": {},
@@ -657,5 +657,5 @@
657
657
  ]
658
658
  }
659
659
  },
660
- "version": "0.0.28"
660
+ "version": "0.0.29-beta.pr31.48"
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.28",
4
+ "version": "0.0.29-beta.pr31.48",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"