@mailmodo/cli 0.0.56-beta.pr58.107 → 0.0.56-beta.pr58.94

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.
Files changed (39) hide show
  1. package/dist/commands/deploy/index.js +3 -5
  2. package/dist/commands/init/index.js +3 -11
  3. package/dist/commands/login/index.js +5 -2
  4. package/dist/lib/api-client.d.ts +0 -2
  5. package/dist/lib/api-client.js +2 -2
  6. package/dist/lib/base-command.d.ts +13 -10
  7. package/dist/lib/base-command.js +27 -86
  8. package/dist/lib/commands/edit/diff.js +2 -2
  9. package/dist/lib/commands/edit/persist.js +4 -5
  10. package/dist/lib/commands/emails/editor.js +1 -1
  11. package/dist/lib/commands/init/analysis.js +1 -5
  12. package/dist/lib/commands/login/output.d.ts +2 -2
  13. package/dist/lib/commands/login/output.js +18 -5
  14. package/dist/lib/config.d.ts +0 -2
  15. package/dist/lib/config.js +10 -19
  16. package/dist/lib/constants.d.ts +2 -3
  17. package/dist/lib/constants.js +5 -4
  18. package/dist/lib/messages.d.ts +0 -18
  19. package/dist/lib/messages.js +0 -38
  20. package/dist/lib/templates/missing-templates.d.ts +1 -1
  21. package/dist/lib/templates/missing-templates.js +2 -2
  22. package/dist/lib/yaml-config.d.ts +0 -1
  23. package/dist/lib/yaml-config.js +0 -8
  24. package/oclif.manifest.json +38 -205
  25. package/package.json +1 -1
  26. package/dist/commands/report/index.d.ts +0 -22
  27. package/dist/commands/report/index.js +0 -123
  28. package/dist/lib/commands/report/output-entries.d.ts +0 -2
  29. package/dist/lib/commands/report/output-entries.js +0 -59
  30. package/dist/lib/commands/report/output-timeseries.d.ts +0 -2
  31. package/dist/lib/commands/report/output-timeseries.js +0 -28
  32. package/dist/lib/commands/report/output.d.ts +0 -3
  33. package/dist/lib/commands/report/output.js +0 -56
  34. package/dist/lib/commands/report/payload.d.ts +0 -2
  35. package/dist/lib/commands/report/payload.js +0 -49
  36. package/dist/lib/commands/report/prompt.d.ts +0 -2
  37. package/dist/lib/commands/report/prompt.js +0 -82
  38. package/dist/lib/commands/report/types.d.ts +0 -97
  39. package/dist/lib/commands/report/types.js +0 -1
@@ -3,7 +3,7 @@ import { confirm } 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';
6
- import { MISSING_TEMPLATES, restoredFromServerHint, } from '../../lib/messages.js';
6
+ import { MISSING_TEMPLATES } from '../../lib/messages.js';
7
7
  import { buildDeployPayload } from '../../lib/commands/deploy/payload.js';
8
8
  import { logDeploySuccessInstructions, logPreDeploySummary, } from '../../lib/commands/deploy/output.js';
9
9
  import { pauseSequence, resumeSequence, } from '../../lib/commands/deploy/sequence-status.js';
@@ -44,10 +44,8 @@ export default class Deploy extends BaseCommand {
44
44
  const yamlConfig = await this.ensureYaml();
45
45
  const missingIds = getMissingTemplateIds(yamlConfig);
46
46
  if (missingIds.length > 0) {
47
- const result = await handleMissingTemplates(ctx, yamlConfig, missingIds, baseFlags);
48
- if (result === 'restored')
49
- ctx.log(`\n ${chalk.green('✓')} ${restoredFromServerHint(missingIds)}\n`);
50
- else if (result === 'regenerated')
47
+ const regenerated = await handleMissingTemplates(ctx, yamlConfig, missingIds, baseFlags);
48
+ if (regenerated)
51
49
  ctx.log(`\n ${chalk.green('✓')} ${MISSING_TEMPLATES.REVIEW_HINT}\n`);
52
50
  return;
53
51
  }
@@ -1,7 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../lib/base-command.js';
3
3
  import { API_ENDPOINTS } from '../../lib/constants.js';
4
- import { loadYaml, saveYaml, } from '../../lib/yaml-config.js';
4
+ import { loadYaml, saveYaml } from '../../lib/yaml-config.js';
5
5
  import { analyzeProduct, promptProductUrl, } from '../../lib/commands/init/analysis.js';
6
6
  import { confirmOverwrite, logInitSuccess, } from '../../lib/commands/init/output.js';
7
7
  import { applyMonthlyCap, buildEmailConfigs, buildYamlConfig, preserveUserFields, saveAllTemplates, } from '../../lib/commands/init/payload.js';
@@ -20,13 +20,6 @@ export default class Init extends BaseCommand {
20
20
  await this.ensureAuth();
21
21
  const ctx = this.makeCtx();
22
22
  const baseFlags = { json: flags.json, yes: flags.yes };
23
- let serverYaml = null;
24
- if (this.isBlankDirectory()) {
25
- const result = await this.promptInitServerRestore(baseFlags);
26
- if (result.restored)
27
- return;
28
- serverYaml = result.serverYaml;
29
- }
30
23
  const existing = await loadYaml();
31
24
  if (!(await confirmOverwrite(ctx, baseFlags, existing)))
32
25
  return;
@@ -40,9 +33,8 @@ export default class Init extends BaseCommand {
40
33
  const generatedEmails = generateResponse.data?.emails || [];
41
34
  const emailConfigs = buildEmailConfigs(analysisPayload, generatedEmails);
42
35
  const yamlConfig = buildYamlConfig(analysisPayload, emailConfigs, productUrl);
43
- const fieldSource = existing ?? serverYaml;
44
- if (fieldSource)
45
- preserveUserFields(yamlConfig, fieldSource);
36
+ if (existing)
37
+ preserveUserFields(yamlConfig, existing);
46
38
  await saveYaml(yamlConfig);
47
39
  await applyMonthlyCap(ctx, yamlConfig);
48
40
  await saveAllTemplates(analysisPayload.recommendedEmails, generatedEmails);
@@ -27,7 +27,9 @@ export default class Login extends BaseCommand {
27
27
  if (!envKey) {
28
28
  const existing = await loadConfig();
29
29
  if (existing?.apiKey) {
30
- logAlreadyLoggedIn(ctx, existing, { json: flags.json });
30
+ const existingClient = new ApiClient(existing.apiKey);
31
+ const yamlRestored = await this.recoverYamlAfterLogin(existingClient);
32
+ logAlreadyLoggedIn(ctx, existing, yamlRestored, { json: flags.json });
31
33
  return;
32
34
  }
33
35
  }
@@ -40,7 +42,8 @@ export default class Login extends BaseCommand {
40
42
  }
41
43
  const { email, totalFreeRemaining, paidEmailsRemaining, plan } = response.data;
42
44
  await saveConfig({ apiKey: trimmedKey, email, totalFreeRemaining });
43
- logLoginSuccess(ctx, { email, paidEmailsRemaining, plan, totalFreeRemaining }, { json: flags.json });
45
+ const yamlRestored = await this.recoverYamlAfterLogin(client);
46
+ logLoginSuccess(ctx, { email, paidEmailsRemaining, plan, totalFreeRemaining }, yamlRestored, { json: flags.json });
44
47
  }
45
48
  makeCtx() {
46
49
  return {
@@ -8,8 +8,6 @@ export interface ApiRequestDebugInfo {
8
8
  causeCode?: string;
9
9
  /** Fully resolved request URL (origin, path, and query string). */
10
10
  fullUrl: string;
11
- /** Truncated JSON summary of the request body sent, when present. */
12
- requestBody?: string;
13
11
  /** Truncated JSON summary of a non-empty error response body, when available. */
14
12
  responseSummary?: string;
15
13
  }
@@ -82,12 +82,12 @@ export class ApiClient {
82
82
  const data = await response.json().catch(() => ({}));
83
83
  if (!response.ok) {
84
84
  const errorData = data;
85
+ const summary = summarizeResponseBody(data);
85
86
  return {
86
87
  data: data,
87
88
  debug: {
88
89
  ...debug,
89
- requestBody: summarizeResponseBody(body),
90
- responseSummary: summarizeResponseBody(data),
90
+ responseSummary: summary,
91
91
  },
92
92
  error: errorData?.message ||
93
93
  errorData?.error ||
@@ -68,22 +68,25 @@ export declare abstract class BaseCommand extends Command {
68
68
  * settings and all email sequence definitions.
69
69
  */
70
70
  protected ensureYaml(): Promise<MailmodoYaml>;
71
- protected isBlankDirectory(): boolean;
72
- protected fetchYamlText(): Promise<null | string>;
73
- private promptBlankDirRestore;
74
- protected promptInitServerRestore(flags: {
75
- json?: boolean;
76
- yes?: boolean;
77
- }): Promise<{
78
- restored: boolean;
79
- serverYaml: MailmodoYaml | null;
80
- }>;
81
71
  private fetchAndWriteYaml;
82
72
  /**
83
73
  * Attempts to fetch mailmodo.yaml from the server and save it locally.
84
74
  * Returns null silently on any failure so callers can fall through to an error.
85
75
  */
86
76
  private restoreYamlFromServer;
77
+ /**
78
+ * If `mailmodo.yaml` is absent from the current directory, attempts to restore
79
+ * it from the server using the given client. Returns `true` if the file was
80
+ * successfully written, `false` otherwise (file already present, server 404,
81
+ * or any network error). Silent — never throws.
82
+ *
83
+ * After ensuring the YAML is available, also silently restores any template
84
+ * files that are missing from the local mailmodo/ folder, so a returning user
85
+ * gets both their config and templates back without having to run init again.
86
+ *
87
+ * Used by `mailmodo login` right after the API key is validated.
88
+ */
89
+ protected recoverYamlAfterLogin(client: ApiClient): Promise<boolean>;
87
90
  /**
88
91
  * Uploads the current local mailmodo.yaml to the server as a backup.
89
92
  * Best-effort: silently ignores all errors so the originating command
@@ -1,17 +1,17 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- import { input, select } from '@inquirer/prompts';
4
+ import { input } from '@inquirer/prompts';
5
5
  import { Command, Flags } from '@oclif/core';
6
6
  import chalk from 'chalk';
7
7
  import ora from 'ora';
8
8
  import { ApiClient } from './api-client.js';
9
9
  import { loadConfig } from './config.js';
10
- import { API_ENDPOINTS, IS_DEV_MODE, TEMPLATES_DIR, YAML_FILE, } from './constants.js';
10
+ import { API_ENDPOINTS, IS_DEV_MODE, YAML_FILE } from './constants.js';
11
11
  import { syncTemplatesToServer, syncTemplateToServer, fetchTemplatesFromServer, fetchTemplateFromServer, } from './templates/sync.js';
12
12
  import { getMissingTemplateIds } from './templates/missing-templates.js';
13
- import { BLANK_DIR, ERRORS, INFO, PROMPTS, quotaExhaustedMessage, recordLabel, VALIDATION, } from './messages.js';
14
- import { loadYaml, parseYamlText, saveYaml, } from './yaml-config.js';
13
+ import { ERRORS, INFO, PROMPTS, quotaExhaustedMessage, recordLabel, VALIDATION, } from './messages.js';
14
+ import { loadYaml, saveYaml } from './yaml-config.js';
15
15
  export const FREE_TIER = 'free';
16
16
  /**
17
17
  * Abstract base command providing shared functionality for all Mailmodo CLI commands.
@@ -92,90 +92,11 @@ export class BaseCommand extends Command {
92
92
  const config = await loadYaml();
93
93
  if (config)
94
94
  return config;
95
- if (this.isBlankDirectory())
96
- return this.promptBlankDirRestore();
97
95
  const restored = await this.restoreYamlFromServer();
98
96
  if (restored)
99
97
  return restored;
100
98
  this.error(ERRORS.NO_YAML);
101
99
  }
102
- isBlankDirectory() {
103
- return (!existsSync(join(process.cwd(), YAML_FILE)) &&
104
- !existsSync(join(process.cwd(), TEMPLATES_DIR)));
105
- }
106
- async fetchYamlText() {
107
- try {
108
- let client = this.apiClient;
109
- if (!client) {
110
- const apiKey = process.env.MAILMODO_API_KEY ?? (await loadConfig())?.apiKey;
111
- if (!apiKey)
112
- return null;
113
- client = new ApiClient(apiKey);
114
- }
115
- const response = await client.getRawText(API_ENDPOINTS.ASSETS_YAML);
116
- return response.ok && response.data ? response.data : null;
117
- }
118
- catch {
119
- return null;
120
- }
121
- }
122
- async promptBlankDirRestore() {
123
- const yamlText = await this.fetchYamlText();
124
- if (!yamlText)
125
- this.error(ERRORS.NO_YAML);
126
- const autoRestore = this.argv.includes('--yes') || this.argv.includes('--json');
127
- if (!autoRestore) {
128
- this.log(`\n ${BLANK_DIR.PROMPT}\n`);
129
- const choice = await select({
130
- choices: [
131
- { name: BLANK_DIR.CHOICE_RESTORE, value: 'restore' },
132
- { name: BLANK_DIR.CHOICE_SKIP, value: 'skip' },
133
- ],
134
- message: 'How would you like to proceed?',
135
- });
136
- if (choice === 'skip') {
137
- this.log(`\n ${BLANK_DIR.SKIP_HINT}\n`);
138
- this.exit(0);
139
- }
140
- }
141
- await writeFile(join(process.cwd(), YAML_FILE), yamlText);
142
- this.logToStderr(INFO.YAML_RESTORED_FROM_SERVER);
143
- const yaml = await loadYaml();
144
- if (!yaml)
145
- this.error(ERRORS.NO_YAML);
146
- const client = this.apiClient;
147
- if (client)
148
- await this.fetchMissingTemplates(client, yaml);
149
- return yaml;
150
- }
151
- async promptInitServerRestore(flags) {
152
- const yamlText = await this.fetchYamlText();
153
- if (!yamlText)
154
- return { restored: false, serverYaml: null };
155
- let shouldRestore = Boolean(flags.yes || flags.json);
156
- if (!shouldRestore) {
157
- const choice = await select({
158
- choices: [
159
- { name: BLANK_DIR.CHOICE_RESTORE_INIT, value: 'restore' },
160
- { name: BLANK_DIR.CHOICE_FRESH, value: 'fresh' },
161
- ],
162
- message: BLANK_DIR.PROMPT_INIT,
163
- });
164
- shouldRestore = choice === 'restore';
165
- }
166
- if (!shouldRestore) {
167
- return { restored: false, serverYaml: parseYamlText(yamlText) };
168
- }
169
- await writeFile(join(process.cwd(), YAML_FILE), yamlText);
170
- const yaml = await loadYaml();
171
- if (!yaml)
172
- return { restored: false, serverYaml: null };
173
- const client = this.apiClient;
174
- if (client)
175
- await this.fetchMissingTemplates(client, yaml);
176
- this.log(`\n ${BLANK_DIR.RESTORED_INIT}\n`);
177
- return { restored: true, serverYaml: yaml };
178
- }
179
100
  async fetchAndWriteYaml(client) {
180
101
  try {
181
102
  const response = await client.getRawText(API_ENDPOINTS.ASSETS_YAML);
@@ -212,6 +133,29 @@ export class BaseCommand extends Command {
212
133
  return null;
213
134
  }
214
135
  }
136
+ /**
137
+ * If `mailmodo.yaml` is absent from the current directory, attempts to restore
138
+ * it from the server using the given client. Returns `true` if the file was
139
+ * successfully written, `false` otherwise (file already present, server 404,
140
+ * or any network error). Silent — never throws.
141
+ *
142
+ * After ensuring the YAML is available, also silently restores any template
143
+ * files that are missing from the local mailmodo/ folder, so a returning user
144
+ * gets both their config and templates back without having to run init again.
145
+ *
146
+ * Used by `mailmodo login` right after the API key is validated.
147
+ */
148
+ async recoverYamlAfterLogin(client) {
149
+ let yamlRestored = false;
150
+ if (!existsSync(join(process.cwd(), YAML_FILE))) {
151
+ yamlRestored = await this.fetchAndWriteYaml(client);
152
+ }
153
+ // Always check for missing templates, whether YAML was just restored or already present
154
+ const yaml = await loadYaml();
155
+ if (yaml)
156
+ await this.fetchMissingTemplates(client, yaml);
157
+ return yamlRestored;
158
+ }
215
159
  /**
216
160
  * Uploads the current local mailmodo.yaml to the server as a backup.
217
161
  * Best-effort: silently ignores all errors so the originating command
@@ -558,9 +502,6 @@ export class BaseCommand extends Command {
558
502
  status > 0
559
503
  ? chalk.dim(` Status: ${String(status)}`)
560
504
  : chalk.dim(' Status: (No HTTP response — network or client error)'),
561
- ...(debug.requestBody
562
- ? [chalk.dim(` Payload: ${debug.requestBody}`)]
563
- : []),
564
505
  ...(debug.responseSummary
565
506
  ? [chalk.dim(` Response: ${debug.responseSummary}`)]
566
507
  : []),
@@ -42,12 +42,12 @@ export function buildDiffPreview(email, updated, templateHtml) {
42
42
  diff.subject = subjectChanged
43
43
  ? { new: updated.subject, old: email.subject }
44
44
  : { unchanged: true, value: email.subject };
45
- if (email.previewText || updated.previewText) {
45
+ if (email.previewText ?? updated.previewText) {
46
46
  diff.previewText = previewChanged
47
47
  ? { new: updated.previewText, old: email.previewText }
48
48
  : { unchanged: true, value: email.previewText };
49
49
  }
50
- if (templateHtml || updated.html) {
50
+ if (templateHtml ?? updated.html) {
51
51
  const oldText = templateHtml
52
52
  ? truncate(stripHtml(templateHtml), 500)
53
53
  : null;
@@ -22,11 +22,11 @@ async function persistChanges(ctx, editCtx, updated) {
22
22
  await ctx.syncYaml();
23
23
  await ctx.syncTemplate(editCtx.email.id);
24
24
  }
25
- function logJsonResult(ctx, email, updated, oldSubject, oldPreviewText) {
25
+ function logJsonResult(ctx, email, updated, oldSubject) {
26
26
  ctx.log(JSON.stringify({
27
27
  diff: {
28
- previewText: updated.previewText && updated.previewText !== oldPreviewText
29
- ? { new: updated.previewText, old: oldPreviewText }
28
+ previewText: updated.previewText && updated.previewText !== email.previewText
29
+ ? { new: updated.previewText, old: email.previewText }
30
30
  : undefined,
31
31
  subject: oldSubject === email.subject
32
32
  ? undefined
@@ -52,11 +52,10 @@ async function handleAcceptOutput(ctx, email) {
52
52
  export async function finalizeEdit(ctx, editCtx, opts) {
53
53
  const { flags, updated } = opts;
54
54
  const oldSubject = editCtx.email.subject;
55
- const oldPreviewText = editCtx.email.previewText;
56
55
  applyEmailChanges(editCtx.email, updated);
57
56
  await persistChanges(ctx, editCtx, updated);
58
57
  if (flags.json) {
59
- logJsonResult(ctx, editCtx.email, updated, oldSubject, oldPreviewText);
58
+ logJsonResult(ctx, editCtx.email, updated, oldSubject);
60
59
  }
61
60
  else if (flags.yes) {
62
61
  ctx.log(`\n Updated ${chalk.green('mailmodo.yaml')}\n`);
@@ -27,7 +27,7 @@ export async function openTemplateInEditor(ctx, template) {
27
27
  stdio: 'inherit',
28
28
  });
29
29
  child.on('error', () => resolve(false));
30
- child.on('close', (code) => resolve(code === 0));
30
+ child.on('close', () => resolve(true));
31
31
  });
32
32
  if (launched)
33
33
  return;
@@ -4,12 +4,8 @@ import { API_ENDPOINTS } from '../../constants.js';
4
4
  import { isValidUrl } from '../../utils.js';
5
5
  import { logAnalysisSummary } from './output.js';
6
6
  export async function promptProductUrl(flagUrl) {
7
- if (flagUrl) {
8
- if (!isValidUrl(flagUrl)) {
9
- throw new Error(`Invalid URL: "${flagUrl}". Please enter a valid URL (e.g., https://myapp.com)`);
10
- }
7
+ if (flagUrl)
11
8
  return flagUrl;
12
- }
13
9
  return input({
14
10
  message: 'What is your product URL?',
15
11
  validate(value) {
@@ -1,8 +1,8 @@
1
1
  import type { LoginCtx, ValidateResponse } from './types.js';
2
2
  import type { MailmodoConfig } from '../../config.js';
3
- export declare function logAlreadyLoggedIn(ctx: LoginCtx, existing: MailmodoConfig, opts: {
3
+ export declare function logAlreadyLoggedIn(ctx: LoginCtx, existing: MailmodoConfig, yamlRestored: boolean, opts: {
4
4
  json: boolean;
5
5
  }): void;
6
- export declare function logLoginSuccess(ctx: LoginCtx, data: Pick<ValidateResponse, 'email' | 'paidEmailsRemaining' | 'plan' | 'totalFreeRemaining'>, opts: {
6
+ export declare function logLoginSuccess(ctx: LoginCtx, data: Pick<ValidateResponse, 'email' | 'paidEmailsRemaining' | 'plan' | 'totalFreeRemaining'>, yamlRestored: boolean, opts: {
7
7
  json: boolean;
8
8
  }): void;
@@ -1,10 +1,12 @@
1
1
  import chalk from 'chalk';
2
- export function logAlreadyLoggedIn(ctx, existing, opts) {
2
+ import { INFO } from '../../messages.js';
3
+ export function logAlreadyLoggedIn(ctx, existing, yamlRestored, opts) {
3
4
  if (opts.json) {
4
5
  ctx.log(JSON.stringify({
5
6
  email: existing.email ?? null,
6
7
  status: 'already_logged_in',
7
8
  totalFreeRemaining: existing.totalFreeRemaining ?? null,
9
+ yamlRestored,
8
10
  }, null, 2));
9
11
  return;
10
12
  }
@@ -13,10 +15,15 @@ export function logAlreadyLoggedIn(ctx, existing, opts) {
13
15
  ? chalk.green(existing.email.trim())
14
16
  : chalk.dim('(unknown)');
15
17
  ctx.log(` Email: ${emailDisplay}\n`);
16
- ctx.log(` ${chalk.dim('1.')} Run ${chalk.cyan('mailmodo init')} to generate an email sequence.`);
17
- ctx.log(` ${chalk.dim('2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
18
+ if (yamlRestored) {
19
+ ctx.log(` ${INFO.YAML_RESTORED_ON_LOGIN}`);
20
+ }
21
+ else {
22
+ ctx.log(` ${chalk.dim('1.')} Run ${chalk.cyan('mailmodo init')} to generate an email sequence.`);
23
+ }
24
+ ctx.log(` ${chalk.dim(yamlRestored ? '1.' : '2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
18
25
  }
19
- export function logLoginSuccess(ctx, data, opts) {
26
+ export function logLoginSuccess(ctx, data, yamlRestored, opts) {
20
27
  const { email, plan, totalFreeRemaining, paidEmailsRemaining } = data;
21
28
  if (opts.json) {
22
29
  ctx.log(JSON.stringify({
@@ -25,6 +32,7 @@ export function logLoginSuccess(ctx, data, opts) {
25
32
  plan,
26
33
  status: 'authenticated',
27
34
  totalFreeRemaining,
35
+ yamlRestored,
28
36
  }, null, 2));
29
37
  return;
30
38
  }
@@ -36,5 +44,10 @@ export function logLoginSuccess(ctx, data, opts) {
36
44
  if (plan === 'paid') {
37
45
  ctx.log(` Current paid block: ${chalk.cyan(String(paidEmailsRemaining))} emails remaining\n`);
38
46
  }
39
- ctx.log(` Next: Run ${chalk.cyan("'mailmodo init'")} to generate your email sequences.\n`);
47
+ if (yamlRestored) {
48
+ ctx.log(` ${INFO.YAML_RESTORED_ON_LOGIN}\n`);
49
+ }
50
+ else {
51
+ ctx.log(` Next: Run ${chalk.cyan("'mailmodo init'")} to generate your email sequences.\n`);
52
+ }
40
53
  }
@@ -28,8 +28,6 @@ export declare function saveConfig(config: MailmodoConfig): Promise<void>;
28
28
  export declare function clearConfig(): Promise<void>;
29
29
  /**
30
30
  * Returns the absolute path to the Mailmodo config directory (~/.mailmodo).
31
- * Honors MAILMODO_CONFIG_DIR env var when set (used in tests to redirect away
32
- * from the user's real config).
33
31
  *
34
32
  * @returns {string} The config directory path.
35
33
  */
@@ -2,12 +2,8 @@ import { existsSync } from 'node:fs';
2
2
  import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
- function configDir() {
6
- return process.env.MAILMODO_CONFIG_DIR ?? join(homedir(), '.mailmodo');
7
- }
8
- function configFile() {
9
- return join(configDir(), 'config');
10
- }
5
+ const CONFIG_DIR = join(homedir(), '.mailmodo');
6
+ const CONFIG_FILE = join(CONFIG_DIR, 'config');
11
7
  /**
12
8
  * Loads the Mailmodo CLI configuration from ~/.mailmodo/config.
13
9
  * The config file stores the API key and account metadata as JSON.
@@ -17,11 +13,10 @@ function configFile() {
17
13
  * or null if the config file does not exist or is corrupted.
18
14
  */
19
15
  export async function loadConfig() {
20
- const file = configFile();
21
- if (!existsSync(file))
16
+ if (!existsSync(CONFIG_FILE))
22
17
  return null;
23
18
  try {
24
- const content = await readFile(file, 'utf8');
19
+ const content = await readFile(CONFIG_FILE, 'utf8');
25
20
  return JSON.parse(content);
26
21
  }
27
22
  catch {
@@ -37,22 +32,20 @@ export async function loadConfig() {
37
32
  * at minimum an apiKey. Optional fields: email, totalFreeRemaining.
38
33
  */
39
34
  export async function saveConfig(config) {
40
- const dir = configDir();
41
- if (!existsSync(dir)) {
42
- await mkdir(dir, { recursive: true });
35
+ if (!existsSync(CONFIG_DIR)) {
36
+ await mkdir(CONFIG_DIR, { recursive: true });
43
37
  }
44
- await writeFile(configFile(), JSON.stringify(config, null, 2));
38
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
45
39
  }
46
40
  /**
47
41
  * Deletes the saved CLI config file (~/.mailmodo/config), removing the stored API key.
48
42
  * No-op if the file does not exist.
49
43
  */
50
44
  export async function clearConfig() {
51
- const file = configFile();
52
- if (!existsSync(file))
45
+ if (!existsSync(CONFIG_FILE))
53
46
  return;
54
47
  try {
55
- await unlink(file);
48
+ await unlink(CONFIG_FILE);
56
49
  }
57
50
  catch {
58
51
  // Ignore missing file or permission errors; caller can treat as best-effort sign-out.
@@ -60,11 +53,9 @@ export async function clearConfig() {
60
53
  }
61
54
  /**
62
55
  * Returns the absolute path to the Mailmodo config directory (~/.mailmodo).
63
- * Honors MAILMODO_CONFIG_DIR env var when set (used in tests to redirect away
64
- * from the user's real config).
65
56
  *
66
57
  * @returns {string} The config directory path.
67
58
  */
68
59
  export function getConfigDir() {
69
- return configDir();
60
+ return CONFIG_DIR;
70
61
  }
@@ -4,7 +4,7 @@
4
4
  * when this is true so end users never see internal request metadata.
5
5
  */
6
6
  export declare const IS_DEV_MODE: boolean;
7
- export declare const API_BASE_URL: string;
7
+ export declare const API_BASE_URL = "https://app-vertex-debug.azurewebsites.net";
8
8
  export declare const API_ENDPOINTS: Readonly<{
9
9
  ANALYTICS: "/analytics";
10
10
  ANALYZE: "/analyze";
@@ -26,13 +26,12 @@ export declare const API_ENDPOINTS: Readonly<{
26
26
  GENERATE: "/email/generate";
27
27
  LOGS: "/logs";
28
28
  PREVIEW: "/preview";
29
- REPORTS: "/reports";
30
29
  SEQUENCES: "/sequences";
31
30
  SEQUENCES_DEPLOY: "/sequences/deploy";
32
31
  SEQUENCES_SDK: "/sequences/sdk";
33
32
  SEQUENCES_VALIDATE: "/sequences/validate";
34
33
  }>;
35
- export declare const LOGIN_URL: string;
34
+ export declare const LOGIN_URL = "https://app-vertex-debug.azurewebsites.net/signup.html";
36
35
  export declare const PREVIEW_PORT = 3421;
37
36
  export declare const DEFAULT_BRAND_COLOR = "#1A56DB";
38
37
  export declare const TEMPLATES_DIR = "mailmodo";
@@ -1,12 +1,13 @@
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.dev';
4
3
  /**
5
4
  * True when the CLI is running in a dev/debug context. Verbose request
6
5
  * diagnostics (URL, status, response body, error code) are only surfaced
7
6
  * when this is true so end users never see internal request metadata.
8
7
  */
9
8
  export const IS_DEV_MODE = Boolean(process.env.MAILMODO_DEV_TSX || process.env.MAILMODO_DEBUG);
9
+ // const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
10
+ const PRODUCTION_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
10
11
  export const API_BASE_URL = process.env.MAILMODO_DEV_TSX
11
12
  ? DEV_API_BASE_URL
12
13
  : PRODUCTION_API_BASE_URL;
@@ -31,14 +32,14 @@ export const API_ENDPOINTS = Object.freeze({
31
32
  GENERATE: '/email/generate',
32
33
  LOGS: '/logs',
33
34
  PREVIEW: '/preview',
34
- REPORTS: '/reports',
35
35
  SEQUENCES: '/sequences',
36
36
  SEQUENCES_DEPLOY: '/sequences/deploy',
37
37
  SEQUENCES_SDK: '/sequences/sdk',
38
38
  SEQUENCES_VALIDATE: '/sequences/validate',
39
39
  });
40
- const DEV_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/signup';
41
- const PRODUCTION_LOGIN_URL = 'https://app.mailmodo.dev/signup';
40
+ const DEV_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/signup.html';
41
+ // const PRODUCTION_LOGIN_URL = 'https://mailmodo.com/cli';
42
+ const PRODUCTION_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/signup.html';
42
43
  export const LOGIN_URL = process.env.MAILMODO_DEV_TSX
43
44
  ? DEV_LOGIN_URL
44
45
  : PRODUCTION_LOGIN_URL;
@@ -13,16 +13,6 @@ export declare const PROMPTS: {
13
13
  readonly REPLY_TO: "Reply-to address (optional, press Enter to use sender email):";
14
14
  readonly SENDER_EMAIL: "Sender email address:";
15
15
  };
16
- export declare const BLANK_DIR: {
17
- readonly CHOICE_FRESH: "Start fresh — new emails will be generated (your domain, sender, and address settings will be kept)";
18
- readonly CHOICE_RESTORE: "Restore project files from server";
19
- readonly CHOICE_RESTORE_INIT: "Restore existing project files from server";
20
- readonly CHOICE_SKIP: "Skip — I'll navigate to my project directory manually";
21
- readonly PROMPT: "No mailmodo.yaml or templates found in this directory.";
22
- readonly PROMPT_INIT: "A project was found on the server. What would you like to do?";
23
- readonly RESTORED_INIT: `Project restored from server. Run ${string} to re-deploy, or ${string} to review your emails.`;
24
- readonly SKIP_HINT: `Navigate to your project directory and run your command there, or run ${string} to start a new project here.`;
25
- };
26
16
  export declare const MISSING_TEMPLATES: {
27
17
  readonly ABORT_HINT: `Restore the missing files from version control, then run ${string} again.`;
28
18
  readonly CHOICE_ABORT: "Abort (restore from version control)";
@@ -64,13 +54,6 @@ export declare const INFO: {
64
54
  readonly YAML_RESTORED_FROM_SERVER: string;
65
55
  readonly YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${string} to re-deploy your sequences.`;
66
56
  };
67
- export declare const REPORTS: {
68
- readonly FETCH_SPINNER: " Fetching report...";
69
- readonly FROM_TO_REQUIRED: "--from and --to must both be provided together.";
70
- readonly NO_DATA: "No data found for the given filters and time range.";
71
- readonly TIME_RANGE_REQUIRED: "A time range is required. Use --preset or --from/--to.";
72
- readonly TOO_MANY_ITEMS: "Filter arrays cannot exceed 100 items each.";
73
- };
74
57
  export declare const DEPLOY: {
75
58
  readonly CHANGES_HEADER: "Changes vs. last deployment:";
76
59
  readonly DEPLOYING_HEADER: "Deploying:";
@@ -80,7 +63,6 @@ export declare const DEPLOY: {
80
63
  readonly SDK_ONBOARDING_HEADER: string;
81
64
  readonly SUCCESS: `${string} Emails are live.`;
82
65
  };
83
- export declare function restoredFromServerHint(ids: string[]): string;
84
66
  export declare function pauseSuccess(sequenceId: string): string;
85
67
  export declare function pauseAlready(sequenceId: string): string;
86
68
  export declare function resumeSuccess(sequenceId: string): string;