@mailmodo/cli 0.0.30-beta.pr32.53 → 0.0.30

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.
@@ -62,8 +62,7 @@ export default class Deploy extends BaseCommand {
62
62
  async buildDeployPayload(yamlConfig) {
63
63
  const emailsWithHtml = await Promise.all(yamlConfig.emails.map(async (email) => {
64
64
  const html = (await loadTemplate(`${email.id}.html`)) || '';
65
- const plainHtml = (await loadTemplate(`${email.id}_plain.html`)) || html;
66
- return { ...this.mapEmailToPayload(email), html, plainHtml };
65
+ return { ...this.mapEmailToPayload(email), html, plainHtml: html };
67
66
  }));
68
67
  return {
69
68
  ...this.buildProjectPayload(yamlConfig.project),
@@ -10,17 +10,6 @@ export default class Edit extends BaseCommand {
10
10
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
12
  };
13
- run(): Promise<void>;
14
- private runEditStep;
15
- private handleUserAction;
16
- private callEditApi;
17
- private finalizeEdit;
18
- private applyEmailChanges;
19
- private persistChanges;
20
- private logJsonResult;
21
- private handleAcceptOutput;
22
- private promptEditAction;
23
- private askChangeDescription;
24
13
  private showFieldDiff;
25
14
  private stripHtml;
26
15
  private truncate;
@@ -31,4 +20,5 @@ export default class Edit extends BaseCommand {
31
20
  private showUnchanged;
32
21
  private buildDiffPreview;
33
22
  private showChangeSummary;
23
+ run(): Promise<void>;
34
24
  }
@@ -1,9 +1,9 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { confirm, input, select } from '@inquirer/prompts';
2
+ import { confirm, input } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { BaseCommand } from '../../lib/base-command.js';
5
5
  import { API_ENDPOINTS } from '../../lib/constants.js';
6
- import { loadTemplate, getTemplateFilename, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
6
+ import { loadTemplate, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
7
7
  export default class Edit extends BaseCommand {
8
8
  static args = {
9
9
  id: Args.string({
@@ -22,152 +22,6 @@ export default class Edit extends BaseCommand {
22
22
  description: 'Natural language description of the change',
23
23
  }),
24
24
  };
25
- async run() {
26
- const { args, flags } = await this.parse(Edit);
27
- await this.ensureAuth();
28
- const yamlConfig = await this.ensureYaml();
29
- const emailIndex = yamlConfig.emails.findIndex((e) => e.id === args.id);
30
- if (emailIndex === -1) {
31
- this.error(`Email '${args.id}' not found in mailmodo.yaml.`);
32
- }
33
- const email = yamlConfig.emails[emailIndex];
34
- const templateFilename = getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle);
35
- const ctx = {
36
- email,
37
- emailIndex,
38
- templateFilename,
39
- templateHtml: await loadTemplate(templateFilename),
40
- yamlConfig,
41
- };
42
- const editFlags = {
43
- json: flags.json ?? false,
44
- yes: flags.yes ?? false,
45
- };
46
- const initialChange = flags.change?.trim() || (await this.askChangeDescription());
47
- await this.runEditStep(ctx, initialChange, editFlags);
48
- }
49
- async runEditStep(ctx, changeDescription, flags) {
50
- const response = await this.withApiSpinner({ json: flags.json, text: ' Applying AI edits...' }, () => this.callEditApi(changeDescription, ctx.email, ctx.templateHtml));
51
- if (!response.ok) {
52
- this.handleApiError(response);
53
- }
54
- const updated = response.data;
55
- if (flags.json) {
56
- this.log(JSON.stringify(this.buildDiffPreview(ctx.email, updated, ctx.templateHtml), null, 2));
57
- }
58
- else {
59
- this.showChangeSummary(ctx.email, updated, ctx.templateHtml);
60
- }
61
- if (flags.yes) {
62
- await this.finalizeEdit(ctx, updated, flags);
63
- return;
64
- }
65
- await this.handleUserAction(ctx, updated, changeDescription, flags);
66
- }
67
- async handleUserAction(ctx, updated, changeDescription, flags) {
68
- this.log('');
69
- const action = await this.promptEditAction();
70
- if (action === 'skip') {
71
- this.log('\n Changes discarded.\n');
72
- return;
73
- }
74
- if (action === 'retry') {
75
- const newChange = await this.askChangeDescription();
76
- await this.runEditStep(ctx, newChange, flags);
77
- return;
78
- }
79
- await this.finalizeEdit(ctx, updated, flags);
80
- }
81
- callEditApi(changeDescription, email, templateHtml) {
82
- return this.apiClient.post(API_ENDPOINTS.EDIT, {
83
- changeRequest: changeDescription,
84
- currentEmail: {
85
- condition: email.condition,
86
- goal: email.goal,
87
- html: templateHtml,
88
- id: email.id,
89
- previewText: email.previewText,
90
- subject: email.subject,
91
- trigger: email.trigger,
92
- },
93
- });
94
- }
95
- async finalizeEdit(ctx, updated, flags) {
96
- const oldSubject = ctx.email.subject;
97
- this.applyEmailChanges(ctx.email, updated);
98
- await this.persistChanges(ctx, updated);
99
- if (flags.json) {
100
- this.logJsonResult(ctx.email, updated, oldSubject);
101
- }
102
- else if (flags.yes) {
103
- this.log(`\n Updated ${chalk.green('mailmodo.yaml')}\n`);
104
- }
105
- else {
106
- await this.handleAcceptOutput(ctx.email);
107
- }
108
- }
109
- applyEmailChanges(email, updated) {
110
- if (updated.subject)
111
- email.subject = updated.subject;
112
- if (updated.previewText)
113
- email.previewText = updated.previewText;
114
- if (updated.ctaText)
115
- email.ctaText = updated.ctaText;
116
- }
117
- async persistChanges(ctx, updated) {
118
- const updatedYaml = {
119
- ...ctx.yamlConfig,
120
- emails: [...ctx.yamlConfig.emails],
121
- };
122
- updatedYaml.emails[ctx.emailIndex] = ctx.email;
123
- await saveYaml(updatedYaml);
124
- if (updated.html) {
125
- await saveTemplate(ctx.templateFilename, updated.html);
126
- }
127
- }
128
- logJsonResult(email, updated, oldSubject) {
129
- this.log(JSON.stringify({
130
- diff: {
131
- previewText: updated.previewText && updated.previewText !== email.previewText
132
- ? { new: updated.previewText, old: email.previewText }
133
- : undefined,
134
- subject: oldSubject === email.subject
135
- ? undefined
136
- : { new: email.subject, old: oldSubject },
137
- },
138
- email,
139
- status: 'updated',
140
- }, null, 2));
141
- }
142
- async handleAcceptOutput(email) {
143
- this.log(`\n Updated ${chalk.green('mailmodo.yaml')}`);
144
- const shouldPreview = await confirm({
145
- default: true,
146
- message: 'Preview the change?',
147
- });
148
- if (shouldPreview) {
149
- await this.config.runCommand('preview', [email.id]);
150
- }
151
- else {
152
- this.log(` Run: ${chalk.cyan(`mailmodo preview ${email.id}`)}\n`);
153
- }
154
- }
155
- async promptEditAction() {
156
- return select({
157
- message: 'Accept, try again, or skip?',
158
- choices: [
159
- { name: 'Accept', value: 'accept' },
160
- { name: 'Try again', value: 'retry' },
161
- { name: 'Skip', value: 'skip' },
162
- ],
163
- });
164
- }
165
- async askChangeDescription() {
166
- return input({
167
- message: 'What do you want to change?',
168
- validate: (value) => value?.trim() ? true : 'Please describe the change',
169
- });
170
- }
171
25
  showFieldDiff(label, oldVal, newVal) {
172
26
  if (!newVal || oldVal === newVal)
173
27
  return false;
@@ -285,4 +139,88 @@ export default class Edit extends BaseCommand {
285
139
  this.showSuggestedChanges(email, updated, templateHtml, changed);
286
140
  this.showUnchanged(email, templateHtml, changed);
287
141
  }
142
+ async run() {
143
+ const { args, flags } = await this.parse(Edit);
144
+ await this.ensureAuth();
145
+ const yamlConfig = await this.ensureYaml();
146
+ const emailIndex = yamlConfig.emails.findIndex((e) => e.id === args.id);
147
+ if (emailIndex === -1) {
148
+ this.error(`Email '${args.id}' not found in mailmodo.yaml.`);
149
+ }
150
+ const email = yamlConfig.emails[emailIndex];
151
+ const templateHtml = await loadTemplate(`${email.id}.html`);
152
+ let changeDescription = flags.change;
153
+ if (!changeDescription) {
154
+ changeDescription = await input({
155
+ message: 'What do you want to change?',
156
+ validate: (value) => value?.trim() ? true : 'Please describe the change',
157
+ });
158
+ }
159
+ const response = await this.withApiSpinner({ json: flags.json, text: ' Applying AI edits...' }, () => this.apiClient.post(API_ENDPOINTS.EDIT, {
160
+ changeRequest: changeDescription,
161
+ currentEmail: {
162
+ condition: email.condition,
163
+ goal: email.goal,
164
+ html: templateHtml,
165
+ id: email.id,
166
+ previewText: email.previewText,
167
+ subject: email.subject,
168
+ trigger: email.trigger,
169
+ },
170
+ }));
171
+ if (!response.ok) {
172
+ this.handleApiError(response);
173
+ }
174
+ const updated = response.data;
175
+ if (flags.json) {
176
+ this.log(JSON.stringify(this.buildDiffPreview(email, updated, templateHtml), null, 2));
177
+ }
178
+ else {
179
+ this.showChangeSummary(email, updated, templateHtml);
180
+ }
181
+ if (!flags.yes) {
182
+ const accepted = await confirm({
183
+ default: true,
184
+ message: 'Accept changes?',
185
+ });
186
+ if (!accepted) {
187
+ this.log('\n Changes discarded.\n');
188
+ return;
189
+ }
190
+ }
191
+ const oldSubject = email.subject;
192
+ const newSubject = updated.subject || email.subject;
193
+ if (updated.subject)
194
+ email.subject = updated.subject;
195
+ if (updated.previewText)
196
+ email.previewText = updated.previewText;
197
+ if (updated.ctaText)
198
+ email.ctaText = updated.ctaText;
199
+ const updatedYaml = {
200
+ ...yamlConfig,
201
+ emails: [...yamlConfig.emails],
202
+ };
203
+ updatedYaml.emails[emailIndex] = email;
204
+ await saveYaml(updatedYaml);
205
+ if (updated.html) {
206
+ await saveTemplate(`${email.id}.html`, updated.html);
207
+ }
208
+ if (flags.json) {
209
+ this.log(JSON.stringify({
210
+ diff: {
211
+ previewText: updated.previewText && updated.previewText !== email.previewText
212
+ ? { new: updated.previewText, old: email.previewText }
213
+ : undefined,
214
+ subject: oldSubject === newSubject
215
+ ? undefined
216
+ : { new: newSubject, old: oldSubject },
217
+ },
218
+ email,
219
+ status: 'updated',
220
+ }, null, 2));
221
+ return;
222
+ }
223
+ this.log(`\n Updated ${chalk.green('mailmodo.yaml')}`);
224
+ this.log(` Preview the change: ${chalk.cyan(`mailmodo preview ${email.id}`)}\n`);
225
+ }
288
226
  }
@@ -7,6 +7,4 @@ export default class Emails extends BaseCommand {
7
7
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  };
9
9
  run(): Promise<void>;
10
- private openTemplateInEditor;
11
- private trySpawnEditor;
12
10
  }
@@ -1,8 +1,5 @@
1
- import { spawn } from 'node:child_process';
2
- import { join } from 'node:path';
3
- import { confirm, input } from '@inquirer/prompts';
1
+ import { input } from '@inquirer/prompts';
4
2
  import chalk from 'chalk';
5
- import open from 'open';
6
3
  import { BaseCommand } from '../../lib/base-command.js';
7
4
  export default class Emails extends BaseCommand {
8
5
  static description = 'List and view configured email sequences';
@@ -64,53 +61,7 @@ export default class Emails extends BaseCommand {
64
61
  this.log(` ${chalk.bold('Goal:')} ${email.goal}`);
65
62
  }
66
63
  this.log('');
67
- const openIt = await confirm({
68
- default: true,
69
- message: 'Open template in editor?',
70
- });
71
- if (openIt) {
72
- await this.openTemplateInEditor(email.template);
73
- }
74
64
  }
75
65
  }
76
66
  }
77
- async openTemplateInEditor(template) {
78
- const templatePath = join(process.cwd(), template);
79
- this.log(`\n Opening ${template}...\n`);
80
- const editor = process.env.VISUAL || process.env.EDITOR;
81
- if (editor) {
82
- const [cmd, ...editorArgs] = editor.trim().split(/\s+/);
83
- const launched = await new Promise((resolve) => {
84
- const child = spawn(cmd, [...editorArgs, templatePath], {
85
- stdio: 'inherit',
86
- });
87
- child.on('error', () => resolve(false));
88
- child.on('close', () => resolve(true));
89
- });
90
- if (launched)
91
- return;
92
- }
93
- if (await this.trySpawnEditor('code', templatePath))
94
- return;
95
- try {
96
- await open(templatePath);
97
- }
98
- catch {
99
- this.log(` ${chalk.dim(`Could not open editor. Open the file manually: ${templatePath}`)}`);
100
- }
101
- }
102
- trySpawnEditor(editor, filePath) {
103
- const [cmd, args] = process.platform === 'win32'
104
- ? ['cmd.exe', ['/c', editor, filePath]]
105
- : [editor, [filePath]];
106
- return new Promise((resolve) => {
107
- const child = spawn(cmd, [...args], { stdio: 'ignore' });
108
- child.on('error', () => {
109
- resolve(false);
110
- });
111
- child.on('close', (code) => {
112
- resolve(code === 0);
113
- });
114
- });
115
- }
116
67
  }
@@ -139,7 +139,7 @@ export default class Init extends BaseCommand {
139
139
  project: {
140
140
  brandColor: analysisPayload.brand?.color || DEFAULT_BRAND_COLOR,
141
141
  description: analysisPayload.description,
142
- emailStyle: 'plain',
142
+ emailStyle: 'branded',
143
143
  fromEmail: '',
144
144
  fromName: `Team ${analysisPayload.productName}`,
145
145
  logoUrl: analysisPayload.brand?.logoUrl || '',
@@ -23,12 +23,8 @@ export default class Preview extends BaseCommand {
23
23
  */
24
24
  private sendTestEmail;
25
25
  /**
26
- * Probes ports starting at startPort and returns the first one not in use.
27
- */
28
- private findAvailablePort;
29
- /**
30
- * Starts a local HTTP server to serve the rendered email template,
31
- * then opens the user's default browser to view it.
26
+ * Starts a local HTTP server on PREVIEW_PORT to serve the rendered email
27
+ * template, then opens the user's default browser to view it.
32
28
  */
33
29
  private startPreviewServer;
34
30
  }
@@ -4,7 +4,7 @@ import chalk from 'chalk';
4
4
  import open from 'open';
5
5
  import { BaseCommand } from '../../lib/base-command.js';
6
6
  import { API_ENDPOINTS, PREVIEW_PORT } from '../../lib/constants.js';
7
- import { loadTemplate, getEmailStyle, getTemplateFilename, } from '../../lib/yaml-config.js';
7
+ import { loadTemplate } from '../../lib/yaml-config.js';
8
8
  /* eslint-disable camelcase */
9
9
  const SAMPLE_DATA = Object.freeze({
10
10
  app_url: 'https://yourapp.com',
@@ -84,8 +84,7 @@ export default class Preview extends BaseCommand {
84
84
  app_url: yamlConfig.project?.url || 'https://yourapp.com', // eslint-disable-line camelcase
85
85
  product_name: yamlConfig.project?.name || 'YourApp', // eslint-disable-line camelcase
86
86
  };
87
- const effectiveStyle = getEmailStyle(email.style, yamlConfig.project?.emailStyle);
88
- const templateHtml = await loadTemplate(getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle));
87
+ const templateHtml = await loadTemplate(`${email.id}.html`);
89
88
  if (flags.send) {
90
89
  const rendered = templateHtml
91
90
  ? renderTemplate(templateHtml, sampleData)
@@ -101,10 +100,7 @@ export default class Preview extends BaseCommand {
101
100
  await this.renderText(email, templateHtml, sampleData, flags.json);
102
101
  return;
103
102
  }
104
- await this.startPreviewServer(email, templateHtml, sampleData, {
105
- effectiveStyle,
106
- jsonOutput: flags.json,
107
- });
103
+ await this.startPreviewServer(email, templateHtml, sampleData, flags.json);
108
104
  }
109
105
  /**
110
106
  * Renders a plain text version of the email to stdout. Used by AI agents
@@ -158,28 +154,10 @@ export default class Preview extends BaseCommand {
158
154
  this.log('');
159
155
  }
160
156
  /**
161
- * Probes ports starting at startPort and returns the first one not in use.
157
+ * Starts a local HTTP server on PREVIEW_PORT to serve the rendered email
158
+ * template, then opens the user's default browser to view it.
162
159
  */
163
- async findAvailablePort(startPort, endPort = startPort + 10) {
164
- if (startPort > endPort) {
165
- throw new Error(`No available port found starting from port ${endPort - 10}`);
166
- }
167
- const available = await new Promise((resolve) => {
168
- const probe = createServer();
169
- probe.once('error', () => resolve(false));
170
- probe.once('listening', () => probe.close(() => resolve(true)));
171
- probe.listen(startPort);
172
- });
173
- return available
174
- ? startPort
175
- : this.findAvailablePort(startPort + 1, endPort);
176
- }
177
- /**
178
- * Starts a local HTTP server to serve the rendered email template,
179
- * then opens the user's default browser to view it.
180
- */
181
- async startPreviewServer(email, templateHtml, sampleData, opts) {
182
- const { effectiveStyle, jsonOutput } = opts;
160
+ async startPreviewServer(email, templateHtml, sampleData, jsonOutput) {
183
161
  const rendered = templateHtml
184
162
  ? renderTemplate(templateHtml, sampleData)
185
163
  : '<p>No template found.</p>';
@@ -205,7 +183,7 @@ export default class Preview extends BaseCommand {
205
183
  <body>
206
184
  <div class="preview-bar">
207
185
  <h3>Mailmodo Preview — ${email.id}</h3>
208
- <span>Style: ${effectiveStyle} · Press Ctrl+C in terminal to stop</span>
186
+ <span>Style: ${email.style || 'branded'} · Press Ctrl+C in terminal to stop</span>
209
187
  </div>
210
188
  <div class="email-frame">
211
189
  <div class="email-header">
@@ -216,15 +194,11 @@ export default class Preview extends BaseCommand {
216
194
  </div>
217
195
  </body>
218
196
  </html>`;
219
- const port = await this.findAvailablePort(PREVIEW_PORT);
220
- if (!jsonOutput && port !== PREVIEW_PORT) {
221
- this.log(`\n ${chalk.yellow('!')} Port ${PREVIEW_PORT} is already in use. Opening preview on port ${chalk.cyan(String(port))}.`);
222
- }
223
197
  if (jsonOutput) {
224
198
  this.log(JSON.stringify({
225
199
  id: email.id,
226
- style: effectiveStyle,
227
- url: `http://localhost:${port}`,
200
+ style: email.style || 'branded',
201
+ url: `http://localhost:${PREVIEW_PORT}`,
228
202
  }, null, 2));
229
203
  }
230
204
  const server = createServer((_req, res) => {
@@ -232,11 +206,11 @@ export default class Preview extends BaseCommand {
232
206
  res.end(wrapperHtml);
233
207
  });
234
208
  await new Promise((resolve) => {
235
- server.listen(port, () => resolve());
209
+ server.listen(PREVIEW_PORT, () => resolve());
236
210
  });
237
- const url = `http://localhost:${port}`;
211
+ const url = `http://localhost:${PREVIEW_PORT}`;
238
212
  if (!jsonOutput) {
239
- this.log(`\n Style: ${chalk.cyan(effectiveStyle)}`);
213
+ this.log(`\n Style: ${chalk.cyan(email.style || 'branded')}`);
240
214
  this.log(` Preview server at ${chalk.cyan(url)}`);
241
215
  this.log(` Opening in browser...\n`);
242
216
  this.log(` ${chalk.dim('Press Ctrl+C to stop the preview server.')}\n`);
@@ -68,5 +68,3 @@ export declare function saveTemplate(filename: string, html: string, cwd?: strin
68
68
  * or null if the file doesn't exist or can't be read.
69
69
  */
70
70
  export declare function loadTemplate(filename: string, cwd?: string): Promise<null | string>;
71
- export declare function getEmailStyle(emailStyle?: 'branded' | 'plain', projectStyle?: 'branded' | 'plain'): 'branded' | 'plain';
72
- export declare function getTemplateFilename(emailId: string, emailStyle?: 'branded' | 'plain', projectStyle?: 'branded' | 'plain'): string;
@@ -72,10 +72,3 @@ export async function loadTemplate(filename, cwd) {
72
72
  return null;
73
73
  }
74
74
  }
75
- export function getEmailStyle(emailStyle, projectStyle) {
76
- return emailStyle ?? projectStyle ?? 'branded';
77
- }
78
- export function getTemplateFilename(emailId, emailStyle, projectStyle) {
79
- const style = getEmailStyle(emailStyle, projectStyle);
80
- return style === 'plain' ? `${emailId}_plain.html` : `${emailId}.html`;
81
- }
@@ -365,45 +365,6 @@
365
365
  "index.js"
366
366
  ]
367
367
  },
368
- "login": {
369
- "aliases": [],
370
- "args": {},
371
- "description": "Authenticate with Mailmodo using your API key",
372
- "examples": [
373
- "<%= config.bin %> login",
374
- "MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
375
- ],
376
- "flags": {
377
- "json": {
378
- "description": "Output as JSON",
379
- "name": "json",
380
- "allowNo": false,
381
- "type": "boolean"
382
- },
383
- "yes": {
384
- "char": "y",
385
- "description": "Skip confirmation prompts",
386
- "name": "yes",
387
- "allowNo": false,
388
- "type": "boolean"
389
- }
390
- },
391
- "hasDynamicHelp": false,
392
- "hiddenAliases": [],
393
- "id": "login",
394
- "pluginAlias": "@mailmodo/cli",
395
- "pluginName": "@mailmodo/cli",
396
- "pluginType": "core",
397
- "strict": true,
398
- "enableJsonFlag": false,
399
- "isESM": true,
400
- "relativePath": [
401
- "dist",
402
- "commands",
403
- "login",
404
- "index.js"
405
- ]
406
- },
407
368
  "logout": {
408
369
  "aliases": [],
409
370
  "args": {},
@@ -655,7 +616,46 @@
655
616
  "status",
656
617
  "index.js"
657
618
  ]
619
+ },
620
+ "login": {
621
+ "aliases": [],
622
+ "args": {},
623
+ "description": "Authenticate with Mailmodo using your API key",
624
+ "examples": [
625
+ "<%= config.bin %> login",
626
+ "MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
627
+ ],
628
+ "flags": {
629
+ "json": {
630
+ "description": "Output as JSON",
631
+ "name": "json",
632
+ "allowNo": false,
633
+ "type": "boolean"
634
+ },
635
+ "yes": {
636
+ "char": "y",
637
+ "description": "Skip confirmation prompts",
638
+ "name": "yes",
639
+ "allowNo": false,
640
+ "type": "boolean"
641
+ }
642
+ },
643
+ "hasDynamicHelp": false,
644
+ "hiddenAliases": [],
645
+ "id": "login",
646
+ "pluginAlias": "@mailmodo/cli",
647
+ "pluginName": "@mailmodo/cli",
648
+ "pluginType": "core",
649
+ "strict": true,
650
+ "enableJsonFlag": false,
651
+ "isESM": true,
652
+ "relativePath": [
653
+ "dist",
654
+ "commands",
655
+ "login",
656
+ "index.js"
657
+ ]
658
658
  }
659
659
  },
660
- "version": "0.0.30-beta.pr32.53"
660
+ "version": "0.0.30"
661
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.30-beta.pr32.53",
4
+ "version": "0.0.30",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"