@mailmodo/cli 0.0.55-beta.pr57.93 → 0.0.55

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 (125) hide show
  1. package/dist/commands/billing/index.d.ts +11 -1
  2. package/dist/commands/billing/index.js +184 -28
  3. package/dist/commands/contacts/index.d.ts +19 -1
  4. package/dist/commands/contacts/index.js +114 -21
  5. package/dist/commands/deploy/index.js +4 -4
  6. package/dist/commands/deployments/index.d.ts +4 -1
  7. package/dist/commands/deployments/index.js +52 -11
  8. package/dist/commands/domain/index.d.ts +14 -1
  9. package/dist/commands/domain/index.js +100 -19
  10. package/dist/commands/edit/index.d.ts +20 -2
  11. package/dist/commands/edit/index.js +258 -30
  12. package/dist/commands/emails/index.d.ts +2 -1
  13. package/dist/commands/emails/index.js +91 -26
  14. package/dist/commands/init/index.d.ts +3 -1
  15. package/dist/commands/init/index.js +199 -41
  16. package/dist/commands/login/index.d.ts +0 -2
  17. package/dist/commands/login/index.js +76 -32
  18. package/dist/commands/logs/index.d.ts +8 -1
  19. package/dist/commands/logs/index.js +55 -12
  20. package/dist/commands/preview/index.d.ts +19 -1
  21. package/dist/commands/preview/index.js +212 -30
  22. package/dist/commands/sdk/index.d.ts +3 -1
  23. package/dist/commands/sdk/index.js +46 -14
  24. package/dist/commands/settings/index.d.ts +22 -1
  25. package/dist/commands/settings/index.js +246 -34
  26. package/dist/commands/status/index.d.ts +0 -1
  27. package/dist/commands/status/index.js +39 -13
  28. package/dist/lib/{commands/deploy → deploy}/domain-setup.d.ts +1 -1
  29. package/dist/lib/{commands/deploy → deploy}/domain-setup.js +2 -2
  30. package/dist/lib/{commands/deploy → deploy}/output.d.ts +1 -1
  31. package/dist/lib/{commands/deploy → deploy}/output.js +2 -2
  32. package/dist/lib/{commands/deploy → deploy}/payload.d.ts +1 -1
  33. package/dist/lib/{commands/deploy → deploy}/payload.js +2 -2
  34. package/dist/lib/{commands/deploy → deploy}/sequence-status.js +2 -2
  35. package/dist/lib/{commands/deploy → deploy}/types.d.ts +4 -4
  36. package/dist/lib/templates/missing-templates.d.ts +1 -1
  37. package/dist/lib/templates/missing-templates.js +1 -1
  38. package/oclif.manifest.json +54 -54
  39. package/package.json +1 -1
  40. package/dist/lib/commands/billing/checkout-status.d.ts +0 -3
  41. package/dist/lib/commands/billing/checkout-status.js +0 -63
  42. package/dist/lib/commands/billing/format.d.ts +0 -7
  43. package/dist/lib/commands/billing/format.js +0 -63
  44. package/dist/lib/commands/billing/purchase-cap.d.ts +0 -7
  45. package/dist/lib/commands/billing/purchase-cap.js +0 -57
  46. package/dist/lib/commands/billing/types.d.ts +0 -72
  47. package/dist/lib/commands/contacts/actions.d.ts +0 -3
  48. package/dist/lib/commands/contacts/actions.js +0 -49
  49. package/dist/lib/commands/contacts/export-delete.d.ts +0 -9
  50. package/dist/lib/commands/contacts/export-delete.js +0 -51
  51. package/dist/lib/commands/contacts/types.d.ts +0 -35
  52. package/dist/lib/commands/contacts/types.js +0 -1
  53. package/dist/lib/commands/deploy/types.js +0 -1
  54. package/dist/lib/commands/deployments/output.d.ts +0 -2
  55. package/dist/lib/commands/deployments/output.js +0 -68
  56. package/dist/lib/commands/deployments/types.d.ts +0 -24
  57. package/dist/lib/commands/deployments/types.js +0 -1
  58. package/dist/lib/commands/domain/setup.d.ts +0 -8
  59. package/dist/lib/commands/domain/setup.js +0 -53
  60. package/dist/lib/commands/domain/types.d.ts +0 -56
  61. package/dist/lib/commands/domain/types.js +0 -1
  62. package/dist/lib/commands/domain/verify.d.ts +0 -5
  63. package/dist/lib/commands/domain/verify.js +0 -50
  64. package/dist/lib/commands/edit/diff.d.ts +0 -7
  65. package/dist/lib/commands/edit/diff.js +0 -65
  66. package/dist/lib/commands/edit/display.d.ts +0 -5
  67. package/dist/lib/commands/edit/display.js +0 -53
  68. package/dist/lib/commands/edit/flow.d.ts +0 -8
  69. package/dist/lib/commands/edit/flow.js +0 -70
  70. package/dist/lib/commands/edit/persist.d.ts +0 -5
  71. package/dist/lib/commands/edit/persist.js +0 -65
  72. package/dist/lib/commands/edit/types.d.ts +0 -37
  73. package/dist/lib/commands/edit/types.js +0 -1
  74. package/dist/lib/commands/emails/editor.d.ts +0 -2
  75. package/dist/lib/commands/emails/editor.js +0 -43
  76. package/dist/lib/commands/emails/output.d.ts +0 -4
  77. package/dist/lib/commands/emails/output.js +0 -36
  78. package/dist/lib/commands/emails/types.d.ts +0 -3
  79. package/dist/lib/commands/emails/types.js +0 -1
  80. package/dist/lib/commands/init/analysis.d.ts +0 -3
  81. package/dist/lib/commands/init/analysis.js +0 -69
  82. package/dist/lib/commands/init/output.d.ts +0 -12
  83. package/dist/lib/commands/init/output.js +0 -39
  84. package/dist/lib/commands/init/payload.d.ts +0 -8
  85. package/dist/lib/commands/init/payload.js +0 -78
  86. package/dist/lib/commands/init/types.d.ts +0 -57
  87. package/dist/lib/commands/init/types.js +0 -1
  88. package/dist/lib/commands/login/output.d.ts +0 -8
  89. package/dist/lib/commands/login/output.js +0 -53
  90. package/dist/lib/commands/login/types.d.ts +0 -19
  91. package/dist/lib/commands/login/types.js +0 -1
  92. package/dist/lib/commands/logs/output.d.ts +0 -2
  93. package/dist/lib/commands/logs/output.js +0 -52
  94. package/dist/lib/commands/logs/types.d.ts +0 -23
  95. package/dist/lib/commands/logs/types.js +0 -1
  96. package/dist/lib/commands/preview/actions.d.ts +0 -11
  97. package/dist/lib/commands/preview/actions.js +0 -43
  98. package/dist/lib/commands/preview/render.d.ts +0 -3
  99. package/dist/lib/commands/preview/render.js +0 -30
  100. package/dist/lib/commands/preview/server.d.ts +0 -8
  101. package/dist/lib/commands/preview/server.js +0 -63
  102. package/dist/lib/commands/preview/types.d.ts +0 -19
  103. package/dist/lib/commands/preview/types.js +0 -1
  104. package/dist/lib/commands/preview/wrapper-html.d.ts +0 -2
  105. package/dist/lib/commands/preview/wrapper-html.js +0 -35
  106. package/dist/lib/commands/sdk/output.d.ts +0 -2
  107. package/dist/lib/commands/sdk/output.js +0 -42
  108. package/dist/lib/commands/sdk/types.d.ts +0 -21
  109. package/dist/lib/commands/sdk/types.js +0 -1
  110. package/dist/lib/commands/settings/actions.d.ts +0 -10
  111. package/dist/lib/commands/settings/actions.js +0 -56
  112. package/dist/lib/commands/settings/display.d.ts +0 -15
  113. package/dist/lib/commands/settings/display.js +0 -69
  114. package/dist/lib/commands/settings/logo-domain.d.ts +0 -3
  115. package/dist/lib/commands/settings/logo-domain.js +0 -47
  116. package/dist/lib/commands/settings/prompt.d.ts +0 -2
  117. package/dist/lib/commands/settings/prompt.js +0 -82
  118. package/dist/lib/commands/settings/types.d.ts +0 -65
  119. package/dist/lib/commands/settings/types.js +0 -1
  120. package/dist/lib/commands/status/output.d.ts +0 -2
  121. package/dist/lib/commands/status/output.js +0 -49
  122. package/dist/lib/commands/status/types.d.ts +0 -28
  123. package/dist/lib/commands/status/types.js +0 -1
  124. /package/dist/lib/{commands/deploy → deploy}/sequence-status.d.ts +0 -0
  125. /package/dist/lib/{commands/billing → deploy}/types.js +0 -0
@@ -1,19 +0,0 @@
1
- import type { ApiResponse } from '../../api-client.js';
2
- export type PreviewCtx = {
3
- error(msg: string): never;
4
- exit(code?: number): never;
5
- log(msg?: string): void;
6
- onApiError(resp: {
7
- error?: string;
8
- status: number;
9
- }): never;
10
- post<T>(path: string, body?: unknown): Promise<ApiResponse<T>>;
11
- spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
12
- syncYaml(): Promise<void>;
13
- };
14
- export type PreviewEmail = {
15
- id: string;
16
- previewText?: string;
17
- style?: string;
18
- subject: string;
19
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import type { PreviewEmail } from './types.js';
2
- export declare function buildWrapperHtml(email: PreviewEmail, rendered: string, sampleData: Record<string, string>, effectiveStyle: 'branded' | 'plain'): string;
@@ -1,35 +0,0 @@
1
- export function buildWrapperHtml(email, rendered, sampleData, effectiveStyle) {
2
- return `<!DOCTYPE html>
3
- <html>
4
- <head>
5
- <meta charset="utf-8">
6
- <title>Preview: ${email.subject}</title>
7
- <style>
8
- body { margin: 0; padding: 2rem; background: #f5f5f5; font-family: system-ui; }
9
- .preview-bar { background: #1a1a2e; color: #fff; padding: 0.75rem 1.5rem; border-radius: 0.5rem;
10
- margin-bottom: 1.5rem; display: flex; justify-content: space-between; align-items: center; }
11
- .preview-bar h3 { margin: 0; font-size: 0.875rem; }
12
- .preview-bar span { font-size: 0.75rem; opacity: 0.7; }
13
- .email-frame { background: #fff; max-width: 40rem; margin: 0 auto; border-radius: 0.5rem;
14
- box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.1); overflow: hidden; }
15
- .email-header { padding: 1rem 1.5rem; border-bottom: 1px solid #eee; }
16
- .email-header .subject { font-weight: 600; font-size: 1rem; }
17
- .email-header .meta { font-size: 0.75rem; color: #666; margin-top: 0.25rem; }
18
- .email-body { padding: 1.5rem; }
19
- </style>
20
- </head>
21
- <body>
22
- <div class="preview-bar">
23
- <h3>Mailmodo Preview — ${email.id}</h3>
24
- <span>Style: ${effectiveStyle} · Press Ctrl+C in terminal to stop</span>
25
- </div>
26
- <div class="email-frame">
27
- <div class="email-header">
28
- <div class="subject">${email.subject}</div>
29
- <div class="meta">To: ${sampleData.first_name} · From: ${sampleData.product_name}</div>
30
- </div>
31
- <div class="email-body">${rendered}</div>
32
- </div>
33
- </body>
34
- </html>`;
35
- }
@@ -1,2 +0,0 @@
1
- import type { SdkCtx, SdkSnippetsResponse } from './types.js';
2
- export declare function renderSdkSnippets(ctx: SdkCtx, data: SdkSnippetsResponse): void;
@@ -1,42 +0,0 @@
1
- import chalk from 'chalk';
2
- import { SDK_IMPORT_SNIPPET, SDK_INSTALL_COMMAND } from '../../constants.js';
3
- import { SEPARATOR } from '../../messages.js';
4
- function renderCallBlock(ctx, label, calls) {
5
- if (calls.length === 0)
6
- return;
7
- ctx.log(` ${chalk.dim(label)}`);
8
- for (const call of calls) {
9
- ctx.log(` ${chalk.dim(call)}`);
10
- }
11
- }
12
- function renderSequenceBlock(ctx, snippet) {
13
- const productName = snippet.productName || 'Unnamed sequence';
14
- ctx.log(` ${chalk.bold(productName)} ${chalk.dim(`(${snippet.sequenceId})`)}`);
15
- const trackCalls = [...new Set(snippet.sdkSnippet?.trackCalls ?? [])];
16
- const identifyCalls = [...new Set(snippet.sdkSnippet?.identifyCalls ?? [])];
17
- renderCallBlock(ctx, '// track() calls', trackCalls);
18
- renderCallBlock(ctx, '// identify() calls', identifyCalls);
19
- if (trackCalls.length === 0 && identifyCalls.length === 0) {
20
- ctx.log(` ${chalk.dim('No track() or identify() calls available.')}`);
21
- }
22
- }
23
- export function renderSdkSnippets(ctx, data) {
24
- const snippets = data.sdkSnippets ?? [];
25
- if (snippets.length === 0) {
26
- ctx.log(`\n ${chalk.dim('No active deployed sequences.')}`);
27
- ctx.log(` Run ${chalk.cyan('mailmodo deploy')} to deploy one.\n`);
28
- return;
29
- }
30
- ctx.log(`\n ${chalk.bold(String(snippets.length))} active ${snippets.length === 1 ? 'sequence' : 'sequences'}:\n`);
31
- ctx.log(` ${SEPARATOR}`);
32
- ctx.log(` ${chalk.bold('SDK EVENT REFERENCE')}`);
33
- ctx.log(` ${SEPARATOR}\n`);
34
- ctx.log(` ${chalk.cyan(SDK_INSTALL_COMMAND)}\n`);
35
- ctx.log(` ${chalk.dim(SDK_IMPORT_SNIPPET)}\n`);
36
- for (const [index, snippet] of snippets.entries()) {
37
- renderSequenceBlock(ctx, snippet);
38
- if (index < snippets.length - 1)
39
- ctx.log('');
40
- }
41
- ctx.log(` ${SEPARATOR}\n`);
42
- }
@@ -1,21 +0,0 @@
1
- import type { ApiResponse } from '../../api-client.js';
2
- export interface SdkSnippetEntry {
3
- productName: string;
4
- sdkSnippet: {
5
- identifyCalls: string[];
6
- trackCalls: string[];
7
- };
8
- sequenceId: string;
9
- }
10
- export interface SdkSnippetsResponse {
11
- sdkSnippets: SdkSnippetEntry[];
12
- }
13
- export type SdkCtx = {
14
- get<T = Record<string, unknown>>(path: string, params?: Record<string, string>): Promise<ApiResponse<T>>;
15
- log(msg?: string): void;
16
- onApiError(resp: {
17
- error?: string;
18
- status: number;
19
- }): never;
20
- spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
21
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,10 +0,0 @@
1
- import type { SettingsCtx, SettingsYaml } from './types.js';
2
- export declare function applyMonthlyCapChange(ctx: SettingsCtx, yamlConfig: SettingsYaml, opts: {
3
- isJson: boolean;
4
- knownTier: null | string;
5
- rawValue: string;
6
- }): Promise<void>;
7
- export declare function applySetFlag(ctx: SettingsCtx, yamlConfig: SettingsYaml, opts: {
8
- isJson: boolean;
9
- setFlag: string;
10
- }): Promise<void>;
@@ -1,56 +0,0 @@
1
- import chalk from 'chalk';
2
- import { FREE_TIER } from '../../base-command.js';
3
- import { INFO } from '../../messages.js';
4
- import { settingKeyToProp } from '../../utils.js';
5
- import { saveYaml } from '../../yaml-config.js';
6
- export async function applyMonthlyCapChange(ctx, yamlConfig, opts) {
7
- const parsed = Number(opts.rawValue);
8
- if (!Number.isInteger(parsed) || parsed < 1) {
9
- ctx.error('monthly_cap must be a positive integer (blocks).');
10
- }
11
- await ctx.ensureAuth();
12
- const tier = opts.knownTier ?? (await ctx.fetchBillingTier());
13
- if (tier === FREE_TIER) {
14
- ctx.warnFreeTierCapBlocked(opts.isJson);
15
- return;
16
- }
17
- const data = await ctx.applyBillingCap({ cap: parsed, json: opts.isJson });
18
- yamlConfig.project.monthlyCap = data.capBlocks;
19
- await saveYaml(yamlConfig);
20
- await ctx.syncYaml();
21
- if (opts.isJson) {
22
- ctx.log(JSON.stringify({ monthlyCap: data.capBlocks, status: 'updated' }, null, 2));
23
- return;
24
- }
25
- ctx.log(`\n ${chalk.green('✓')} monthly_cap updated to ${chalk.cyan(String(data.capBlocks))} (${data.capEmails.toLocaleString()} emails)\n`);
26
- }
27
- export async function applySetFlag(ctx, yamlConfig, opts) {
28
- const { project } = yamlConfig;
29
- const eqIndex = opts.setFlag.indexOf('=');
30
- if (eqIndex === -1) {
31
- ctx.error('Invalid format. Use --set key=value (e.g., --set brand_color=#0F3460)');
32
- }
33
- const key = opts.setFlag.slice(0, eqIndex).trim();
34
- const propKey = settingKeyToProp(key);
35
- const value = opts.setFlag.slice(eqIndex + 1).trim();
36
- if (!(propKey in project) && key !== 'logo_file') {
37
- ctx.error(`Unknown setting: ${key}`);
38
- }
39
- if (propKey === 'monthlyCap') {
40
- await applyMonthlyCapChange(ctx, yamlConfig, {
41
- isJson: opts.isJson,
42
- knownTier: null,
43
- rawValue: value,
44
- });
45
- return;
46
- }
47
- project[propKey] = value;
48
- await saveYaml(yamlConfig);
49
- await ctx.syncYaml();
50
- if (opts.isJson) {
51
- ctx.log(JSON.stringify({ [propKey]: value, status: 'updated' }, null, 2));
52
- return;
53
- }
54
- ctx.log(`\n ${chalk.green('✓')} ${key} updated to ${chalk.cyan(value)}`);
55
- ctx.log(` ${INFO.DEPLOY_TO_APPLY}\n`);
56
- }
@@ -1,15 +0,0 @@
1
- import type { SettingsCtx } from './types.js';
2
- export declare const SETTINGS_GROUPS: Readonly<{
3
- billing: string[];
4
- brand: string[];
5
- domain: string[];
6
- identity: string[];
7
- integrations: string[];
8
- }>;
9
- export declare const SETUP_HINTS: Record<string, string>;
10
- export declare function fetchDomainVerified(ctx: SettingsCtx, domain: string | undefined): Promise<boolean | null>;
11
- export declare function displaySettingsGroup(ctx: SettingsCtx, group: string, opts: {
12
- domainVerified: boolean | null;
13
- keys: string[];
14
- project: Record<string, unknown>;
15
- }): void;
@@ -1,69 +0,0 @@
1
- import chalk from 'chalk';
2
- import { API_ENDPOINTS } from '../../constants.js';
3
- import { settingKeyToProp } from '../../utils.js';
4
- export const SETTINGS_GROUPS = Object.freeze({
5
- billing: ['monthly_cap'],
6
- brand: ['email_style', 'brand_color', 'logo_url', 'logo_file'],
7
- domain: ['domain', 'address'],
8
- identity: ['from_name', 'from_email', 'reply_to'],
9
- integrations: ['webhook_url'],
10
- });
11
- export const SETUP_HINTS = {
12
- address: "'mailmodo domain'",
13
- domain: "'mailmodo domain'",
14
- monthlyCap: "'mailmodo billing --cap <n>'",
15
- };
16
- export async function fetchDomainVerified(ctx, domain) {
17
- if (!domain)
18
- return null;
19
- try {
20
- await ctx.ensureAuth();
21
- const response = await ctx.get(API_ENDPOINTS.DOMAIN_STATUS, { domain });
22
- if (!response.ok) {
23
- ctx.log(` ${chalk.dim('Could not fetch domain status. Run')} ${chalk.cyan("'mailmodo domain --status'")} ${chalk.dim('to check manually.')}`);
24
- return null;
25
- }
26
- return response.data?.verified === true;
27
- }
28
- catch {
29
- ctx.log(` ${chalk.dim('Could not reach API for domain status. Skipping verification check.')}`);
30
- return null;
31
- }
32
- }
33
- function domainDisplay(key, value, domainVerified) {
34
- let v = value ? String(value) : chalk.dim('(not set)');
35
- if (key === 'domain' && value && domainVerified === true)
36
- v += ` ${chalk.green('✓ verified')}`;
37
- if (key === 'domain' && value && domainVerified === false)
38
- v += ` ${chalk.red('✗ not verified')}`;
39
- return v;
40
- }
41
- export function displaySettingsGroup(ctx, group, opts) {
42
- const { domainVerified, keys, project } = opts;
43
- const availableKeys = keys.filter((key) => (group === 'brand' && key === 'logo_file') ||
44
- settingKeyToProp(key) in project);
45
- const groupTitle = ` ${chalk.bold(group.charAt(0).toUpperCase() + group.slice(1))}`;
46
- if (availableKeys.length === 0) {
47
- const hint = SETUP_HINTS[settingKeyToProp(keys[0])];
48
- if (hint) {
49
- ctx.log(groupTitle);
50
- ctx.log(` ${'─'.repeat(49)}`);
51
- ctx.log(` ${chalk.dim(`Run ${hint} to configure.`)}`);
52
- ctx.log('');
53
- }
54
- return;
55
- }
56
- ctx.log(groupTitle);
57
- ctx.log(` ${'─'.repeat(49)}`);
58
- for (const key of availableKeys) {
59
- ctx.log(` ${key.padEnd(16)} ${domainDisplay(key, project[settingKeyToProp(key)], domainVerified)}`);
60
- }
61
- const missingKeys = keys.filter((key) => !(settingKeyToProp(key) in project) &&
62
- !(group === 'brand' && key === 'logo_file'));
63
- for (const key of missingKeys) {
64
- const hint = SETUP_HINTS[settingKeyToProp(key)];
65
- if (hint)
66
- ctx.log(` ${key.padEnd(16)} ${chalk.dim(`(run ${hint} to set up)`)}`);
67
- }
68
- ctx.log('');
69
- }
@@ -1,3 +0,0 @@
1
- import type { SettingsCtx, SettingsYaml } from './types.js';
2
- export declare function handleLogoUpload(ctx: SettingsCtx, yamlConfig: SettingsYaml): Promise<void>;
3
- export declare function handleDomainChange(ctx: SettingsCtx, yamlConfig: SettingsYaml): Promise<void>;
@@ -1,47 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
- import { resolve } from 'node:path';
4
- import { input } from '@inquirer/prompts';
5
- import chalk from 'chalk';
6
- import { API_ENDPOINTS } from '../../constants.js';
7
- import { saveYaml } from '../../yaml-config.js';
8
- const MIME_TYPES = {
9
- jpeg: 'image/jpeg',
10
- jpg: 'image/jpeg',
11
- png: 'image/png',
12
- svg: 'image/svg+xml',
13
- };
14
- export async function handleLogoUpload(ctx, yamlConfig) {
15
- const logoPath = await input({ message: 'Path to logo file:' });
16
- const resolvedPath = resolve(logoPath);
17
- if (!existsSync(resolvedPath)) {
18
- ctx.log(`\n File not found: ${resolvedPath}\n`);
19
- return;
20
- }
21
- await ctx.ensureAuth();
22
- const fileBuffer = await readFile(resolvedPath);
23
- const ext = resolvedPath.split('.').pop()?.toLowerCase();
24
- const mimeType = MIME_TYPES[ext ?? ''] ?? 'application/octet-stream';
25
- const formData = new FormData();
26
- formData.append('logo', new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), logoPath.split(/[/\\]/).pop() || 'logo.png');
27
- const response = await ctx.spinner(' Uploading logo file...', false, () => ctx.postFormData(API_ENDPOINTS.ASSETS_LOGO, formData));
28
- if (!response.ok) {
29
- ctx.onApiError(response);
30
- }
31
- yamlConfig.project.logoUrl = response.data?.url || '';
32
- yamlConfig.project.logoFile = logoPath;
33
- await saveYaml(yamlConfig);
34
- await ctx.syncYaml();
35
- ctx.log(`\n Logo uploaded and hosted at:`);
36
- ctx.log(` ${chalk.cyan(String(response.data?.url))}`);
37
- ctx.log(` Run ${chalk.cyan("'mailmodo deploy'")} to apply to all branded emails.\n`);
38
- }
39
- export async function handleDomainChange(ctx, yamlConfig) {
40
- await ctx.ensureAuth();
41
- const inputs = await ctx.collectDomainInputs(yamlConfig, false);
42
- const { dnsRecords, dnsGuideUrl } = await ctx.registerDomainAndSave(yamlConfig, inputs, false);
43
- ctx.log(`\n Domain and sender details updated. You will need to re-verify.`);
44
- ctx.showDnsRecords(dnsRecords, dnsGuideUrl, false);
45
- ctx.log(` Run ${chalk.cyan("'mailmodo domain --verify'")} once records are added.`);
46
- ctx.log(` Emails will not send until the new domain is verified.`);
47
- }
@@ -1,2 +0,0 @@
1
- import type { SettingsCtx, SettingsYaml } from './types.js';
2
- export declare function promptEditSetting(ctx: SettingsCtx, yamlConfig: SettingsYaml, tier: null | string): Promise<void>;
@@ -1,82 +0,0 @@
1
- import { input, select } from '@inquirer/prompts';
2
- import chalk from 'chalk';
3
- import { FREE_TIER } from '../../base-command.js';
4
- import { INFO } from '../../messages.js';
5
- import { settingKeyToProp } from '../../utils.js';
6
- import { saveYaml } from '../../yaml-config.js';
7
- import { applyMonthlyCapChange } from './actions.js';
8
- import { SETUP_HINTS } from './display.js';
9
- import { handleDomainChange, handleLogoUpload } from './logo-domain.js';
10
- async function handleUnknownEditKey(ctx, yamlConfig, editKey) {
11
- if (editKey === 'logo_file') {
12
- await handleLogoUpload(ctx, yamlConfig);
13
- return;
14
- }
15
- const editPropKey = settingKeyToProp(editKey);
16
- const hint = SETUP_HINTS[editPropKey];
17
- if (hint) {
18
- ctx.log(`\n ${editKey} is not configured yet. Run ${chalk.cyan(hint)} to set it up.\n`);
19
- }
20
- else {
21
- ctx.log(`\n Unknown setting: ${editKey}\n`);
22
- }
23
- }
24
- async function dispatchEditKey(ctx, yamlConfig, editKey, tier) {
25
- if (editKey === 'logo_file') {
26
- await handleLogoUpload(ctx, yamlConfig);
27
- return;
28
- }
29
- if (editKey === 'domain') {
30
- await handleDomainChange(ctx, yamlConfig);
31
- return;
32
- }
33
- if (editKey === 'monthly_cap') {
34
- const newValue = await input({ message: 'New monthly cap (blocks):' });
35
- await applyMonthlyCapChange(ctx, yamlConfig, {
36
- isJson: false,
37
- knownTier: tier,
38
- rawValue: newValue,
39
- });
40
- return;
41
- }
42
- if (editKey === 'email_style') {
43
- const style = await select({
44
- choices: [
45
- { name: 'plain', value: 'plain' },
46
- { name: 'branded', value: 'branded' },
47
- ],
48
- message: 'Email style:',
49
- });
50
- yamlConfig.project.emailStyle = style;
51
- await saveYaml(yamlConfig);
52
- await ctx.syncYaml();
53
- ctx.log(`\n ${chalk.green('✓')} email_style updated to ${chalk.cyan(style)}`);
54
- ctx.log(` ${INFO.DEPLOY_TO_APPLY}\n`);
55
- return;
56
- }
57
- const editPropKey = settingKeyToProp(editKey);
58
- const newValue = await input({ message: `New value for ${editKey}:` });
59
- yamlConfig.project[editPropKey] = newValue;
60
- await saveYaml(yamlConfig);
61
- await ctx.syncYaml();
62
- ctx.log(`\n ${chalk.green('✓')} Updated. ${INFO.DEPLOY_TO_APPLY}\n`);
63
- }
64
- export async function promptEditSetting(ctx, yamlConfig, tier) {
65
- const { project } = yamlConfig;
66
- const editKey = await input({
67
- default: 'n',
68
- message: "Edit a setting? (key or 'n'):",
69
- });
70
- if (editKey === 'n')
71
- return;
72
- if (editKey === 'monthly_cap' && tier === FREE_TIER) {
73
- ctx.warnFreeTierCapBlocked(false);
74
- return;
75
- }
76
- const editPropKey = settingKeyToProp(editKey);
77
- if (!(editPropKey in project)) {
78
- await handleUnknownEditKey(ctx, yamlConfig, editKey);
79
- return;
80
- }
81
- await dispatchEditKey(ctx, yamlConfig, editKey, tier);
82
- }
@@ -1,65 +0,0 @@
1
- import type { ApiResponse } from '../../api-client.js';
2
- import type { BillingCapUpdateResult } from '../../base-command.js';
3
- import type { MailmodoYaml } from '../../yaml-config.js';
4
- export type SettingsYaml = MailmodoYaml;
5
- export type SettingsCtx = {
6
- applyBillingCap(opts: {
7
- cap: number;
8
- json: boolean;
9
- }): Promise<BillingCapUpdateResult>;
10
- collectDomainInputs(yaml: MailmodoYaml, skip: boolean): Promise<{
11
- address: string;
12
- domain: string;
13
- fromEmail: string;
14
- fromName: string;
15
- replyTo: string;
16
- }>;
17
- ensureAuth(): Promise<void>;
18
- error(msg: string): never;
19
- fetchBillingStatus(): Promise<null | {
20
- cap?: {
21
- inBlocks: null | number;
22
- };
23
- tier?: string;
24
- }>;
25
- fetchBillingTier(): Promise<null | string>;
26
- get<T>(path: string, params?: Record<string, string>): Promise<ApiResponse<T>>;
27
- log(msg?: string): void;
28
- onApiError(resp: {
29
- error?: string;
30
- status: number;
31
- }): never;
32
- postFormData<T>(path: string, formData: FormData): Promise<ApiResponse<T>>;
33
- registerDomainAndSave(yaml: MailmodoYaml, inputs: {
34
- address: string;
35
- domain: string;
36
- fromEmail: string;
37
- fromName?: string;
38
- replyTo?: string;
39
- }, json: boolean): Promise<{
40
- dnsGuideUrl?: string;
41
- dnsRecords: Array<{
42
- host: string;
43
- type: string;
44
- value: string;
45
- }>;
46
- }>;
47
- showDnsRecords(records: Array<{
48
- host: string;
49
- type: string;
50
- value: string;
51
- }>, guideUrl: string | undefined, json: boolean): void;
52
- spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
53
- syncYaml(): Promise<void>;
54
- warnFreeTierCapBlocked(json: boolean): void;
55
- };
56
- export interface LogoUploadResponse {
57
- url: string;
58
- }
59
- export interface DomainStatusResponse {
60
- bounceRate: number;
61
- domain: string;
62
- spamRate: number;
63
- status: string;
64
- verified: boolean;
65
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import type { AnalyticsResponse, StatusCtx } from './types.js';
2
- export declare function logStatusOutput(ctx: StatusCtx, data: AnalyticsResponse): void;
@@ -1,49 +0,0 @@
1
- import chalk from 'chalk';
2
- function logMetricRow(ctx, metric) {
3
- const id = (metric.emailId || '').padEnd(30);
4
- const sent = String(metric.sent ?? 0).padEnd(7);
5
- const openRate = (metric.open || '0%').padEnd(7);
6
- const clickRate = (metric.click || '0%').padEnd(8);
7
- const convRate = metric.conv || '0%';
8
- ctx.log(` ${id}${sent}${openRate}${clickRate}${convRate}`);
9
- }
10
- function logEmailsTable(ctx, emails) {
11
- ctx.log(`\n ${chalk.bold('Last 7 days')}${''.padEnd(20)}Sent Open Click Conv`);
12
- ctx.log(` ${'─'.repeat(62)}`);
13
- if (emails?.length) {
14
- for (const metric of emails) {
15
- logMetricRow(ctx, metric);
16
- }
17
- }
18
- else {
19
- ctx.log(` ${chalk.dim('No data yet. Deploy emails first.')}`);
20
- }
21
- }
22
- function logQuota(ctx, quota) {
23
- if (quota.plan === 'free') {
24
- ctx.log(` Free tier remaining: ${chalk.cyan(String(quota.freeRemaining))} emails`);
25
- return;
26
- }
27
- if (quota.plan === 'paid') {
28
- if (quota.currentBlockEmailsRemaining !== null &&
29
- quota.currentBlockEmailsRemaining !== undefined) {
30
- const { blockSize } = quota;
31
- const sent = blockSize - quota.currentBlockEmailsRemaining;
32
- const remaining = quota.currentBlockEmailsRemaining;
33
- ctx.log(` Current paid block (${blockSize} emails) : ${chalk.cyan(`${sent} emails sent, ${remaining} emails remaining`)}`);
34
- }
35
- ctx.log(` Blocks used: ${quota.blocksUsed}`);
36
- }
37
- }
38
- export function logStatusOutput(ctx, data) {
39
- const { emails, monthlySent, quota } = data;
40
- logEmailsTable(ctx, emails);
41
- ctx.log('');
42
- if (monthlySent !== null && monthlySent !== undefined) {
43
- ctx.log(` Emails sent this month: ${chalk.bold(String(monthlySent))}`);
44
- }
45
- if (quota) {
46
- logQuota(ctx, quota);
47
- }
48
- ctx.log('');
49
- }
@@ -1,28 +0,0 @@
1
- import type { ApiResponse } from '../../api-client.js';
2
- export interface EmailMetric {
3
- click: string;
4
- conv: string;
5
- emailId: string;
6
- open: string;
7
- sent: number;
8
- }
9
- export interface AnalyticsResponse {
10
- emails: EmailMetric[];
11
- monthlySent: number;
12
- quota: {
13
- blockSize: number;
14
- blocksUsed: number;
15
- currentBlockEmailsRemaining: number;
16
- freeRemaining: number;
17
- plan: string;
18
- };
19
- }
20
- export type StatusCtx = {
21
- get<T = Record<string, unknown>>(path: string, params?: Record<string, string>): Promise<ApiResponse<T>>;
22
- log(msg?: string): void;
23
- onApiError(resp: {
24
- error?: string;
25
- status: number;
26
- }): never;
27
- spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
28
- };
@@ -1 +0,0 @@
1
- export {};
File without changes