@mailmodo/cli 0.0.56-beta.pr58.101 → 0.0.56-beta.pr58.103
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/report/index.d.ts +22 -0
- package/dist/commands/report/index.js +123 -0
- package/dist/lib/api-client.d.ts +2 -0
- package/dist/lib/api-client.js +2 -2
- package/dist/lib/base-command.js +3 -0
- package/dist/lib/commands/report/output-entries.d.ts +2 -0
- package/dist/lib/commands/report/output-entries.js +48 -0
- package/dist/lib/commands/report/output-timeseries.d.ts +2 -0
- package/dist/lib/commands/report/output-timeseries.js +28 -0
- package/dist/lib/commands/report/output.d.ts +3 -0
- package/dist/lib/commands/report/output.js +56 -0
- package/dist/lib/commands/report/payload.d.ts +2 -0
- package/dist/lib/commands/report/payload.js +49 -0
- package/dist/lib/commands/report/prompt.d.ts +2 -0
- package/dist/lib/commands/report/prompt.js +80 -0
- package/dist/lib/commands/report/types.d.ts +97 -0
- package/dist/lib/commands/report/types.js +1 -0
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +1 -0
- package/dist/lib/messages.d.ts +7 -0
- package/dist/lib/messages.js +7 -0
- package/oclif.manifest.json +214 -47
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BaseCommand } from '../../lib/base-command.js';
|
|
2
|
+
export default class Report extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
contact: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'email-id': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
event: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
from: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'group-by': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
preset: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
sequence: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
to: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
};
|
|
20
|
+
run(): Promise<void>;
|
|
21
|
+
private makeCtx;
|
|
22
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
|
+
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
4
|
+
import { REPORTS } from '../../lib/messages.js';
|
|
5
|
+
import { buildReportPayload } from '../../lib/commands/report/payload.js';
|
|
6
|
+
import { renderReport } from '../../lib/commands/report/output.js';
|
|
7
|
+
import { promptReportFlags } from '../../lib/commands/report/prompt.js';
|
|
8
|
+
export default class Report extends BaseCommand {
|
|
9
|
+
static description = 'Fetch an email analytics report';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> report --preset last7d',
|
|
12
|
+
'<%= config.bin %> report --preset last30d --group-by emailId',
|
|
13
|
+
'<%= config.bin %> report --from YYYY-MM-DD --to YYYY-MM-DD --output timeseries',
|
|
14
|
+
'<%= config.bin %> report --preset last7d --output entries --page 1',
|
|
15
|
+
'<%= config.bin %> report --preset last30d --sequence welcome-flow --event opened',
|
|
16
|
+
'<%= config.bin %> report --preset last7d --json',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...BaseCommand.baseFlags,
|
|
20
|
+
contact: Flags.string({
|
|
21
|
+
description: 'Filter by contact email (repeatable)',
|
|
22
|
+
multiple: true,
|
|
23
|
+
}),
|
|
24
|
+
'email-id': Flags.string({
|
|
25
|
+
description: 'Filter by email template ID (repeatable)',
|
|
26
|
+
multiple: true,
|
|
27
|
+
}),
|
|
28
|
+
event: Flags.string({
|
|
29
|
+
description: 'Filter by event type (repeatable)',
|
|
30
|
+
multiple: true,
|
|
31
|
+
options: [
|
|
32
|
+
'bounced',
|
|
33
|
+
'clicked',
|
|
34
|
+
'complained',
|
|
35
|
+
'delivered',
|
|
36
|
+
'opened',
|
|
37
|
+
'sent',
|
|
38
|
+
'skipped',
|
|
39
|
+
'unsubscribed',
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
from: Flags.string({
|
|
43
|
+
description: 'Start of time range, inclusive (YYYY-MM-DD)',
|
|
44
|
+
exclusive: ['preset'],
|
|
45
|
+
}),
|
|
46
|
+
'group-by': Flags.string({
|
|
47
|
+
default: 'none',
|
|
48
|
+
description: 'Group results by dimension',
|
|
49
|
+
options: [
|
|
50
|
+
'contact',
|
|
51
|
+
'day',
|
|
52
|
+
'emailId',
|
|
53
|
+
'hour',
|
|
54
|
+
'none',
|
|
55
|
+
'sequenceId',
|
|
56
|
+
'status',
|
|
57
|
+
],
|
|
58
|
+
}),
|
|
59
|
+
limit: Flags.integer({
|
|
60
|
+
default: 50,
|
|
61
|
+
description: 'Entries per page, max 200 (entries output only)',
|
|
62
|
+
max: 200,
|
|
63
|
+
}),
|
|
64
|
+
output: Flags.string({
|
|
65
|
+
default: 'summary',
|
|
66
|
+
description: 'Output shape: summary | entries | timeseries',
|
|
67
|
+
options: ['entries', 'summary', 'timeseries'],
|
|
68
|
+
}),
|
|
69
|
+
page: Flags.integer({
|
|
70
|
+
default: 1,
|
|
71
|
+
description: 'Page number (entries output only)',
|
|
72
|
+
}),
|
|
73
|
+
preset: Flags.string({
|
|
74
|
+
description: 'Relative time range preset',
|
|
75
|
+
exclusive: ['from', 'to'],
|
|
76
|
+
options: [
|
|
77
|
+
'last30d',
|
|
78
|
+
'last7d',
|
|
79
|
+
'last90d',
|
|
80
|
+
'lastMonth',
|
|
81
|
+
'thisMonth',
|
|
82
|
+
'today',
|
|
83
|
+
'yesterday',
|
|
84
|
+
],
|
|
85
|
+
}),
|
|
86
|
+
sequence: Flags.string({
|
|
87
|
+
description: 'Filter by sequence ID (repeatable)',
|
|
88
|
+
multiple: true,
|
|
89
|
+
}),
|
|
90
|
+
to: Flags.string({
|
|
91
|
+
description: 'End of time range, exclusive (YYYY-MM-DD)',
|
|
92
|
+
exclusive: ['preset'],
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
async run() {
|
|
96
|
+
const { flags } = await this.parse(Report);
|
|
97
|
+
await this.ensureAuth();
|
|
98
|
+
if ((flags.from && !flags.to) || (!flags.from && flags.to)) {
|
|
99
|
+
this.error(REPORTS.FROM_TO_REQUIRED);
|
|
100
|
+
}
|
|
101
|
+
const resolved = flags.json
|
|
102
|
+
? flags
|
|
103
|
+
: await promptReportFlags(flags);
|
|
104
|
+
const ctx = this.makeCtx();
|
|
105
|
+
const payload = buildReportPayload(resolved, (m) => this.error(m));
|
|
106
|
+
const response = await ctx.spinner(REPORTS.FETCH_SPINNER, flags.json, () => ctx.post(API_ENDPOINTS.REPORTS, payload));
|
|
107
|
+
if (!response.ok)
|
|
108
|
+
ctx.onApiError(response);
|
|
109
|
+
if (flags.json) {
|
|
110
|
+
this.log(JSON.stringify(response.data, null, 2));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
renderReport(ctx, response.data, resolved.output);
|
|
114
|
+
}
|
|
115
|
+
makeCtx() {
|
|
116
|
+
return {
|
|
117
|
+
log: (msg) => this.log(msg),
|
|
118
|
+
onApiError: (r) => this.handleApiError(r),
|
|
119
|
+
post: (path, body) => this.apiClient.post(path, body),
|
|
120
|
+
spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface ApiRequestDebugInfo {
|
|
|
8
8
|
causeCode?: string;
|
|
9
9
|
/** Fully resolved request URL (origin, path, and query string). */
|
|
10
10
|
fullUrl: string;
|
|
11
|
+
/** Truncated JSON summary of the request body sent, when present. */
|
|
12
|
+
requestBody?: string;
|
|
11
13
|
/** Truncated JSON summary of a non-empty error response body, when available. */
|
|
12
14
|
responseSummary?: string;
|
|
13
15
|
}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -82,12 +82,12 @@ export class ApiClient {
|
|
|
82
82
|
const data = await response.json().catch(() => ({}));
|
|
83
83
|
if (!response.ok) {
|
|
84
84
|
const errorData = data;
|
|
85
|
-
const summary = summarizeResponseBody(data);
|
|
86
85
|
return {
|
|
87
86
|
data: data,
|
|
88
87
|
debug: {
|
|
89
88
|
...debug,
|
|
90
|
-
|
|
89
|
+
requestBody: summarizeResponseBody(body),
|
|
90
|
+
responseSummary: summarizeResponseBody(data),
|
|
91
91
|
},
|
|
92
92
|
error: errorData?.message ||
|
|
93
93
|
errorData?.error ||
|
package/dist/lib/base-command.js
CHANGED
|
@@ -558,6 +558,9 @@ export class BaseCommand extends Command {
|
|
|
558
558
|
status > 0
|
|
559
559
|
? chalk.dim(` Status: ${String(status)}`)
|
|
560
560
|
: chalk.dim(' Status: (No HTTP response — network or client error)'),
|
|
561
|
+
...(debug.requestBody
|
|
562
|
+
? [chalk.dim(` Payload: ${debug.requestBody}`)]
|
|
563
|
+
: []),
|
|
561
564
|
...(debug.responseSummary
|
|
562
565
|
? [chalk.dim(` Response: ${debug.responseSummary}`)]
|
|
563
566
|
: []),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
function statusColor(status) {
|
|
3
|
+
switch (status) {
|
|
4
|
+
case 'bounced':
|
|
5
|
+
case 'complained': {
|
|
6
|
+
return chalk.red;
|
|
7
|
+
}
|
|
8
|
+
case 'clicked':
|
|
9
|
+
case 'delivered':
|
|
10
|
+
case 'opened':
|
|
11
|
+
case 'sent': {
|
|
12
|
+
return chalk.green;
|
|
13
|
+
}
|
|
14
|
+
case 'skipped': {
|
|
15
|
+
return chalk.yellow;
|
|
16
|
+
}
|
|
17
|
+
default: {
|
|
18
|
+
return chalk.white;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function renderEntries(ctx, data) {
|
|
23
|
+
const { entries, limit, page, total } = data;
|
|
24
|
+
ctx.log(`\n ${'Time'.padEnd(22)}${'Email'.padEnd(24)}${'Sequence'.padEnd(20)}${'Status'.padEnd(12)}Contact`);
|
|
25
|
+
ctx.log(` ${'─'.repeat(90)}`);
|
|
26
|
+
if (!entries?.length) {
|
|
27
|
+
ctx.log(` ${chalk.dim('No entries found.')}`);
|
|
28
|
+
ctx.log('');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const time = (entry.timestamp || '').padEnd(22);
|
|
33
|
+
const emailId = (entry.emailId || '').padEnd(24);
|
|
34
|
+
const seqId = (entry.sequenceId || '').padEnd(20);
|
|
35
|
+
const colorFn = statusColor(entry.status);
|
|
36
|
+
const status = colorFn((entry.status || '').padEnd(12));
|
|
37
|
+
ctx.log(` ${time}${emailId}${seqId}${status}${entry.contact || ''}`);
|
|
38
|
+
if (entry.reason) {
|
|
39
|
+
const label = entry.status === 'skipped' ? 'condition not met' : 'reason';
|
|
40
|
+
ctx.log(` ${' '.repeat(78)}${chalk.dim(`(${label}: ${entry.reason})`)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const totalPages = Math.ceil(total / limit);
|
|
44
|
+
ctx.log(`\n Page ${page} of ${totalPages} · ${total} total entries`);
|
|
45
|
+
if (page < totalPages)
|
|
46
|
+
ctx.log(` ${chalk.dim(`Next: --page ${page + 1}`)}`);
|
|
47
|
+
ctx.log('');
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
function fmtRate(n) {
|
|
3
|
+
return (n * 100).toFixed(1) + '%';
|
|
4
|
+
}
|
|
5
|
+
export function renderTimeseries(ctx, data) {
|
|
6
|
+
const { bucket, series, timeRange } = data;
|
|
7
|
+
const from = timeRange.from.slice(0, 10);
|
|
8
|
+
const to = timeRange.to.slice(0, 10);
|
|
9
|
+
ctx.log(`\n ${chalk.dim(`Bucket: ${bucket} (${from} → ${to})`)}`);
|
|
10
|
+
ctx.log(`\n ${'Time'.padEnd(22)}${'Sent'.padEnd(7)}${'Delivered'.padEnd(11)}${'Opened'.padEnd(8)}${'Clicked'.padEnd(9)}${'OpenRate'.padEnd(10)}${'ClickRate'.padEnd(10)}BounceRate`);
|
|
11
|
+
ctx.log(` ${'─'.repeat(87)}`);
|
|
12
|
+
if (!series?.length) {
|
|
13
|
+
ctx.log(` ${chalk.dim('No timeseries data.')}`);
|
|
14
|
+
ctx.log('');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const point of series) {
|
|
18
|
+
ctx.log(` ${(point.ts || '').padEnd(22)}` +
|
|
19
|
+
`${String(point.sent).padEnd(7)}` +
|
|
20
|
+
`${String(point.delivered).padEnd(11)}` +
|
|
21
|
+
`${String(point.opened).padEnd(8)}` +
|
|
22
|
+
`${String(point.clicked).padEnd(9)}` +
|
|
23
|
+
`${fmtRate(point.openRate).padEnd(10)}` +
|
|
24
|
+
`${fmtRate(point.clickRate).padEnd(10)}` +
|
|
25
|
+
`${fmtRate(point.bounceRate)}`);
|
|
26
|
+
}
|
|
27
|
+
ctx.log('');
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { renderEntries } from './output-entries.js';
|
|
3
|
+
import { renderTimeseries } from './output-timeseries.js';
|
|
4
|
+
function fmtRate(n) {
|
|
5
|
+
return (n * 100).toFixed(1) + '%';
|
|
6
|
+
}
|
|
7
|
+
function statsColumns(s) {
|
|
8
|
+
return [
|
|
9
|
+
String(s.sent).padEnd(7),
|
|
10
|
+
String(s.delivered).padEnd(11),
|
|
11
|
+
String(s.opened).padEnd(8),
|
|
12
|
+
String(s.clicked).padEnd(9),
|
|
13
|
+
String(s.bounced).padEnd(9),
|
|
14
|
+
fmtRate(s.openRate).padEnd(10),
|
|
15
|
+
fmtRate(s.clickRate).padEnd(10),
|
|
16
|
+
fmtRate(s.bounceRate),
|
|
17
|
+
].join('');
|
|
18
|
+
}
|
|
19
|
+
function renderGroupedTable(ctx, data) {
|
|
20
|
+
ctx.log(`\n ${'Key'.padEnd(20)}${'Sent'.padEnd(7)}${'Delivered'.padEnd(11)}` +
|
|
21
|
+
`${'Opened'.padEnd(8)}${'Clicked'.padEnd(9)}${'Bounced'.padEnd(9)}` +
|
|
22
|
+
`${'OpenRate'.padEnd(10)}${'ClickRate'.padEnd(10)}BounceRate`);
|
|
23
|
+
ctx.log(` ${'─'.repeat(93)}`);
|
|
24
|
+
for (const g of data.groups) {
|
|
25
|
+
const key = g.key.slice(0, 19).padEnd(20);
|
|
26
|
+
ctx.log(` ${key}${statsColumns(g)}`);
|
|
27
|
+
}
|
|
28
|
+
ctx.log(` ${'─'.repeat(93)}`);
|
|
29
|
+
ctx.log(` ${chalk.bold('TOTAL'.padEnd(20))}${statsColumns(data.totals)}`);
|
|
30
|
+
}
|
|
31
|
+
function renderTotalsOnly(ctx, data) {
|
|
32
|
+
const t = data.totals;
|
|
33
|
+
ctx.log(`\n Sent: ${t.sent} Delivered: ${t.delivered} Opened: ${t.opened}` +
|
|
34
|
+
` Clicked: ${t.clicked} Bounced: ${t.bounced}`);
|
|
35
|
+
ctx.log(` Open rate: ${fmtRate(t.openRate)} Click rate: ${fmtRate(t.clickRate)}` +
|
|
36
|
+
` Bounce rate: ${fmtRate(t.bounceRate)}`);
|
|
37
|
+
}
|
|
38
|
+
export function renderSummary(ctx, data) {
|
|
39
|
+
const { from, to } = data.timeRange;
|
|
40
|
+
ctx.log(`\n ${chalk.dim(`${from.slice(0, 10)} → ${to.slice(0, 10)}`)}`);
|
|
41
|
+
if (data.groups.length > 0) {
|
|
42
|
+
renderGroupedTable(ctx, data);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
renderTotalsOnly(ctx, data);
|
|
46
|
+
}
|
|
47
|
+
ctx.log('');
|
|
48
|
+
}
|
|
49
|
+
export function renderReport(ctx, data, mode) {
|
|
50
|
+
if (mode === 'entries')
|
|
51
|
+
renderEntries(ctx, data);
|
|
52
|
+
else if (mode === 'timeseries')
|
|
53
|
+
renderTimeseries(ctx, data);
|
|
54
|
+
else
|
|
55
|
+
renderSummary(ctx, data);
|
|
56
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { REPORTS } from '../../messages.js';
|
|
2
|
+
function buildTimeRange(flags) {
|
|
3
|
+
if (flags.preset)
|
|
4
|
+
return { preset: flags.preset };
|
|
5
|
+
if (flags.from && flags.to)
|
|
6
|
+
return { from: flags.from, to: flags.to };
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
function buildFilters(flags) {
|
|
10
|
+
const f = {};
|
|
11
|
+
if (flags.sequence?.length)
|
|
12
|
+
f.sequenceIds = flags.sequence;
|
|
13
|
+
if (flags['email-id']?.length)
|
|
14
|
+
f.emailIds = flags['email-id'];
|
|
15
|
+
if (flags.contact?.length)
|
|
16
|
+
f.contactEmails = flags.contact;
|
|
17
|
+
if (flags.event?.length)
|
|
18
|
+
f.events = flags.event;
|
|
19
|
+
return Object.keys(f).length > 0 ? f : undefined;
|
|
20
|
+
}
|
|
21
|
+
function validateFilterSizes(flags, onError) {
|
|
22
|
+
const arrays = [
|
|
23
|
+
flags.sequence,
|
|
24
|
+
flags['email-id'],
|
|
25
|
+
flags.contact,
|
|
26
|
+
flags.event,
|
|
27
|
+
];
|
|
28
|
+
if (arrays.some((a) => a && a.length > 100))
|
|
29
|
+
onError(REPORTS.TOO_MANY_ITEMS);
|
|
30
|
+
}
|
|
31
|
+
export function buildReportPayload(flags, onError) {
|
|
32
|
+
validateFilterSizes(flags, onError);
|
|
33
|
+
const timeRange = buildTimeRange(flags);
|
|
34
|
+
if (!timeRange)
|
|
35
|
+
onError(REPORTS.TIME_RANGE_REQUIRED);
|
|
36
|
+
const payload = {
|
|
37
|
+
groupBy: flags['group-by'],
|
|
38
|
+
output: flags.output,
|
|
39
|
+
timeRange,
|
|
40
|
+
};
|
|
41
|
+
const filters = buildFilters(flags);
|
|
42
|
+
if (filters)
|
|
43
|
+
payload.filters = filters;
|
|
44
|
+
if (flags.output === 'entries') {
|
|
45
|
+
payload.page = flags.page;
|
|
46
|
+
payload.limit = flags.limit;
|
|
47
|
+
}
|
|
48
|
+
return payload;
|
|
49
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
const PRESET_CHOICES = [
|
|
3
|
+
{ name: 'Today', value: 'today' },
|
|
4
|
+
{ name: 'Yesterday', value: 'yesterday' },
|
|
5
|
+
{ name: 'Last 7 days', value: 'last7d' },
|
|
6
|
+
{ name: 'Last 30 days', value: 'last30d' },
|
|
7
|
+
{ name: 'Last 90 days', value: 'last90d' },
|
|
8
|
+
{ name: 'This month', value: 'thisMonth' },
|
|
9
|
+
{ name: 'Last month', value: 'lastMonth' },
|
|
10
|
+
];
|
|
11
|
+
const OUTPUT_CHOICES = [
|
|
12
|
+
{ name: 'Summary — grouped aggregates', value: 'summary' },
|
|
13
|
+
{ name: 'Entries — paginated event log', value: 'entries' },
|
|
14
|
+
{ name: 'Timeseries — bucketed over time', value: 'timeseries' },
|
|
15
|
+
];
|
|
16
|
+
const GROUP_BY_CHOICES = [
|
|
17
|
+
{ name: 'None (totals only)', value: 'none' },
|
|
18
|
+
{ name: 'Email ID', value: 'emailId' },
|
|
19
|
+
{ name: 'Sequence ID', value: 'sequenceId' },
|
|
20
|
+
{ name: 'Day', value: 'day' },
|
|
21
|
+
{ name: 'Hour', value: 'hour' },
|
|
22
|
+
{ name: 'Contact', value: 'contact' },
|
|
23
|
+
{ name: 'Status', value: 'status' },
|
|
24
|
+
];
|
|
25
|
+
function isValidIsoDate(value) {
|
|
26
|
+
if (!value?.trim())
|
|
27
|
+
return 'Date is required';
|
|
28
|
+
if (Number.isNaN(new Date(value).getTime()))
|
|
29
|
+
return 'Enter a valid date in YYYY-MM-DD format';
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
async function promptTimeRange() {
|
|
33
|
+
const usePreset = await select({
|
|
34
|
+
choices: [
|
|
35
|
+
{ name: 'Use a preset (today, last7d, last30d…)', value: true },
|
|
36
|
+
{ name: 'Enter a custom date range', value: false },
|
|
37
|
+
],
|
|
38
|
+
message: 'Time range:',
|
|
39
|
+
});
|
|
40
|
+
if (usePreset) {
|
|
41
|
+
const preset = await select({
|
|
42
|
+
choices: PRESET_CHOICES,
|
|
43
|
+
message: 'Preset:',
|
|
44
|
+
});
|
|
45
|
+
return { preset };
|
|
46
|
+
}
|
|
47
|
+
const from = await input({
|
|
48
|
+
message: 'Start date (YYYY-MM-DD):',
|
|
49
|
+
validate: isValidIsoDate,
|
|
50
|
+
});
|
|
51
|
+
const to = await input({
|
|
52
|
+
message: 'End date, exclusive (YYYY-MM-DD):',
|
|
53
|
+
validate(v) {
|
|
54
|
+
const check = isValidIsoDate(v);
|
|
55
|
+
if (check !== true)
|
|
56
|
+
return check;
|
|
57
|
+
if (new Date(v) <= new Date(from))
|
|
58
|
+
return 'End date must be after start date';
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
return { from, to };
|
|
63
|
+
}
|
|
64
|
+
export async function promptReportFlags(flags) {
|
|
65
|
+
const hasTimeRange = Boolean(flags.preset || (flags.from && flags.to));
|
|
66
|
+
if (hasTimeRange)
|
|
67
|
+
return flags;
|
|
68
|
+
const timeRange = await promptTimeRange();
|
|
69
|
+
const output = await select({
|
|
70
|
+
choices: OUTPUT_CHOICES,
|
|
71
|
+
default: flags.output,
|
|
72
|
+
message: 'Output mode:',
|
|
73
|
+
});
|
|
74
|
+
const groupBy = await select({
|
|
75
|
+
choices: GROUP_BY_CHOICES,
|
|
76
|
+
default: flags['group-by'],
|
|
77
|
+
message: 'Group by:',
|
|
78
|
+
});
|
|
79
|
+
return { ...flags, ...timeRange, 'group-by': groupBy, output };
|
|
80
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ApiResponse } from '../../api-client.js';
|
|
2
|
+
export interface ReportFilters {
|
|
3
|
+
contactEmails?: string[];
|
|
4
|
+
emailIds?: string[];
|
|
5
|
+
events?: string[];
|
|
6
|
+
sequenceIds?: string[];
|
|
7
|
+
}
|
|
8
|
+
export type ReportTimeRange = {
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
} | {
|
|
12
|
+
preset: string;
|
|
13
|
+
};
|
|
14
|
+
export interface ReportPayload {
|
|
15
|
+
filters?: ReportFilters;
|
|
16
|
+
groupBy: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
output: string;
|
|
19
|
+
page?: number;
|
|
20
|
+
timeRange?: ReportTimeRange;
|
|
21
|
+
}
|
|
22
|
+
export interface ReportGroup {
|
|
23
|
+
bounceRate: number;
|
|
24
|
+
bounced: number;
|
|
25
|
+
clickRate: number;
|
|
26
|
+
clicked: number;
|
|
27
|
+
complained: number;
|
|
28
|
+
delivered: number;
|
|
29
|
+
key: string;
|
|
30
|
+
openRate: number;
|
|
31
|
+
opened: number;
|
|
32
|
+
sent: number;
|
|
33
|
+
skipped: number;
|
|
34
|
+
unsubscribed: number;
|
|
35
|
+
}
|
|
36
|
+
export type ReportTotals = Omit<ReportGroup, 'key'>;
|
|
37
|
+
export interface SummaryResponse {
|
|
38
|
+
groupBy: string;
|
|
39
|
+
groups: ReportGroup[];
|
|
40
|
+
timeRange: {
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
};
|
|
44
|
+
totals: ReportTotals;
|
|
45
|
+
}
|
|
46
|
+
export interface EntryRecord {
|
|
47
|
+
contact: string;
|
|
48
|
+
emailId: string;
|
|
49
|
+
reason?: null | string;
|
|
50
|
+
sequenceId: string;
|
|
51
|
+
status: string;
|
|
52
|
+
timestamp: string;
|
|
53
|
+
}
|
|
54
|
+
export interface EntriesResponse {
|
|
55
|
+
entries: EntryRecord[];
|
|
56
|
+
limit: number;
|
|
57
|
+
page: number;
|
|
58
|
+
timeRange: {
|
|
59
|
+
from: string;
|
|
60
|
+
to: string;
|
|
61
|
+
};
|
|
62
|
+
total: number;
|
|
63
|
+
}
|
|
64
|
+
export type TimeseriesPoint = ReportTotals & {
|
|
65
|
+
ts: string;
|
|
66
|
+
};
|
|
67
|
+
export interface TimeseriesResponse {
|
|
68
|
+
bucket: string;
|
|
69
|
+
series: TimeseriesPoint[];
|
|
70
|
+
timeRange: {
|
|
71
|
+
from: string;
|
|
72
|
+
to: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export type ReportResponse = EntriesResponse | SummaryResponse | TimeseriesResponse;
|
|
76
|
+
export type ReportFlags = {
|
|
77
|
+
contact?: string[];
|
|
78
|
+
'email-id'?: string[];
|
|
79
|
+
event?: string[];
|
|
80
|
+
from?: string;
|
|
81
|
+
'group-by': string;
|
|
82
|
+
limit: number;
|
|
83
|
+
output: string;
|
|
84
|
+
page: number;
|
|
85
|
+
preset?: string;
|
|
86
|
+
sequence?: string[];
|
|
87
|
+
to?: string;
|
|
88
|
+
};
|
|
89
|
+
export type ReportCtx = {
|
|
90
|
+
log(msg?: string): void;
|
|
91
|
+
onApiError(resp: {
|
|
92
|
+
error?: string;
|
|
93
|
+
status: number;
|
|
94
|
+
}): never;
|
|
95
|
+
post<T = Record<string, unknown>>(path: string, body?: unknown): Promise<ApiResponse<T>>;
|
|
96
|
+
spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
|
|
97
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/lib/constants.d.ts
CHANGED
package/dist/lib/constants.js
CHANGED
package/dist/lib/messages.d.ts
CHANGED
|
@@ -64,6 +64,13 @@ export declare const INFO: {
|
|
|
64
64
|
readonly YAML_RESTORED_FROM_SERVER: string;
|
|
65
65
|
readonly YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${string} to re-deploy your sequences.`;
|
|
66
66
|
};
|
|
67
|
+
export declare const REPORTS: {
|
|
68
|
+
readonly FETCH_SPINNER: " Fetching report...";
|
|
69
|
+
readonly FROM_TO_REQUIRED: "--from and --to must both be provided together.";
|
|
70
|
+
readonly NO_DATA: "No data found for the given filters and time range.";
|
|
71
|
+
readonly TIME_RANGE_REQUIRED: "A time range is required. Use --preset or --from/--to.";
|
|
72
|
+
readonly TOO_MANY_ITEMS: "Filter arrays cannot exceed 100 items each.";
|
|
73
|
+
};
|
|
67
74
|
export declare const DEPLOY: {
|
|
68
75
|
readonly CHANGES_HEADER: "Changes vs. last deployment:";
|
|
69
76
|
readonly DEPLOYING_HEADER: "Deploying:";
|
package/dist/lib/messages.js
CHANGED
|
@@ -64,6 +64,13 @@ export const INFO = {
|
|
|
64
64
|
YAML_RESTORED_FROM_SERVER: chalk.dim(' mailmodo.yaml not found locally — restored from server.'),
|
|
65
65
|
YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${chalk.cyan("'mailmodo deploy'")} to re-deploy your sequences.`,
|
|
66
66
|
};
|
|
67
|
+
export const REPORTS = {
|
|
68
|
+
FETCH_SPINNER: ' Fetching report...',
|
|
69
|
+
FROM_TO_REQUIRED: '--from and --to must both be provided together.',
|
|
70
|
+
NO_DATA: 'No data found for the given filters and time range.',
|
|
71
|
+
TIME_RANGE_REQUIRED: 'A time range is required. Use --preset or --from/--to.',
|
|
72
|
+
TOO_MANY_ITEMS: 'Filter arrays cannot exceed 100 items each.',
|
|
73
|
+
};
|
|
67
74
|
export const DEPLOY = {
|
|
68
75
|
CHANGES_HEADER: 'Changes vs. last deployment:',
|
|
69
76
|
DEPLOYING_HEADER: 'Deploying:',
|
package/oclif.manifest.json
CHANGED
|
@@ -289,6 +289,58 @@
|
|
|
289
289
|
"index.js"
|
|
290
290
|
]
|
|
291
291
|
},
|
|
292
|
+
"edit": {
|
|
293
|
+
"aliases": [],
|
|
294
|
+
"args": {
|
|
295
|
+
"id": {
|
|
296
|
+
"description": "Email template ID to edit",
|
|
297
|
+
"name": "id",
|
|
298
|
+
"required": true
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"description": "Edit an email using AI-assisted natural language changes",
|
|
302
|
+
"examples": [
|
|
303
|
+
"<%= config.bin %> edit welcome",
|
|
304
|
+
"<%= config.bin %> edit welcome --change \"make subject more urgent\" --yes"
|
|
305
|
+
],
|
|
306
|
+
"flags": {
|
|
307
|
+
"json": {
|
|
308
|
+
"description": "Output as JSON",
|
|
309
|
+
"name": "json",
|
|
310
|
+
"allowNo": false,
|
|
311
|
+
"type": "boolean"
|
|
312
|
+
},
|
|
313
|
+
"yes": {
|
|
314
|
+
"char": "y",
|
|
315
|
+
"description": "Skip confirmation prompts",
|
|
316
|
+
"name": "yes",
|
|
317
|
+
"allowNo": false,
|
|
318
|
+
"type": "boolean"
|
|
319
|
+
},
|
|
320
|
+
"change": {
|
|
321
|
+
"description": "Natural language description of the change",
|
|
322
|
+
"name": "change",
|
|
323
|
+
"hasDynamicHelp": false,
|
|
324
|
+
"multiple": false,
|
|
325
|
+
"type": "option"
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
"hasDynamicHelp": false,
|
|
329
|
+
"hiddenAliases": [],
|
|
330
|
+
"id": "edit",
|
|
331
|
+
"pluginAlias": "@mailmodo/cli",
|
|
332
|
+
"pluginName": "@mailmodo/cli",
|
|
333
|
+
"pluginType": "core",
|
|
334
|
+
"strict": true,
|
|
335
|
+
"enableJsonFlag": false,
|
|
336
|
+
"isESM": true,
|
|
337
|
+
"relativePath": [
|
|
338
|
+
"dist",
|
|
339
|
+
"commands",
|
|
340
|
+
"edit",
|
|
341
|
+
"index.js"
|
|
342
|
+
]
|
|
343
|
+
},
|
|
292
344
|
"emails": {
|
|
293
345
|
"aliases": [],
|
|
294
346
|
"args": {},
|
|
@@ -579,14 +631,17 @@
|
|
|
579
631
|
"index.js"
|
|
580
632
|
]
|
|
581
633
|
},
|
|
582
|
-
"
|
|
634
|
+
"report": {
|
|
583
635
|
"aliases": [],
|
|
584
636
|
"args": {},
|
|
585
|
-
"description": "
|
|
637
|
+
"description": "Fetch an email analytics report",
|
|
586
638
|
"examples": [
|
|
587
|
-
"<%= config.bin %>
|
|
588
|
-
"<%= config.bin %>
|
|
589
|
-
"<%= config.bin %>
|
|
639
|
+
"<%= config.bin %> report --preset last7d",
|
|
640
|
+
"<%= config.bin %> report --preset last30d --group-by emailId",
|
|
641
|
+
"<%= config.bin %> report --from YYYY-MM-DD --to YYYY-MM-DD --output timeseries",
|
|
642
|
+
"<%= config.bin %> report --preset last7d --output entries --page 1",
|
|
643
|
+
"<%= config.bin %> report --preset last30d --sequence welcome-flow --event opened",
|
|
644
|
+
"<%= config.bin %> report --preset last7d --json"
|
|
590
645
|
],
|
|
591
646
|
"flags": {
|
|
592
647
|
"json": {
|
|
@@ -602,9 +657,126 @@
|
|
|
602
657
|
"allowNo": false,
|
|
603
658
|
"type": "boolean"
|
|
604
659
|
},
|
|
605
|
-
"
|
|
606
|
-
"description": "
|
|
607
|
-
"name": "
|
|
660
|
+
"contact": {
|
|
661
|
+
"description": "Filter by contact email (repeatable)",
|
|
662
|
+
"name": "contact",
|
|
663
|
+
"hasDynamicHelp": false,
|
|
664
|
+
"multiple": true,
|
|
665
|
+
"type": "option"
|
|
666
|
+
},
|
|
667
|
+
"email-id": {
|
|
668
|
+
"description": "Filter by email template ID (repeatable)",
|
|
669
|
+
"name": "email-id",
|
|
670
|
+
"hasDynamicHelp": false,
|
|
671
|
+
"multiple": true,
|
|
672
|
+
"type": "option"
|
|
673
|
+
},
|
|
674
|
+
"event": {
|
|
675
|
+
"description": "Filter by event type (repeatable)",
|
|
676
|
+
"name": "event",
|
|
677
|
+
"hasDynamicHelp": false,
|
|
678
|
+
"multiple": true,
|
|
679
|
+
"options": [
|
|
680
|
+
"bounced",
|
|
681
|
+
"clicked",
|
|
682
|
+
"complained",
|
|
683
|
+
"delivered",
|
|
684
|
+
"opened",
|
|
685
|
+
"sent",
|
|
686
|
+
"skipped",
|
|
687
|
+
"unsubscribed"
|
|
688
|
+
],
|
|
689
|
+
"type": "option"
|
|
690
|
+
},
|
|
691
|
+
"from": {
|
|
692
|
+
"description": "Start of time range, inclusive (YYYY-MM-DD)",
|
|
693
|
+
"exclusive": [
|
|
694
|
+
"preset"
|
|
695
|
+
],
|
|
696
|
+
"name": "from",
|
|
697
|
+
"hasDynamicHelp": false,
|
|
698
|
+
"multiple": false,
|
|
699
|
+
"type": "option"
|
|
700
|
+
},
|
|
701
|
+
"group-by": {
|
|
702
|
+
"description": "Group results by dimension",
|
|
703
|
+
"name": "group-by",
|
|
704
|
+
"default": "none",
|
|
705
|
+
"hasDynamicHelp": false,
|
|
706
|
+
"multiple": false,
|
|
707
|
+
"options": [
|
|
708
|
+
"contact",
|
|
709
|
+
"day",
|
|
710
|
+
"emailId",
|
|
711
|
+
"hour",
|
|
712
|
+
"none",
|
|
713
|
+
"sequenceId",
|
|
714
|
+
"status"
|
|
715
|
+
],
|
|
716
|
+
"type": "option"
|
|
717
|
+
},
|
|
718
|
+
"limit": {
|
|
719
|
+
"description": "Entries per page, max 200 (entries output only)",
|
|
720
|
+
"name": "limit",
|
|
721
|
+
"default": 50,
|
|
722
|
+
"hasDynamicHelp": false,
|
|
723
|
+
"multiple": false,
|
|
724
|
+
"type": "option"
|
|
725
|
+
},
|
|
726
|
+
"output": {
|
|
727
|
+
"description": "Output shape: summary | entries | timeseries",
|
|
728
|
+
"name": "output",
|
|
729
|
+
"default": "summary",
|
|
730
|
+
"hasDynamicHelp": false,
|
|
731
|
+
"multiple": false,
|
|
732
|
+
"options": [
|
|
733
|
+
"entries",
|
|
734
|
+
"summary",
|
|
735
|
+
"timeseries"
|
|
736
|
+
],
|
|
737
|
+
"type": "option"
|
|
738
|
+
},
|
|
739
|
+
"page": {
|
|
740
|
+
"description": "Page number (entries output only)",
|
|
741
|
+
"name": "page",
|
|
742
|
+
"default": 1,
|
|
743
|
+
"hasDynamicHelp": false,
|
|
744
|
+
"multiple": false,
|
|
745
|
+
"type": "option"
|
|
746
|
+
},
|
|
747
|
+
"preset": {
|
|
748
|
+
"description": "Relative time range preset",
|
|
749
|
+
"exclusive": [
|
|
750
|
+
"from",
|
|
751
|
+
"to"
|
|
752
|
+
],
|
|
753
|
+
"name": "preset",
|
|
754
|
+
"hasDynamicHelp": false,
|
|
755
|
+
"multiple": false,
|
|
756
|
+
"options": [
|
|
757
|
+
"last30d",
|
|
758
|
+
"last7d",
|
|
759
|
+
"last90d",
|
|
760
|
+
"lastMonth",
|
|
761
|
+
"thisMonth",
|
|
762
|
+
"today",
|
|
763
|
+
"yesterday"
|
|
764
|
+
],
|
|
765
|
+
"type": "option"
|
|
766
|
+
},
|
|
767
|
+
"sequence": {
|
|
768
|
+
"description": "Filter by sequence ID (repeatable)",
|
|
769
|
+
"name": "sequence",
|
|
770
|
+
"hasDynamicHelp": false,
|
|
771
|
+
"multiple": true,
|
|
772
|
+
"type": "option"
|
|
773
|
+
},
|
|
774
|
+
"to": {
|
|
775
|
+
"description": "End of time range, exclusive (YYYY-MM-DD)",
|
|
776
|
+
"exclusive": [
|
|
777
|
+
"preset"
|
|
778
|
+
],
|
|
779
|
+
"name": "to",
|
|
608
780
|
"hasDynamicHelp": false,
|
|
609
781
|
"multiple": false,
|
|
610
782
|
"type": "option"
|
|
@@ -612,7 +784,7 @@
|
|
|
612
784
|
},
|
|
613
785
|
"hasDynamicHelp": false,
|
|
614
786
|
"hiddenAliases": [],
|
|
615
|
-
"id": "
|
|
787
|
+
"id": "report",
|
|
616
788
|
"pluginAlias": "@mailmodo/cli",
|
|
617
789
|
"pluginName": "@mailmodo/cli",
|
|
618
790
|
"pluginType": "core",
|
|
@@ -622,18 +794,18 @@
|
|
|
622
794
|
"relativePath": [
|
|
623
795
|
"dist",
|
|
624
796
|
"commands",
|
|
625
|
-
"
|
|
797
|
+
"report",
|
|
626
798
|
"index.js"
|
|
627
799
|
]
|
|
628
800
|
},
|
|
629
|
-
"
|
|
801
|
+
"sdk": {
|
|
630
802
|
"aliases": [],
|
|
631
803
|
"args": {},
|
|
632
|
-
"description": "
|
|
804
|
+
"description": "Show the SDK track() / identify() reference for deployed sequences",
|
|
633
805
|
"examples": [
|
|
634
|
-
"<%= config.bin %>
|
|
635
|
-
"<%= config.bin %>
|
|
636
|
-
"<%= config.bin %>
|
|
806
|
+
"<%= config.bin %> sdk",
|
|
807
|
+
"<%= config.bin %> sdk --sequence-id a1b2c3d4",
|
|
808
|
+
"<%= config.bin %> sdk --json"
|
|
637
809
|
],
|
|
638
810
|
"flags": {
|
|
639
811
|
"json": {
|
|
@@ -649,9 +821,9 @@
|
|
|
649
821
|
"allowNo": false,
|
|
650
822
|
"type": "boolean"
|
|
651
823
|
},
|
|
652
|
-
"
|
|
653
|
-
"description": "
|
|
654
|
-
"name": "
|
|
824
|
+
"sequence-id": {
|
|
825
|
+
"description": "Limit output to a single active sequence by ID (default: all active sequences)",
|
|
826
|
+
"name": "sequence-id",
|
|
655
827
|
"hasDynamicHelp": false,
|
|
656
828
|
"multiple": false,
|
|
657
829
|
"type": "option"
|
|
@@ -659,7 +831,7 @@
|
|
|
659
831
|
},
|
|
660
832
|
"hasDynamicHelp": false,
|
|
661
833
|
"hiddenAliases": [],
|
|
662
|
-
"id": "
|
|
834
|
+
"id": "sdk",
|
|
663
835
|
"pluginAlias": "@mailmodo/cli",
|
|
664
836
|
"pluginName": "@mailmodo/cli",
|
|
665
837
|
"pluginType": "core",
|
|
@@ -669,17 +841,18 @@
|
|
|
669
841
|
"relativePath": [
|
|
670
842
|
"dist",
|
|
671
843
|
"commands",
|
|
672
|
-
"
|
|
844
|
+
"sdk",
|
|
673
845
|
"index.js"
|
|
674
846
|
]
|
|
675
847
|
},
|
|
676
|
-
"
|
|
848
|
+
"settings": {
|
|
677
849
|
"aliases": [],
|
|
678
850
|
"args": {},
|
|
679
|
-
"description": "View
|
|
851
|
+
"description": "View and update project settings",
|
|
680
852
|
"examples": [
|
|
681
|
-
"<%= config.bin %>
|
|
682
|
-
"<%= config.bin %>
|
|
853
|
+
"<%= config.bin %> settings",
|
|
854
|
+
"<%= config.bin %> settings --set brand_color=#0F3460",
|
|
855
|
+
"<%= config.bin %> settings --json"
|
|
683
856
|
],
|
|
684
857
|
"flags": {
|
|
685
858
|
"json": {
|
|
@@ -694,11 +867,18 @@
|
|
|
694
867
|
"name": "yes",
|
|
695
868
|
"allowNo": false,
|
|
696
869
|
"type": "boolean"
|
|
870
|
+
},
|
|
871
|
+
"set": {
|
|
872
|
+
"description": "Set a setting (format: key=value)",
|
|
873
|
+
"name": "set",
|
|
874
|
+
"hasDynamicHelp": false,
|
|
875
|
+
"multiple": false,
|
|
876
|
+
"type": "option"
|
|
697
877
|
}
|
|
698
878
|
},
|
|
699
879
|
"hasDynamicHelp": false,
|
|
700
880
|
"hiddenAliases": [],
|
|
701
|
-
"id": "
|
|
881
|
+
"id": "settings",
|
|
702
882
|
"pluginAlias": "@mailmodo/cli",
|
|
703
883
|
"pluginName": "@mailmodo/cli",
|
|
704
884
|
"pluginType": "core",
|
|
@@ -708,23 +888,17 @@
|
|
|
708
888
|
"relativePath": [
|
|
709
889
|
"dist",
|
|
710
890
|
"commands",
|
|
711
|
-
"
|
|
891
|
+
"settings",
|
|
712
892
|
"index.js"
|
|
713
893
|
]
|
|
714
894
|
},
|
|
715
|
-
"
|
|
895
|
+
"status": {
|
|
716
896
|
"aliases": [],
|
|
717
|
-
"args": {
|
|
718
|
-
|
|
719
|
-
"description": "Email template ID to edit",
|
|
720
|
-
"name": "id",
|
|
721
|
-
"required": true
|
|
722
|
-
}
|
|
723
|
-
},
|
|
724
|
-
"description": "Edit an email using AI-assisted natural language changes",
|
|
897
|
+
"args": {},
|
|
898
|
+
"description": "View email performance metrics and quota usage",
|
|
725
899
|
"examples": [
|
|
726
|
-
"<%= config.bin %>
|
|
727
|
-
"<%= config.bin %>
|
|
900
|
+
"<%= config.bin %> status",
|
|
901
|
+
"<%= config.bin %> status --json"
|
|
728
902
|
],
|
|
729
903
|
"flags": {
|
|
730
904
|
"json": {
|
|
@@ -739,18 +913,11 @@
|
|
|
739
913
|
"name": "yes",
|
|
740
914
|
"allowNo": false,
|
|
741
915
|
"type": "boolean"
|
|
742
|
-
},
|
|
743
|
-
"change": {
|
|
744
|
-
"description": "Natural language description of the change",
|
|
745
|
-
"name": "change",
|
|
746
|
-
"hasDynamicHelp": false,
|
|
747
|
-
"multiple": false,
|
|
748
|
-
"type": "option"
|
|
749
916
|
}
|
|
750
917
|
},
|
|
751
918
|
"hasDynamicHelp": false,
|
|
752
919
|
"hiddenAliases": [],
|
|
753
|
-
"id": "
|
|
920
|
+
"id": "status",
|
|
754
921
|
"pluginAlias": "@mailmodo/cli",
|
|
755
922
|
"pluginName": "@mailmodo/cli",
|
|
756
923
|
"pluginType": "core",
|
|
@@ -760,10 +927,10 @@
|
|
|
760
927
|
"relativePath": [
|
|
761
928
|
"dist",
|
|
762
929
|
"commands",
|
|
763
|
-
"
|
|
930
|
+
"status",
|
|
764
931
|
"index.js"
|
|
765
932
|
]
|
|
766
933
|
}
|
|
767
934
|
},
|
|
768
|
-
"version": "0.0.56-beta.pr58.
|
|
935
|
+
"version": "0.0.56-beta.pr58.103"
|
|
769
936
|
}
|
package/package.json
CHANGED