@mailmodo/cli 0.0.7 → 0.0.8-beta.pr10.14
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/bin/dev.js +16 -10
- package/bin/run.js +2 -2
- package/dist/commands/billing/index.js +10 -3
- package/dist/commands/contacts/index.js +7 -2
- package/dist/commands/deploy/index.js +6 -3
- package/dist/commands/domain/index.js +9 -3
- package/dist/commands/edit/index.js +12 -5
- package/dist/commands/emails/index.js +3 -1
- package/dist/commands/init/index.js +48 -15
- package/dist/commands/login/index.js +1 -3
- package/dist/commands/logs/index.js +4 -1
- package/dist/commands/preview/index.js +10 -3
- package/dist/commands/settings/index.js +2 -1
- package/dist/lib/api-client.d.ts +16 -13
- package/dist/lib/api-client.js +55 -10
- package/dist/lib/base-command.d.ts +14 -2
- package/dist/lib/base-command.js +40 -5
- package/dist/lib/config.js +1 -0
- package/dist/lib/constants.d.ts +17 -17
- package/dist/lib/constants.js +23 -18
- package/dist/lib/yaml-config.js +5 -1
- package/oclif.manifest.json +80 -80
- package/package.json +1 -1
package/bin/dev.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {spawnSync} from 'node:child_process'
|
|
4
|
-
import {fileURLToPath} from 'node:url'
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
|
-
import {execute} from '@oclif/core'
|
|
6
|
+
import { execute } from '@oclif/core';
|
|
7
7
|
|
|
8
8
|
if (process.env.MAILMODO_DEV_TSX) {
|
|
9
|
-
await execute({development: true, dir: import.meta.url})
|
|
9
|
+
await execute({ development: true, dir: import.meta.url });
|
|
10
10
|
} else {
|
|
11
|
-
const scriptPath = fileURLToPath(import.meta.url)
|
|
12
|
-
const result = spawnSync(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
12
|
+
const result = spawnSync(
|
|
13
|
+
process.execPath,
|
|
14
|
+
['--import', 'tsx', scriptPath, ...process.argv.slice(2)],
|
|
15
|
+
{
|
|
16
|
+
env: { ...process.env, MAILMODO_DEV_TSX: '1' },
|
|
17
|
+
stdio: 'inherit',
|
|
18
|
+
}
|
|
19
|
+
);
|
|
16
20
|
|
|
17
21
|
if (result.status !== 0) {
|
|
18
|
-
throw new Error(
|
|
22
|
+
throw new Error(
|
|
23
|
+
`dev bootstrap failed with exit code ${result.status ?? 1}`
|
|
24
|
+
);
|
|
19
25
|
}
|
|
20
26
|
}
|
package/bin/run.js
CHANGED
|
@@ -12,8 +12,13 @@ export default class Billing extends BaseCommand {
|
|
|
12
12
|
];
|
|
13
13
|
static flags = {
|
|
14
14
|
...BaseCommand.baseFlags,
|
|
15
|
-
cap: Flags.integer({
|
|
16
|
-
|
|
15
|
+
cap: Flags.integer({
|
|
16
|
+
description: 'Set monthly block cap (max blocks to auto-charge)',
|
|
17
|
+
}),
|
|
18
|
+
status: Flags.boolean({
|
|
19
|
+
default: false,
|
|
20
|
+
description: 'Show billing status only',
|
|
21
|
+
}),
|
|
17
22
|
};
|
|
18
23
|
async run() {
|
|
19
24
|
const { flags } = await this.parse(Billing);
|
|
@@ -79,7 +84,9 @@ export default class Billing extends BaseCommand {
|
|
|
79
84
|
* @param {boolean} jsonOutput - Whether to output JSON instead of formatted text.
|
|
80
85
|
*/
|
|
81
86
|
async setCap(cap, jsonOutput) {
|
|
82
|
-
const response = await this.apiClient.patch(API_ENDPOINTS.BILLING_CAP, {
|
|
87
|
+
const response = await this.apiClient.patch(API_ENDPOINTS.BILLING_CAP, {
|
|
88
|
+
cap,
|
|
89
|
+
});
|
|
83
90
|
if (!response.ok) {
|
|
84
91
|
this.handleApiError(response);
|
|
85
92
|
}
|
|
@@ -13,8 +13,13 @@ export default class Contacts extends BaseCommand {
|
|
|
13
13
|
];
|
|
14
14
|
static flags = {
|
|
15
15
|
...BaseCommand.baseFlags,
|
|
16
|
-
delete: Flags.string({
|
|
17
|
-
|
|
16
|
+
delete: Flags.string({
|
|
17
|
+
description: 'GDPR hard delete a contact by email',
|
|
18
|
+
}),
|
|
19
|
+
export: Flags.boolean({
|
|
20
|
+
default: false,
|
|
21
|
+
description: 'Export all contacts as CSV',
|
|
22
|
+
}),
|
|
18
23
|
search: Flags.string({ description: 'Search for a contact by email' }),
|
|
19
24
|
};
|
|
20
25
|
async run() {
|
|
@@ -28,7 +28,10 @@ export default class Deploy extends BaseCommand {
|
|
|
28
28
|
this.log(` This is a one-time setup. Takes about 5 minutes.\n`);
|
|
29
29
|
}
|
|
30
30
|
if (!flags.yes) {
|
|
31
|
-
const setupNow = await confirm({
|
|
31
|
+
const setupNow = await confirm({
|
|
32
|
+
default: true,
|
|
33
|
+
message: 'Set up your sending domain now?',
|
|
34
|
+
});
|
|
32
35
|
if (!setupNow) {
|
|
33
36
|
this.log(`\n Sequences saved but ${chalk.yellow('NOT deployed')}.`);
|
|
34
37
|
this.log(` Emails will not send until your domain is verified.`);
|
|
@@ -83,7 +86,7 @@ export default class Deploy extends BaseCommand {
|
|
|
83
86
|
this.log(` ${chalk.dim('// On user signup:')}`);
|
|
84
87
|
this.log(` ${chalk.dim("track('user.signup', { email, first_name, app_url })")}\n`);
|
|
85
88
|
this.log(` ${chalk.dim('// When user creates a project:')}`);
|
|
86
|
-
this.log(` ${chalk.dim(
|
|
89
|
+
this.log(` ${chalk.dim('identify(email, { has_created_project: true })')}\n`);
|
|
87
90
|
this.log(` ${chalk.dim('// On trial expiry:')}`);
|
|
88
91
|
this.log(` ${chalk.dim("track('user.trial_expiry', { email, first_name })")}\n`);
|
|
89
92
|
this.log(` Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}\n`);
|
|
@@ -115,7 +118,7 @@ export default class Deploy extends BaseCommand {
|
|
|
115
118
|
});
|
|
116
119
|
senderEmail = await input({
|
|
117
120
|
message: 'Sender email address:',
|
|
118
|
-
validate: (v) =>
|
|
121
|
+
validate: (v) => v?.includes('@') ? true : 'Please enter a valid email',
|
|
119
122
|
});
|
|
120
123
|
address = await input({
|
|
121
124
|
message: 'Business address (required by law for email footers):',
|
|
@@ -13,8 +13,14 @@ export default class Domain extends BaseCommand {
|
|
|
13
13
|
];
|
|
14
14
|
static flags = {
|
|
15
15
|
...BaseCommand.baseFlags,
|
|
16
|
-
status: Flags.boolean({
|
|
17
|
-
|
|
16
|
+
status: Flags.boolean({
|
|
17
|
+
default: false,
|
|
18
|
+
description: 'Show domain health status',
|
|
19
|
+
}),
|
|
20
|
+
verify: Flags.boolean({
|
|
21
|
+
default: false,
|
|
22
|
+
description: 'Verify DNS records',
|
|
23
|
+
}),
|
|
18
24
|
};
|
|
19
25
|
async run() {
|
|
20
26
|
const { flags } = await this.parse(Domain);
|
|
@@ -58,7 +64,7 @@ export default class Domain extends BaseCommand {
|
|
|
58
64
|
senderEmail = await input({
|
|
59
65
|
default: yamlConfig.project?.fromEmail,
|
|
60
66
|
message: 'Sender email address:',
|
|
61
|
-
validate: (v) =>
|
|
67
|
+
validate: (v) => v?.includes('@') ? true : 'Please enter a valid email',
|
|
62
68
|
});
|
|
63
69
|
address = await input({
|
|
64
70
|
default: yamlConfig.project?.address,
|
|
@@ -3,7 +3,7 @@ import { confirm, input } from '@inquirer/prompts';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
5
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
6
|
-
import { loadTemplate, saveTemplate, saveYaml } from '../../lib/yaml-config.js';
|
|
6
|
+
import { loadTemplate, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
|
|
7
7
|
export default class Edit extends BaseCommand {
|
|
8
8
|
static args = {
|
|
9
9
|
id: Args.string({ description: 'Email ID to edit', required: true }),
|
|
@@ -15,7 +15,9 @@ export default class Edit extends BaseCommand {
|
|
|
15
15
|
];
|
|
16
16
|
static flags = {
|
|
17
17
|
...BaseCommand.baseFlags,
|
|
18
|
-
change: Flags.string({
|
|
18
|
+
change: Flags.string({
|
|
19
|
+
description: 'Natural language description of the change',
|
|
20
|
+
}),
|
|
19
21
|
};
|
|
20
22
|
async run() {
|
|
21
23
|
const { args, flags } = await this.parse(Edit);
|
|
@@ -34,7 +36,7 @@ export default class Edit extends BaseCommand {
|
|
|
34
36
|
if (!changeDescription) {
|
|
35
37
|
changeDescription = await input({
|
|
36
38
|
message: 'What do you want to change?',
|
|
37
|
-
validate: (value) =>
|
|
39
|
+
validate: (value) => value?.trim() ? true : 'Please describe the change',
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
const response = await this.apiClient.post(API_ENDPOINTS.EDIT, {
|
|
@@ -61,7 +63,10 @@ export default class Edit extends BaseCommand {
|
|
|
61
63
|
this.log(` ${chalk.green(`+ ${newSubject}`)}`);
|
|
62
64
|
}
|
|
63
65
|
if (!flags.yes) {
|
|
64
|
-
const accepted = await confirm({
|
|
66
|
+
const accepted = await confirm({
|
|
67
|
+
default: true,
|
|
68
|
+
message: 'Accept changes?',
|
|
69
|
+
});
|
|
65
70
|
if (!accepted) {
|
|
66
71
|
this.log('\n Changes discarded.\n');
|
|
67
72
|
return;
|
|
@@ -83,7 +88,9 @@ export default class Edit extends BaseCommand {
|
|
|
83
88
|
if (flags.json) {
|
|
84
89
|
this.log(JSON.stringify({
|
|
85
90
|
diff: {
|
|
86
|
-
subject: oldSubject === newSubject
|
|
91
|
+
subject: oldSubject === newSubject
|
|
92
|
+
? undefined
|
|
93
|
+
: { new: newSubject, old: oldSubject },
|
|
87
94
|
},
|
|
88
95
|
email,
|
|
89
96
|
status: 'updated',
|
|
@@ -25,7 +25,9 @@ export default class Emails extends BaseCommand {
|
|
|
25
25
|
const id = email.id.padEnd(maxIdLen + 2);
|
|
26
26
|
const trigger = `trigger: ${email.trigger}`.padEnd(maxTriggerLen + 12);
|
|
27
27
|
const delay = `delay: ${email.delay}`;
|
|
28
|
-
const condition = email.condition
|
|
28
|
+
const condition = email.condition
|
|
29
|
+
? chalk.dim(` [if ${email.condition}]`)
|
|
30
|
+
: '';
|
|
29
31
|
this.log(` ${chalk.cyan(id)} ${trigger} ${delay}${condition}`);
|
|
30
32
|
}
|
|
31
33
|
this.log('');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
2
|
+
import { editor, input, select } from '@inquirer/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
5
|
import { API_ENDPOINTS, DEFAULT_BRAND_COLOR } from '../../lib/constants.js';
|
|
6
|
-
import { saveTemplate, saveYaml } from '../../lib/yaml-config.js';
|
|
6
|
+
import { saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
|
|
7
7
|
function isValidUrl(value) {
|
|
8
8
|
try {
|
|
9
9
|
return Boolean(new URL(value));
|
|
@@ -55,20 +55,51 @@ export default class Init extends BaseCommand {
|
|
|
55
55
|
}
|
|
56
56
|
this.log('');
|
|
57
57
|
}
|
|
58
|
+
let analysisPayload = analysis;
|
|
58
59
|
if (!flags.yes) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const userAction = await select({
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: 'Yes - continue with this result', value: 'yes' },
|
|
63
|
+
{ name: 'No - stop here', value: 'no' },
|
|
64
|
+
{ name: 'Edit - update the result before continuing', value: 'edit' },
|
|
65
|
+
],
|
|
66
|
+
message: 'Does this look right?',
|
|
67
|
+
});
|
|
68
|
+
if (userAction === 'no') {
|
|
69
|
+
this.log(`\n Stopped. Run ${chalk.cyan('mailmodo init')} again when you are ready.\n`);
|
|
62
70
|
return;
|
|
63
71
|
}
|
|
72
|
+
if (userAction === 'edit') {
|
|
73
|
+
const editedAnalysis = await editor({
|
|
74
|
+
default: JSON.stringify(analysis, null, 2),
|
|
75
|
+
message: 'Edit the analysis JSON. Save and close to continue.',
|
|
76
|
+
postfix: '.json',
|
|
77
|
+
validate(value) {
|
|
78
|
+
if (!value?.trim())
|
|
79
|
+
return 'Edited analysis cannot be empty';
|
|
80
|
+
try {
|
|
81
|
+
JSON.parse(value);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return 'Please provide valid JSON';
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
analysisPayload = JSON.parse(editedAnalysis);
|
|
90
|
+
}
|
|
64
91
|
}
|
|
92
|
+
this.log('analysisPayload', analysisPayload);
|
|
65
93
|
this.log('\n Generating emails...\n');
|
|
66
|
-
const generateResponse = await this.apiClient.post(API_ENDPOINTS.GENERATE, {
|
|
94
|
+
const generateResponse = await this.apiClient.post(API_ENDPOINTS.GENERATE, {
|
|
95
|
+
analysis: analysisPayload,
|
|
96
|
+
productUrl,
|
|
97
|
+
});
|
|
67
98
|
if (!generateResponse.ok) {
|
|
68
99
|
this.handleApiError(generateResponse);
|
|
69
100
|
}
|
|
70
101
|
const generatedEmails = generateResponse.data?.emails || [];
|
|
71
|
-
const emailConfigs =
|
|
102
|
+
const emailConfigs = analysisPayload.recommendedEmails.map((rec, index) => {
|
|
72
103
|
const generated = generatedEmails[index];
|
|
73
104
|
return {
|
|
74
105
|
delay: rec.delay || '0',
|
|
@@ -77,27 +108,29 @@ export default class Init extends BaseCommand {
|
|
|
77
108
|
...(rec.condition ? { condition: rec.condition } : {}),
|
|
78
109
|
subject: generated?.subject || `Email for ${rec.id}`,
|
|
79
110
|
template: `mailmodo/${rec.id}.html`,
|
|
80
|
-
...(generated?.previewText
|
|
111
|
+
...(generated?.previewText
|
|
112
|
+
? { previewText: generated.previewText }
|
|
113
|
+
: {}),
|
|
81
114
|
goal: rec.goal,
|
|
82
115
|
};
|
|
83
116
|
});
|
|
84
117
|
const yamlConfig = {
|
|
85
118
|
emails: emailConfigs,
|
|
86
119
|
project: {
|
|
87
|
-
brandColor:
|
|
120
|
+
brandColor: analysisPayload.brand?.color || DEFAULT_BRAND_COLOR,
|
|
88
121
|
emailStyle: 'branded',
|
|
89
122
|
fromEmail: '',
|
|
90
|
-
fromName: `Team ${
|
|
91
|
-
logoUrl:
|
|
92
|
-
name:
|
|
123
|
+
fromName: `Team ${analysisPayload.productName}`,
|
|
124
|
+
logoUrl: analysisPayload.brand?.logoUrl || '',
|
|
125
|
+
name: analysisPayload.productName,
|
|
93
126
|
replyTo: '',
|
|
94
|
-
type:
|
|
127
|
+
type: analysisPayload.pricingModel,
|
|
95
128
|
url: productUrl,
|
|
96
129
|
webhookUrl: '',
|
|
97
130
|
},
|
|
98
131
|
};
|
|
99
132
|
await saveYaml(yamlConfig);
|
|
100
|
-
const templateSaves =
|
|
133
|
+
const templateSaves = analysisPayload.recommendedEmails.flatMap((rec, index) => {
|
|
101
134
|
const generated = generatedEmails[index];
|
|
102
135
|
const saves = [];
|
|
103
136
|
if (generated?.html) {
|
|
@@ -111,7 +144,7 @@ export default class Init extends BaseCommand {
|
|
|
111
144
|
await Promise.all(templateSaves);
|
|
112
145
|
if (flags.json) {
|
|
113
146
|
this.log(JSON.stringify({
|
|
114
|
-
brandDetected:
|
|
147
|
+
brandDetected: analysisPayload.brand,
|
|
115
148
|
emails: emailConfigs,
|
|
116
149
|
emailsCreated: emailConfigs.length,
|
|
117
150
|
style: yamlConfig.project.emailStyle,
|
|
@@ -34,15 +34,13 @@ export default class Login extends BaseCommand {
|
|
|
34
34
|
validate(value) {
|
|
35
35
|
if (!value?.trim())
|
|
36
36
|
return 'API key is required';
|
|
37
|
-
if (!value.startsWith('mm_'))
|
|
38
|
-
return 'Invalid API key format. Keys start with mm_';
|
|
39
37
|
return true;
|
|
40
38
|
},
|
|
41
39
|
});
|
|
42
40
|
}
|
|
43
41
|
const trimmedKey = apiKey.trim();
|
|
44
42
|
const client = new ApiClient(trimmedKey);
|
|
45
|
-
const response = await client.
|
|
43
|
+
const response = await client.post(API_ENDPOINTS.AUTH_VALIDATE, {});
|
|
46
44
|
if (!response.ok) {
|
|
47
45
|
this.handleApiError(response);
|
|
48
46
|
}
|
|
@@ -13,7 +13,10 @@ export default class Logs extends BaseCommand {
|
|
|
13
13
|
static flags = {
|
|
14
14
|
...BaseCommand.baseFlags,
|
|
15
15
|
email: Flags.string({ description: 'Filter logs by contact email' }),
|
|
16
|
-
failed: Flags.boolean({
|
|
16
|
+
failed: Flags.boolean({
|
|
17
|
+
default: false,
|
|
18
|
+
description: 'Show only failed/bounced events',
|
|
19
|
+
}),
|
|
17
20
|
};
|
|
18
21
|
async run() {
|
|
19
22
|
const { flags } = await this.parse(Logs);
|
|
@@ -63,7 +63,10 @@ export default class Preview extends BaseCommand {
|
|
|
63
63
|
static flags = {
|
|
64
64
|
...BaseCommand.baseFlags,
|
|
65
65
|
send: Flags.string({ description: 'Send test email to this address' }),
|
|
66
|
-
text: Flags.boolean({
|
|
66
|
+
text: Flags.boolean({
|
|
67
|
+
default: false,
|
|
68
|
+
description: 'Output plain text version (for AI agents)',
|
|
69
|
+
}),
|
|
67
70
|
};
|
|
68
71
|
async run() {
|
|
69
72
|
const { args, flags } = await this.parse(Preview);
|
|
@@ -97,7 +100,9 @@ export default class Preview extends BaseCommand {
|
|
|
97
100
|
* and CI pipelines that cannot open a browser.
|
|
98
101
|
*/
|
|
99
102
|
async renderText(email, templateHtml, sampleData, jsonOutput) {
|
|
100
|
-
const rendered = templateHtml
|
|
103
|
+
const rendered = templateHtml
|
|
104
|
+
? renderTemplate(templateHtml, sampleData)
|
|
105
|
+
: '';
|
|
101
106
|
const plainText = htmlToText(rendered);
|
|
102
107
|
if (jsonOutput) {
|
|
103
108
|
this.log(JSON.stringify({
|
|
@@ -139,7 +144,9 @@ export default class Preview extends BaseCommand {
|
|
|
139
144
|
* template, then opens the user's default browser to view it.
|
|
140
145
|
*/
|
|
141
146
|
async startPreviewServer(email, templateHtml, sampleData, jsonOutput) {
|
|
142
|
-
const rendered = templateHtml
|
|
147
|
+
const rendered = templateHtml
|
|
148
|
+
? renderTemplate(templateHtml, sampleData)
|
|
149
|
+
: '<p>No template found.</p>';
|
|
143
150
|
const wrapperHtml = `<!DOCTYPE html>
|
|
144
151
|
<html>
|
|
145
152
|
<head>
|
|
@@ -50,7 +50,8 @@ export default class Settings extends BaseCommand {
|
|
|
50
50
|
if (!(propKey in project)) {
|
|
51
51
|
this.error(`Unknown setting: ${key}`);
|
|
52
52
|
}
|
|
53
|
-
project[propKey] =
|
|
53
|
+
project[propKey] =
|
|
54
|
+
propKey === 'monthlyCap' ? Number(value) : value;
|
|
54
55
|
await saveYaml(yamlConfig);
|
|
55
56
|
if (flags.json) {
|
|
56
57
|
this.log(JSON.stringify({ [propKey]: value, status: 'updated' }, null, 2));
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request context attached to API responses for troubleshooting failed calls.
|
|
3
|
+
* Only the resolved URL is stored; it encodes origin, path, and query string.
|
|
4
|
+
*/
|
|
5
|
+
export interface ApiRequestDebugInfo {
|
|
6
|
+
/** Node/DNS/network error code when `fetch` throws before an HTTP response. */
|
|
7
|
+
causeCode?: string;
|
|
8
|
+
/** Fully resolved request URL (origin, path, and query string). */
|
|
9
|
+
fullUrl: string;
|
|
10
|
+
/** Truncated JSON summary of a non-empty error response body, when available. */
|
|
11
|
+
responseSummary?: string;
|
|
12
|
+
}
|
|
1
13
|
export interface ApiResponse<T = Record<string, unknown>> {
|
|
2
14
|
data: T;
|
|
15
|
+
/** Populated for tracing; especially useful when `ok` is false. */
|
|
16
|
+
debug?: ApiRequestDebugInfo;
|
|
3
17
|
error?: string;
|
|
4
18
|
ok: boolean;
|
|
5
19
|
status: number;
|
|
6
20
|
}
|
|
7
|
-
/**
|
|
8
|
-
* HTTP client for the Mailmodo CLI API.
|
|
9
|
-
* Wraps the native fetch API with Bearer token authentication,
|
|
10
|
-
* consistent error handling, and typed responses.
|
|
11
|
-
*
|
|
12
|
-
* All requests include:
|
|
13
|
-
* - Authorization header with the user's API key
|
|
14
|
-
* - Content-Type: application/json
|
|
15
|
-
* - User-Agent: @mailmodo/cli
|
|
16
|
-
*
|
|
17
|
-
* Network errors (ECONNREFUSED, ENOTFOUND) return a friendly message
|
|
18
|
-
* indicating the API may not be available, rather than a raw stack trace.
|
|
19
|
-
*/
|
|
20
21
|
export declare class ApiClient {
|
|
21
22
|
private apiKey;
|
|
22
23
|
private baseUrl;
|
|
23
24
|
constructor(apiKey: string);
|
|
25
|
+
private resolveUrl;
|
|
26
|
+
private requestDebug;
|
|
24
27
|
/**
|
|
25
28
|
* Sends an HTTP request to the Mailmodo API and returns a typed response.
|
|
26
29
|
*
|
package/dist/lib/api-client.js
CHANGED
|
@@ -12,6 +12,22 @@ import { API_BASE_URL } from './constants.js';
|
|
|
12
12
|
* Network errors (ECONNREFUSED, ENOTFOUND) return a friendly message
|
|
13
13
|
* indicating the API may not be available, rather than a raw stack trace.
|
|
14
14
|
*/
|
|
15
|
+
const RESPONSE_BODY_DEBUG_MAX = 800;
|
|
16
|
+
function summarizeResponseBody(data) {
|
|
17
|
+
if (data === null || data === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
try {
|
|
20
|
+
const s = JSON.stringify(data);
|
|
21
|
+
if (s === '{}' || s === '[]')
|
|
22
|
+
return undefined;
|
|
23
|
+
return s.length > RESPONSE_BODY_DEBUG_MAX
|
|
24
|
+
? `${s.slice(0, RESPONSE_BODY_DEBUG_MAX)}…`
|
|
25
|
+
: s;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
15
31
|
export class ApiClient {
|
|
16
32
|
apiKey;
|
|
17
33
|
baseUrl;
|
|
@@ -19,6 +35,18 @@ export class ApiClient {
|
|
|
19
35
|
this.baseUrl = API_BASE_URL;
|
|
20
36
|
this.apiKey = apiKey;
|
|
21
37
|
}
|
|
38
|
+
resolveUrl(path, params) {
|
|
39
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
40
|
+
if (params) {
|
|
41
|
+
for (const [key, value] of Object.entries(params)) {
|
|
42
|
+
url.searchParams.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return url;
|
|
46
|
+
}
|
|
47
|
+
requestDebug(url) {
|
|
48
|
+
return { fullUrl: url.toString() };
|
|
49
|
+
}
|
|
22
50
|
/**
|
|
23
51
|
* Sends an HTTP request to the Mailmodo API and returns a typed response.
|
|
24
52
|
*
|
|
@@ -31,14 +59,10 @@ export class ApiClient {
|
|
|
31
59
|
* data (parsed JSON body), and optional error (string message on failure).
|
|
32
60
|
*/
|
|
33
61
|
async request(method, path, body, params) {
|
|
34
|
-
const url =
|
|
35
|
-
|
|
36
|
-
for (const [key, value] of Object.entries(params)) {
|
|
37
|
-
url.searchParams.set(key, value);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
62
|
+
const url = this.resolveUrl(path, params);
|
|
63
|
+
const debug = this.requestDebug(url);
|
|
40
64
|
const headers = {
|
|
41
|
-
|
|
65
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
42
66
|
'Content-Type': 'application/json',
|
|
43
67
|
'User-Agent': '@mailmodo/cli',
|
|
44
68
|
};
|
|
@@ -51,8 +75,13 @@ export class ApiClient {
|
|
|
51
75
|
const data = await response.json().catch(() => ({}));
|
|
52
76
|
if (!response.ok) {
|
|
53
77
|
const errorData = data;
|
|
78
|
+
const summary = summarizeResponseBody(data);
|
|
54
79
|
return {
|
|
55
80
|
data: data,
|
|
81
|
+
debug: {
|
|
82
|
+
...debug,
|
|
83
|
+
responseSummary: summary,
|
|
84
|
+
},
|
|
56
85
|
error: errorData?.message ||
|
|
57
86
|
errorData?.error ||
|
|
58
87
|
`Request failed with status ${response.status}`,
|
|
@@ -65,11 +94,16 @@ export class ApiClient {
|
|
|
65
94
|
catch (error) {
|
|
66
95
|
const err = error;
|
|
67
96
|
const isConnectionError = err?.cause?.code === 'ECONNREFUSED' || err?.cause?.code === 'ENOTFOUND';
|
|
97
|
+
const causeCode = err?.cause?.code;
|
|
68
98
|
return {
|
|
69
99
|
data: {},
|
|
100
|
+
debug: {
|
|
101
|
+
...debug,
|
|
102
|
+
...(causeCode ? { causeCode } : {}),
|
|
103
|
+
},
|
|
70
104
|
error: isConnectionError
|
|
71
105
|
? 'Cannot connect to Mailmodo API. The API service may not be available yet.'
|
|
72
|
-
:
|
|
106
|
+
: err?.message || 'An unexpected network error occurred.',
|
|
73
107
|
ok: false,
|
|
74
108
|
status: 0,
|
|
75
109
|
};
|
|
@@ -88,12 +122,13 @@ export class ApiClient {
|
|
|
88
122
|
return this.request('POST', path, body);
|
|
89
123
|
}
|
|
90
124
|
async postFormData(path, formData) {
|
|
91
|
-
const url =
|
|
125
|
+
const url = this.resolveUrl(path);
|
|
126
|
+
const debug = this.requestDebug(url);
|
|
92
127
|
try {
|
|
93
128
|
const response = await fetch(url.toString(), {
|
|
94
129
|
body: formData,
|
|
95
130
|
headers: {
|
|
96
|
-
|
|
131
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
97
132
|
'User-Agent': '@mailmodo/cli',
|
|
98
133
|
},
|
|
99
134
|
method: 'POST',
|
|
@@ -101,8 +136,13 @@ export class ApiClient {
|
|
|
101
136
|
const data = await response.json().catch(() => ({}));
|
|
102
137
|
if (!response.ok) {
|
|
103
138
|
const errorData = data;
|
|
139
|
+
const summary = summarizeResponseBody(data);
|
|
104
140
|
return {
|
|
105
141
|
data: data,
|
|
142
|
+
debug: {
|
|
143
|
+
...debug,
|
|
144
|
+
responseSummary: summary,
|
|
145
|
+
},
|
|
106
146
|
error: errorData?.message ||
|
|
107
147
|
errorData?.error ||
|
|
108
148
|
`Upload failed with status ${response.status}`,
|
|
@@ -114,8 +154,13 @@ export class ApiClient {
|
|
|
114
154
|
}
|
|
115
155
|
catch (error) {
|
|
116
156
|
const err = error;
|
|
157
|
+
const causeCode = err?.cause?.code;
|
|
117
158
|
return {
|
|
118
159
|
data: {},
|
|
160
|
+
debug: {
|
|
161
|
+
...debug,
|
|
162
|
+
...(causeCode ? { causeCode } : {}),
|
|
163
|
+
},
|
|
119
164
|
error: err?.message || 'File upload failed.',
|
|
120
165
|
ok: false,
|
|
121
166
|
status: 0,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { ApiClient } from './api-client.js';
|
|
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
5
|
/**
|
|
@@ -34,12 +34,24 @@ export declare abstract class BaseCommand extends Command {
|
|
|
34
34
|
* Handles a failed API response by mapping HTTP status codes to
|
|
35
35
|
* user-friendly error messages and exiting the process.
|
|
36
36
|
*
|
|
37
|
-
* @param {{ status: number; error?: string }} response - The API response object with ok=false.
|
|
37
|
+
* @param {{ status: number; error?: string; debug?: ApiRequestDebugInfo }} response - The API response object with ok=false.
|
|
38
38
|
* Status 401 prompts re-authentication, 429 indicates rate limiting,
|
|
39
39
|
* all others display the server's error message or a generic fallback.
|
|
40
|
+
* When `debug` is present, extra lines list Full URL, Status, optional Response
|
|
41
|
+
* body summary, and optional Error Code (network failures).
|
|
40
42
|
*/
|
|
41
43
|
protected handleApiError(response: {
|
|
44
|
+
debug?: ApiRequestDebugInfo;
|
|
42
45
|
error?: string;
|
|
43
46
|
status: number;
|
|
44
47
|
}): never;
|
|
48
|
+
/**
|
|
49
|
+
* Builds the terminal error string for a failed API call, appending request
|
|
50
|
+
* metadata when {@link ApiRequestDebugInfo} is available.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} message - Primary error text (HTTP message or generic).
|
|
53
|
+
* @param {{ status: number; debug?: ApiRequestDebugInfo }} response - Failed API response.
|
|
54
|
+
* @returns {string} Message plus indented Request details for troubleshooting.
|
|
55
|
+
*/
|
|
56
|
+
private formatApiFailure;
|
|
45
57
|
}
|
package/dist/lib/base-command.js
CHANGED
|
@@ -11,7 +11,11 @@ import { loadYaml } from './yaml-config.js';
|
|
|
11
11
|
export class BaseCommand extends Command {
|
|
12
12
|
static baseFlags = {
|
|
13
13
|
json: Flags.boolean({ default: false, description: 'Output as JSON' }),
|
|
14
|
-
yes: Flags.boolean({
|
|
14
|
+
yes: Flags.boolean({
|
|
15
|
+
char: 'y',
|
|
16
|
+
default: false,
|
|
17
|
+
description: 'Skip confirmation prompts',
|
|
18
|
+
}),
|
|
15
19
|
};
|
|
16
20
|
apiClient;
|
|
17
21
|
/**
|
|
@@ -53,17 +57,48 @@ export class BaseCommand extends Command {
|
|
|
53
57
|
* Handles a failed API response by mapping HTTP status codes to
|
|
54
58
|
* user-friendly error messages and exiting the process.
|
|
55
59
|
*
|
|
56
|
-
* @param {{ status: number; error?: string }} response - The API response object with ok=false.
|
|
60
|
+
* @param {{ status: number; error?: string; debug?: ApiRequestDebugInfo }} response - The API response object with ok=false.
|
|
57
61
|
* Status 401 prompts re-authentication, 429 indicates rate limiting,
|
|
58
62
|
* all others display the server's error message or a generic fallback.
|
|
63
|
+
* When `debug` is present, extra lines list Full URL, Status, optional Response
|
|
64
|
+
* body summary, and optional Error Code (network failures).
|
|
59
65
|
*/
|
|
60
66
|
handleApiError(response) {
|
|
61
67
|
if (response.status === 401) {
|
|
62
|
-
this.error(`Invalid API key. Run ${chalk.cyan('mailmodo login')} to re-authenticate
|
|
68
|
+
this.error(this.formatApiFailure(`Invalid API key. Run ${chalk.cyan('mailmodo login')} to re-authenticate.`, response));
|
|
63
69
|
}
|
|
64
70
|
if (response.status === 429) {
|
|
65
|
-
this.error('Rate limit exceeded. Please try again later.');
|
|
71
|
+
this.error(this.formatApiFailure('Rate limit exceeded. Please try again later.', response));
|
|
66
72
|
}
|
|
67
|
-
this.error(response.error || 'An unexpected API error occurred.');
|
|
73
|
+
this.error(this.formatApiFailure(response.error || 'An unexpected API error occurred.', response));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Builds the terminal error string for a failed API call, appending request
|
|
77
|
+
* metadata when {@link ApiRequestDebugInfo} is available.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} message - Primary error text (HTTP message or generic).
|
|
80
|
+
* @param {{ status: number; debug?: ApiRequestDebugInfo }} response - Failed API response.
|
|
81
|
+
* @returns {string} Message plus indented Request details for troubleshooting.
|
|
82
|
+
*/
|
|
83
|
+
formatApiFailure(message, response) {
|
|
84
|
+
const { debug, status } = response;
|
|
85
|
+
if (!debug) {
|
|
86
|
+
return message;
|
|
87
|
+
}
|
|
88
|
+
return [
|
|
89
|
+
message,
|
|
90
|
+
'',
|
|
91
|
+
chalk.dim('Request:'),
|
|
92
|
+
chalk.dim(` URL: ${debug.fullUrl}`),
|
|
93
|
+
status > 0
|
|
94
|
+
? chalk.dim(` Status: ${String(status)}`)
|
|
95
|
+
: chalk.dim(' Status: (No HTTP response — network or client error)'),
|
|
96
|
+
...(debug.responseSummary
|
|
97
|
+
? [chalk.dim(` Response: ${debug.responseSummary}`)]
|
|
98
|
+
: []),
|
|
99
|
+
...(debug.causeCode
|
|
100
|
+
? [chalk.dim(` Error Code: ${debug.causeCode}`)]
|
|
101
|
+
: []),
|
|
102
|
+
].join('\n');
|
|
68
103
|
}
|
|
69
104
|
}
|
package/dist/lib/config.js
CHANGED
|
@@ -13,6 +13,7 @@ const CONFIG_FILE = join(CONFIG_DIR, 'config');
|
|
|
13
13
|
* or null if the config file does not exist or is corrupted.
|
|
14
14
|
*/
|
|
15
15
|
export async function loadConfig() {
|
|
16
|
+
console.log('Loading config from', CONFIG_FILE);
|
|
16
17
|
if (!existsSync(CONFIG_FILE))
|
|
17
18
|
return null;
|
|
18
19
|
try {
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
export declare const API_BASE_URL: string;
|
|
2
2
|
export declare const API_ENDPOINTS: Readonly<{
|
|
3
|
-
ANALYTICS: "/analytics";
|
|
4
|
-
ANALYZE: "/analyze";
|
|
5
|
-
ASSETS_LOGO: "/assets/logo";
|
|
6
|
-
AUTH_VALIDATE: "/auth/validate";
|
|
7
|
-
BILLING_CAP: "/billing/cap";
|
|
8
|
-
BILLING_STATUS: "/billing/status";
|
|
9
|
-
CONTACTS: "/contacts";
|
|
10
|
-
CONTACTS_EXPORT: "/contacts/export";
|
|
11
|
-
DOMAIN: "/domain";
|
|
12
|
-
DOMAIN_STATUS: "/domain/status";
|
|
13
|
-
DOMAIN_VERIFY: "/domain/verify";
|
|
14
|
-
EDIT: "/edit";
|
|
15
|
-
EVENTS: "/events";
|
|
16
|
-
GENERATE: "/generate";
|
|
17
|
-
LOGS: "/logs";
|
|
18
|
-
PREVIEW: "/preview";
|
|
19
|
-
SEQUENCES: "/sequences";
|
|
3
|
+
ANALYTICS: "/api/analytics";
|
|
4
|
+
ANALYZE: "/api/analyze";
|
|
5
|
+
ASSETS_LOGO: "/api/assets/logo";
|
|
6
|
+
AUTH_VALIDATE: "/api/auth/validate";
|
|
7
|
+
BILLING_CAP: "/api/billing/cap";
|
|
8
|
+
BILLING_STATUS: "/api/billing/status";
|
|
9
|
+
CONTACTS: "/api/contacts";
|
|
10
|
+
CONTACTS_EXPORT: "/api/contacts/export";
|
|
11
|
+
DOMAIN: "/api/domain";
|
|
12
|
+
DOMAIN_STATUS: "/api/domain/status";
|
|
13
|
+
DOMAIN_VERIFY: "/api/domain/verify";
|
|
14
|
+
EDIT: "/api/edit";
|
|
15
|
+
EVENTS: "/api/events";
|
|
16
|
+
GENERATE: "/api/generate";
|
|
17
|
+
LOGS: "/api/logs";
|
|
18
|
+
PREVIEW: "/api/preview";
|
|
19
|
+
SEQUENCES: "/api/sequences";
|
|
20
20
|
}>;
|
|
21
21
|
export declare const SIGNUP_URL = "https://mailmodo.com/cli";
|
|
22
22
|
export declare const DOCS_URL = "https://mailmodo.com/docs/cli";
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
/** Set by `bin/dev.js` when running the CLI locally (tsx bootstrap). */
|
|
2
|
+
const DEV_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
|
|
3
|
+
const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
|
|
4
|
+
export const API_BASE_URL = process.env.MAILMODO_DEV_TSX
|
|
5
|
+
? DEV_API_BASE_URL
|
|
6
|
+
: PRODUCTION_API_BASE_URL;
|
|
2
7
|
export const API_ENDPOINTS = Object.freeze({
|
|
3
|
-
ANALYTICS: '/analytics',
|
|
4
|
-
ANALYZE: '/analyze',
|
|
5
|
-
ASSETS_LOGO: '/assets/logo',
|
|
6
|
-
AUTH_VALIDATE: '/auth/validate',
|
|
7
|
-
BILLING_CAP: '/billing/cap',
|
|
8
|
-
BILLING_STATUS: '/billing/status',
|
|
9
|
-
CONTACTS: '/contacts',
|
|
10
|
-
CONTACTS_EXPORT: '/contacts/export',
|
|
11
|
-
DOMAIN: '/domain',
|
|
12
|
-
DOMAIN_STATUS: '/domain/status',
|
|
13
|
-
DOMAIN_VERIFY: '/domain/verify',
|
|
14
|
-
EDIT: '/edit',
|
|
15
|
-
EVENTS: '/events',
|
|
16
|
-
GENERATE: '/generate',
|
|
17
|
-
LOGS: '/logs',
|
|
18
|
-
PREVIEW: '/preview',
|
|
19
|
-
SEQUENCES: '/sequences',
|
|
8
|
+
ANALYTICS: '/api/analytics',
|
|
9
|
+
ANALYZE: '/api/analyze',
|
|
10
|
+
ASSETS_LOGO: '/api/assets/logo',
|
|
11
|
+
AUTH_VALIDATE: '/api/auth/validate',
|
|
12
|
+
BILLING_CAP: '/api/billing/cap',
|
|
13
|
+
BILLING_STATUS: '/api/billing/status',
|
|
14
|
+
CONTACTS: '/api/contacts',
|
|
15
|
+
CONTACTS_EXPORT: '/api/contacts/export',
|
|
16
|
+
DOMAIN: '/api/domain',
|
|
17
|
+
DOMAIN_STATUS: '/api/domain/status',
|
|
18
|
+
DOMAIN_VERIFY: '/api/domain/verify',
|
|
19
|
+
EDIT: '/api/edit',
|
|
20
|
+
EVENTS: '/api/events',
|
|
21
|
+
GENERATE: '/api/generate',
|
|
22
|
+
LOGS: '/api/logs',
|
|
23
|
+
PREVIEW: '/api/preview',
|
|
24
|
+
SEQUENCES: '/api/sequences',
|
|
20
25
|
});
|
|
21
26
|
export const SIGNUP_URL = 'https://mailmodo.com/cli';
|
|
22
27
|
export const DOCS_URL = 'https://mailmodo.com/docs/cli';
|
package/dist/lib/yaml-config.js
CHANGED
|
@@ -31,7 +31,11 @@ export async function loadYaml(cwd) {
|
|
|
31
31
|
*/
|
|
32
32
|
export async function saveYaml(config, cwd) {
|
|
33
33
|
const filePath = join(cwd || process.cwd(), YAML_FILE);
|
|
34
|
-
const content = dump(config, {
|
|
34
|
+
const content = dump(config, {
|
|
35
|
+
lineWidth: -1,
|
|
36
|
+
noRefs: true,
|
|
37
|
+
quotingType: '"',
|
|
38
|
+
});
|
|
35
39
|
await writeFile(filePath, content);
|
|
36
40
|
}
|
|
37
41
|
/**
|
package/oclif.manifest.json
CHANGED
|
@@ -205,6 +205,45 @@
|
|
|
205
205
|
"index.js"
|
|
206
206
|
]
|
|
207
207
|
},
|
|
208
|
+
"emails": {
|
|
209
|
+
"aliases": [],
|
|
210
|
+
"args": {},
|
|
211
|
+
"description": "List and view configured email sequences",
|
|
212
|
+
"examples": [
|
|
213
|
+
"<%= config.bin %> emails",
|
|
214
|
+
"<%= config.bin %> emails --json"
|
|
215
|
+
],
|
|
216
|
+
"flags": {
|
|
217
|
+
"json": {
|
|
218
|
+
"description": "Output as JSON",
|
|
219
|
+
"name": "json",
|
|
220
|
+
"allowNo": false,
|
|
221
|
+
"type": "boolean"
|
|
222
|
+
},
|
|
223
|
+
"yes": {
|
|
224
|
+
"char": "y",
|
|
225
|
+
"description": "Skip confirmation prompts",
|
|
226
|
+
"name": "yes",
|
|
227
|
+
"allowNo": false,
|
|
228
|
+
"type": "boolean"
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"hasDynamicHelp": false,
|
|
232
|
+
"hiddenAliases": [],
|
|
233
|
+
"id": "emails",
|
|
234
|
+
"pluginAlias": "@mailmodo/cli",
|
|
235
|
+
"pluginName": "@mailmodo/cli",
|
|
236
|
+
"pluginType": "core",
|
|
237
|
+
"strict": true,
|
|
238
|
+
"enableJsonFlag": false,
|
|
239
|
+
"isESM": true,
|
|
240
|
+
"relativePath": [
|
|
241
|
+
"dist",
|
|
242
|
+
"commands",
|
|
243
|
+
"emails",
|
|
244
|
+
"index.js"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
208
247
|
"edit": {
|
|
209
248
|
"aliases": [],
|
|
210
249
|
"args": {
|
|
@@ -257,13 +296,13 @@
|
|
|
257
296
|
"index.js"
|
|
258
297
|
]
|
|
259
298
|
},
|
|
260
|
-
"
|
|
299
|
+
"init": {
|
|
261
300
|
"aliases": [],
|
|
262
301
|
"args": {},
|
|
263
|
-
"description": "
|
|
302
|
+
"description": "Analyze your product and generate email sequences",
|
|
264
303
|
"examples": [
|
|
265
|
-
"<%= config.bin %>
|
|
266
|
-
"<%= config.bin %>
|
|
304
|
+
"<%= config.bin %> init",
|
|
305
|
+
"<%= config.bin %> init --url https://myapp.com --yes"
|
|
267
306
|
],
|
|
268
307
|
"flags": {
|
|
269
308
|
"json": {
|
|
@@ -278,11 +317,18 @@
|
|
|
278
317
|
"name": "yes",
|
|
279
318
|
"allowNo": false,
|
|
280
319
|
"type": "boolean"
|
|
320
|
+
},
|
|
321
|
+
"url": {
|
|
322
|
+
"description": "Product URL to analyze",
|
|
323
|
+
"name": "url",
|
|
324
|
+
"hasDynamicHelp": false,
|
|
325
|
+
"multiple": false,
|
|
326
|
+
"type": "option"
|
|
281
327
|
}
|
|
282
328
|
},
|
|
283
329
|
"hasDynamicHelp": false,
|
|
284
330
|
"hiddenAliases": [],
|
|
285
|
-
"id": "
|
|
331
|
+
"id": "init",
|
|
286
332
|
"pluginAlias": "@mailmodo/cli",
|
|
287
333
|
"pluginName": "@mailmodo/cli",
|
|
288
334
|
"pluginType": "core",
|
|
@@ -292,17 +338,19 @@
|
|
|
292
338
|
"relativePath": [
|
|
293
339
|
"dist",
|
|
294
340
|
"commands",
|
|
295
|
-
"
|
|
341
|
+
"init",
|
|
296
342
|
"index.js"
|
|
297
343
|
]
|
|
298
344
|
},
|
|
299
|
-
"
|
|
345
|
+
"logs": {
|
|
300
346
|
"aliases": [],
|
|
301
347
|
"args": {},
|
|
302
|
-
"description": "
|
|
348
|
+
"description": "View email send logs and delivery events",
|
|
303
349
|
"examples": [
|
|
304
|
-
"<%= config.bin %>
|
|
305
|
-
"<%= config.bin %>
|
|
350
|
+
"<%= config.bin %> logs",
|
|
351
|
+
"<%= config.bin %> logs --email sarah@example.com",
|
|
352
|
+
"<%= config.bin %> logs --failed",
|
|
353
|
+
"<%= config.bin %> logs --json"
|
|
306
354
|
],
|
|
307
355
|
"flags": {
|
|
308
356
|
"json": {
|
|
@@ -318,17 +366,23 @@
|
|
|
318
366
|
"allowNo": false,
|
|
319
367
|
"type": "boolean"
|
|
320
368
|
},
|
|
321
|
-
"
|
|
322
|
-
"description": "
|
|
323
|
-
"name": "
|
|
369
|
+
"email": {
|
|
370
|
+
"description": "Filter logs by contact email",
|
|
371
|
+
"name": "email",
|
|
324
372
|
"hasDynamicHelp": false,
|
|
325
373
|
"multiple": false,
|
|
326
374
|
"type": "option"
|
|
375
|
+
},
|
|
376
|
+
"failed": {
|
|
377
|
+
"description": "Show only failed/bounced events",
|
|
378
|
+
"name": "failed",
|
|
379
|
+
"allowNo": false,
|
|
380
|
+
"type": "boolean"
|
|
327
381
|
}
|
|
328
382
|
},
|
|
329
383
|
"hasDynamicHelp": false,
|
|
330
384
|
"hiddenAliases": [],
|
|
331
|
-
"id": "
|
|
385
|
+
"id": "logs",
|
|
332
386
|
"pluginAlias": "@mailmodo/cli",
|
|
333
387
|
"pluginName": "@mailmodo/cli",
|
|
334
388
|
"pluginType": "core",
|
|
@@ -338,7 +392,7 @@
|
|
|
338
392
|
"relativePath": [
|
|
339
393
|
"dist",
|
|
340
394
|
"commands",
|
|
341
|
-
"
|
|
395
|
+
"logs",
|
|
342
396
|
"index.js"
|
|
343
397
|
]
|
|
344
398
|
},
|
|
@@ -381,15 +435,14 @@
|
|
|
381
435
|
"index.js"
|
|
382
436
|
]
|
|
383
437
|
},
|
|
384
|
-
"
|
|
438
|
+
"settings": {
|
|
385
439
|
"aliases": [],
|
|
386
440
|
"args": {},
|
|
387
|
-
"description": "View
|
|
441
|
+
"description": "View and update project settings",
|
|
388
442
|
"examples": [
|
|
389
|
-
"<%= config.bin %>
|
|
390
|
-
"<%= config.bin %>
|
|
391
|
-
"<%= config.bin %>
|
|
392
|
-
"<%= config.bin %> logs --json"
|
|
443
|
+
"<%= config.bin %> settings",
|
|
444
|
+
"<%= config.bin %> settings --set brand_color=#0F3460",
|
|
445
|
+
"<%= config.bin %> settings --json"
|
|
393
446
|
],
|
|
394
447
|
"flags": {
|
|
395
448
|
"json": {
|
|
@@ -405,23 +458,17 @@
|
|
|
405
458
|
"allowNo": false,
|
|
406
459
|
"type": "boolean"
|
|
407
460
|
},
|
|
408
|
-
"
|
|
409
|
-
"description": "
|
|
410
|
-
"name": "
|
|
461
|
+
"set": {
|
|
462
|
+
"description": "Set a setting (format: key=value)",
|
|
463
|
+
"name": "set",
|
|
411
464
|
"hasDynamicHelp": false,
|
|
412
465
|
"multiple": false,
|
|
413
466
|
"type": "option"
|
|
414
|
-
},
|
|
415
|
-
"failed": {
|
|
416
|
-
"description": "Show only failed/bounced events",
|
|
417
|
-
"name": "failed",
|
|
418
|
-
"allowNo": false,
|
|
419
|
-
"type": "boolean"
|
|
420
467
|
}
|
|
421
468
|
},
|
|
422
469
|
"hasDynamicHelp": false,
|
|
423
470
|
"hiddenAliases": [],
|
|
424
|
-
"id": "
|
|
471
|
+
"id": "settings",
|
|
425
472
|
"pluginAlias": "@mailmodo/cli",
|
|
426
473
|
"pluginName": "@mailmodo/cli",
|
|
427
474
|
"pluginType": "core",
|
|
@@ -431,7 +478,7 @@
|
|
|
431
478
|
"relativePath": [
|
|
432
479
|
"dist",
|
|
433
480
|
"commands",
|
|
434
|
-
"
|
|
481
|
+
"settings",
|
|
435
482
|
"index.js"
|
|
436
483
|
]
|
|
437
484
|
},
|
|
@@ -493,53 +540,6 @@
|
|
|
493
540
|
"index.js"
|
|
494
541
|
]
|
|
495
542
|
},
|
|
496
|
-
"settings": {
|
|
497
|
-
"aliases": [],
|
|
498
|
-
"args": {},
|
|
499
|
-
"description": "View and update project settings",
|
|
500
|
-
"examples": [
|
|
501
|
-
"<%= config.bin %> settings",
|
|
502
|
-
"<%= config.bin %> settings --set brand_color=#0F3460",
|
|
503
|
-
"<%= config.bin %> settings --json"
|
|
504
|
-
],
|
|
505
|
-
"flags": {
|
|
506
|
-
"json": {
|
|
507
|
-
"description": "Output as JSON",
|
|
508
|
-
"name": "json",
|
|
509
|
-
"allowNo": false,
|
|
510
|
-
"type": "boolean"
|
|
511
|
-
},
|
|
512
|
-
"yes": {
|
|
513
|
-
"char": "y",
|
|
514
|
-
"description": "Skip confirmation prompts",
|
|
515
|
-
"name": "yes",
|
|
516
|
-
"allowNo": false,
|
|
517
|
-
"type": "boolean"
|
|
518
|
-
},
|
|
519
|
-
"set": {
|
|
520
|
-
"description": "Set a setting (format: key=value)",
|
|
521
|
-
"name": "set",
|
|
522
|
-
"hasDynamicHelp": false,
|
|
523
|
-
"multiple": false,
|
|
524
|
-
"type": "option"
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
"hasDynamicHelp": false,
|
|
528
|
-
"hiddenAliases": [],
|
|
529
|
-
"id": "settings",
|
|
530
|
-
"pluginAlias": "@mailmodo/cli",
|
|
531
|
-
"pluginName": "@mailmodo/cli",
|
|
532
|
-
"pluginType": "core",
|
|
533
|
-
"strict": true,
|
|
534
|
-
"enableJsonFlag": false,
|
|
535
|
-
"isESM": true,
|
|
536
|
-
"relativePath": [
|
|
537
|
-
"dist",
|
|
538
|
-
"commands",
|
|
539
|
-
"settings",
|
|
540
|
-
"index.js"
|
|
541
|
-
]
|
|
542
|
-
},
|
|
543
543
|
"status": {
|
|
544
544
|
"aliases": [],
|
|
545
545
|
"args": {},
|
|
@@ -580,5 +580,5 @@
|
|
|
580
580
|
]
|
|
581
581
|
}
|
|
582
582
|
},
|
|
583
|
-
"version": "0.0.
|
|
583
|
+
"version": "0.0.8-beta.pr10.14"
|
|
584
584
|
}
|