@mailmodo/cli 0.0.29 → 0.0.30-beta.pr32.50

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 {
@@ -69,7 +69,7 @@ export default class Domain extends BaseCommand {
69
69
  await saveYaml(yamlConfig);
70
70
  await saveConfig({ ...config, domain });
71
71
  const records = response.data?.dnsRecords || [];
72
- const guideUrl = response.data?.dnsGuideUrl ?? DNS_GUIDE_URL;
72
+ const guideUrl = response.data?.dnsGuideUrl;
73
73
  if (flags.json) {
74
74
  this.log(JSON.stringify({ dnsRecords: records, domain }, null, 2));
75
75
  return;
@@ -82,7 +82,8 @@ export default class Domain extends BaseCommand {
82
82
  this.log(` Value: ${record.value}\n`);
83
83
  }
84
84
  this.log(` DNS changes take 5–30 minutes to propagate.`);
85
- this.log(` Full guide: ${chalk.cyan(guideUrl)}\n`);
85
+ if (guideUrl)
86
+ this.log(` Full guide: ${chalk.cyan(guideUrl)}\n`);
86
87
  if (!flags.yes) {
87
88
  const action = await input({
88
89
  default: '',
@@ -107,7 +108,7 @@ export default class Domain extends BaseCommand {
107
108
  if (!response.ok) {
108
109
  this.handleApiError(response);
109
110
  }
110
- const { dkim, dmarc, returnPath, domainStatus } = response.data;
111
+ const { dkim, dmarc, dnsGuideUrl, returnPath, domainStatus } = response.data;
111
112
  if (jsonOutput) {
112
113
  this.log(JSON.stringify({ dkim, dmarc, returnPath, domainStatus }, null, 2));
113
114
  return;
@@ -133,7 +134,8 @@ export default class Domain extends BaseCommand {
133
134
  this.log(` - Cloudflare: proxy must be OFF (grey cloud, not orange)`);
134
135
  }
135
136
  this.log(`\n Fix the records and run ${chalk.cyan('mailmodo domain --verify')} again.`);
136
- this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
137
+ if (dnsGuideUrl)
138
+ this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
137
139
  }
138
140
  }
139
141
  /**
@@ -10,6 +10,17 @@ export default class Edit extends BaseCommand {
10
10
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
12
  };
13
+ run(): Promise<void>;
14
+ private runEditStep;
15
+ private handleUserAction;
16
+ private callEditApi;
17
+ private finalizeEdit;
18
+ private applyEmailChanges;
19
+ private persistChanges;
20
+ private logJsonResult;
21
+ private handleAcceptOutput;
22
+ private promptEditAction;
23
+ private askChangeDescription;
13
24
  private showFieldDiff;
14
25
  private stripHtml;
15
26
  private truncate;
@@ -20,5 +31,4 @@ export default class Edit extends BaseCommand {
20
31
  private showUnchanged;
21
32
  private buildDiffPreview;
22
33
  private showChangeSummary;
23
- run(): Promise<void>;
24
34
  }
@@ -1,5 +1,5 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { confirm, input } from '@inquirer/prompts';
2
+ import { confirm, 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 } from '../../lib/constants.js';
@@ -22,6 +22,149 @@ export default class Edit extends BaseCommand {
22
22
  description: 'Natural language description of the change',
23
23
  }),
24
24
  };
25
+ async run() {
26
+ const { args, flags } = await this.parse(Edit);
27
+ await this.ensureAuth();
28
+ const yamlConfig = await this.ensureYaml();
29
+ const emailIndex = yamlConfig.emails.findIndex((e) => e.id === args.id);
30
+ if (emailIndex === -1) {
31
+ this.error(`Email '${args.id}' not found in mailmodo.yaml.`);
32
+ }
33
+ const ctx = {
34
+ email: yamlConfig.emails[emailIndex],
35
+ emailIndex,
36
+ templateHtml: await loadTemplate(`${yamlConfig.emails[emailIndex].id}.html`),
37
+ yamlConfig,
38
+ };
39
+ const editFlags = {
40
+ json: flags.json ?? false,
41
+ yes: flags.yes ?? false,
42
+ };
43
+ const initialChange = flags.change ?? (await this.askChangeDescription());
44
+ await this.runEditStep(ctx, initialChange, editFlags);
45
+ }
46
+ async runEditStep(ctx, changeDescription, flags) {
47
+ const response = await this.withApiSpinner({ json: flags.json, text: ' Applying AI edits...' }, () => this.callEditApi(changeDescription, ctx.email, ctx.templateHtml));
48
+ if (!response.ok) {
49
+ this.handleApiError(response);
50
+ }
51
+ const updated = response.data;
52
+ if (flags.json) {
53
+ this.log(JSON.stringify(this.buildDiffPreview(ctx.email, updated, ctx.templateHtml), null, 2));
54
+ }
55
+ else {
56
+ this.showChangeSummary(ctx.email, updated, ctx.templateHtml);
57
+ }
58
+ if (flags.yes) {
59
+ await this.finalizeEdit(ctx, updated, flags);
60
+ return;
61
+ }
62
+ await this.handleUserAction(ctx, updated, changeDescription, flags);
63
+ }
64
+ async handleUserAction(ctx, updated, changeDescription, flags) {
65
+ this.log('');
66
+ const action = await this.promptEditAction();
67
+ if (action === 'skip') {
68
+ this.log('\n Changes discarded.\n');
69
+ return;
70
+ }
71
+ if (action === 'retry') {
72
+ const newChange = await this.askChangeDescription();
73
+ await this.runEditStep(ctx, newChange, flags);
74
+ return;
75
+ }
76
+ await this.finalizeEdit(ctx, updated, flags);
77
+ }
78
+ callEditApi(changeDescription, email, templateHtml) {
79
+ return this.apiClient.post(API_ENDPOINTS.EDIT, {
80
+ changeRequest: changeDescription,
81
+ currentEmail: {
82
+ condition: email.condition,
83
+ goal: email.goal,
84
+ html: templateHtml,
85
+ id: email.id,
86
+ previewText: email.previewText,
87
+ subject: email.subject,
88
+ trigger: email.trigger,
89
+ },
90
+ });
91
+ }
92
+ async finalizeEdit(ctx, updated, flags) {
93
+ const oldSubject = ctx.email.subject;
94
+ this.applyEmailChanges(ctx.email, updated);
95
+ await this.persistChanges(ctx, updated);
96
+ if (flags.json) {
97
+ this.logJsonResult(ctx.email, updated, oldSubject);
98
+ }
99
+ else if (flags.yes) {
100
+ this.log(`\n Updated ${chalk.green('mailmodo.yaml')}\n`);
101
+ }
102
+ else {
103
+ await this.handleAcceptOutput(ctx.email);
104
+ }
105
+ }
106
+ applyEmailChanges(email, updated) {
107
+ if (updated.subject)
108
+ email.subject = updated.subject;
109
+ if (updated.previewText)
110
+ email.previewText = updated.previewText;
111
+ if (updated.ctaText)
112
+ email.ctaText = updated.ctaText;
113
+ }
114
+ async persistChanges(ctx, updated) {
115
+ const updatedYaml = {
116
+ ...ctx.yamlConfig,
117
+ emails: [...ctx.yamlConfig.emails],
118
+ };
119
+ updatedYaml.emails[ctx.emailIndex] = ctx.email;
120
+ await saveYaml(updatedYaml);
121
+ if (updated.html) {
122
+ await saveTemplate(`${ctx.email.id}.html`, updated.html);
123
+ }
124
+ }
125
+ logJsonResult(email, updated, oldSubject) {
126
+ this.log(JSON.stringify({
127
+ diff: {
128
+ previewText: updated.previewText && updated.previewText !== email.previewText
129
+ ? { new: updated.previewText, old: email.previewText }
130
+ : undefined,
131
+ subject: oldSubject === email.subject
132
+ ? undefined
133
+ : { new: email.subject, old: oldSubject },
134
+ },
135
+ email,
136
+ status: 'updated',
137
+ }, null, 2));
138
+ }
139
+ async handleAcceptOutput(email) {
140
+ this.log(`\n Updated ${chalk.green('mailmodo.yaml')}`);
141
+ const shouldPreview = await confirm({
142
+ default: true,
143
+ message: 'Preview the change?',
144
+ });
145
+ if (shouldPreview) {
146
+ await this.config.runCommand('preview', [email.id]);
147
+ }
148
+ else {
149
+ this.log(` Run: ${chalk.cyan(`mailmodo preview ${email.id}`)}\n`);
150
+ }
151
+ }
152
+ async promptEditAction() {
153
+ return select({
154
+ message: 'Accept, try again, or skip?',
155
+ choices: [
156
+ { name: 'Accept', value: 'accept' },
157
+ { name: 'Try again', value: 'retry' },
158
+ { name: 'Skip', value: 'skip' },
159
+ ],
160
+ });
161
+ }
162
+ async askChangeDescription() {
163
+ return input({
164
+ message: 'What do you want to change?',
165
+ validate: (value) => value?.trim() ? true : 'Please describe the change',
166
+ });
167
+ }
25
168
  showFieldDiff(label, oldVal, newVal) {
26
169
  if (!newVal || oldVal === newVal)
27
170
  return false;
@@ -139,88 +282,4 @@ export default class Edit extends BaseCommand {
139
282
  this.showSuggestedChanges(email, updated, templateHtml, changed);
140
283
  this.showUnchanged(email, templateHtml, changed);
141
284
  }
142
- async run() {
143
- const { args, flags } = await this.parse(Edit);
144
- await this.ensureAuth();
145
- const yamlConfig = await this.ensureYaml();
146
- const emailIndex = yamlConfig.emails.findIndex((e) => e.id === args.id);
147
- if (emailIndex === -1) {
148
- this.error(`Email '${args.id}' not found in mailmodo.yaml.`);
149
- }
150
- const email = yamlConfig.emails[emailIndex];
151
- const templateHtml = await loadTemplate(`${email.id}.html`);
152
- let changeDescription = flags.change;
153
- if (!changeDescription) {
154
- changeDescription = await input({
155
- message: 'What do you want to change?',
156
- validate: (value) => value?.trim() ? true : 'Please describe the change',
157
- });
158
- }
159
- const response = await this.withApiSpinner({ json: flags.json, text: ' Applying AI edits...' }, () => this.apiClient.post(API_ENDPOINTS.EDIT, {
160
- changeRequest: changeDescription,
161
- currentEmail: {
162
- condition: email.condition,
163
- goal: email.goal,
164
- html: templateHtml,
165
- id: email.id,
166
- previewText: email.previewText,
167
- subject: email.subject,
168
- trigger: email.trigger,
169
- },
170
- }));
171
- if (!response.ok) {
172
- this.handleApiError(response);
173
- }
174
- const updated = response.data;
175
- if (flags.json) {
176
- this.log(JSON.stringify(this.buildDiffPreview(email, updated, templateHtml), null, 2));
177
- }
178
- else {
179
- this.showChangeSummary(email, updated, templateHtml);
180
- }
181
- if (!flags.yes) {
182
- const accepted = await confirm({
183
- default: true,
184
- message: 'Accept changes?',
185
- });
186
- if (!accepted) {
187
- this.log('\n Changes discarded.\n');
188
- return;
189
- }
190
- }
191
- const oldSubject = email.subject;
192
- const newSubject = updated.subject || email.subject;
193
- if (updated.subject)
194
- email.subject = updated.subject;
195
- if (updated.previewText)
196
- email.previewText = updated.previewText;
197
- if (updated.ctaText)
198
- email.ctaText = updated.ctaText;
199
- const updatedYaml = {
200
- ...yamlConfig,
201
- emails: [...yamlConfig.emails],
202
- };
203
- updatedYaml.emails[emailIndex] = email;
204
- await saveYaml(updatedYaml);
205
- if (updated.html) {
206
- await saveTemplate(`${email.id}.html`, updated.html);
207
- }
208
- if (flags.json) {
209
- this.log(JSON.stringify({
210
- diff: {
211
- previewText: updated.previewText && updated.previewText !== email.previewText
212
- ? { new: updated.previewText, old: email.previewText }
213
- : undefined,
214
- subject: oldSubject === newSubject
215
- ? undefined
216
- : { new: newSubject, old: oldSubject },
217
- },
218
- email,
219
- status: 'updated',
220
- }, null, 2));
221
- return;
222
- }
223
- this.log(`\n Updated ${chalk.green('mailmodo.yaml')}`);
224
- this.log(` Preview the change: ${chalk.cyan(`mailmodo preview ${email.id}`)}\n`);
225
- }
226
285
  }
@@ -7,4 +7,6 @@ export default class Emails extends BaseCommand {
7
7
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  };
9
9
  run(): Promise<void>;
10
+ private openTemplateInEditor;
11
+ private trySpawnEditor;
10
12
  }
@@ -1,5 +1,8 @@
1
- import { input } from '@inquirer/prompts';
1
+ import { spawn } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ import { confirm, input } from '@inquirer/prompts';
2
4
  import chalk from 'chalk';
5
+ import open from 'open';
3
6
  import { BaseCommand } from '../../lib/base-command.js';
4
7
  export default class Emails extends BaseCommand {
5
8
  static description = 'List and view configured email sequences';
@@ -61,7 +64,48 @@ export default class Emails extends BaseCommand {
61
64
  this.log(` ${chalk.bold('Goal:')} ${email.goal}`);
62
65
  }
63
66
  this.log('');
67
+ const openIt = await confirm({
68
+ default: true,
69
+ message: 'Open template in editor?',
70
+ });
71
+ if (openIt) {
72
+ await this.openTemplateInEditor(email.template);
73
+ }
64
74
  }
65
75
  }
66
76
  }
77
+ async openTemplateInEditor(template) {
78
+ const templatePath = join(process.cwd(), template);
79
+ this.log(`\n Opening ${template}...\n`);
80
+ const editor = process.env.VISUAL || process.env.EDITOR;
81
+ if (editor) {
82
+ const child = spawn(editor, [templatePath], { stdio: 'inherit' });
83
+ await new Promise((resolve) => {
84
+ child.on('close', resolve);
85
+ });
86
+ return;
87
+ }
88
+ if (await this.trySpawnEditor('code', templatePath))
89
+ return;
90
+ try {
91
+ await open(templatePath);
92
+ }
93
+ catch {
94
+ this.log(` ${chalk.dim(`Could not open editor. Open the file manually: ${templatePath}`)}`);
95
+ }
96
+ }
97
+ trySpawnEditor(editor, filePath) {
98
+ const [cmd, args] = process.platform === 'win32'
99
+ ? ['cmd.exe', ['/c', editor, filePath]]
100
+ : [editor, [filePath]];
101
+ return new Promise((resolve) => {
102
+ const child = spawn(cmd, [...args], { stdio: 'ignore' });
103
+ child.on('error', () => {
104
+ resolve(false);
105
+ });
106
+ child.on('close', (code) => {
107
+ resolve(code === 0);
108
+ });
109
+ });
110
+ }
67
111
  }
@@ -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'];
@@ -12,6 +12,8 @@ export interface ApiRequestDebugInfo {
12
12
  responseSummary?: string;
13
13
  }
14
14
  export interface ApiResponse<T = Record<string, unknown>> {
15
+ /** Warning string from the API when the user's card is expiring or has issues. */
16
+ addCardWarning?: string;
15
17
  data: T;
16
18
  /** Populated for tracing; especially useful when `ok` is false. */
17
19
  debug?: ApiRequestDebugInfo;
@@ -14,6 +14,12 @@ import { fetchFileNoAuth, fetchFileWithBearerAuth, } from './fetch-file.js';
14
14
  * indicating the API may not be available, rather than a raw stack trace.
15
15
  */
16
16
  const RESPONSE_BODY_DEBUG_MAX = 800;
17
+ function extractAddCardWarning(data) {
18
+ const raw = data;
19
+ return typeof raw.addCardWarning === 'string'
20
+ ? raw.addCardWarning
21
+ : undefined;
22
+ }
17
23
  function summarizeResponseBody(data) {
18
24
  if (data === null || data === undefined)
19
25
  return undefined;
@@ -90,7 +96,13 @@ export class ApiClient {
90
96
  status: response.status,
91
97
  };
92
98
  }
93
- return { data: data, ok: true, status: response.status };
99
+ const addCardWarning = extractAddCardWarning(data);
100
+ return {
101
+ ...(addCardWarning === undefined ? {} : { addCardWarning }),
102
+ data: data,
103
+ ok: true,
104
+ status: response.status,
105
+ };
94
106
  }
95
107
  catch (error) {
96
108
  const err = error;
@@ -164,7 +176,13 @@ export class ApiClient {
164
176
  status: response.status,
165
177
  };
166
178
  }
167
- return { data: data, ok: true, status: response.status };
179
+ const addCardWarning = extractAddCardWarning(data);
180
+ return {
181
+ ...(addCardWarning === undefined ? {} : { addCardWarning }),
182
+ data: data,
183
+ ok: true,
184
+ status: response.status,
185
+ };
168
186
  }
169
187
  catch (error) {
170
188
  const err = error;
@@ -63,12 +63,12 @@ export class BaseCommand extends Command {
63
63
  color: 'cyan',
64
64
  text: options.text,
65
65
  }).start();
66
- try {
67
- return await work();
68
- }
69
- finally {
70
- spinner.stop();
66
+ const result = await work().finally(() => spinner.stop());
67
+ const warning = result.addCardWarning;
68
+ if (typeof warning === 'string' && warning) {
69
+ this.warn(warning);
71
70
  }
71
+ return result;
72
72
  }
73
73
  /**
74
74
  * Loads and returns the mailmodo.yaml configuration from the current directory.
@@ -228,19 +228,13 @@
228
228
  "index.js"
229
229
  ]
230
230
  },
231
- "edit": {
231
+ "emails": {
232
232
  "aliases": [],
233
- "args": {
234
- "id": {
235
- "description": "Email template ID to edit",
236
- "name": "id",
237
- "required": true
238
- }
239
- },
240
- "description": "Edit an email using AI-assisted natural language changes",
233
+ "args": {},
234
+ "description": "List and view configured email sequences",
241
235
  "examples": [
242
- "<%= config.bin %> edit welcome",
243
- "<%= config.bin %> edit welcome --change \"make subject more urgent\" --yes"
236
+ "<%= config.bin %> emails",
237
+ "<%= config.bin %> emails --json"
244
238
  ],
245
239
  "flags": {
246
240
  "json": {
@@ -255,18 +249,11 @@
255
249
  "name": "yes",
256
250
  "allowNo": false,
257
251
  "type": "boolean"
258
- },
259
- "change": {
260
- "description": "Natural language description of the change",
261
- "name": "change",
262
- "hasDynamicHelp": false,
263
- "multiple": false,
264
- "type": "option"
265
252
  }
266
253
  },
267
254
  "hasDynamicHelp": false,
268
255
  "hiddenAliases": [],
269
- "id": "edit",
256
+ "id": "emails",
270
257
  "pluginAlias": "@mailmodo/cli",
271
258
  "pluginName": "@mailmodo/cli",
272
259
  "pluginType": "core",
@@ -276,17 +263,23 @@
276
263
  "relativePath": [
277
264
  "dist",
278
265
  "commands",
279
- "edit",
266
+ "emails",
280
267
  "index.js"
281
268
  ]
282
269
  },
283
- "emails": {
270
+ "edit": {
284
271
  "aliases": [],
285
- "args": {},
286
- "description": "List and view configured email sequences",
272
+ "args": {
273
+ "id": {
274
+ "description": "Email template ID to edit",
275
+ "name": "id",
276
+ "required": true
277
+ }
278
+ },
279
+ "description": "Edit an email using AI-assisted natural language changes",
287
280
  "examples": [
288
- "<%= config.bin %> emails",
289
- "<%= config.bin %> emails --json"
281
+ "<%= config.bin %> edit welcome",
282
+ "<%= config.bin %> edit welcome --change \"make subject more urgent\" --yes"
290
283
  ],
291
284
  "flags": {
292
285
  "json": {
@@ -301,11 +294,18 @@
301
294
  "name": "yes",
302
295
  "allowNo": false,
303
296
  "type": "boolean"
297
+ },
298
+ "change": {
299
+ "description": "Natural language description of the change",
300
+ "name": "change",
301
+ "hasDynamicHelp": false,
302
+ "multiple": false,
303
+ "type": "option"
304
304
  }
305
305
  },
306
306
  "hasDynamicHelp": false,
307
307
  "hiddenAliases": [],
308
- "id": "emails",
308
+ "id": "edit",
309
309
  "pluginAlias": "@mailmodo/cli",
310
310
  "pluginName": "@mailmodo/cli",
311
311
  "pluginType": "core",
@@ -315,7 +315,7 @@
315
315
  "relativePath": [
316
316
  "dist",
317
317
  "commands",
318
- "emails",
318
+ "edit",
319
319
  "index.js"
320
320
  ]
321
321
  },
@@ -570,14 +570,13 @@
570
570
  "index.js"
571
571
  ]
572
572
  },
573
- "settings": {
573
+ "status": {
574
574
  "aliases": [],
575
575
  "args": {},
576
- "description": "View and update project settings",
576
+ "description": "View email performance metrics and quota usage",
577
577
  "examples": [
578
- "<%= config.bin %> settings",
579
- "<%= config.bin %> settings --set brand_color=#0F3460",
580
- "<%= config.bin %> settings --json"
578
+ "<%= config.bin %> status",
579
+ "<%= config.bin %> status --json"
581
580
  ],
582
581
  "flags": {
583
582
  "json": {
@@ -592,18 +591,11 @@
592
591
  "name": "yes",
593
592
  "allowNo": false,
594
593
  "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
594
  }
603
595
  },
604
596
  "hasDynamicHelp": false,
605
597
  "hiddenAliases": [],
606
- "id": "settings",
598
+ "id": "status",
607
599
  "pluginAlias": "@mailmodo/cli",
608
600
  "pluginName": "@mailmodo/cli",
609
601
  "pluginType": "core",
@@ -613,17 +605,18 @@
613
605
  "relativePath": [
614
606
  "dist",
615
607
  "commands",
616
- "settings",
608
+ "status",
617
609
  "index.js"
618
610
  ]
619
611
  },
620
- "status": {
612
+ "settings": {
621
613
  "aliases": [],
622
614
  "args": {},
623
- "description": "View email performance metrics and quota usage",
615
+ "description": "View and update project settings",
624
616
  "examples": [
625
- "<%= config.bin %> status",
626
- "<%= config.bin %> status --json"
617
+ "<%= config.bin %> settings",
618
+ "<%= config.bin %> settings --set brand_color=#0F3460",
619
+ "<%= config.bin %> settings --json"
627
620
  ],
628
621
  "flags": {
629
622
  "json": {
@@ -638,11 +631,18 @@
638
631
  "name": "yes",
639
632
  "allowNo": false,
640
633
  "type": "boolean"
634
+ },
635
+ "set": {
636
+ "description": "Set a setting (format: key=value)",
637
+ "name": "set",
638
+ "hasDynamicHelp": false,
639
+ "multiple": false,
640
+ "type": "option"
641
641
  }
642
642
  },
643
643
  "hasDynamicHelp": false,
644
644
  "hiddenAliases": [],
645
- "id": "status",
645
+ "id": "settings",
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
+ "settings",
656
656
  "index.js"
657
657
  ]
658
658
  }
659
659
  },
660
- "version": "0.0.29"
660
+ "version": "0.0.30-beta.pr32.50"
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.29",
4
+ "version": "0.0.30-beta.pr32.50",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"