@mailmodo/cli 0.0.54-beta.pr56.91 → 0.0.54

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.
@@ -68,14 +68,12 @@ export default class Billing extends BaseCommand {
68
68
  }
69
69
  this.log(`\n ${chalk.bold('Stripe Checkout')} — add or update your payment method.`);
70
70
  this.log(` ${chalk.dim(checkoutUrl)}\n`);
71
- if (!process.env.CI) {
72
- try {
73
- await open(checkoutUrl);
74
- this.log(` ${INFO.BROWSER_OPENING}\n`);
75
- }
76
- catch {
77
- this.log(` ${INFO.BROWSER_OPEN_FAILED}\n`);
78
- }
71
+ try {
72
+ await open(checkoutUrl);
73
+ this.log(` ${INFO.BROWSER_OPENING}\n`);
74
+ }
75
+ catch {
76
+ this.log(` ${INFO.BROWSER_OPEN_FAILED}\n`);
79
77
  }
80
78
  }
81
79
  async showStatus(jsonOutput, statusOnly) {
@@ -178,7 +176,6 @@ export default class Billing extends BaseCommand {
178
176
  return;
179
177
  yamlConfig.project.monthlyCap = capBlocks;
180
178
  await saveYaml(yamlConfig);
181
- await this.syncYamlToServer();
182
179
  }
183
180
  pluralize(word, count) {
184
181
  return count === 1 ? word : `${word}s`;
@@ -8,6 +8,37 @@ export default class Deploy extends BaseCommand {
8
8
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  };
11
+ private fetchDomainVerifyForDeploy;
11
12
  run(): Promise<void>;
12
- private makeCtx;
13
+ private validateSequence;
14
+ /**
15
+ * Calls `POST /sequences/{id}/status` with `{ status: "paused" }` and prints
16
+ * a confirmation-aware success/no-op message. Skips the prompt entirely when
17
+ * `--yes` is set so the command stays scriptable. `--json` always emits the
18
+ * raw server payload (sequenceId, status, alreadyInStatus).
19
+ */
20
+ private pauseSequence;
21
+ /**
22
+ * Calls `POST /sequences/{id}/status` with `{ status: "active" }`. No prompt
23
+ * — resuming is the safe direction (it does not start sends that weren't
24
+ * already queued). `--json` always emits the raw server payload (sequenceId,
25
+ * status, alreadyInStatus).
26
+ */
27
+ private resumeSequence;
28
+ private updateSequenceStatus;
29
+ private sequenceStatusPath;
30
+ private buildDeployPayload;
31
+ private resolveMonthlyCapForDeploy;
32
+ private mapEmailToPayload;
33
+ private buildBrandSection;
34
+ private buildProductSection;
35
+ private buildSenderSection;
36
+ private buildProjectPayload;
37
+ private confirmDeploy;
38
+ private ensureDomainReady;
39
+ private logPreDeploySummary;
40
+ private logDiff;
41
+ private logDeploySuccessInstructions;
42
+ private runDomainSetup;
43
+ private verifyDomain;
13
44
  }
@@ -1,14 +1,10 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { confirm } from '@inquirer/prompts';
2
+ import { confirm, input } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
- import { API_ENDPOINTS } from '../../lib/constants.js';
6
- import { MISSING_TEMPLATES } from '../../lib/messages.js';
7
- import { buildDeployPayload } from '../../lib/deploy/payload.js';
8
- import { logDeploySuccessInstructions, logPreDeploySummary, } from '../../lib/deploy/output.js';
9
- import { pauseSequence, resumeSequence, } from '../../lib/deploy/sequence-status.js';
10
- import { ensureDomainReady, validateDeploySequence, } from '../../lib/deploy/domain-setup.js';
11
- import { getMissingTemplateIds, handleMissingTemplates, } from '../../lib/templates/missing-templates.js';
5
+ import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, SDK_IMPORT_SNIPPET, SDK_INSTALL_COMMAND, } from '../../lib/constants.js';
6
+ import { ERRORS, INFO, pauseAlready, pauseSuccess, PROMPTS, resumeAlready, resumeSuccess, SEPARATOR, } from '../../lib/messages.js';
7
+ import { loadTemplate, } from '../../lib/yaml-config.js';
12
8
  export default class Deploy extends BaseCommand {
13
9
  static description = 'Deploy, pause, or resume an email sequence';
14
10
  static examples = [
@@ -28,47 +24,37 @@ export default class Deploy extends BaseCommand {
28
24
  exclusive: ['pause'],
29
25
  }),
30
26
  };
27
+ fetchDomainVerifyForDeploy(jsonOutput, domain) {
28
+ return this.withApiSpinner({ json: jsonOutput, text: ' Checking domain verification...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
29
+ domain: domain || '',
30
+ }));
31
+ }
31
32
  async run() {
32
33
  const { flags } = await this.parse(Deploy);
33
34
  await this.ensureAuth();
34
- const ctx = this.makeCtx();
35
35
  const baseFlags = { json: flags.json, yes: flags.yes };
36
36
  if (flags.pause) {
37
- await pauseSequence(ctx, flags.pause, baseFlags);
37
+ await this.pauseSequence(flags.pause, baseFlags);
38
38
  return;
39
39
  }
40
40
  if (flags.resume) {
41
- await resumeSequence(ctx, flags.resume, baseFlags);
41
+ await this.resumeSequence(flags.resume, baseFlags);
42
42
  return;
43
43
  }
44
44
  const yamlConfig = await this.ensureYaml();
45
- const missingIds = getMissingTemplateIds(yamlConfig);
46
- if (missingIds.length > 0) {
47
- const regenerated = await handleMissingTemplates(ctx, yamlConfig, missingIds, baseFlags);
48
- if (regenerated)
49
- ctx.log(`\n ${chalk.green('✓')} ${MISSING_TEMPLATES.REVIEW_HINT}\n`);
50
- return;
51
- }
52
- const domainReady = await ensureDomainReady(ctx, yamlConfig, baseFlags);
45
+ const domainReady = await this.ensureDomainReady(yamlConfig, flags);
53
46
  if (!domainReady)
54
47
  return;
55
- const payload = await buildDeployPayload(ctx, yamlConfig);
56
- const validateResult = await validateDeploySequence(ctx, payload, baseFlags);
57
- logPreDeploySummary(ctx, yamlConfig, validateResult, flags.json);
58
- if (!flags.yes) {
59
- const proceed = await confirm({
60
- default: true,
61
- message: `Deploy ${yamlConfig.emails.length} emails?`,
62
- });
63
- if (!proceed) {
64
- this.log('\n Deploy cancelled.\n');
65
- return;
66
- }
48
+ const payload = await this.buildDeployPayload(yamlConfig);
49
+ const validateResult = await this.validateSequence(payload, flags);
50
+ this.logPreDeploySummary(yamlConfig, validateResult, flags.json);
51
+ const confirmed = await this.confirmDeploy(yamlConfig, flags);
52
+ if (!confirmed)
53
+ return;
54
+ const response = await this.withApiSpinner({ json: flags.json, text: ' Deploying email sequences...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_DEPLOY, payload));
55
+ if (!response.ok) {
56
+ this.handleApiError(response);
67
57
  }
68
- const response = await ctx.spinner(' Deploying email sequences...', flags.json, () => ctx.post(API_ENDPOINTS.SEQUENCES_DEPLOY, payload));
69
- if (!response.ok)
70
- ctx.onApiError(response);
71
- await ctx.syncYaml();
72
58
  if (flags.json) {
73
59
  this.log(JSON.stringify({
74
60
  deployed: response.data.deployed,
@@ -79,25 +65,290 @@ export default class Deploy extends BaseCommand {
79
65
  }, null, 2));
80
66
  return;
81
67
  }
82
- logDeploySuccessInstructions(ctx, response.data.sdkSnippet);
68
+ this.logDeploySuccessInstructions(response.data.sdkSnippet);
83
69
  }
84
- makeCtx() {
70
+ async validateSequence(payload, flags) {
71
+ const response = await this.withApiSpinner({ json: flags.json, text: ' Validating sequence...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_VALIDATE, payload));
72
+ if (!response.ok) {
73
+ if (response.data.error === 'senderDomainNotFound') {
74
+ this.error(ERRORS.DOMAIN_NOT_REGISTERED);
75
+ }
76
+ if (response.data.error === 'senderDomainNotVerified') {
77
+ this.error(ERRORS.DOMAIN_NOT_VERIFIED);
78
+ }
79
+ this.handleApiError(response);
80
+ }
81
+ return response.data;
82
+ }
83
+ /**
84
+ * Calls `POST /sequences/{id}/status` with `{ status: "paused" }` and prints
85
+ * a confirmation-aware success/no-op message. Skips the prompt entirely when
86
+ * `--yes` is set so the command stays scriptable. `--json` always emits the
87
+ * raw server payload (sequenceId, status, alreadyInStatus).
88
+ */
89
+ async pauseSequence(sequenceId, flags) {
90
+ if (!flags.yes) {
91
+ const confirmed = await confirm({
92
+ default: false,
93
+ message: PROMPTS.PAUSE_CONFIRM,
94
+ });
95
+ if (!confirmed) {
96
+ this.log(`\n ${INFO.PAUSE_CANCELLED}\n`);
97
+ return;
98
+ }
99
+ }
100
+ const data = await this.updateSequenceStatus(sequenceId, 'paused', flags, ' Pausing sequence...');
101
+ if (flags.json) {
102
+ this.log(JSON.stringify(data, null, 2));
103
+ return;
104
+ }
105
+ const message = data.alreadyInStatus
106
+ ? pauseAlready(data.sequenceId || sequenceId)
107
+ : pauseSuccess(data.sequenceId || sequenceId);
108
+ this.log(`\n ${message}\n`);
109
+ }
110
+ /**
111
+ * Calls `POST /sequences/{id}/status` with `{ status: "active" }`. No prompt
112
+ * — resuming is the safe direction (it does not start sends that weren't
113
+ * already queued). `--json` always emits the raw server payload (sequenceId,
114
+ * status, alreadyInStatus).
115
+ */
116
+ async resumeSequence(sequenceId, flags) {
117
+ const data = await this.updateSequenceStatus(sequenceId, 'active', flags, ' Resuming sequence...');
118
+ if (flags.json) {
119
+ this.log(JSON.stringify(data, null, 2));
120
+ return;
121
+ }
122
+ const message = data.alreadyInStatus
123
+ ? resumeAlready(data.sequenceId || sequenceId)
124
+ : resumeSuccess(data.sequenceId || sequenceId);
125
+ this.log(`\n ${message}\n`);
126
+ }
127
+ async updateSequenceStatus(sequenceId, status, flags, spinnerText) {
128
+ const response = await this.withApiSpinner({ json: flags.json, text: spinnerText }, () => this.apiClient.post(this.sequenceStatusPath(sequenceId), { status }));
129
+ if (!response.ok) {
130
+ this.handleApiError(response);
131
+ }
132
+ return response.data;
133
+ }
134
+ sequenceStatusPath(sequenceId) {
135
+ return `${API_ENDPOINTS.SEQUENCES}/${encodeURIComponent(sequenceId)}/status`;
136
+ }
137
+ async buildDeployPayload(yamlConfig) {
138
+ const [emailsWithHtml, monthlyCap] = await Promise.all([
139
+ Promise.all(yamlConfig.emails.map(async (email) => {
140
+ const html = (await loadTemplate(`${email.id}.html`)) || '';
141
+ const plainHtml = (await loadTemplate(`${email.id}_plain.html`)) || html;
142
+ return { ...this.mapEmailToPayload(email), html, plainHtml };
143
+ })),
144
+ this.resolveMonthlyCapForDeploy(yamlConfig.project.monthlyCap),
145
+ ]);
146
+ return {
147
+ ...this.buildProjectPayload(yamlConfig.project, monthlyCap),
148
+ emails: emailsWithHtml,
149
+ };
150
+ }
151
+ async resolveMonthlyCapForDeploy(yamlMonthlyCap) {
152
+ if (yamlMonthlyCap !== undefined)
153
+ return yamlMonthlyCap;
154
+ const billingStatus = await this.fetchBillingStatus();
155
+ return billingStatus?.cap?.inBlocks ?? undefined;
156
+ }
157
+ mapEmailToPayload(email) {
158
+ return {
159
+ condition: email.condition || null,
160
+ ctaText: email.ctaText || '',
161
+ delay: email.delay,
162
+ goal: email.goal || '',
163
+ id: email.id,
164
+ isReminder: false,
165
+ previewText: email.previewText || '',
166
+ priority: 'medium',
167
+ subject: email.subject,
168
+ trigger: email.trigger,
169
+ };
170
+ }
171
+ buildBrandSection(project) {
85
172
  return {
86
- collectDomainInputs: (yaml, skip) => this.collectDomainSetupInputs(yaml, skip),
87
- error: (msg) => this.error(msg),
88
- exit: (code) => this.exit(code),
89
- get: (path, params) => this.apiClient.get(path, params),
90
- getBillingCap: async () => {
91
- const s = await this.fetchBillingStatus();
92
- return s?.cap?.inBlocks ?? undefined;
93
- },
94
- log: (msg) => this.log(msg),
95
- onApiError: (r) => this.handleApiError(r),
96
- post: (path, body) => this.apiClient.post(path, body),
97
- registerDomainAndSave: (yaml, inputs, json) => this.registerDomain(yaml, inputs, json),
98
- showDnsRecords: (records, url, json) => this.logDnsRecords(records, url, json),
99
- spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
100
- syncYaml: () => this.syncYamlToServer(),
173
+ colors: [project?.brandColor || DEFAULT_BRAND_COLOR],
174
+ logoUrl: project?.logoUrl || '',
101
175
  };
102
176
  }
177
+ buildProductSection(project) {
178
+ return {
179
+ businessType: project?.type || '',
180
+ description: project?.description || '',
181
+ pricingModel: project?.pricingModel || '',
182
+ productName: project?.name || '',
183
+ saasModel: project?.saasModel || '',
184
+ targetUser: project?.targetUser || '',
185
+ url: project?.url || '',
186
+ };
187
+ }
188
+ buildSenderSection(project) {
189
+ return {
190
+ address: project?.address || '',
191
+ domain: project?.domain || '',
192
+ fromEmail: project?.fromEmail || '',
193
+ fromName: project?.fromName || '',
194
+ replyTo: project?.replyTo || project?.fromEmail || '',
195
+ };
196
+ }
197
+ buildProjectPayload(project, monthlyCap) {
198
+ return {
199
+ brand: this.buildBrandSection(project),
200
+ emailStyle: project?.emailStyle || 'branded',
201
+ ...(monthlyCap === undefined ? {} : { monthlyCap }),
202
+ product: this.buildProductSection(project),
203
+ senderDetails: this.buildSenderSection(project),
204
+ ...(project?.webhookUrl ? { webhookUrl: project.webhookUrl } : {}),
205
+ };
206
+ }
207
+ async confirmDeploy(yamlConfig, flags) {
208
+ if (flags.yes)
209
+ return true;
210
+ const proceed = await confirm({
211
+ default: true,
212
+ message: `Deploy ${yamlConfig.emails.length} emails?`,
213
+ });
214
+ if (!proceed) {
215
+ this.log('\n Deploy cancelled.\n');
216
+ }
217
+ return proceed;
218
+ }
219
+ async ensureDomainReady(yamlConfig, flags) {
220
+ const domainVerify = await this.fetchDomainVerifyForDeploy(flags.json, yamlConfig.project?.domain);
221
+ if (domainVerify.ok && domainVerify.data?.domainStatus === 'VERIFIED') {
222
+ return true;
223
+ }
224
+ if (yamlConfig.project?.domain) {
225
+ if (!flags.json) {
226
+ this.log(`\n ${INFO.DOMAIN_PENDING_VERIFICATION}\n`);
227
+ }
228
+ return false;
229
+ }
230
+ if (!flags.json) {
231
+ this.log(`\n No sending domain set up yet.`);
232
+ this.log(` You need a verified domain before sending emails.`);
233
+ this.log(` This is a one-time setup. Takes about 5 minutes.\n`);
234
+ }
235
+ if (!flags.yes) {
236
+ const setupNow = await confirm({
237
+ default: true,
238
+ message: 'Set up your sending domain now?',
239
+ });
240
+ if (!setupNow) {
241
+ this.log(`\n ${INFO.SEQUENCES_NOT_DEPLOYED}`);
242
+ this.log(` Emails will not send until your domain is verified.`);
243
+ this.log(` ${INFO.DOMAIN_NOT_DEPLOYED_HINT}\n`);
244
+ return false;
245
+ }
246
+ }
247
+ return this.runDomainSetup(yamlConfig, flags);
248
+ }
249
+ logPreDeploySummary(yamlConfig, validateResult, jsonOutput) {
250
+ if (jsonOutput)
251
+ return;
252
+ this.log(`\n ${chalk.green('✓')} Domain: ${yamlConfig.project?.domain || 'verified'}\n`);
253
+ if (!validateResult.existingDeployment || !validateResult.diff) {
254
+ this.log(` Deploying:`);
255
+ for (const email of yamlConfig.emails) {
256
+ this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger}`);
257
+ }
258
+ }
259
+ else {
260
+ this.logDiff(validateResult.diff);
261
+ }
262
+ this.log('');
263
+ }
264
+ logDiff(diff) {
265
+ if (!diff.hasChanges) {
266
+ this.log(` No changes from last deployment.`);
267
+ return;
268
+ }
269
+ this.log(` Changes vs. last deployment:`);
270
+ for (const email of diff.added) {
271
+ this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
272
+ }
273
+ for (const email of diff.removed) {
274
+ this.log(` ${chalk.red('-')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
275
+ }
276
+ for (const email of diff.modified) {
277
+ const fields = email.changedFields?.join(', ') || '';
278
+ this.log(` ${chalk.yellow('~')} ${email.id.padEnd(24)} ${fields}`);
279
+ }
280
+ if (diff.unchanged.length > 0) {
281
+ this.log(` ${chalk.dim(`∙ ${diff.unchanged.length} unchanged`)}`);
282
+ }
283
+ }
284
+ logDeploySuccessInstructions(sdkSnippet) {
285
+ this.log(` ${chalk.green('Deployed.')} Emails are live.\n`);
286
+ this.log(` ${SEPARATOR}`);
287
+ this.log(` ${chalk.bold('ADD THIS TO YOUR APP (one-time only):')}`);
288
+ this.log(` ${SEPARATOR}\n`);
289
+ this.log(` ${chalk.cyan(sdkSnippet.install ?? SDK_INSTALL_COMMAND)}\n`);
290
+ this.log(` ${chalk.dim(SDK_IMPORT_SNIPPET)}\n`);
291
+ if (sdkSnippet.examples) {
292
+ this.log(` ${chalk.dim('// Example usage:')}`);
293
+ this.log(` ${chalk.dim(sdkSnippet.examples.track)}`);
294
+ this.log(` ${chalk.dim(sdkSnippet.examples.identify)}\n`);
295
+ }
296
+ const trackCalls = [...new Set(sdkSnippet.trackCalls ?? [])];
297
+ for (const call of trackCalls) {
298
+ this.log(` ${chalk.dim(call)}`);
299
+ }
300
+ if (trackCalls.length > 0)
301
+ this.log('');
302
+ const identifyCalls = [...new Set(sdkSnippet.identifyCalls ?? [])];
303
+ for (const call of identifyCalls) {
304
+ this.log(` ${chalk.dim(call)}`);
305
+ }
306
+ if (identifyCalls.length > 0)
307
+ this.log('');
308
+ this.log(` Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}\n`);
309
+ this.log(` ${SEPARATOR}\n`);
310
+ }
311
+ async runDomainSetup(yamlConfig, flags) {
312
+ const inputs = await this.collectDomainSetupInputs(yamlConfig, flags.yes);
313
+ const { dnsRecords, dnsGuideUrl } = await this.registerDomain(yamlConfig, inputs, flags.json);
314
+ this.logDnsRecords(dnsRecords, dnsGuideUrl, flags.json);
315
+ if (flags.yes) {
316
+ return this.verifyDomain(flags.json, inputs.domain);
317
+ }
318
+ const action = await input({
319
+ default: '',
320
+ message: PROMPTS.ENTER_AFTER_RECORDS,
321
+ });
322
+ if (action.toLowerCase() === 'skip') {
323
+ this.log(`\n ${INFO.SEQUENCES_NOT_DEPLOYED}`);
324
+ this.log(` ${INFO.DOMAIN_NOT_DEPLOYED_HINT}\n`);
325
+ return false;
326
+ }
327
+ return this.verifyDomain(flags.json, inputs.domain);
328
+ }
329
+ async verifyDomain(jsonOutput, domain) {
330
+ const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
331
+ domain,
332
+ }));
333
+ if (!verify.ok) {
334
+ this.handleApiError(verify);
335
+ }
336
+ const { dkim, dmarc, dnsGuideUrl, domainStatus, returnPath } = verify.data;
337
+ const allPassed = domainStatus === 'VERIFIED';
338
+ if (!jsonOutput) {
339
+ this.log(` DKIM ${dkim ? chalk.green('✓') : chalk.red('✗')}`);
340
+ this.log(` DMARC ${dmarc ? chalk.green('✓') : chalk.red('✗')}`);
341
+ this.log(` Return-Path ${returnPath ? chalk.green('✓') : chalk.red('✗')}`);
342
+ if (allPassed) {
343
+ this.log(`\n ${chalk.green('Domain verified.')} Continuing deploy...\n`);
344
+ }
345
+ else {
346
+ this.log(`\n ${INFO.DNS_RECORDS_FAILED}`);
347
+ this.log(`\n ${INFO.DNS_FIX_AND_VERIFY}`);
348
+ if (dnsGuideUrl)
349
+ this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
350
+ }
351
+ }
352
+ return allPassed;
353
+ }
103
354
  }
@@ -4,7 +4,6 @@ import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
5
  import { API_ENDPOINTS } from '../../lib/constants.js';
6
6
  import { loadTemplate, getTemplateFilename, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
7
- import { handleMissingTemplates } from '../../lib/templates/missing-templates.js';
8
7
  export default class Edit extends BaseCommand {
9
8
  static args = {
10
9
  id: Args.string({
@@ -44,23 +43,6 @@ export default class Edit extends BaseCommand {
44
43
  json: flags.json ?? false,
45
44
  yes: flags.yes ?? false,
46
45
  };
47
- if (!ctx.templateHtml) {
48
- const regenCtx = {
49
- error: (msg) => this.error(msg),
50
- exit: (code) => this.exit(code),
51
- log: (msg) => this.log(msg),
52
- onApiError: (r) => this.handleApiError(r),
53
- post: (path, body) => this.apiClient.post(path, body),
54
- spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
55
- syncYaml: () => this.syncYamlToServer(),
56
- };
57
- const regenerated = await handleMissingTemplates(regenCtx, yamlConfig, [email.id], editFlags);
58
- if (!regenerated)
59
- return;
60
- ctx.templateHtml = await loadTemplate(templateFilename);
61
- if (!ctx.templateHtml)
62
- this.error('Template regeneration failed.');
63
- }
64
46
  const initialChange = flags.change?.trim() || (await this.askChangeDescription());
65
47
  await this.runEditStep(ctx, initialChange, editFlags);
66
48
  }
@@ -143,7 +125,6 @@ export default class Edit extends BaseCommand {
143
125
  if (updated.html) {
144
126
  await saveTemplate(ctx.templateFilename, updated.html);
145
127
  }
146
- await this.syncYamlToServer();
147
128
  }
148
129
  logJsonResult(email, updated, oldSubject) {
149
130
  this.log(JSON.stringify({
@@ -9,6 +9,5 @@ export default class Init extends BaseCommand {
9
9
  };
10
10
  run(): Promise<void>;
11
11
  private persistMonthlyCap;
12
- private preserveUserFields;
13
12
  private confirmOverwriteIfNeeded;
14
13
  }
@@ -35,8 +35,7 @@ export default class Init extends BaseCommand {
35
35
  async run() {
36
36
  const { flags } = await this.parse(Init);
37
37
  await this.ensureAuth();
38
- const existing = await loadYaml();
39
- if (!(await this.confirmOverwriteIfNeeded(flags, existing)))
38
+ if (!(await this.confirmOverwriteIfNeeded(flags)))
40
39
  return;
41
40
  let productUrl = flags.url;
42
41
  if (!productUrl) {
@@ -148,8 +147,6 @@ export default class Init extends BaseCommand {
148
147
  webhookUrl: '',
149
148
  },
150
149
  };
151
- if (existing)
152
- this.preserveUserFields(yamlConfig, existing);
153
150
  await saveYaml(yamlConfig);
154
151
  await this.persistMonthlyCap(yamlConfig);
155
152
  const templateSaves = analysisPayload.recommendedEmails.flatMap((rec, index) => {
@@ -164,7 +161,6 @@ export default class Init extends BaseCommand {
164
161
  return saves;
165
162
  });
166
163
  await Promise.all(templateSaves);
167
- await this.syncYamlToServer();
168
164
  if (flags.json) {
169
165
  this.log(JSON.stringify({
170
166
  brandDetected: analysisPayload.brand,
@@ -185,26 +181,8 @@ export default class Init extends BaseCommand {
185
181
  await saveYaml(yamlConfig);
186
182
  }
187
183
  }
188
- preserveUserFields(config, existing) {
189
- const p = existing.project;
190
- if (p.fromEmail)
191
- config.project.fromEmail = p.fromEmail;
192
- if (p.replyTo)
193
- config.project.replyTo = p.replyTo;
194
- if (p.fromName)
195
- config.project.fromName = p.fromName;
196
- if (p.webhookUrl)
197
- config.project.webhookUrl = p.webhookUrl;
198
- if (p.emailStyle)
199
- config.project.emailStyle = p.emailStyle;
200
- if (p.domain)
201
- config.project.domain = p.domain;
202
- if (p.address)
203
- config.project.address = p.address;
204
- if (p.logoFile)
205
- config.project.logoFile = p.logoFile;
206
- }
207
- async confirmOverwriteIfNeeded(flags, existing) {
184
+ async confirmOverwriteIfNeeded(flags) {
185
+ const existing = await loadYaml();
208
186
  if (!existing)
209
187
  return true;
210
188
  if (flags.yes)
@@ -27,14 +27,11 @@ export default class Login extends BaseCommand {
27
27
  if (!envKey) {
28
28
  const existing = await loadConfig();
29
29
  if (existing?.apiKey) {
30
- const existingClient = new ApiClient(existing.apiKey);
31
- const yamlRestored = await this.recoverYamlAfterLogin(existingClient);
32
30
  if (flags.json) {
33
31
  this.log(JSON.stringify({
34
32
  email: existing.email ?? null,
35
33
  status: 'already_logged_in',
36
34
  totalFreeRemaining: existing.totalFreeRemaining ?? null,
37
- yamlRestored,
38
35
  }, null, 2));
39
36
  return;
40
37
  }
@@ -43,13 +40,8 @@ export default class Login extends BaseCommand {
43
40
  ? chalk.green(existing.email.trim())
44
41
  : chalk.dim('(unknown)');
45
42
  this.log(` Email: ${emailDisplay}\n`);
46
- if (yamlRestored) {
47
- this.log(` ${INFO.YAML_RESTORED_ON_LOGIN}`);
48
- }
49
- else {
50
- this.log(` ${chalk.dim('1.')} Run ${chalk.cyan('mailmodo init')} to generate an email sequence.`);
51
- }
52
- this.log(` ${chalk.dim(yamlRestored ? '1.' : '2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
43
+ this.log(` ${chalk.dim('1.')} Run ${chalk.cyan('mailmodo init')} to generate an email sequence.`);
44
+ this.log(` ${chalk.dim('2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
53
45
  return;
54
46
  }
55
47
  }
@@ -87,7 +79,6 @@ export default class Login extends BaseCommand {
87
79
  email,
88
80
  totalFreeRemaining,
89
81
  });
90
- const yamlRestored = await this.recoverYamlAfterLogin(client);
91
82
  if (flags.json) {
92
83
  this.log(JSON.stringify({
93
84
  email,
@@ -95,7 +86,6 @@ export default class Login extends BaseCommand {
95
86
  totalFreeRemaining,
96
87
  paidEmailsRemaining,
97
88
  status: 'authenticated',
98
- yamlRestored,
99
89
  }, null, 2));
100
90
  return;
101
91
  }
@@ -107,11 +97,6 @@ export default class Login extends BaseCommand {
107
97
  if (plan === 'paid') {
108
98
  this.log(` Current paid block: ${chalk.cyan(String(paidEmailsRemaining))} emails remaining\n`);
109
99
  }
110
- if (yamlRestored) {
111
- this.log(` ${INFO.YAML_RESTORED_ON_LOGIN}\n`);
112
- }
113
- else {
114
- this.log(` Next: Run ${chalk.cyan("'mailmodo init'")} to generate your email sequences.\n`);
115
- }
100
+ this.log(` Next: Run ${chalk.cyan("'mailmodo init'")} to generate your email sequences.\n`);
116
101
  }
117
102
  }