@mailmodo/cli 0.0.50-beta.pr52.80 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,30 +3,12 @@ export default class Deploy extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- pause: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
- resume: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
6
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
7
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
8
  };
11
9
  private fetchDomainVerifyForDeploy;
12
10
  run(): Promise<void>;
13
11
  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
12
  private buildDeployPayload;
31
13
  private resolveMonthlyCapForDeploy;
32
14
  private mapEmailToPayload;
@@ -1,28 +1,17 @@
1
- import { Flags } from '@oclif/core';
2
1
  import { confirm, input } from '@inquirer/prompts';
3
2
  import chalk from 'chalk';
4
3
  import { BaseCommand } from '../../lib/base-command.js';
5
4
  import { API_ENDPOINTS, DEFAULT_BRAND_COLOR } from '../../lib/constants.js';
6
- import { ERRORS, INFO, pauseAlready, pauseSuccess, PROMPTS, resumeAlready, resumeSuccess, SEPARATOR, } from '../../lib/messages.js';
5
+ import { ERRORS, INFO, PROMPTS, SEPARATOR } from '../../lib/messages.js';
7
6
  import { loadTemplate, } from '../../lib/yaml-config.js';
8
7
  export default class Deploy extends BaseCommand {
9
- static description = 'Deploy, pause, or resume an email sequence';
8
+ static description = 'Deploy email sequences and verify sending domain';
10
9
  static examples = [
11
10
  '<%= config.bin %> deploy',
12
11
  '<%= config.bin %> deploy --yes',
13
- '<%= config.bin %> deploy --pause seq_abc123',
14
- '<%= config.bin %> deploy --resume seq_abc123 --json',
15
12
  ];
16
13
  static flags = {
17
14
  ...BaseCommand.baseFlags,
18
- pause: Flags.string({
19
- description: 'Pause a deployed sequence by ID (stops scheduled + triggered sends)',
20
- exclusive: ['resume'],
21
- }),
22
- resume: Flags.string({
23
- description: 'Resume a paused sequence by ID',
24
- exclusive: ['pause'],
25
- }),
26
15
  };
27
16
  fetchDomainVerifyForDeploy(jsonOutput, domain) {
28
17
  return this.withApiSpinner({ json: jsonOutput, text: ' Checking domain verification...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
@@ -32,15 +21,6 @@ export default class Deploy extends BaseCommand {
32
21
  async run() {
33
22
  const { flags } = await this.parse(Deploy);
34
23
  await this.ensureAuth();
35
- const baseFlags = { json: flags.json, yes: flags.yes };
36
- if (flags.pause) {
37
- await this.pauseSequence(flags.pause, baseFlags);
38
- return;
39
- }
40
- if (flags.resume) {
41
- await this.resumeSequence(flags.resume, baseFlags);
42
- return;
43
- }
44
24
  const yamlConfig = await this.ensureYaml();
45
25
  const domainReady = await this.ensureDomainReady(yamlConfig, flags);
46
26
  if (!domainReady)
@@ -80,60 +60,6 @@ export default class Deploy extends BaseCommand {
80
60
  }
81
61
  return response.data;
82
62
  }
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
63
  async buildDeployPayload(yamlConfig) {
138
64
  const [emailsWithHtml, monthlyCap] = await Promise.all([
139
65
  Promise.all(yamlConfig.emails.map(async (email) => {
@@ -9,7 +9,6 @@ export declare const PROMPTS: {
9
9
  readonly DOMAIN: "What domain will you send from?";
10
10
  readonly ENTER_AFTER_RECORDS: "Press Enter once you've added the records, or 'skip'.";
11
11
  readonly FROM_NAME: "Display name (optional, shown as sender name):";
12
- readonly PAUSE_CONFIRM: "Pause this sequence? All scheduled and triggered sends will stop until you resume.";
13
12
  readonly REPLY_TO: "Reply-to address (optional, press Enter to use sender email):";
14
13
  readonly SENDER_EMAIL: "Sender email address:";
15
14
  };
@@ -38,13 +37,8 @@ export declare const INFO: {
38
37
  Then: ${string}`;
39
38
  readonly DOMAIN_PENDING_VERIFICATION: `Your domain is not verified yet. Please verify it first. Run ${string} to check the status.`;
40
39
  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
- readonly PAUSE_CANCELLED: "Pause cancelled. Sequence is still live.";
42
40
  readonly SEQUENCES_NOT_DEPLOYED: `Sequences saved but ${string}.`;
43
41
  };
44
- export declare function pauseSuccess(sequenceId: string): string;
45
- export declare function pauseAlready(sequenceId: string): string;
46
- export declare function resumeSuccess(sequenceId: string): string;
47
- export declare function resumeAlready(sequenceId: string): string;
48
42
  export declare function yamlParseError(detail: string): string;
49
43
  export declare function recordLabel(index: number): string;
50
44
  /**
@@ -10,7 +10,6 @@ export const PROMPTS = {
10
10
  DOMAIN: 'What domain will you send from?',
11
11
  ENTER_AFTER_RECORDS: "Press Enter once you've added the records, or 'skip'.",
12
12
  FROM_NAME: 'Display name (optional, shown as sender name):',
13
- PAUSE_CONFIRM: 'Pause this sequence? All scheduled and triggered sends will stop until you resume.',
14
13
  REPLY_TO: 'Reply-to address (optional, press Enter to use sender email):',
15
14
  SENDER_EMAIL: 'Sender email address:',
16
15
  };
@@ -38,21 +37,8 @@ export const INFO = {
38
37
  DOMAIN_NOT_DEPLOYED_HINT: `When ready, run: ${chalk.cyan('mailmodo domain')}\n Then: ${chalk.cyan('mailmodo deploy')}`,
39
38
  DOMAIN_PENDING_VERIFICATION: `Your domain is not verified yet. Please verify it first. Run ${chalk.cyan('mailmodo domain --verify')} to check the status.`,
40
39
  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
- PAUSE_CANCELLED: 'Pause cancelled. Sequence is still live.',
42
40
  SEQUENCES_NOT_DEPLOYED: `Sequences saved but ${chalk.yellow('NOT deployed')}.`,
43
41
  };
44
- export function pauseSuccess(sequenceId) {
45
- return `Sequence ${chalk.cyan(sequenceId)} paused. Run ${chalk.cyan(`mailmodo deploy --resume ${sequenceId}`)} to resume.`;
46
- }
47
- export function pauseAlready(sequenceId) {
48
- return `Sequence ${chalk.cyan(sequenceId)} is already paused. No change made.`;
49
- }
50
- export function resumeSuccess(sequenceId) {
51
- return `Sequence ${chalk.cyan(sequenceId)} resumed. Scheduled and triggered sends are live again.`;
52
- }
53
- export function resumeAlready(sequenceId) {
54
- return `Sequence ${chalk.cyan(sequenceId)} is already active. No change made.`;
55
- }
56
42
  export function yamlParseError(detail) {
57
43
  return `mailmodo.yaml has invalid YAML syntax:\n${detail}`;
58
44
  }
@@ -140,12 +140,10 @@
140
140
  "deploy": {
141
141
  "aliases": [],
142
142
  "args": {},
143
- "description": "Deploy, pause, or resume an email sequence",
143
+ "description": "Deploy email sequences and verify sending domain",
144
144
  "examples": [
145
145
  "<%= config.bin %> deploy",
146
- "<%= config.bin %> deploy --yes",
147
- "<%= config.bin %> deploy --pause seq_abc123",
148
- "<%= config.bin %> deploy --resume seq_abc123 --json"
146
+ "<%= config.bin %> deploy --yes"
149
147
  ],
150
148
  "flags": {
151
149
  "json": {
@@ -160,26 +158,6 @@
160
158
  "name": "yes",
161
159
  "allowNo": false,
162
160
  "type": "boolean"
163
- },
164
- "pause": {
165
- "description": "Pause a deployed sequence by ID (stops scheduled + triggered sends)",
166
- "exclusive": [
167
- "resume"
168
- ],
169
- "name": "pause",
170
- "hasDynamicHelp": false,
171
- "multiple": false,
172
- "type": "option"
173
- },
174
- "resume": {
175
- "description": "Resume a paused sequence by ID",
176
- "exclusive": [
177
- "pause"
178
- ],
179
- "name": "resume",
180
- "hasDynamicHelp": false,
181
- "multiple": false,
182
- "type": "option"
183
161
  }
184
162
  },
185
163
  "hasDynamicHelp": false,
@@ -198,45 +176,6 @@
198
176
  "index.js"
199
177
  ]
200
178
  },
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
- },
240
179
  "domain": {
241
180
  "aliases": [],
242
181
  "args": {},
@@ -718,5 +657,5 @@
718
657
  ]
719
658
  }
720
659
  },
721
- "version": "0.0.50-beta.pr52.80"
660
+ "version": "0.0.50"
722
661
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mailmodo/cli",
3
3
  "description": "Email lifecycle automation for the AI-native builder generation.",
4
- "version": "0.0.50-beta.pr52.80",
4
+ "version": "0.0.50",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"
@@ -1,14 +0,0 @@
1
- import { BaseCommand } from '../../lib/base-command.js';
2
- export default class Deployments extends BaseCommand {
3
- static description: string;
4
- static examples: string[];
5
- static flags: {
6
- json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
- yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
- };
9
- run(): Promise<void>;
10
- private renderTable;
11
- private statusColor;
12
- private colWidth;
13
- private formatDate;
14
- }
@@ -1,76 +0,0 @@
1
- import chalk from 'chalk';
2
- import { BaseCommand } from '../../lib/base-command.js';
3
- import { API_ENDPOINTS } from '../../lib/constants.js';
4
- export default class Deployments extends BaseCommand {
5
- static description = 'List every deployed sequence on this account, with the IDs needed for deploy --pause / --resume';
6
- static examples = [
7
- '<%= config.bin %> deployments',
8
- '<%= config.bin %> deployments --json',
9
- ];
10
- static flags = {
11
- ...BaseCommand.baseFlags,
12
- };
13
- async run() {
14
- const { flags } = await this.parse(Deployments);
15
- await this.ensureAuth();
16
- const response = await this.withApiSpinner({ json: flags.json, text: ' Loading deployments...' }, () => this.apiClient.get(API_ENDPOINTS.SEQUENCES));
17
- if (!response.ok) {
18
- this.handleApiError(response);
19
- }
20
- if (flags.json) {
21
- this.log(JSON.stringify(response.data, null, 2));
22
- return;
23
- }
24
- this.renderTable(response.data);
25
- }
26
- renderTable(data) {
27
- const sequences = data.sequences ?? [];
28
- if (sequences.length === 0) {
29
- this.log(`\n ${chalk.dim('No deployed sequences yet.')}`);
30
- this.log(` Run ${chalk.cyan('mailmodo deploy')} to deploy one.\n`);
31
- return;
32
- }
33
- const rows = sequences.map((seq) => ({
34
- emails: String(seq.emailCount ?? 0),
35
- product: seq.productName ?? '',
36
- sequenceId: seq.sequenceId ?? '',
37
- status: seq.status ?? '',
38
- updated: this.formatDate(seq.updatedAt),
39
- }));
40
- const widths = {
41
- emails: this.colWidth(rows, 'emails', 'Emails'),
42
- product: this.colWidth(rows, 'product', 'Product'),
43
- sequenceId: this.colWidth(rows, 'sequenceId', 'Sequence ID'),
44
- status: this.colWidth(rows, 'status', 'Status'),
45
- updated: this.colWidth(rows, 'updated', 'Updated'),
46
- };
47
- this.log(`\n ${chalk.bold(String(sequences.length))} deployed ${sequences.length === 1 ? 'sequence' : 'sequences'}:\n`);
48
- this.log(` ${chalk.bold('Product'.padEnd(widths.product))}${chalk.bold('Status'.padEnd(widths.status))}${chalk.bold('Emails'.padEnd(widths.emails))}${chalk.bold('Sequence ID'.padEnd(widths.sequenceId))}${chalk.bold('Updated')}`);
49
- this.log(` ${'─'.repeat(widths.product + widths.status + widths.emails + widths.sequenceId + widths.updated)}`);
50
- for (const row of rows) {
51
- const status = this.statusColor(row.status)(row.status.padEnd(widths.status));
52
- this.log(` ${row.product.padEnd(widths.product)}${status}${row.emails.padEnd(widths.emails)}${chalk.cyan(row.sequenceId.padEnd(widths.sequenceId))}${chalk.dim(row.updated)}`);
53
- }
54
- this.log('');
55
- this.log(` Pause: ${chalk.cyan('mailmodo deploy --pause <sequence-id>')}`);
56
- this.log(` Resume: ${chalk.cyan('mailmodo deploy --resume <sequence-id>')}\n`);
57
- }
58
- statusColor(status) {
59
- if (status === 'active')
60
- return chalk.green;
61
- if (status === 'paused')
62
- return chalk.yellow;
63
- return chalk.white;
64
- }
65
- colWidth(rows, key, header) {
66
- return Math.max(...rows.map((r) => r[key].length), header.length) + 2;
67
- }
68
- formatDate(iso) {
69
- if (!iso)
70
- return '';
71
- const parsed = new Date(iso);
72
- if (Number.isNaN(parsed.getTime()))
73
- return iso;
74
- return parsed.toISOString().slice(0, 10);
75
- }
76
- }