@mailmodo/cli 0.0.56-beta.pr58.101 → 0.0.56-beta.pr58.102
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/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 +215 -48
- 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
|
+
}
|
|
@@ -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": {},
|
|
@@ -374,13 +426,12 @@
|
|
|
374
426
|
"index.js"
|
|
375
427
|
]
|
|
376
428
|
},
|
|
377
|
-
"
|
|
429
|
+
"logout": {
|
|
378
430
|
"aliases": [],
|
|
379
431
|
"args": {},
|
|
380
|
-
"description": "
|
|
432
|
+
"description": "Sign out by removing saved credentials from this machine",
|
|
381
433
|
"examples": [
|
|
382
|
-
"<%= config.bin %>
|
|
383
|
-
"MAILMODO_API_KEY=YOUR_API_KEY <%= config.bin %> login"
|
|
434
|
+
"<%= config.bin %> logout"
|
|
384
435
|
],
|
|
385
436
|
"flags": {
|
|
386
437
|
"json": {
|
|
@@ -399,7 +450,7 @@
|
|
|
399
450
|
},
|
|
400
451
|
"hasDynamicHelp": false,
|
|
401
452
|
"hiddenAliases": [],
|
|
402
|
-
"id": "
|
|
453
|
+
"id": "logout",
|
|
403
454
|
"pluginAlias": "@mailmodo/cli",
|
|
404
455
|
"pluginName": "@mailmodo/cli",
|
|
405
456
|
"pluginType": "core",
|
|
@@ -409,16 +460,17 @@
|
|
|
409
460
|
"relativePath": [
|
|
410
461
|
"dist",
|
|
411
462
|
"commands",
|
|
412
|
-
"
|
|
463
|
+
"logout",
|
|
413
464
|
"index.js"
|
|
414
465
|
]
|
|
415
466
|
},
|
|
416
|
-
"
|
|
467
|
+
"login": {
|
|
417
468
|
"aliases": [],
|
|
418
469
|
"args": {},
|
|
419
|
-
"description": "
|
|
470
|
+
"description": "Authenticate with Mailmodo using your API key",
|
|
420
471
|
"examples": [
|
|
421
|
-
"<%= config.bin %>
|
|
472
|
+
"<%= config.bin %> login",
|
|
473
|
+
"MAILMODO_API_KEY=YOUR_API_KEY <%= config.bin %> login"
|
|
422
474
|
],
|
|
423
475
|
"flags": {
|
|
424
476
|
"json": {
|
|
@@ -437,7 +489,7 @@
|
|
|
437
489
|
},
|
|
438
490
|
"hasDynamicHelp": false,
|
|
439
491
|
"hiddenAliases": [],
|
|
440
|
-
"id": "
|
|
492
|
+
"id": "login",
|
|
441
493
|
"pluginAlias": "@mailmodo/cli",
|
|
442
494
|
"pluginName": "@mailmodo/cli",
|
|
443
495
|
"pluginType": "core",
|
|
@@ -447,7 +499,7 @@
|
|
|
447
499
|
"relativePath": [
|
|
448
500
|
"dist",
|
|
449
501
|
"commands",
|
|
450
|
-
"
|
|
502
|
+
"login",
|
|
451
503
|
"index.js"
|
|
452
504
|
]
|
|
453
505
|
},
|
|
@@ -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,7 +841,7 @@
|
|
|
669
841
|
"relativePath": [
|
|
670
842
|
"dist",
|
|
671
843
|
"commands",
|
|
672
|
-
"
|
|
844
|
+
"sdk",
|
|
673
845
|
"index.js"
|
|
674
846
|
]
|
|
675
847
|
},
|
|
@@ -712,19 +884,14 @@
|
|
|
712
884
|
"index.js"
|
|
713
885
|
]
|
|
714
886
|
},
|
|
715
|
-
"
|
|
887
|
+
"settings": {
|
|
716
888
|
"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",
|
|
889
|
+
"args": {},
|
|
890
|
+
"description": "View and update project settings",
|
|
725
891
|
"examples": [
|
|
726
|
-
"<%= config.bin %>
|
|
727
|
-
"<%= config.bin %>
|
|
892
|
+
"<%= config.bin %> settings",
|
|
893
|
+
"<%= config.bin %> settings --set brand_color=#0F3460",
|
|
894
|
+
"<%= config.bin %> settings --json"
|
|
728
895
|
],
|
|
729
896
|
"flags": {
|
|
730
897
|
"json": {
|
|
@@ -740,9 +907,9 @@
|
|
|
740
907
|
"allowNo": false,
|
|
741
908
|
"type": "boolean"
|
|
742
909
|
},
|
|
743
|
-
"
|
|
744
|
-
"description": "
|
|
745
|
-
"name": "
|
|
910
|
+
"set": {
|
|
911
|
+
"description": "Set a setting (format: key=value)",
|
|
912
|
+
"name": "set",
|
|
746
913
|
"hasDynamicHelp": false,
|
|
747
914
|
"multiple": false,
|
|
748
915
|
"type": "option"
|
|
@@ -750,7 +917,7 @@
|
|
|
750
917
|
},
|
|
751
918
|
"hasDynamicHelp": false,
|
|
752
919
|
"hiddenAliases": [],
|
|
753
|
-
"id": "
|
|
920
|
+
"id": "settings",
|
|
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
|
+
"settings",
|
|
764
931
|
"index.js"
|
|
765
932
|
]
|
|
766
933
|
}
|
|
767
934
|
},
|
|
768
|
-
"version": "0.0.56-beta.pr58.
|
|
935
|
+
"version": "0.0.56-beta.pr58.102"
|
|
769
936
|
}
|
package/package.json
CHANGED