@mailmodo/cli 0.0.49 → 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.
- package/dist/commands/edit/index.js +1 -0
- package/dist/commands/init/index.d.ts +1 -0
- package/dist/commands/init/index.js +10 -6
- package/dist/lib/base-command.d.ts +30 -3
- package/dist/lib/base-command.js +38 -6
- package/dist/lib/constants.d.ts +6 -0
- package/dist/lib/constants.js +6 -0
- package/dist/lib/messages.d.ts +27 -0
- package/dist/lib/messages.js +57 -0
- package/oclif.manifest.json +62 -62
- package/package.json +1 -1
|
@@ -49,6 +49,7 @@ export default class Edit extends BaseCommand {
|
|
|
49
49
|
async runEditStep(ctx, changeDescription, flags) {
|
|
50
50
|
const response = await this.withApiSpinner({ json: flags.json, text: ' Applying AI edits...' }, () => this.callEditApi(changeDescription, ctx.email, ctx.templateHtml));
|
|
51
51
|
if (!response.ok) {
|
|
52
|
+
this.handleAiQuotaError(response, 'edit');
|
|
52
53
|
this.handleApiError(response);
|
|
53
54
|
}
|
|
54
55
|
const updated = response.data;
|
|
@@ -64,6 +64,7 @@ export default class Init extends BaseCommand {
|
|
|
64
64
|
url: productUrl,
|
|
65
65
|
}));
|
|
66
66
|
if (!analysisResponse.ok) {
|
|
67
|
+
this.handleAiQuotaError(analysisResponse, 'init');
|
|
67
68
|
this.handleApiError(analysisResponse);
|
|
68
69
|
}
|
|
69
70
|
const analysis = analysisResponse.data;
|
|
@@ -154,12 +155,7 @@ export default class Init extends BaseCommand {
|
|
|
154
155
|
},
|
|
155
156
|
};
|
|
156
157
|
await saveYaml(yamlConfig);
|
|
157
|
-
|
|
158
|
-
const monthlyCap = billingStatus?.cap?.inBlocks;
|
|
159
|
-
if (monthlyCap !== null && monthlyCap !== undefined) {
|
|
160
|
-
yamlConfig.project.monthlyCap = monthlyCap;
|
|
161
|
-
await saveYaml(yamlConfig);
|
|
162
|
-
}
|
|
158
|
+
await this.persistMonthlyCap(yamlConfig);
|
|
163
159
|
const templateSaves = analysisPayload.recommendedEmails.flatMap((rec, index) => {
|
|
164
160
|
const generated = generatedEmails[index];
|
|
165
161
|
const saves = [];
|
|
@@ -184,6 +180,14 @@ export default class Init extends BaseCommand {
|
|
|
184
180
|
this.log(` Created ${chalk.green('mailmodo.yaml')} + ${chalk.green(String(emailConfigs.length))} email templates in ${chalk.green('/mailmodo')}\n`);
|
|
185
181
|
this.log(` Run ${chalk.cyan("'mailmodo emails'")} to review.\n`);
|
|
186
182
|
}
|
|
183
|
+
async persistMonthlyCap(yamlConfig) {
|
|
184
|
+
const billingStatus = await this.fetchBillingStatus();
|
|
185
|
+
const monthlyCap = billingStatus?.cap?.inBlocks;
|
|
186
|
+
if (monthlyCap !== null && monthlyCap !== undefined) {
|
|
187
|
+
yamlConfig.project.monthlyCap = monthlyCap;
|
|
188
|
+
await saveYaml(yamlConfig);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
187
191
|
async confirmOverwriteIfNeeded(flags) {
|
|
188
192
|
const existing = await loadYaml();
|
|
189
193
|
if (!existing)
|
|
@@ -2,6 +2,11 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { ApiClient, type ApiRequestDebugInfo } from './api-client.js';
|
|
3
3
|
import { type MailmodoConfig } from './config.js';
|
|
4
4
|
import { type MailmodoYaml } from './yaml-config.js';
|
|
5
|
+
/**
|
|
6
|
+
* Kind of AI quota that was exhausted. Drives both the headline default
|
|
7
|
+
* message and the follow-up tip pointing the user at the next-best action.
|
|
8
|
+
*/
|
|
9
|
+
export type AiQuotaFeature = 'edit' | 'init';
|
|
5
10
|
export interface BillingCapUpdateResult {
|
|
6
11
|
autoChargeBlockCount?: number;
|
|
7
12
|
capBlocks: number;
|
|
@@ -76,6 +81,25 @@ export declare abstract class BaseCommand extends Command {
|
|
|
76
81
|
error?: string;
|
|
77
82
|
status: number;
|
|
78
83
|
}): never;
|
|
84
|
+
/**
|
|
85
|
+
* Intercepts a 429 "Monthly quota exhausted" response from an AI rate-limited
|
|
86
|
+
* endpoint (analyze for `init`, email edit for `edit`) and exits with a
|
|
87
|
+
* feature-specific message that includes the server's error text, the reset
|
|
88
|
+
* date, optional retry hint, and the next-best action. Returns without
|
|
89
|
+
* exiting when the response is not a 429 so callers can fall through to
|
|
90
|
+
* `handleApiError` for non-quota failures.
|
|
91
|
+
*
|
|
92
|
+
* @param {{ status: number; error?: string; data?: unknown; debug?: ApiRequestDebugInfo }} response - The failed API response.
|
|
93
|
+
* Must carry the parsed JSON body (`data`) so reset/retry fields can be surfaced.
|
|
94
|
+
* @param {AiQuotaFeature} feature - Which AI quota was hit. Drives the default headline + tip
|
|
95
|
+
* shown when the server omits an explicit `error` string.
|
|
96
|
+
*/
|
|
97
|
+
protected handleAiQuotaError(response: {
|
|
98
|
+
data?: unknown;
|
|
99
|
+
debug?: ApiRequestDebugInfo;
|
|
100
|
+
error?: string;
|
|
101
|
+
status: number;
|
|
102
|
+
}, feature: AiQuotaFeature): void;
|
|
79
103
|
protected collectDomainSetupInputs(yamlConfig: MailmodoYaml, skipPrompts: boolean): Promise<{
|
|
80
104
|
address: string;
|
|
81
105
|
domain: string;
|
|
@@ -144,12 +168,15 @@ export declare abstract class BaseCommand extends Command {
|
|
|
144
168
|
value: string;
|
|
145
169
|
}>, guideUrl: string | undefined, json: boolean): void;
|
|
146
170
|
/**
|
|
147
|
-
* Builds the terminal error string for a failed API call
|
|
148
|
-
*
|
|
171
|
+
* Builds the terminal error string for a failed API call. In production only
|
|
172
|
+
* the user-facing message is returned; verbose request diagnostics (URL,
|
|
173
|
+
* status, response body, error code) are appended only when [[IS_DEV_MODE]]
|
|
174
|
+
* is true so end users never see internal request metadata.
|
|
149
175
|
*
|
|
150
176
|
* @param {string} message - Primary error text (HTTP message or generic).
|
|
151
177
|
* @param {{ status: number; debug?: ApiRequestDebugInfo }} response - Failed API response.
|
|
152
|
-
* @returns {string} Message
|
|
178
|
+
* @returns {string} Message alone in production, or message plus indented
|
|
179
|
+
* Request details when running in dev/debug mode.
|
|
153
180
|
*/
|
|
154
181
|
private formatApiFailure;
|
|
155
182
|
}
|
package/dist/lib/base-command.js
CHANGED
|
@@ -4,8 +4,8 @@ import chalk from 'chalk';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { ApiClient } from './api-client.js';
|
|
6
6
|
import { loadConfig } from './config.js';
|
|
7
|
-
import { API_ENDPOINTS } from './constants.js';
|
|
8
|
-
import { ERRORS, INFO, PROMPTS, recordLabel, VALIDATION } from './messages.js';
|
|
7
|
+
import { API_ENDPOINTS, IS_DEV_MODE } from './constants.js';
|
|
8
|
+
import { ERRORS, INFO, PROMPTS, quotaExhaustedMessage, recordLabel, VALIDATION, } from './messages.js';
|
|
9
9
|
import { loadYaml, saveYaml } from './yaml-config.js';
|
|
10
10
|
export const FREE_TIER = 'free';
|
|
11
11
|
/**
|
|
@@ -107,6 +107,35 @@ export class BaseCommand extends Command {
|
|
|
107
107
|
}
|
|
108
108
|
this.error(this.formatApiFailure(response.error || ERRORS.UNEXPECTED_API, response));
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Intercepts a 429 "Monthly quota exhausted" response from an AI rate-limited
|
|
112
|
+
* endpoint (analyze for `init`, email edit for `edit`) and exits with a
|
|
113
|
+
* feature-specific message that includes the server's error text, the reset
|
|
114
|
+
* date, optional retry hint, and the next-best action. Returns without
|
|
115
|
+
* exiting when the response is not a 429 so callers can fall through to
|
|
116
|
+
* `handleApiError` for non-quota failures.
|
|
117
|
+
*
|
|
118
|
+
* @param {{ status: number; error?: string; data?: unknown; debug?: ApiRequestDebugInfo }} response - The failed API response.
|
|
119
|
+
* Must carry the parsed JSON body (`data`) so reset/retry fields can be surfaced.
|
|
120
|
+
* @param {AiQuotaFeature} feature - Which AI quota was hit. Drives the default headline + tip
|
|
121
|
+
* shown when the server omits an explicit `error` string.
|
|
122
|
+
*/
|
|
123
|
+
handleAiQuotaError(response, feature) {
|
|
124
|
+
if (response.status !== 429)
|
|
125
|
+
return;
|
|
126
|
+
const body = (response.data ?? {});
|
|
127
|
+
const isInit = feature === 'init';
|
|
128
|
+
const message = quotaExhaustedMessage({
|
|
129
|
+
defaultMessage: isInit
|
|
130
|
+
? ERRORS.QUOTA_INIT_DEFAULT
|
|
131
|
+
: ERRORS.QUOTA_EDIT_DEFAULT,
|
|
132
|
+
limitReset: body.limitReset,
|
|
133
|
+
retryAfter: body.retryAfter,
|
|
134
|
+
serverError: typeof body.error === 'string' ? body.error : undefined,
|
|
135
|
+
tip: isInit ? ERRORS.QUOTA_INIT_TIP : ERRORS.QUOTA_EDIT_TIP,
|
|
136
|
+
});
|
|
137
|
+
this.error(this.formatApiFailure(message, response));
|
|
138
|
+
}
|
|
110
139
|
async collectDomainSetupInputs(yamlConfig, skipPrompts) {
|
|
111
140
|
if (skipPrompts) {
|
|
112
141
|
const domain = yamlConfig.project?.domain || '';
|
|
@@ -267,16 +296,19 @@ export class BaseCommand extends Command {
|
|
|
267
296
|
this.log(` Full guide: ${chalk.cyan(guideUrl)}\n`);
|
|
268
297
|
}
|
|
269
298
|
/**
|
|
270
|
-
* Builds the terminal error string for a failed API call
|
|
271
|
-
*
|
|
299
|
+
* Builds the terminal error string for a failed API call. In production only
|
|
300
|
+
* the user-facing message is returned; verbose request diagnostics (URL,
|
|
301
|
+
* status, response body, error code) are appended only when [[IS_DEV_MODE]]
|
|
302
|
+
* is true so end users never see internal request metadata.
|
|
272
303
|
*
|
|
273
304
|
* @param {string} message - Primary error text (HTTP message or generic).
|
|
274
305
|
* @param {{ status: number; debug?: ApiRequestDebugInfo }} response - Failed API response.
|
|
275
|
-
* @returns {string} Message
|
|
306
|
+
* @returns {string} Message alone in production, or message plus indented
|
|
307
|
+
* Request details when running in dev/debug mode.
|
|
276
308
|
*/
|
|
277
309
|
formatApiFailure(message, response) {
|
|
278
310
|
const { debug, status } = response;
|
|
279
|
-
if (!debug) {
|
|
311
|
+
if (!IS_DEV_MODE || !debug) {
|
|
280
312
|
return message;
|
|
281
313
|
}
|
|
282
314
|
return [
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when the CLI is running in a dev/debug context. Verbose request
|
|
3
|
+
* diagnostics (URL, status, response body, error code) are only surfaced
|
|
4
|
+
* when this is true so end users never see internal request metadata.
|
|
5
|
+
*/
|
|
6
|
+
export declare const IS_DEV_MODE: boolean;
|
|
1
7
|
export declare const API_BASE_URL = "https://app-vertex-debug.azurewebsites.net";
|
|
2
8
|
export declare const API_ENDPOINTS: Readonly<{
|
|
3
9
|
ANALYTICS: "/analytics";
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Set by `bin/dev.js` when running the CLI locally (tsx bootstrap). */
|
|
2
2
|
const DEV_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
|
|
3
|
+
/**
|
|
4
|
+
* True when the CLI is running in a dev/debug context. Verbose request
|
|
5
|
+
* diagnostics (URL, status, response body, error code) are only surfaced
|
|
6
|
+
* when this is true so end users never see internal request metadata.
|
|
7
|
+
*/
|
|
8
|
+
export const IS_DEV_MODE = Boolean(process.env.MAILMODO_DEV_TSX || process.env.MAILMODO_DEBUG);
|
|
3
9
|
// const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
|
|
4
10
|
const PRODUCTION_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
|
|
5
11
|
export const API_BASE_URL = process.env.MAILMODO_DEV_TSX
|
package/dist/lib/messages.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export declare const ERRORS: {
|
|
|
19
19
|
readonly INVALID_API_KEY: `Invalid API key. Run ${string} to re-authenticate.`;
|
|
20
20
|
readonly NOT_LOGGED_IN: `Not logged in. Run ${string} to authenticate.`;
|
|
21
21
|
readonly NO_YAML: `No mailmodo.yaml found. Run ${string} first.`;
|
|
22
|
+
readonly QUOTA_EDIT_DEFAULT: "Edit limit reached for this month.";
|
|
23
|
+
readonly QUOTA_EDIT_TIP: "AI edits are limited to 50 per account per month. Manually edit the template file under ./mailmodo to make further changes.";
|
|
24
|
+
readonly QUOTA_INIT_DEFAULT: "Regeneration limit reached.";
|
|
25
|
+
readonly QUOTA_INIT_TIP: `Regenerations are limited to 5 per account per month. Run ${string} to modify individual emails instead.`;
|
|
22
26
|
readonly RATE_LIMIT: "Rate limit exceeded. Please try again later.";
|
|
23
27
|
readonly UNEXPECTED_API: "An unexpected API error occurred.";
|
|
24
28
|
};
|
|
@@ -37,3 +41,26 @@ export declare const INFO: {
|
|
|
37
41
|
};
|
|
38
42
|
export declare function yamlParseError(detail: string): string;
|
|
39
43
|
export declare function recordLabel(index: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* Parses an ISO-8601 timestamp into a `YYYY-MM-DD` date string.
|
|
46
|
+
* Returns the raw input if parsing fails so the original value is preserved.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatQuotaResetDate(value?: string): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Formats a `retry-after` value (seconds) into a short, human-readable hint
|
|
51
|
+
* suitable for appending to a 429 error message.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatRetryAfter(seconds?: number): string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Builds the multi-line message printed when an AI quota (init regeneration or
|
|
56
|
+
* edit) is exhausted. `serverError` is the message returned by the API; the
|
|
57
|
+
* default text is used when the server omits it. The tip lists the next action
|
|
58
|
+
* the user can take (e.g. switch from init to edit).
|
|
59
|
+
*/
|
|
60
|
+
export declare function quotaExhaustedMessage(input: {
|
|
61
|
+
defaultMessage: string;
|
|
62
|
+
limitReset?: string;
|
|
63
|
+
retryAfter?: number;
|
|
64
|
+
serverError?: string;
|
|
65
|
+
tip: string;
|
|
66
|
+
}): string;
|
package/dist/lib/messages.js
CHANGED
|
@@ -20,6 +20,10 @@ export const ERRORS = {
|
|
|
20
20
|
INVALID_API_KEY: `Invalid API key. Run ${chalk.cyan('mailmodo login')} to re-authenticate.`,
|
|
21
21
|
NOT_LOGGED_IN: `Not logged in. Run ${chalk.cyan('mailmodo login')} to authenticate.`,
|
|
22
22
|
NO_YAML: `No mailmodo.yaml found. Run ${chalk.cyan('mailmodo init')} first.`,
|
|
23
|
+
QUOTA_EDIT_DEFAULT: 'Edit limit reached for this month.',
|
|
24
|
+
QUOTA_EDIT_TIP: 'AI edits are limited to 50 per account per month. Manually edit the template file under ./mailmodo to make further changes.',
|
|
25
|
+
QUOTA_INIT_DEFAULT: 'Regeneration limit reached.',
|
|
26
|
+
QUOTA_INIT_TIP: `Regenerations are limited to 5 per account per month. Run ${chalk.cyan('mailmodo edit <id>')} to modify individual emails instead.`,
|
|
23
27
|
RATE_LIMIT: 'Rate limit exceeded. Please try again later.',
|
|
24
28
|
UNEXPECTED_API: 'An unexpected API error occurred.',
|
|
25
29
|
};
|
|
@@ -42,3 +46,56 @@ export function recordLabel(index) {
|
|
|
42
46
|
const labels = ['DKIM', 'DMARC', 'Return Path'];
|
|
43
47
|
return labels[index] || `Record ${index + 1}`;
|
|
44
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Parses an ISO-8601 timestamp into a `YYYY-MM-DD` date string.
|
|
51
|
+
* Returns the raw input if parsing fails so the original value is preserved.
|
|
52
|
+
*/
|
|
53
|
+
export function formatQuotaResetDate(value) {
|
|
54
|
+
if (!value)
|
|
55
|
+
return undefined;
|
|
56
|
+
const parsed = new Date(value);
|
|
57
|
+
if (Number.isNaN(parsed.getTime()))
|
|
58
|
+
return value;
|
|
59
|
+
const year = parsed.getUTCFullYear();
|
|
60
|
+
const month = String(parsed.getUTCMonth() + 1).padStart(2, '0');
|
|
61
|
+
const day = String(parsed.getUTCDate()).padStart(2, '0');
|
|
62
|
+
return `${year}-${month}-${day}`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Formats a `retry-after` value (seconds) into a short, human-readable hint
|
|
66
|
+
* suitable for appending to a 429 error message.
|
|
67
|
+
*/
|
|
68
|
+
export function formatRetryAfter(seconds) {
|
|
69
|
+
if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds <= 0)
|
|
70
|
+
return undefined;
|
|
71
|
+
if (seconds < 60)
|
|
72
|
+
return `${Math.round(seconds)} seconds`;
|
|
73
|
+
const minutes = Math.round(seconds / 60);
|
|
74
|
+
if (minutes < 60)
|
|
75
|
+
return `${minutes} minutes`;
|
|
76
|
+
const hours = Math.round(seconds / 3600);
|
|
77
|
+
if (hours < 24)
|
|
78
|
+
return `${hours} hours`;
|
|
79
|
+
const days = Math.round(seconds / 86_400);
|
|
80
|
+
return `${days} days`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds the multi-line message printed when an AI quota (init regeneration or
|
|
84
|
+
* edit) is exhausted. `serverError` is the message returned by the API; the
|
|
85
|
+
* default text is used when the server omits it. The tip lists the next action
|
|
86
|
+
* the user can take (e.g. switch from init to edit).
|
|
87
|
+
*/
|
|
88
|
+
export function quotaExhaustedMessage(input) {
|
|
89
|
+
const headline = input.serverError || input.defaultMessage;
|
|
90
|
+
const lines = [headline];
|
|
91
|
+
const resetDate = formatQuotaResetDate(input.limitReset);
|
|
92
|
+
if (resetDate && !headline.includes(resetDate)) {
|
|
93
|
+
lines.push(`Resets on ${resetDate}.`);
|
|
94
|
+
}
|
|
95
|
+
const retryHint = formatRetryAfter(input.retryAfter);
|
|
96
|
+
if (retryHint) {
|
|
97
|
+
lines.push(`Try again in ${retryHint}.`);
|
|
98
|
+
}
|
|
99
|
+
lines.push('', input.tip);
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -365,12 +365,13 @@
|
|
|
365
365
|
"index.js"
|
|
366
366
|
]
|
|
367
367
|
},
|
|
368
|
-
"
|
|
368
|
+
"login": {
|
|
369
369
|
"aliases": [],
|
|
370
370
|
"args": {},
|
|
371
|
-
"description": "
|
|
371
|
+
"description": "Authenticate with Mailmodo using your API key",
|
|
372
372
|
"examples": [
|
|
373
|
-
"<%= config.bin %>
|
|
373
|
+
"<%= config.bin %> login",
|
|
374
|
+
"MAILMODO_API_KEY=YOUR_API_KEY <%= config.bin %> login"
|
|
374
375
|
],
|
|
375
376
|
"flags": {
|
|
376
377
|
"json": {
|
|
@@ -389,7 +390,7 @@
|
|
|
389
390
|
},
|
|
390
391
|
"hasDynamicHelp": false,
|
|
391
392
|
"hiddenAliases": [],
|
|
392
|
-
"id": "
|
|
393
|
+
"id": "login",
|
|
393
394
|
"pluginAlias": "@mailmodo/cli",
|
|
394
395
|
"pluginName": "@mailmodo/cli",
|
|
395
396
|
"pluginType": "core",
|
|
@@ -399,17 +400,16 @@
|
|
|
399
400
|
"relativePath": [
|
|
400
401
|
"dist",
|
|
401
402
|
"commands",
|
|
402
|
-
"
|
|
403
|
+
"login",
|
|
403
404
|
"index.js"
|
|
404
405
|
]
|
|
405
406
|
},
|
|
406
|
-
"
|
|
407
|
+
"logout": {
|
|
407
408
|
"aliases": [],
|
|
408
409
|
"args": {},
|
|
409
|
-
"description": "
|
|
410
|
+
"description": "Sign out by removing saved credentials from this machine",
|
|
410
411
|
"examples": [
|
|
411
|
-
"<%= config.bin %>
|
|
412
|
-
"MAILMODO_API_KEY=YOUR_API_KEY <%= config.bin %> login"
|
|
412
|
+
"<%= config.bin %> logout"
|
|
413
413
|
],
|
|
414
414
|
"flags": {
|
|
415
415
|
"json": {
|
|
@@ -428,7 +428,7 @@
|
|
|
428
428
|
},
|
|
429
429
|
"hasDynamicHelp": false,
|
|
430
430
|
"hiddenAliases": [],
|
|
431
|
-
"id": "
|
|
431
|
+
"id": "logout",
|
|
432
432
|
"pluginAlias": "@mailmodo/cli",
|
|
433
433
|
"pluginName": "@mailmodo/cli",
|
|
434
434
|
"pluginType": "core",
|
|
@@ -438,23 +438,19 @@
|
|
|
438
438
|
"relativePath": [
|
|
439
439
|
"dist",
|
|
440
440
|
"commands",
|
|
441
|
-
"
|
|
441
|
+
"logout",
|
|
442
442
|
"index.js"
|
|
443
443
|
]
|
|
444
444
|
},
|
|
445
|
-
"
|
|
445
|
+
"logs": {
|
|
446
446
|
"aliases": [],
|
|
447
|
-
"args": {
|
|
448
|
-
|
|
449
|
-
"description": "Email template ID to preview",
|
|
450
|
-
"name": "id"
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
"description": "Preview an email in browser, as text, or send a test",
|
|
447
|
+
"args": {},
|
|
448
|
+
"description": "View email send logs and delivery events",
|
|
454
449
|
"examples": [
|
|
455
|
-
"<%= config.bin %>
|
|
456
|
-
"<%= config.bin %>
|
|
457
|
-
"<%= config.bin %>
|
|
450
|
+
"<%= config.bin %> logs",
|
|
451
|
+
"<%= config.bin %> logs --email sarah@example.com",
|
|
452
|
+
"<%= config.bin %> logs --failed",
|
|
453
|
+
"<%= config.bin %> logs --json"
|
|
458
454
|
],
|
|
459
455
|
"flags": {
|
|
460
456
|
"json": {
|
|
@@ -470,23 +466,39 @@
|
|
|
470
466
|
"allowNo": false,
|
|
471
467
|
"type": "boolean"
|
|
472
468
|
},
|
|
473
|
-
"
|
|
474
|
-
"description": "
|
|
475
|
-
"name": "
|
|
469
|
+
"email": {
|
|
470
|
+
"description": "Filter logs by contact email",
|
|
471
|
+
"name": "email",
|
|
476
472
|
"hasDynamicHelp": false,
|
|
477
473
|
"multiple": false,
|
|
478
474
|
"type": "option"
|
|
479
475
|
},
|
|
480
|
-
"
|
|
481
|
-
"description": "
|
|
482
|
-
"name": "
|
|
476
|
+
"failed": {
|
|
477
|
+
"description": "Show only failed/bounced events",
|
|
478
|
+
"name": "failed",
|
|
483
479
|
"allowNo": false,
|
|
484
480
|
"type": "boolean"
|
|
481
|
+
},
|
|
482
|
+
"limit": {
|
|
483
|
+
"description": "Entries per page (max 200)",
|
|
484
|
+
"name": "limit",
|
|
485
|
+
"default": 50,
|
|
486
|
+
"hasDynamicHelp": false,
|
|
487
|
+
"multiple": false,
|
|
488
|
+
"type": "option"
|
|
489
|
+
},
|
|
490
|
+
"page": {
|
|
491
|
+
"description": "Page number",
|
|
492
|
+
"name": "page",
|
|
493
|
+
"default": 1,
|
|
494
|
+
"hasDynamicHelp": false,
|
|
495
|
+
"multiple": false,
|
|
496
|
+
"type": "option"
|
|
485
497
|
}
|
|
486
498
|
},
|
|
487
499
|
"hasDynamicHelp": false,
|
|
488
500
|
"hiddenAliases": [],
|
|
489
|
-
"id": "
|
|
501
|
+
"id": "logs",
|
|
490
502
|
"pluginAlias": "@mailmodo/cli",
|
|
491
503
|
"pluginName": "@mailmodo/cli",
|
|
492
504
|
"pluginType": "core",
|
|
@@ -496,19 +508,23 @@
|
|
|
496
508
|
"relativePath": [
|
|
497
509
|
"dist",
|
|
498
510
|
"commands",
|
|
499
|
-
"
|
|
511
|
+
"logs",
|
|
500
512
|
"index.js"
|
|
501
513
|
]
|
|
502
514
|
},
|
|
503
|
-
"
|
|
515
|
+
"preview": {
|
|
504
516
|
"aliases": [],
|
|
505
|
-
"args": {
|
|
506
|
-
|
|
517
|
+
"args": {
|
|
518
|
+
"id": {
|
|
519
|
+
"description": "Email template ID to preview",
|
|
520
|
+
"name": "id"
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
"description": "Preview an email in browser, as text, or send a test",
|
|
507
524
|
"examples": [
|
|
508
|
-
"<%= config.bin %>
|
|
509
|
-
"<%= config.bin %>
|
|
510
|
-
"<%= config.bin %>
|
|
511
|
-
"<%= config.bin %> logs --json"
|
|
525
|
+
"<%= config.bin %> preview welcome",
|
|
526
|
+
"<%= config.bin %> preview welcome --text",
|
|
527
|
+
"<%= config.bin %> preview welcome --send me@example.com"
|
|
512
528
|
],
|
|
513
529
|
"flags": {
|
|
514
530
|
"json": {
|
|
@@ -524,39 +540,23 @@
|
|
|
524
540
|
"allowNo": false,
|
|
525
541
|
"type": "boolean"
|
|
526
542
|
},
|
|
527
|
-
"
|
|
528
|
-
"description": "
|
|
529
|
-
"name": "
|
|
543
|
+
"send": {
|
|
544
|
+
"description": "Send test email to this address",
|
|
545
|
+
"name": "send",
|
|
530
546
|
"hasDynamicHelp": false,
|
|
531
547
|
"multiple": false,
|
|
532
548
|
"type": "option"
|
|
533
549
|
},
|
|
534
|
-
"
|
|
535
|
-
"description": "
|
|
536
|
-
"name": "
|
|
550
|
+
"text": {
|
|
551
|
+
"description": "Output plain text version (for AI agents)",
|
|
552
|
+
"name": "text",
|
|
537
553
|
"allowNo": false,
|
|
538
554
|
"type": "boolean"
|
|
539
|
-
},
|
|
540
|
-
"limit": {
|
|
541
|
-
"description": "Entries per page (max 200)",
|
|
542
|
-
"name": "limit",
|
|
543
|
-
"default": 50,
|
|
544
|
-
"hasDynamicHelp": false,
|
|
545
|
-
"multiple": false,
|
|
546
|
-
"type": "option"
|
|
547
|
-
},
|
|
548
|
-
"page": {
|
|
549
|
-
"description": "Page number",
|
|
550
|
-
"name": "page",
|
|
551
|
-
"default": 1,
|
|
552
|
-
"hasDynamicHelp": false,
|
|
553
|
-
"multiple": false,
|
|
554
|
-
"type": "option"
|
|
555
555
|
}
|
|
556
556
|
},
|
|
557
557
|
"hasDynamicHelp": false,
|
|
558
558
|
"hiddenAliases": [],
|
|
559
|
-
"id": "
|
|
559
|
+
"id": "preview",
|
|
560
560
|
"pluginAlias": "@mailmodo/cli",
|
|
561
561
|
"pluginName": "@mailmodo/cli",
|
|
562
562
|
"pluginType": "core",
|
|
@@ -566,7 +566,7 @@
|
|
|
566
566
|
"relativePath": [
|
|
567
567
|
"dist",
|
|
568
568
|
"commands",
|
|
569
|
-
"
|
|
569
|
+
"preview",
|
|
570
570
|
"index.js"
|
|
571
571
|
]
|
|
572
572
|
},
|
|
@@ -657,5 +657,5 @@
|
|
|
657
657
|
]
|
|
658
658
|
}
|
|
659
659
|
},
|
|
660
|
-
"version": "0.0.
|
|
660
|
+
"version": "0.0.50"
|
|
661
661
|
}
|