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

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 (136) hide show
  1. package/dist/commands/billing/index.d.ts +1 -11
  2. package/dist/commands/billing/index.js +28 -181
  3. package/dist/commands/contacts/index.d.ts +1 -19
  4. package/dist/commands/contacts/index.js +21 -114
  5. package/dist/commands/deploy/index.d.ts +1 -32
  6. package/dist/commands/deploy/index.js +52 -303
  7. package/dist/commands/deployments/index.d.ts +1 -4
  8. package/dist/commands/deployments/index.js +11 -52
  9. package/dist/commands/domain/index.d.ts +1 -14
  10. package/dist/commands/domain/index.js +19 -100
  11. package/dist/commands/edit/index.d.ts +2 -20
  12. package/dist/commands/edit/index.js +35 -244
  13. package/dist/commands/emails/index.d.ts +1 -2
  14. package/dist/commands/emails/index.js +26 -91
  15. package/dist/commands/init/index.d.ts +1 -2
  16. package/dist/commands/init/index.js +43 -179
  17. package/dist/commands/login/index.d.ts +2 -0
  18. package/dist/commands/login/index.js +35 -64
  19. package/dist/commands/logs/index.d.ts +1 -8
  20. package/dist/commands/logs/index.js +12 -55
  21. package/dist/commands/preview/index.d.ts +1 -19
  22. package/dist/commands/preview/index.js +40 -210
  23. package/dist/commands/sdk/index.d.ts +1 -3
  24. package/dist/commands/sdk/index.js +14 -46
  25. package/dist/commands/settings/index.d.ts +1 -22
  26. package/dist/commands/settings/index.js +35 -241
  27. package/dist/commands/status/index.d.ts +1 -0
  28. package/dist/commands/status/index.js +13 -39
  29. package/dist/lib/api-client.d.ts +5 -0
  30. package/dist/lib/api-client.js +45 -0
  31. package/dist/lib/base-command.d.ts +25 -1
  32. package/dist/lib/base-command.js +91 -5
  33. package/dist/lib/commands/billing/checkout-status.d.ts +3 -0
  34. package/dist/lib/commands/billing/checkout-status.js +63 -0
  35. package/dist/lib/commands/billing/format.d.ts +7 -0
  36. package/dist/lib/commands/billing/format.js +63 -0
  37. package/dist/lib/commands/billing/purchase-cap.d.ts +7 -0
  38. package/dist/lib/commands/billing/purchase-cap.js +57 -0
  39. package/dist/lib/commands/billing/types.d.ts +72 -0
  40. package/dist/lib/commands/billing/types.js +1 -0
  41. package/dist/lib/commands/contacts/actions.d.ts +3 -0
  42. package/dist/lib/commands/contacts/actions.js +49 -0
  43. package/dist/lib/commands/contacts/export-delete.d.ts +9 -0
  44. package/dist/lib/commands/contacts/export-delete.js +51 -0
  45. package/dist/lib/commands/contacts/types.d.ts +35 -0
  46. package/dist/lib/commands/contacts/types.js +1 -0
  47. package/dist/lib/commands/deploy/domain-setup.d.ts +8 -0
  48. package/dist/lib/commands/deploy/domain-setup.js +82 -0
  49. package/dist/lib/commands/deploy/output.d.ts +5 -0
  50. package/dist/lib/commands/deploy/output.js +61 -0
  51. package/dist/lib/commands/deploy/payload.d.ts +41 -0
  52. package/dist/lib/commands/deploy/payload.js +95 -0
  53. package/dist/lib/commands/deploy/sequence-status.d.ts +3 -0
  54. package/dist/lib/commands/deploy/sequence-status.js +56 -0
  55. package/dist/lib/commands/deploy/types.d.ts +88 -0
  56. package/dist/lib/commands/deploy/types.js +1 -0
  57. package/dist/lib/commands/deployments/output.d.ts +2 -0
  58. package/dist/lib/commands/deployments/output.js +68 -0
  59. package/dist/lib/commands/deployments/types.d.ts +24 -0
  60. package/dist/lib/commands/deployments/types.js +1 -0
  61. package/dist/lib/commands/domain/setup.d.ts +8 -0
  62. package/dist/lib/commands/domain/setup.js +53 -0
  63. package/dist/lib/commands/domain/types.d.ts +56 -0
  64. package/dist/lib/commands/domain/types.js +1 -0
  65. package/dist/lib/commands/domain/verify.d.ts +5 -0
  66. package/dist/lib/commands/domain/verify.js +50 -0
  67. package/dist/lib/commands/edit/diff.d.ts +7 -0
  68. package/dist/lib/commands/edit/diff.js +65 -0
  69. package/dist/lib/commands/edit/display.d.ts +5 -0
  70. package/dist/lib/commands/edit/display.js +53 -0
  71. package/dist/lib/commands/edit/flow.d.ts +8 -0
  72. package/dist/lib/commands/edit/flow.js +70 -0
  73. package/dist/lib/commands/edit/persist.d.ts +5 -0
  74. package/dist/lib/commands/edit/persist.js +65 -0
  75. package/dist/lib/commands/edit/types.d.ts +37 -0
  76. package/dist/lib/commands/edit/types.js +1 -0
  77. package/dist/lib/commands/emails/editor.d.ts +2 -0
  78. package/dist/lib/commands/emails/editor.js +43 -0
  79. package/dist/lib/commands/emails/output.d.ts +4 -0
  80. package/dist/lib/commands/emails/output.js +36 -0
  81. package/dist/lib/commands/emails/types.d.ts +3 -0
  82. package/dist/lib/commands/emails/types.js +1 -0
  83. package/dist/lib/commands/init/analysis.d.ts +3 -0
  84. package/dist/lib/commands/init/analysis.js +69 -0
  85. package/dist/lib/commands/init/output.d.ts +12 -0
  86. package/dist/lib/commands/init/output.js +39 -0
  87. package/dist/lib/commands/init/payload.d.ts +8 -0
  88. package/dist/lib/commands/init/payload.js +78 -0
  89. package/dist/lib/commands/init/types.d.ts +57 -0
  90. package/dist/lib/commands/init/types.js +1 -0
  91. package/dist/lib/commands/login/output.d.ts +8 -0
  92. package/dist/lib/commands/login/output.js +53 -0
  93. package/dist/lib/commands/login/types.d.ts +19 -0
  94. package/dist/lib/commands/login/types.js +1 -0
  95. package/dist/lib/commands/logs/output.d.ts +2 -0
  96. package/dist/lib/commands/logs/output.js +52 -0
  97. package/dist/lib/commands/logs/types.d.ts +23 -0
  98. package/dist/lib/commands/logs/types.js +1 -0
  99. package/dist/lib/commands/preview/actions.d.ts +11 -0
  100. package/dist/lib/commands/preview/actions.js +43 -0
  101. package/dist/lib/commands/preview/render.d.ts +3 -0
  102. package/dist/lib/commands/preview/render.js +30 -0
  103. package/dist/lib/commands/preview/server.d.ts +8 -0
  104. package/dist/lib/commands/preview/server.js +63 -0
  105. package/dist/lib/commands/preview/types.d.ts +19 -0
  106. package/dist/lib/commands/preview/types.js +1 -0
  107. package/dist/lib/commands/preview/wrapper-html.d.ts +2 -0
  108. package/dist/lib/commands/preview/wrapper-html.js +35 -0
  109. package/dist/lib/commands/sdk/output.d.ts +2 -0
  110. package/dist/lib/commands/sdk/output.js +42 -0
  111. package/dist/lib/commands/sdk/types.d.ts +21 -0
  112. package/dist/lib/commands/sdk/types.js +1 -0
  113. package/dist/lib/commands/settings/actions.d.ts +10 -0
  114. package/dist/lib/commands/settings/actions.js +56 -0
  115. package/dist/lib/commands/settings/display.d.ts +15 -0
  116. package/dist/lib/commands/settings/display.js +69 -0
  117. package/dist/lib/commands/settings/logo-domain.d.ts +3 -0
  118. package/dist/lib/commands/settings/logo-domain.js +47 -0
  119. package/dist/lib/commands/settings/prompt.d.ts +2 -0
  120. package/dist/lib/commands/settings/prompt.js +82 -0
  121. package/dist/lib/commands/settings/types.d.ts +65 -0
  122. package/dist/lib/commands/settings/types.js +1 -0
  123. package/dist/lib/commands/status/output.d.ts +2 -0
  124. package/dist/lib/commands/status/output.js +49 -0
  125. package/dist/lib/commands/status/types.d.ts +28 -0
  126. package/dist/lib/commands/status/types.js +1 -0
  127. package/dist/lib/constants.d.ts +1 -0
  128. package/dist/lib/constants.js +1 -0
  129. package/dist/lib/messages.d.ts +22 -0
  130. package/dist/lib/messages.js +22 -0
  131. package/dist/lib/templates/missing-templates.d.ts +5 -0
  132. package/dist/lib/templates/missing-templates.js +61 -0
  133. package/dist/lib/templates/types.d.ts +13 -0
  134. package/dist/lib/templates/types.js +1 -0
  135. package/oclif.manifest.json +40 -40
  136. package/package.json +1 -1
@@ -0,0 +1,82 @@
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
+ }
@@ -0,0 +1,65 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { AnalyticsResponse, StatusCtx } from './types.js';
2
+ export declare function logStatusOutput(ctx: StatusCtx, data: AnalyticsResponse): void;
@@ -0,0 +1,49 @@
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
+ }
@@ -0,0 +1,28 @@
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
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -9,6 +9,7 @@ export declare const API_ENDPOINTS: Readonly<{
9
9
  ANALYTICS: "/analytics";
10
10
  ANALYZE: "/analyze";
11
11
  ASSETS_LOGO: "/assets/logo";
12
+ ASSETS_YAML: "/assets/yaml";
12
13
  AUTH_VALIDATE: "/auth/validate";
13
14
  BILLING_CAP: "/billing/cap";
14
15
  BILLING_CHECKOUT: "/billing/checkout";
@@ -15,6 +15,7 @@ export const API_ENDPOINTS = Object.freeze({
15
15
  ANALYTICS: '/analytics',
16
16
  ANALYZE: '/analyze',
17
17
  ASSETS_LOGO: '/assets/logo',
18
+ ASSETS_YAML: '/assets/yaml',
18
19
  AUTH_VALIDATE: '/auth/validate',
19
20
  BILLING_CAP: '/billing/cap',
20
21
  BILLING_CHECKOUT: '/billing/checkout',
@@ -13,6 +13,17 @@ 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 MISSING_TEMPLATES: {
17
+ readonly ABORT_HINT: `Restore the missing files from version control, then run ${string} again.`;
18
+ readonly CHOICE_ABORT: "Abort (restore from version control)";
19
+ readonly CHOICE_REGENERATE: "Re-generate via AI";
20
+ readonly HEADER: `Some email templates are missing from ${string}:`;
21
+ readonly PROMPT_ACTION: "What would you like to do?";
22
+ readonly REGENERATE_NOTE: string;
23
+ readonly REGENERATE_SPINNER: "Regenerating email templates...";
24
+ readonly REVIEW_HINT: `Templates regenerated. Review them with ${string}, then run ${string} again.`;
25
+ readonly YES_ERROR: `Missing templates cannot be resolved with ${string}. Run ${string} without ${string} to regenerate via AI or restore from version control.`;
26
+ };
16
27
  export declare const ERRORS: {
17
28
  readonly DOMAIN_NOT_CONFIGURED: `No domain configured. Run ${string} to set up your sending domain.`;
18
29
  readonly DOMAIN_NOT_REGISTERED: `Sending domain not registered. Run: ${string}`;
@@ -40,6 +51,17 @@ export declare const INFO: {
40
51
  readonly FREE_TIER_CAP_BLOCKED: `Monthly cap is a paid-tier setting and is not available on the free tier. Run ${string} to add a payment method, then set a cap.`;
41
52
  readonly PAUSE_CANCELLED: "Pause cancelled. Sequence is still live.";
42
53
  readonly SEQUENCES_NOT_DEPLOYED: `Sequences saved but ${string}.`;
54
+ readonly YAML_RESTORED_FROM_SERVER: string;
55
+ readonly YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${string} to re-deploy your sequences.`;
56
+ };
57
+ export declare const DEPLOY: {
58
+ readonly CHANGES_HEADER: "Changes vs. last deployment:";
59
+ readonly DEPLOYING_HEADER: "Deploying:";
60
+ readonly NO_CHANGES: "No changes from last deployment.";
61
+ readonly SDK_DOCS_HINT: `Full SDK docs: ${string}`;
62
+ readonly SDK_EXAMPLE_COMMENT: string;
63
+ readonly SDK_ONBOARDING_HEADER: string;
64
+ readonly SUCCESS: `${string} Emails are live.`;
43
65
  };
44
66
  export declare function pauseSuccess(sequenceId: string): string;
45
67
  export declare function pauseAlready(sequenceId: string): string;
@@ -14,6 +14,17 @@ export const PROMPTS = {
14
14
  REPLY_TO: 'Reply-to address (optional, press Enter to use sender email):',
15
15
  SENDER_EMAIL: 'Sender email address:',
16
16
  };
17
+ export const MISSING_TEMPLATES = {
18
+ ABORT_HINT: `Restore the missing files from version control, then run ${chalk.cyan('mailmodo deploy')} again.`,
19
+ CHOICE_ABORT: 'Abort (restore from version control)',
20
+ CHOICE_REGENERATE: 'Re-generate via AI',
21
+ HEADER: `Some email templates are missing from ${chalk.cyan('./mailmodo/')}:`,
22
+ PROMPT_ACTION: 'What would you like to do?',
23
+ REGENERATE_NOTE: chalk.yellow(' Note: any previous manual edits to these files will be replaced with a new AI draft.'),
24
+ REGENERATE_SPINNER: 'Regenerating email templates...',
25
+ REVIEW_HINT: `Templates regenerated. Review them with ${chalk.cyan('mailmodo preview')}, then run ${chalk.cyan('mailmodo deploy')} again.`,
26
+ YES_ERROR: `Missing templates cannot be resolved with ${chalk.cyan('--yes')}. Run ${chalk.cyan('mailmodo deploy')} without ${chalk.cyan('--yes')} to regenerate via AI or restore from version control.`,
27
+ };
17
28
  export const ERRORS = {
18
29
  DOMAIN_NOT_CONFIGURED: `No domain configured. Run ${chalk.cyan('mailmodo domain')} to set up your sending domain.`,
19
30
  DOMAIN_NOT_REGISTERED: `Sending domain not registered. Run: ${chalk.cyan('mailmodo domain')}`,
@@ -40,6 +51,17 @@ export const INFO = {
40
51
  FREE_TIER_CAP_BLOCKED: `Monthly cap is a paid-tier setting and is not available on the free tier. Run ${chalk.cyan("'mailmodo billing --checkout'")} to add a payment method, then set a cap.`,
41
52
  PAUSE_CANCELLED: 'Pause cancelled. Sequence is still live.',
42
53
  SEQUENCES_NOT_DEPLOYED: `Sequences saved but ${chalk.yellow('NOT deployed')}.`,
54
+ YAML_RESTORED_FROM_SERVER: chalk.dim(' mailmodo.yaml not found locally — restored from server.'),
55
+ YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${chalk.cyan("'mailmodo deploy'")} to re-deploy your sequences.`,
56
+ };
57
+ export const DEPLOY = {
58
+ CHANGES_HEADER: 'Changes vs. last deployment:',
59
+ DEPLOYING_HEADER: 'Deploying:',
60
+ NO_CHANGES: 'No changes from last deployment.',
61
+ SDK_DOCS_HINT: `Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}`,
62
+ SDK_EXAMPLE_COMMENT: chalk.dim('// Example usage:'),
63
+ SDK_ONBOARDING_HEADER: chalk.bold('ADD THIS TO YOUR APP (one-time only):'),
64
+ SUCCESS: `${chalk.green('Deployed.')} Emails are live.`,
43
65
  };
44
66
  export function pauseSuccess(sequenceId) {
45
67
  return `Sequence ${chalk.cyan(sequenceId)} paused. Run ${chalk.cyan(`mailmodo deploy --resume ${sequenceId}`)} to resume.`;
@@ -0,0 +1,5 @@
1
+ import { type MailmodoYaml } from '../yaml-config.js';
2
+ import type { DeployFlags } from '../commands/deploy/types.js';
3
+ import type { RegenCtx } from './types.js';
4
+ export declare function getMissingTemplateIds(yamlConfig: MailmodoYaml): string[];
5
+ export declare function handleMissingTemplates(ctx: RegenCtx, yamlConfig: MailmodoYaml, missingIds: string[], flags: DeployFlags): Promise<boolean>;
@@ -0,0 +1,61 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { select } from '@inquirer/prompts';
4
+ import chalk from 'chalk';
5
+ import { API_ENDPOINTS, TEMPLATES_DIR } from '../constants.js';
6
+ import { MISSING_TEMPLATES } from '../messages.js';
7
+ import { getTemplateFilename, saveTemplate, } from '../yaml-config.js';
8
+ import { buildRegeneratePayload } from '../commands/deploy/payload.js';
9
+ export function getMissingTemplateIds(yamlConfig) {
10
+ return yamlConfig.emails
11
+ .filter((e) => !existsSync(join(process.cwd(), TEMPLATES_DIR, getTemplateFilename(e.id, e.style, yamlConfig.project?.emailStyle))))
12
+ .map((e) => e.id);
13
+ }
14
+ async function regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags) {
15
+ const response = await ctx.spinner(` ${MISSING_TEMPLATES.REGENERATE_SPINNER}`, flags.json, () => ctx.post(API_ENDPOINTS.GENERATE, buildRegeneratePayload(yamlConfig, missingIds)));
16
+ if (!response.ok)
17
+ ctx.onApiError(response);
18
+ const saves = [];
19
+ for (const email of response.data?.emails ?? []) {
20
+ if (!/^[\w-]+$/.test(email.id))
21
+ continue;
22
+ if (email.html)
23
+ saves.push(saveTemplate(`${email.id}.html`, email.html));
24
+ if (email.plainHtml)
25
+ saves.push(saveTemplate(`${email.id}_plain.html`, email.plainHtml));
26
+ }
27
+ await Promise.all(saves);
28
+ await ctx.syncYaml();
29
+ }
30
+ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags) {
31
+ if (flags.json) {
32
+ ctx.log(JSON.stringify({
33
+ error: 'missing_templates',
34
+ message: MISSING_TEMPLATES.YES_ERROR,
35
+ missingTemplates: missingIds.map((id) => `${id}.html`),
36
+ }, null, 2));
37
+ ctx.exit(1);
38
+ }
39
+ if (flags.yes)
40
+ ctx.error(MISSING_TEMPLATES.YES_ERROR);
41
+ ctx.log(`\n ${MISSING_TEMPLATES.HEADER}`);
42
+ for (const id of missingIds)
43
+ ctx.log(` ${chalk.red('✗')} mailmodo/${id}.html`);
44
+ ctx.log(`\n ${MISSING_TEMPLATES.REGENERATE_NOTE}\n`);
45
+ const action = await select({
46
+ choices: [
47
+ {
48
+ name: MISSING_TEMPLATES.CHOICE_REGENERATE,
49
+ value: 'regenerate',
50
+ },
51
+ { name: MISSING_TEMPLATES.CHOICE_ABORT, value: 'abort' },
52
+ ],
53
+ message: MISSING_TEMPLATES.PROMPT_ACTION,
54
+ });
55
+ if (action === 'abort') {
56
+ ctx.log(`\n ${MISSING_TEMPLATES.ABORT_HINT}\n`);
57
+ return false;
58
+ }
59
+ await regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags);
60
+ return true;
61
+ }
@@ -0,0 +1,13 @@
1
+ import type { ApiResponse } from '../api-client.js';
2
+ export type RegenCtx = {
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 = Record<string, unknown>>(path: string, body?: Record<string, unknown> | unknown): Promise<ApiResponse<T>>;
11
+ spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
12
+ syncYaml(): Promise<void>;
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -198,6 +198,45 @@
198
198
  "index.js"
199
199
  ]
200
200
  },
201
+ "deployments": {
202
+ "aliases": [],
203
+ "args": {},
204
+ "description": "List every deployed sequence on this account, with the IDs needed for deploy --pause / --resume",
205
+ "examples": [
206
+ "<%= config.bin %> deployments",
207
+ "<%= config.bin %> deployments --json"
208
+ ],
209
+ "flags": {
210
+ "json": {
211
+ "description": "Output as JSON",
212
+ "name": "json",
213
+ "allowNo": false,
214
+ "type": "boolean"
215
+ },
216
+ "yes": {
217
+ "char": "y",
218
+ "description": "Skip confirmation prompts",
219
+ "name": "yes",
220
+ "allowNo": false,
221
+ "type": "boolean"
222
+ }
223
+ },
224
+ "hasDynamicHelp": false,
225
+ "hiddenAliases": [],
226
+ "id": "deployments",
227
+ "pluginAlias": "@mailmodo/cli",
228
+ "pluginName": "@mailmodo/cli",
229
+ "pluginType": "core",
230
+ "strict": true,
231
+ "enableJsonFlag": false,
232
+ "isESM": true,
233
+ "relativePath": [
234
+ "dist",
235
+ "commands",
236
+ "deployments",
237
+ "index.js"
238
+ ]
239
+ },
201
240
  "domain": {
202
241
  "aliases": [],
203
242
  "args": {},
@@ -464,45 +503,6 @@
464
503
  "index.js"
465
504
  ]
466
505
  },
467
- "deployments": {
468
- "aliases": [],
469
- "args": {},
470
- "description": "List every deployed sequence on this account, with the IDs needed for deploy --pause / --resume",
471
- "examples": [
472
- "<%= config.bin %> deployments",
473
- "<%= config.bin %> deployments --json"
474
- ],
475
- "flags": {
476
- "json": {
477
- "description": "Output as JSON",
478
- "name": "json",
479
- "allowNo": false,
480
- "type": "boolean"
481
- },
482
- "yes": {
483
- "char": "y",
484
- "description": "Skip confirmation prompts",
485
- "name": "yes",
486
- "allowNo": false,
487
- "type": "boolean"
488
- }
489
- },
490
- "hasDynamicHelp": false,
491
- "hiddenAliases": [],
492
- "id": "deployments",
493
- "pluginAlias": "@mailmodo/cli",
494
- "pluginName": "@mailmodo/cli",
495
- "pluginType": "core",
496
- "strict": true,
497
- "enableJsonFlag": false,
498
- "isESM": true,
499
- "relativePath": [
500
- "dist",
501
- "commands",
502
- "deployments",
503
- "index.js"
504
- ]
505
- },
506
506
  "logs": {
507
507
  "aliases": [],
508
508
  "args": {},
@@ -765,5 +765,5 @@
765
765
  ]
766
766
  }
767
767
  },
768
- "version": "0.0.54"
768
+ "version": "0.0.55-beta.pr57.93"
769
769
  }
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.54",
4
+ "version": "0.0.55-beta.pr57.93",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"