emailr-cli 1.5.1 → 1.5.2
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/index.js +61 -2215
- package/package.json +11 -10
package/dist/index.js
CHANGED
|
@@ -1,398 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {Command}from'commander';import {Emailr}from'emailr';import S from'fs';import W from'os';import N from'path';import ie from'cli-table3';import h from'chalk';import Be from'http';import {URL}from'url';import ct from'crypto';import {spawn,execSync,exec}from'child_process';var Le=[N.join(W.homedir(),".emailrrc"),N.join(W.homedir(),".config","emailr","config.json")];function m(){if(process.env.EMAILR_API_KEY)return {apiKey:process.env.EMAILR_API_KEY,baseUrl:process.env.EMAILR_BASE_URL,format:process.env.EMAILR_FORMAT||"table"};for(let n of Le)if(S.existsSync(n))try{let t=S.readFileSync(n,"utf-8"),e=JSON.parse(t);if(e.apiKey)return {apiKey:e.apiKey,baseUrl:e.baseUrl,format:e.format||"table"}}catch{}throw new Error(`No API key configured.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
Set the EMAILR_API_KEY environment variable:
|
|
5
|
+
export EMAILR_API_KEY=your-api-key
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { Emailr } from "emailr";
|
|
7
|
+
Or create a config file at ~/.emailrrc:
|
|
8
|
+
{ "apiKey": "your-api-key" }
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import path from "path";
|
|
14
|
-
var CONFIG_PATHS = [
|
|
15
|
-
path.join(os.homedir(), ".emailrrc"),
|
|
16
|
-
path.join(os.homedir(), ".config", "emailr", "config.json")
|
|
17
|
-
];
|
|
18
|
-
function loadConfig() {
|
|
19
|
-
if (process.env.EMAILR_API_KEY) {
|
|
20
|
-
return {
|
|
21
|
-
apiKey: process.env.EMAILR_API_KEY,
|
|
22
|
-
baseUrl: process.env.EMAILR_BASE_URL,
|
|
23
|
-
format: process.env.EMAILR_FORMAT || "table"
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
for (const configPath of CONFIG_PATHS) {
|
|
27
|
-
if (fs.existsSync(configPath)) {
|
|
28
|
-
try {
|
|
29
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
30
|
-
const config = JSON.parse(content);
|
|
31
|
-
if (config.apiKey) {
|
|
32
|
-
return {
|
|
33
|
-
apiKey: config.apiKey,
|
|
34
|
-
baseUrl: config.baseUrl,
|
|
35
|
-
format: config.format || "table"
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
} catch {
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
throw new Error(
|
|
43
|
-
'No API key configured.\n\nSet the EMAILR_API_KEY environment variable:\n export EMAILR_API_KEY=your-api-key\n\nOr create a config file at ~/.emailrrc:\n { "apiKey": "your-api-key" }\n\nOr run: emailr config set api-key <your-api-key>'
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
function getConfigPath() {
|
|
47
|
-
const configDir = path.join(os.homedir(), ".config", "emailr");
|
|
48
|
-
return path.join(configDir, "config.json");
|
|
49
|
-
}
|
|
50
|
-
function saveConfig(config) {
|
|
51
|
-
const configPath = getConfigPath();
|
|
52
|
-
const configDir = path.dirname(configPath);
|
|
53
|
-
if (!fs.existsSync(configDir)) {
|
|
54
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
55
|
-
}
|
|
56
|
-
let existingConfig = {};
|
|
57
|
-
if (fs.existsSync(configPath)) {
|
|
58
|
-
try {
|
|
59
|
-
existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
60
|
-
} catch {
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const newConfig = { ...existingConfig, ...config };
|
|
64
|
-
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n");
|
|
65
|
-
}
|
|
66
|
-
function getConfigValue(key) {
|
|
67
|
-
try {
|
|
68
|
-
const config = loadConfig();
|
|
69
|
-
return config[key]?.toString();
|
|
70
|
-
} catch {
|
|
71
|
-
return void 0;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// src/output.ts
|
|
76
|
-
import Table from "cli-table3";
|
|
77
|
-
import chalk from "chalk";
|
|
78
|
-
function output(data, format = "table") {
|
|
79
|
-
if (format === "json") {
|
|
80
|
-
console.log(JSON.stringify(data, null, 2));
|
|
81
|
-
} else {
|
|
82
|
-
printTable(data);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function printTable(data) {
|
|
86
|
-
if (Array.isArray(data)) {
|
|
87
|
-
printArrayTable(data);
|
|
88
|
-
} else if (typeof data === "object" && data !== null) {
|
|
89
|
-
printObjectTable(data);
|
|
90
|
-
} else {
|
|
91
|
-
console.log(data);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function printArrayTable(data) {
|
|
95
|
-
if (data.length === 0) {
|
|
96
|
-
console.log(chalk.gray("No results"));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const firstItem = data[0];
|
|
100
|
-
if (typeof firstItem !== "object" || firstItem === null) {
|
|
101
|
-
data.forEach((item) => console.log(item));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const keys = Object.keys(firstItem);
|
|
105
|
-
const table = new Table({
|
|
106
|
-
head: keys.map((k) => chalk.cyan(k)),
|
|
107
|
-
style: { head: [], border: [] }
|
|
108
|
-
});
|
|
109
|
-
for (const item of data) {
|
|
110
|
-
const row = keys.map((key) => {
|
|
111
|
-
const value = item[key];
|
|
112
|
-
return formatValue(value);
|
|
113
|
-
});
|
|
114
|
-
table.push(row);
|
|
115
|
-
}
|
|
116
|
-
console.log(table.toString());
|
|
117
|
-
}
|
|
118
|
-
function printObjectTable(data) {
|
|
119
|
-
const table = new Table({
|
|
120
|
-
style: { head: [], border: [] }
|
|
121
|
-
});
|
|
122
|
-
for (const [key, value] of Object.entries(data)) {
|
|
123
|
-
table.push([chalk.cyan(key), formatValue(value)]);
|
|
124
|
-
}
|
|
125
|
-
console.log(table.toString());
|
|
126
|
-
}
|
|
127
|
-
function formatValue(value) {
|
|
128
|
-
if (value === null || value === void 0) {
|
|
129
|
-
return chalk.gray("-");
|
|
130
|
-
}
|
|
131
|
-
if (typeof value === "boolean") {
|
|
132
|
-
return value ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
133
|
-
}
|
|
134
|
-
if (typeof value === "object") {
|
|
135
|
-
return JSON.stringify(value);
|
|
136
|
-
}
|
|
137
|
-
return String(value);
|
|
138
|
-
}
|
|
139
|
-
function success(message) {
|
|
140
|
-
console.log(chalk.green("\u2713"), message);
|
|
141
|
-
}
|
|
142
|
-
function error(message) {
|
|
143
|
-
console.error(chalk.red("\u2717"), message);
|
|
144
|
-
}
|
|
145
|
-
function warn(message) {
|
|
146
|
-
console.warn(chalk.yellow("\u26A0"), message);
|
|
147
|
-
}
|
|
148
|
-
function info(message) {
|
|
149
|
-
console.log(chalk.blue("\u2139"), message);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// src/commands/send.ts
|
|
153
|
-
function createSendCommand() {
|
|
154
|
-
const cmd = new Command("send").description("Send an email").requiredOption("--to <email>", "Recipient email address (comma-separated for multiple)").option("--from <email>", "Sender email address").option("--subject <subject>", "Email subject").option("--html <html>", "HTML content").option("--text <text>", "Plain text content").option("--template <id>", "Template ID to use").option("--template-data <json>", "Template data as JSON").option("--cc <emails>", "CC recipients (comma-separated)").option("--bcc <emails>", "BCC recipients (comma-separated)").option("--reply-to <email>", "Reply-to email address").option("--schedule <datetime>", "Schedule send time (ISO 8601)").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
155
|
-
try {
|
|
156
|
-
const config = loadConfig();
|
|
157
|
-
const client = new Emailr({
|
|
158
|
-
apiKey: config.apiKey,
|
|
159
|
-
baseUrl: config.baseUrl
|
|
160
|
-
});
|
|
161
|
-
const to = options.to.split(",").map((e) => e.trim());
|
|
162
|
-
const request = {
|
|
163
|
-
to: to.length === 1 ? to[0] : to
|
|
164
|
-
};
|
|
165
|
-
if (options.from) request.from = options.from;
|
|
166
|
-
if (options.subject) request.subject = options.subject;
|
|
167
|
-
if (options.html) request.html = options.html;
|
|
168
|
-
if (options.text) request.text = options.text;
|
|
169
|
-
if (options.template) request.template_id = options.template;
|
|
170
|
-
if (options.templateData) {
|
|
171
|
-
try {
|
|
172
|
-
request.template_data = JSON.parse(options.templateData);
|
|
173
|
-
} catch {
|
|
174
|
-
error("Invalid JSON for --template-data");
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (options.cc) {
|
|
179
|
-
const cc = options.cc.split(",").map((e) => e.trim());
|
|
180
|
-
request.cc = cc.length === 1 ? cc[0] : cc;
|
|
181
|
-
}
|
|
182
|
-
if (options.bcc) {
|
|
183
|
-
const bcc = options.bcc.split(",").map((e) => e.trim());
|
|
184
|
-
request.bcc = bcc.length === 1 ? bcc[0] : bcc;
|
|
185
|
-
}
|
|
186
|
-
if (options.replyTo) request.reply_to_email = options.replyTo;
|
|
187
|
-
if (options.schedule) request.scheduled_at = options.schedule;
|
|
188
|
-
const result = await client.emails.send(request);
|
|
189
|
-
if (options.format === "json") {
|
|
190
|
-
output(result, "json");
|
|
191
|
-
} else {
|
|
192
|
-
success(`Email sent successfully!`);
|
|
193
|
-
output({
|
|
194
|
-
"Message ID": result.message_id,
|
|
195
|
-
Recipients: result.recipients,
|
|
196
|
-
Status: result.status,
|
|
197
|
-
...result.scheduled_at && { "Scheduled At": result.scheduled_at }
|
|
198
|
-
}, "table");
|
|
199
|
-
}
|
|
200
|
-
} catch (err) {
|
|
201
|
-
error(err instanceof Error ? err.message : "Failed to send email");
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
return cmd;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/commands/contacts.ts
|
|
209
|
-
import { Command as Command2 } from "commander";
|
|
210
|
-
import { Emailr as Emailr2 } from "emailr";
|
|
211
|
-
function createContactsCommand() {
|
|
212
|
-
const cmd = new Command2("contacts").description("Manage contacts");
|
|
213
|
-
cmd.command("list").description("List all contacts").option("--limit <number>", "Number of contacts to return", "20").option("--offset <number>", "Offset for pagination", "0").option("--subscribed", "Only show subscribed contacts").option("--unsubscribed", "Only show unsubscribed contacts").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
214
|
-
try {
|
|
215
|
-
const config = loadConfig();
|
|
216
|
-
const client = new Emailr2({
|
|
217
|
-
apiKey: config.apiKey,
|
|
218
|
-
baseUrl: config.baseUrl
|
|
219
|
-
});
|
|
220
|
-
const params = {
|
|
221
|
-
limit: parseInt(options.limit, 10),
|
|
222
|
-
offset: parseInt(options.offset, 10)
|
|
223
|
-
};
|
|
224
|
-
if (options.subscribed) params.subscribed = true;
|
|
225
|
-
if (options.unsubscribed) params.subscribed = false;
|
|
226
|
-
const result = await client.contacts.list(params);
|
|
227
|
-
if (options.format === "json") {
|
|
228
|
-
output(result, "json");
|
|
229
|
-
} else {
|
|
230
|
-
const contacts = result.contacts.map((c) => ({
|
|
231
|
-
ID: c.id,
|
|
232
|
-
Email: c.email,
|
|
233
|
-
Name: [c.first_name, c.last_name].filter(Boolean).join(" ") || "-",
|
|
234
|
-
Subscribed: c.subscribed,
|
|
235
|
-
Created: c.created_at
|
|
236
|
-
}));
|
|
237
|
-
output(contacts, "table");
|
|
238
|
-
console.log(`
|
|
239
|
-
Total: ${result.total}`);
|
|
240
|
-
}
|
|
241
|
-
} catch (err) {
|
|
242
|
-
error(err instanceof Error ? err.message : "Failed to list contacts");
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
cmd.command("get <id>").description("Get a contact by ID").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
247
|
-
try {
|
|
248
|
-
const config = loadConfig();
|
|
249
|
-
const client = new Emailr2({
|
|
250
|
-
apiKey: config.apiKey,
|
|
251
|
-
baseUrl: config.baseUrl
|
|
252
|
-
});
|
|
253
|
-
const contact = await client.contacts.get(id);
|
|
254
|
-
output(contact, options.format);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
error(err instanceof Error ? err.message : "Failed to get contact");
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
cmd.command("create").description("Create a new contact").requiredOption("--email <email>", "Contact email address").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--subscribed", "Mark as subscribed (default: true)").option("--metadata <json>", "Metadata as JSON").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
261
|
-
try {
|
|
262
|
-
const config = loadConfig();
|
|
263
|
-
const client = new Emailr2({
|
|
264
|
-
apiKey: config.apiKey,
|
|
265
|
-
baseUrl: config.baseUrl
|
|
266
|
-
});
|
|
267
|
-
const request = {
|
|
268
|
-
email: options.email
|
|
269
|
-
};
|
|
270
|
-
if (options.firstName) request.first_name = options.firstName;
|
|
271
|
-
if (options.lastName) request.last_name = options.lastName;
|
|
272
|
-
if (options.subscribed !== void 0) request.subscribed = options.subscribed;
|
|
273
|
-
if (options.metadata) {
|
|
274
|
-
try {
|
|
275
|
-
request.metadata = JSON.parse(options.metadata);
|
|
276
|
-
} catch {
|
|
277
|
-
error("Invalid JSON for --metadata");
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
const contact = await client.contacts.create(request);
|
|
282
|
-
if (options.format === "json") {
|
|
283
|
-
output(contact, "json");
|
|
284
|
-
} else {
|
|
285
|
-
success(`Contact created: ${contact.id}`);
|
|
286
|
-
output(contact, "table");
|
|
287
|
-
}
|
|
288
|
-
} catch (err) {
|
|
289
|
-
error(err instanceof Error ? err.message : "Failed to create contact");
|
|
290
|
-
process.exit(1);
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
cmd.command("update <id>").description("Update a contact").option("--email <email>", "New email address").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--subscribed", "Mark as subscribed").option("--unsubscribed", "Mark as unsubscribed").option("--metadata <json>", "Metadata as JSON").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
294
|
-
try {
|
|
295
|
-
const config = loadConfig();
|
|
296
|
-
const client = new Emailr2({
|
|
297
|
-
apiKey: config.apiKey,
|
|
298
|
-
baseUrl: config.baseUrl
|
|
299
|
-
});
|
|
300
|
-
const request = {};
|
|
301
|
-
if (options.email) request.email = options.email;
|
|
302
|
-
if (options.firstName) request.first_name = options.firstName;
|
|
303
|
-
if (options.lastName) request.last_name = options.lastName;
|
|
304
|
-
if (options.subscribed) request.subscribed = true;
|
|
305
|
-
if (options.unsubscribed) request.subscribed = false;
|
|
306
|
-
if (options.metadata) {
|
|
307
|
-
try {
|
|
308
|
-
request.metadata = JSON.parse(options.metadata);
|
|
309
|
-
} catch {
|
|
310
|
-
error("Invalid JSON for --metadata");
|
|
311
|
-
process.exit(1);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
const contact = await client.contacts.update(id, request);
|
|
315
|
-
if (options.format === "json") {
|
|
316
|
-
output(contact, "json");
|
|
317
|
-
} else {
|
|
318
|
-
success(`Contact updated: ${contact.id}`);
|
|
319
|
-
output(contact, "table");
|
|
320
|
-
}
|
|
321
|
-
} catch (err) {
|
|
322
|
-
error(err instanceof Error ? err.message : "Failed to update contact");
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
cmd.command("delete <id>").description("Delete a contact").action(async (id) => {
|
|
327
|
-
try {
|
|
328
|
-
const config = loadConfig();
|
|
329
|
-
const client = new Emailr2({
|
|
330
|
-
apiKey: config.apiKey,
|
|
331
|
-
baseUrl: config.baseUrl
|
|
332
|
-
});
|
|
333
|
-
await client.contacts.delete(id);
|
|
334
|
-
success(`Contact deleted: ${id}`);
|
|
335
|
-
} catch (err) {
|
|
336
|
-
error(err instanceof Error ? err.message : "Failed to delete contact");
|
|
337
|
-
process.exit(1);
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
return cmd;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// src/commands/templates.ts
|
|
344
|
-
import { Command as Command3 } from "commander";
|
|
345
|
-
import { Emailr as Emailr3 } from "emailr";
|
|
346
|
-
import fs4 from "fs";
|
|
347
|
-
import path4 from "path";
|
|
348
|
-
|
|
349
|
-
// src/server/template-store.ts
|
|
350
|
-
import fs2 from "fs";
|
|
351
|
-
import os2 from "os";
|
|
352
|
-
import path2 from "path";
|
|
353
|
-
function getTemplatesDir() {
|
|
354
|
-
return path2.join(os2.homedir(), ".config", "emailr", "templates");
|
|
355
|
-
}
|
|
356
|
-
function ensureTemplatesDir() {
|
|
357
|
-
const templatesDir = getTemplatesDir();
|
|
358
|
-
if (!fs2.existsSync(templatesDir)) {
|
|
359
|
-
fs2.mkdirSync(templatesDir, { recursive: true });
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function getTemplatePath(templateId) {
|
|
363
|
-
return path2.join(getTemplatesDir(), `${templateId}.html`);
|
|
364
|
-
}
|
|
365
|
-
function saveTemplate(templateId, htmlContent) {
|
|
366
|
-
ensureTemplatesDir();
|
|
367
|
-
const templatePath = getTemplatePath(templateId);
|
|
368
|
-
fs2.writeFileSync(templatePath, htmlContent, "utf-8");
|
|
369
|
-
}
|
|
370
|
-
function readTemplate(templateId) {
|
|
371
|
-
const templatePath = getTemplatePath(templateId);
|
|
372
|
-
if (!fs2.existsSync(templatePath)) {
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
return fs2.readFileSync(templatePath, "utf-8");
|
|
376
|
-
}
|
|
377
|
-
function templateExists(templateId) {
|
|
378
|
-
const templatePath = getTemplatePath(templateId);
|
|
379
|
-
return fs2.existsSync(templatePath);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// src/server/preview-server.ts
|
|
383
|
-
import http from "http";
|
|
384
|
-
var serverInstance = null;
|
|
385
|
-
var serverPort = null;
|
|
386
|
-
var serverRunning = false;
|
|
387
|
-
function escapeHtml(str) {
|
|
388
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
389
|
-
}
|
|
390
|
-
function isEmptyContent(content) {
|
|
391
|
-
return content === null || content.trim() === "";
|
|
392
|
-
}
|
|
393
|
-
function generateEmptyContentPlaceholder(templateId) {
|
|
394
|
-
const escapedId = escapeHtml(templateId);
|
|
395
|
-
return `<!DOCTYPE html>
|
|
10
|
+
Or run: emailr config set api-key <your-api-key>`)}function w(){let n=N.join(W.homedir(),".config","emailr");return N.join(n,"config.json")}function R(n){let t=w(),e=N.dirname(t);S.existsSync(e)||S.mkdirSync(e,{recursive:true});let o={};if(S.existsSync(t))try{o=JSON.parse(S.readFileSync(t,"utf-8"));}catch{}let i={...o,...n};S.writeFileSync(t,JSON.stringify(i,null,2)+`
|
|
11
|
+
`);}function re(n){try{return m()[n]?.toString()}catch{return}}function l(n,t="table"){t==="json"?console.log(JSON.stringify(n,null,2)):Ne(n);}function Ne(n){Array.isArray(n)?Re(n):typeof n=="object"&&n!==null?De(n):console.log(n);}function Re(n){if(n.length===0){console.log(h.gray("No results"));return}let t=n[0];if(typeof t!="object"||t===null){n.forEach(i=>console.log(i));return}let e=Object.keys(t),o=new ie({head:e.map(i=>h.cyan(i)),style:{head:[],border:[]}});for(let i of n){let a=e.map(r=>{let s=i[r];return se(s)});o.push(a);}console.log(o.toString());}function De(n){let t=new ie({style:{head:[],border:[]}});for(let[e,o]of Object.entries(n))t.push([h.cyan(e),se(o)]);console.log(t.toString());}function se(n){return n==null?h.gray("-"):typeof n=="boolean"?n?h.green("\u2713"):h.red("\u2717"):typeof n=="object"?JSON.stringify(n):String(n)}function d(n){console.log(h.green("\u2713"),n);}function c(n){console.error(h.red("\u2717"),n);}function v(n){console.warn(h.yellow("\u26A0"),n);}function p(n){console.log(h.blue("\u2139"),n);}function le(){return new Command("send").description("Send an email").requiredOption("--to <email>","Recipient email address (comma-separated for multiple)").option("--from <email>","Sender email address").option("--subject <subject>","Email subject").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--template <id>","Template ID to use").option("--template-data <json>","Template data as JSON").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--reply-to <email>","Reply-to email address").option("--schedule <datetime>","Schedule send time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i=t.to.split(",").map(s=>s.trim()),a={to:i.length===1?i[0]:i};if(t.from&&(a.from=t.from),t.subject&&(a.subject=t.subject),t.html&&(a.html=t.html),t.text&&(a.text=t.text),t.template&&(a.template_id=t.template),t.templateData)try{a.template_data=JSON.parse(t.templateData);}catch{c("Invalid JSON for --template-data"),process.exit(1);}if(t.cc){let s=t.cc.split(",").map(f=>f.trim());a.cc=s.length===1?s[0]:s;}if(t.bcc){let s=t.bcc.split(",").map(f=>f.trim());a.bcc=s.length===1?s[0]:s;}t.replyTo&&(a.reply_to_email=t.replyTo),t.schedule&&(a.scheduled_at=t.schedule);let r=await o.emails.send(a);t.format==="json"?l(r,"json"):(d("Email sent successfully!"),l({"Message ID":r.message_id,Recipients:r.recipients,Status:r.status,...r.scheduled_at&&{"Scheduled At":r.scheduled_at}},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to send email"),process.exit(1);}})}function ce(){let n=new Command("contacts").description("Manage contacts");return n.command("list").description("List all contacts").option("--limit <number>","Number of contacts to return","20").option("--offset <number>","Offset for pagination","0").option("--subscribed","Only show subscribed contacts").option("--unsubscribed","Only show unsubscribed contacts").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};t.subscribed&&(i.subscribed=!0),t.unsubscribed&&(i.subscribed=!1);let a=await o.contacts.list(i);if(t.format==="json")l(a,"json");else {let r=a.contacts.map(s=>({ID:s.id,Email:s.email,Name:[s.first_name,s.last_name].filter(Boolean).join(" ")||"-",Subscribed:s.subscribed,Created:s.created_at}));l(r,"table"),console.log(`
|
|
12
|
+
Total: ${a.total}`);}}catch(e){c(e instanceof Error?e.message:"Failed to list contacts"),process.exit(1);}}),n.command("get <id>").description("Get a contact by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).contacts.get(t);l(a,e.format);}catch(o){c(o instanceof Error?o.message:"Failed to get contact"),process.exit(1);}}),n.command("create").description("Create a new contact").requiredOption("--email <email>","Contact email address").option("--first-name <name>","First name").option("--last-name <name>","Last name").option("--subscribed","Mark as subscribed (default: true)").option("--metadata <json>","Metadata as JSON").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i={email:t.email};if(t.firstName&&(i.first_name=t.firstName),t.lastName&&(i.last_name=t.lastName),t.subscribed!==void 0&&(i.subscribed=t.subscribed),t.metadata)try{i.metadata=JSON.parse(t.metadata);}catch{c("Invalid JSON for --metadata"),process.exit(1);}let a=await o.contacts.create(i);t.format==="json"?l(a,"json"):(d(`Contact created: ${a.id}`),l(a,"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create contact"),process.exit(1);}}),n.command("update <id>").description("Update a contact").option("--email <email>","New email address").option("--first-name <name>","First name").option("--last-name <name>","Last name").option("--subscribed","Mark as subscribed").option("--unsubscribed","Mark as unsubscribed").option("--metadata <json>","Metadata as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.email&&(a.email=e.email),e.firstName&&(a.first_name=e.firstName),e.lastName&&(a.last_name=e.lastName),e.subscribed&&(a.subscribed=!0),e.unsubscribed&&(a.subscribed=!1),e.metadata)try{a.metadata=JSON.parse(e.metadata);}catch{c("Invalid JSON for --metadata"),process.exit(1);}let r=await i.contacts.update(t,a);e.format==="json"?l(r,"json"):(d(`Contact updated: ${r.id}`),l(r,"table"));}catch(o){c(o instanceof Error?o.message:"Failed to update contact"),process.exit(1);}}),n.command("delete <id>").description("Delete a contact").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(t),d(`Contact deleted: ${t}`);}catch(e){c(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),n}function de(){return N.join(W.homedir(),".config","emailr","templates")}function qe(){let n=de();S.existsSync(n)||S.mkdirSync(n,{recursive:true});}function G(n){return N.join(de(),`${n}.html`)}function J(n,t){qe();let e=G(n);S.writeFileSync(e,t,"utf-8");}function pe(n){let t=G(n);return S.existsSync(t)?S.readFileSync(t,"utf-8"):null}function fe(n){let t=G(n);return S.existsSync(t)}var b=null,C=null,$=false;function ge(n){return n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function We(n){return n===null||n.trim()===""}function Ge(n){return `<!DOCTYPE html>
|
|
396
13
|
<html lang="en">
|
|
397
14
|
<head>
|
|
398
15
|
<meta charset="UTF-8">
|
|
@@ -458,18 +75,14 @@ function generateEmptyContentPlaceholder(templateId) {
|
|
|
458
75
|
<div class="icon">\u{1F4ED}</div>
|
|
459
76
|
<h1>No Content Available</h1>
|
|
460
77
|
<p>This template exists but has no HTML content to display.</p>
|
|
461
|
-
<div class="template-id">${
|
|
78
|
+
<div class="template-id">${ge(n)}</div>
|
|
462
79
|
<div class="hint">
|
|
463
80
|
<p><strong>To add content:</strong></p>
|
|
464
81
|
<p>Update the template using the CLI with the <code>--html</code> option or provide HTML content when creating the template.</p>
|
|
465
82
|
</div>
|
|
466
83
|
</div>
|
|
467
84
|
</body>
|
|
468
|
-
</html
|
|
469
|
-
}
|
|
470
|
-
function generateNotFoundHtml(templateId) {
|
|
471
|
-
const escapedId = escapeHtml(templateId);
|
|
472
|
-
return `<!DOCTYPE html>
|
|
85
|
+
</html>`}function ue(n){return `<!DOCTYPE html>
|
|
473
86
|
<html lang="en">
|
|
474
87
|
<head>
|
|
475
88
|
<meta charset="UTF-8">
|
|
@@ -521,121 +134,11 @@ function generateNotFoundHtml(templateId) {
|
|
|
521
134
|
<div class="icon">404</div>
|
|
522
135
|
<h1>Template Not Found</h1>
|
|
523
136
|
<p>The requested template could not be found in local storage.</p>
|
|
524
|
-
<div class="template-id">${
|
|
137
|
+
<div class="template-id">${ge(n)}</div>
|
|
525
138
|
<p style="margin-top: 1rem;">Try creating or retrieving the template first using the CLI.</p>
|
|
526
139
|
</div>
|
|
527
140
|
</body>
|
|
528
|
-
</html
|
|
529
|
-
}
|
|
530
|
-
function parseTemplateIdFromPath(urlPath) {
|
|
531
|
-
const match = urlPath.match(/^\/preview\/([^/]+)$/);
|
|
532
|
-
return match ? match[1] : null;
|
|
533
|
-
}
|
|
534
|
-
function createRequestHandler() {
|
|
535
|
-
return (req, res) => {
|
|
536
|
-
if (req.method !== "GET") {
|
|
537
|
-
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
538
|
-
res.end("Method Not Allowed");
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
const urlPath = req.url || "/";
|
|
542
|
-
const templateId = parseTemplateIdFromPath(urlPath);
|
|
543
|
-
if (!templateId) {
|
|
544
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
545
|
-
res.end("Not Found");
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (!templateExists(templateId)) {
|
|
549
|
-
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
550
|
-
res.end(generateNotFoundHtml(templateId));
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
const htmlContent = readTemplate(templateId);
|
|
554
|
-
if (htmlContent === null) {
|
|
555
|
-
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
556
|
-
res.end(generateNotFoundHtml(templateId));
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
if (isEmptyContent(htmlContent)) {
|
|
560
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
561
|
-
res.end(generateEmptyContentPlaceholder(templateId));
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
565
|
-
res.end(htmlContent);
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
var previewServer = {
|
|
569
|
-
async start() {
|
|
570
|
-
if (serverRunning && serverPort !== null) {
|
|
571
|
-
return serverPort;
|
|
572
|
-
}
|
|
573
|
-
return new Promise((resolve, reject) => {
|
|
574
|
-
serverInstance = http.createServer(createRequestHandler());
|
|
575
|
-
serverInstance.listen(0, "127.0.0.1", () => {
|
|
576
|
-
const address = serverInstance.address();
|
|
577
|
-
if (address && typeof address === "object") {
|
|
578
|
-
serverPort = address.port;
|
|
579
|
-
serverRunning = true;
|
|
580
|
-
serverInstance.unref();
|
|
581
|
-
resolve(serverPort);
|
|
582
|
-
} else {
|
|
583
|
-
reject(new Error("Failed to get server address"));
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
serverInstance.on("error", (err) => {
|
|
587
|
-
serverRunning = false;
|
|
588
|
-
serverPort = null;
|
|
589
|
-
serverInstance = null;
|
|
590
|
-
reject(new Error(`Failed to start preview server: ${err.message}`));
|
|
591
|
-
});
|
|
592
|
-
});
|
|
593
|
-
},
|
|
594
|
-
getPort() {
|
|
595
|
-
return serverPort;
|
|
596
|
-
},
|
|
597
|
-
isRunning() {
|
|
598
|
-
return serverRunning;
|
|
599
|
-
},
|
|
600
|
-
async stop() {
|
|
601
|
-
if (serverInstance) {
|
|
602
|
-
return new Promise((resolve) => {
|
|
603
|
-
serverInstance.close(() => {
|
|
604
|
-
serverInstance = null;
|
|
605
|
-
serverPort = null;
|
|
606
|
-
serverRunning = false;
|
|
607
|
-
resolve();
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
function getPreviewServer() {
|
|
614
|
-
return previewServer;
|
|
615
|
-
}
|
|
616
|
-
async function getPreviewUrl(templateId) {
|
|
617
|
-
try {
|
|
618
|
-
const port = await previewServer.start();
|
|
619
|
-
return `http://127.0.0.1:${port}/preview/${templateId}`;
|
|
620
|
-
} catch {
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
function keepServerAlive() {
|
|
625
|
-
if (serverInstance) {
|
|
626
|
-
serverInstance.ref();
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// src/server/live-preview-server.ts
|
|
631
|
-
import http2 from "http";
|
|
632
|
-
import fs3 from "fs";
|
|
633
|
-
import path3 from "path";
|
|
634
|
-
var serverInstance2 = null;
|
|
635
|
-
var serverPort2 = null;
|
|
636
|
-
var fileWatcher = null;
|
|
637
|
-
var sseClients = [];
|
|
638
|
-
var LIVE_RELOAD_SCRIPT = `
|
|
141
|
+
</html>`}function Je(n){let t=n.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function ze(){return (n,t)=>{if(n.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=n.url||"/",o=Je(e);if(!o){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!fe(o)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ue(o));return}let i=pe(o);if(i===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ue(o));return}if(We(i)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(Ge(o));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(i);}}var be={async start(){return $&&C!==null?C:new Promise((n,t)=>{b=Be.createServer(ze()),b.listen(0,"127.0.0.1",()=>{let e=b.address();e&&typeof e=="object"?(C=e.port,$=true,b.unref(),n(C)):t(new Error("Failed to get server address"));}),b.on("error",e=>{$=false,C=null,b=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return C},isRunning(){return $},async stop(){if(b)return new Promise(n=>{b.close(()=>{b=null,C=null,$=false,n();});})}};function he(){return be}async function z(n){try{return `http://127.0.0.1:${await be.start()}/preview/${n}`}catch{return null}}function ye(){b&&b.ref();}var k=null,V=null,q=null,I=[],ve=`
|
|
639
142
|
<script>
|
|
640
143
|
(function() {
|
|
641
144
|
const evtSource = new EventSource('/__live-reload');
|
|
@@ -649,364 +152,32 @@ var LIVE_RELOAD_SCRIPT = `
|
|
|
649
152
|
};
|
|
650
153
|
})();
|
|
651
154
|
</script>
|
|
652
|
-
`;
|
|
653
|
-
function injectLiveReload(html) {
|
|
654
|
-
if (html.includes("</body>")) {
|
|
655
|
-
return html.replace("</body>", `${LIVE_RELOAD_SCRIPT}</body>`);
|
|
656
|
-
}
|
|
657
|
-
return html + LIVE_RELOAD_SCRIPT;
|
|
658
|
-
}
|
|
659
|
-
function notifyReload() {
|
|
660
|
-
sseClients.forEach((client) => {
|
|
661
|
-
try {
|
|
662
|
-
client.write("data: reload\n\n");
|
|
663
|
-
} catch {
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
async function startLivePreviewServer(filePath, onReload) {
|
|
668
|
-
const absolutePath = path3.resolve(filePath);
|
|
669
|
-
return new Promise((resolve, reject) => {
|
|
670
|
-
serverInstance2 = http2.createServer((req, res) => {
|
|
671
|
-
if (req.url === "/__live-reload") {
|
|
672
|
-
res.writeHead(200, {
|
|
673
|
-
"Content-Type": "text/event-stream",
|
|
674
|
-
"Cache-Control": "no-cache",
|
|
675
|
-
"Connection": "keep-alive",
|
|
676
|
-
"Access-Control-Allow-Origin": "*"
|
|
677
|
-
});
|
|
678
|
-
res.write("data: connected\n\n");
|
|
679
|
-
sseClients.push(res);
|
|
680
|
-
req.on("close", () => {
|
|
681
|
-
sseClients = sseClients.filter((c) => c !== res);
|
|
682
|
-
});
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
if (req.method === "GET") {
|
|
686
|
-
try {
|
|
687
|
-
const html = fs3.readFileSync(absolutePath, "utf-8");
|
|
688
|
-
const injectedHtml = injectLiveReload(html);
|
|
689
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
690
|
-
res.end(injectedHtml);
|
|
691
|
-
} catch (err) {
|
|
692
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
693
|
-
res.end(`Error reading file: ${err instanceof Error ? err.message : String(err)}`);
|
|
694
|
-
}
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
698
|
-
res.end("Method Not Allowed");
|
|
699
|
-
});
|
|
700
|
-
serverInstance2.listen(0, "127.0.0.1", () => {
|
|
701
|
-
const address = serverInstance2.address();
|
|
702
|
-
if (address && typeof address === "object") {
|
|
703
|
-
serverPort2 = address.port;
|
|
704
|
-
let debounceTimer = null;
|
|
705
|
-
fileWatcher = fs3.watch(absolutePath, (eventType) => {
|
|
706
|
-
if (eventType === "change") {
|
|
707
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
708
|
-
debounceTimer = setTimeout(() => {
|
|
709
|
-
notifyReload();
|
|
710
|
-
onReload?.();
|
|
711
|
-
}, 100);
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
resolve(serverPort2);
|
|
715
|
-
} else {
|
|
716
|
-
reject(new Error("Failed to get server address"));
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
serverInstance2.on("error", (err) => {
|
|
720
|
-
reject(new Error(`Failed to start server: ${err.message}`));
|
|
721
|
-
});
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
async function stopLivePreviewServer() {
|
|
725
|
-
if (fileWatcher) {
|
|
726
|
-
fileWatcher.close();
|
|
727
|
-
fileWatcher = null;
|
|
728
|
-
}
|
|
729
|
-
sseClients.forEach((client) => {
|
|
730
|
-
try {
|
|
731
|
-
client.end();
|
|
732
|
-
} catch {
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
sseClients = [];
|
|
736
|
-
if (serverInstance2) {
|
|
737
|
-
return new Promise((resolve) => {
|
|
738
|
-
serverInstance2.close(() => {
|
|
739
|
-
serverInstance2 = null;
|
|
740
|
-
serverPort2 = null;
|
|
741
|
-
resolve();
|
|
742
|
-
});
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
}
|
|
155
|
+
`;function Qe(n){return n.includes("</body>")?n.replace("</body>",`${ve}</body>`):n+ve}function Xe(){I.forEach(n=>{try{n.write(`data: reload
|
|
746
156
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const htmlContent = template.html_content ?? "";
|
|
751
|
-
saveTemplate(template.id, htmlContent);
|
|
752
|
-
} catch (err) {
|
|
753
|
-
warn(`Could not save template for preview: ${err instanceof Error ? err.message : String(err)}`);
|
|
754
|
-
return null;
|
|
755
|
-
}
|
|
756
|
-
try {
|
|
757
|
-
const previewUrl = await getPreviewUrl(template.id);
|
|
758
|
-
if (previewUrl === null) {
|
|
759
|
-
warn("Could not start preview server");
|
|
760
|
-
return null;
|
|
761
|
-
}
|
|
762
|
-
return previewUrl;
|
|
763
|
-
} catch (err) {
|
|
764
|
-
warn(`Could not generate preview URL: ${err instanceof Error ? err.message : String(err)}`);
|
|
765
|
-
return null;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
function createTemplatesCommand() {
|
|
769
|
-
const cmd = new Command3("templates").description(`Manage email templates
|
|
157
|
+
`);}catch{}});}async function Y(n,t){let e=N.resolve(n);return new Promise((o,i)=>{k=Be.createServer((a,r)=>{if(a.url==="/__live-reload"){r.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),r.write(`data: connected
|
|
158
|
+
|
|
159
|
+
`),I.push(r),a.on("close",()=>{I=I.filter(s=>s!==r);});return}if(a.method==="GET"){try{let s=S.readFileSync(e,"utf-8"),f=Qe(s);r.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r.end(f);}catch(s){r.writeHead(500,{"Content-Type":"text/plain"}),r.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}r.writeHead(405,{"Content-Type":"text/plain"}),r.end("Method Not Allowed");}),k.listen(0,"127.0.0.1",()=>{let a=k.address();if(a&&typeof a=="object"){V=a.port;let r=null;q=S.watch(e,s=>{s==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{Xe(),t?.();},100));}),o(V);}else i(new Error("Failed to get server address"));}),k.on("error",a=>{i(new Error(`Failed to start server: ${a.message}`));});})}async function Q(){if(q&&(q.close(),q=null),I.forEach(n=>{try{n.end();}catch{}}),I=[],k)return new Promise(n=>{k.close(()=>{k=null,V=null,n();});})}async function xe(n){try{let t=n.html_content??"";J(n.id,t);}catch(t){return v(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await z(n.id);return t===null?(v("Could not start preview server"),null):t}catch(t){return v(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function Se(){let n=new Command("templates").description(`Manage email templates
|
|
770
160
|
|
|
771
161
|
AGENTIC LIVE EDITING:
|
|
772
162
|
draft Create new template with live preview
|
|
773
163
|
edit <id> Edit existing template with live preview
|
|
774
164
|
|
|
775
165
|
Both commands save HTML to a local file and start a hot-reload server.
|
|
776
|
-
Edit the file and see changes instantly in the browser.`);
|
|
777
|
-
|
|
778
|
-
try {
|
|
779
|
-
const config = loadConfig();
|
|
780
|
-
const client = new Emailr3({
|
|
781
|
-
apiKey: config.apiKey,
|
|
782
|
-
baseUrl: config.baseUrl
|
|
783
|
-
});
|
|
784
|
-
const result = await client.templates.list({
|
|
785
|
-
limit: parseInt(options.limit, 10),
|
|
786
|
-
page: parseInt(options.page, 10)
|
|
787
|
-
});
|
|
788
|
-
if (options.format === "json") {
|
|
789
|
-
output(result, "json");
|
|
790
|
-
} else {
|
|
791
|
-
const templates = result.map((t) => ({
|
|
792
|
-
ID: t.id,
|
|
793
|
-
Name: t.name,
|
|
794
|
-
Subject: t.subject,
|
|
795
|
-
Variables: t.variables?.join(", ") || "-",
|
|
796
|
-
Created: t.created_at
|
|
797
|
-
}));
|
|
798
|
-
output(templates, "table");
|
|
799
|
-
console.log(`
|
|
800
|
-
Total: ${result.length}`);
|
|
801
|
-
}
|
|
802
|
-
} catch (err) {
|
|
803
|
-
error(err instanceof Error ? err.message : "Failed to list templates");
|
|
804
|
-
process.exit(1);
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
cmd.command("get <id>").description("Get a template by ID").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
808
|
-
try {
|
|
809
|
-
const config = loadConfig();
|
|
810
|
-
const client = new Emailr3({
|
|
811
|
-
apiKey: config.apiKey,
|
|
812
|
-
baseUrl: config.baseUrl
|
|
813
|
-
});
|
|
814
|
-
const template = await client.templates.get(id);
|
|
815
|
-
const previewUrl = await handleTemplatePreview({
|
|
816
|
-
id: template.id,
|
|
817
|
-
html_content: template.html_content ?? void 0
|
|
818
|
-
});
|
|
819
|
-
if (options.format === "json") {
|
|
820
|
-
output({
|
|
821
|
-
...template,
|
|
822
|
-
preview_url: previewUrl
|
|
823
|
-
}, "json");
|
|
824
|
-
} else {
|
|
825
|
-
const tableData = {
|
|
826
|
-
ID: template.id,
|
|
827
|
-
Name: template.name,
|
|
828
|
-
Subject: template.subject,
|
|
829
|
-
Variables: template.variables?.join(", ") || "-",
|
|
830
|
-
Created: template.created_at
|
|
831
|
-
};
|
|
832
|
-
if (previewUrl) {
|
|
833
|
-
tableData["Preview URL"] = previewUrl;
|
|
834
|
-
}
|
|
835
|
-
output(tableData, "table");
|
|
836
|
-
}
|
|
837
|
-
} catch (err) {
|
|
838
|
-
error(err instanceof Error ? err.message : "Failed to get template");
|
|
839
|
-
process.exit(1);
|
|
840
|
-
}
|
|
841
|
-
});
|
|
842
|
-
cmd.command("create").description(`Create a new template
|
|
166
|
+
Edit the file and see changes instantly in the browser.`);return n.command("list").description("List all templates").option("--limit <number>","Number of templates to return","20").option("--page <number>","Page number","1").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.list({limit:parseInt(t.limit,10),page:parseInt(t.page,10)});if(t.format==="json")l(i,"json");else {let a=i.map(r=>({ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Created:r.created_at}));l(a,"table"),console.log(`
|
|
167
|
+
Total: ${i.length}`);}}catch(e){c(e instanceof Error?e.message:"Failed to list templates"),process.exit(1);}}),n.command("get <id>").description("Get a template by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.get(t),r=await xe({id:a.id,html_content:a.html_content??void 0});if(e.format==="json")l({...a,preview_url:r},"json");else {let s={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Created:a.created_at};r&&(s["Preview URL"]=r),l(s,"table");}}catch(o){c(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),n.command("create").description(`Create a new template
|
|
843
168
|
|
|
844
169
|
TIP: For live preview while building, use "emailr templates draft" first.
|
|
845
|
-
It creates a local file with hot-reload preview, then use this command to save.`).requiredOption("--name <name>",
|
|
846
|
-
|
|
847
|
-
const config = loadConfig();
|
|
848
|
-
const client = new Emailr3({
|
|
849
|
-
apiKey: config.apiKey,
|
|
850
|
-
baseUrl: config.baseUrl
|
|
851
|
-
});
|
|
852
|
-
const request = {
|
|
853
|
-
name: options.name,
|
|
854
|
-
subject: options.subject
|
|
855
|
-
};
|
|
856
|
-
if (options.htmlFile) {
|
|
857
|
-
const fs6 = await import("fs");
|
|
858
|
-
request.html_content = fs6.readFileSync(options.htmlFile, "utf-8");
|
|
859
|
-
} else if (options.html) {
|
|
860
|
-
request.html_content = options.html;
|
|
861
|
-
}
|
|
862
|
-
if (options.textFile) {
|
|
863
|
-
const fs6 = await import("fs");
|
|
864
|
-
request.text_content = fs6.readFileSync(options.textFile, "utf-8");
|
|
865
|
-
} else if (options.text) {
|
|
866
|
-
request.text_content = options.text;
|
|
867
|
-
}
|
|
868
|
-
if (options.from) request.from_email = options.from;
|
|
869
|
-
if (options.replyTo) request.reply_to = options.replyTo;
|
|
870
|
-
if (options.previewText) request.preview_text = options.previewText;
|
|
871
|
-
const template = await client.templates.create(request);
|
|
872
|
-
const previewUrl = await handleTemplatePreview({
|
|
873
|
-
id: template.id,
|
|
874
|
-
html_content: template.html_content ?? void 0
|
|
875
|
-
});
|
|
876
|
-
if (options.format === "json") {
|
|
877
|
-
output({
|
|
878
|
-
...template,
|
|
879
|
-
preview_url: previewUrl
|
|
880
|
-
}, "json");
|
|
881
|
-
} else {
|
|
882
|
-
success(`Template created: ${template.id}`);
|
|
883
|
-
const tableData = {
|
|
884
|
-
ID: template.id,
|
|
885
|
-
Name: template.name,
|
|
886
|
-
Subject: template.subject,
|
|
887
|
-
Variables: template.variables?.join(", ") || "-"
|
|
888
|
-
};
|
|
889
|
-
if (previewUrl) {
|
|
890
|
-
tableData["Preview URL"] = previewUrl;
|
|
891
|
-
}
|
|
892
|
-
output(tableData, "table");
|
|
893
|
-
if (previewUrl) {
|
|
894
|
-
console.log(`
|
|
895
|
-
Open the Preview URL in your browser to view the rendered template.`);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
} catch (err) {
|
|
899
|
-
error(err instanceof Error ? err.message : "Failed to create template");
|
|
900
|
-
process.exit(1);
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
cmd.command("update <id>").description(`Update a template
|
|
170
|
+
It creates a local file with hot-reload preview, then use this command to save.`).requiredOption("--name <name>","Template name").requiredOption("--subject <subject>","Email subject").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read text content from file").option("--from <email>","Default from email").option("--reply-to <email>","Default reply-to email").option("--preview-text <text>","Preview text").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i={name:t.name,subject:t.subject};if(t.htmlFile){let s=await import('fs');i.html_content=s.readFileSync(t.htmlFile,"utf-8");}else t.html&&(i.html_content=t.html);if(t.textFile){let s=await import('fs');i.text_content=s.readFileSync(t.textFile,"utf-8");}else t.text&&(i.text_content=t.text);t.from&&(i.from_email=t.from),t.replyTo&&(i.reply_to=t.replyTo),t.previewText&&(i.preview_text=t.previewText);let a=await o.templates.create(i),r=await xe({id:a.id,html_content:a.html_content??void 0});if(t.format==="json")l({...a,preview_url:r},"json");else {d(`Template created: ${a.id}`);let s={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-"};r&&(s["Preview URL"]=r),l(s,"table"),r&&console.log(`
|
|
171
|
+
Open the Preview URL in your browser to view the rendered template.`);}}catch(e){c(e instanceof Error?e.message:"Failed to create template"),process.exit(1);}}),n.command("update <id>").description(`Update a template
|
|
904
172
|
|
|
905
173
|
TIP: For live preview while editing, use "emailr templates edit <id>" first.
|
|
906
|
-
It downloads the template with hot-reload preview, then use this command to save.`).option("--name <name>",
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
const request = {};
|
|
914
|
-
if (options.name) request.name = options.name;
|
|
915
|
-
if (options.subject) request.subject = options.subject;
|
|
916
|
-
if (options.htmlFile) {
|
|
917
|
-
const fs6 = await import("fs");
|
|
918
|
-
request.html_content = fs6.readFileSync(options.htmlFile, "utf-8");
|
|
919
|
-
} else if (options.html) {
|
|
920
|
-
request.html_content = options.html;
|
|
921
|
-
}
|
|
922
|
-
if (options.textFile) {
|
|
923
|
-
const fs6 = await import("fs");
|
|
924
|
-
request.text_content = fs6.readFileSync(options.textFile, "utf-8");
|
|
925
|
-
} else if (options.text) {
|
|
926
|
-
request.text_content = options.text;
|
|
927
|
-
}
|
|
928
|
-
if (options.from) request.from_email = options.from;
|
|
929
|
-
if (options.replyTo) request.reply_to = options.replyTo;
|
|
930
|
-
if (options.previewText) request.preview_text = options.previewText;
|
|
931
|
-
const template = await client.templates.update(id, request);
|
|
932
|
-
if (options.format === "json") {
|
|
933
|
-
output(template, "json");
|
|
934
|
-
} else {
|
|
935
|
-
success(`Template updated: ${template.id}`);
|
|
936
|
-
const tableData = {
|
|
937
|
-
ID: template.id,
|
|
938
|
-
Name: template.name,
|
|
939
|
-
Subject: template.subject,
|
|
940
|
-
Variables: template.variables?.join(", ") || "-",
|
|
941
|
-
Updated: template.updated_at
|
|
942
|
-
};
|
|
943
|
-
output(tableData, "table");
|
|
944
|
-
}
|
|
945
|
-
} catch (err) {
|
|
946
|
-
error(err instanceof Error ? err.message : "Failed to update template");
|
|
947
|
-
process.exit(1);
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
cmd.command("delete <id>").description("Delete a template").action(async (id) => {
|
|
951
|
-
try {
|
|
952
|
-
const config = loadConfig();
|
|
953
|
-
const client = new Emailr3({
|
|
954
|
-
apiKey: config.apiKey,
|
|
955
|
-
baseUrl: config.baseUrl
|
|
956
|
-
});
|
|
957
|
-
await client.templates.delete(id);
|
|
958
|
-
success(`Template deleted: ${id}`);
|
|
959
|
-
} catch (err) {
|
|
960
|
-
error(err instanceof Error ? err.message : "Failed to delete template");
|
|
961
|
-
process.exit(1);
|
|
962
|
-
}
|
|
963
|
-
});
|
|
964
|
-
cmd.command("preview <id>").description("Preview a template in the browser (keeps server running until Ctrl+C)").option("--no-open", "Do not automatically open browser").action(async (id, options) => {
|
|
965
|
-
try {
|
|
966
|
-
const config = loadConfig();
|
|
967
|
-
const client = new Emailr3({
|
|
968
|
-
apiKey: config.apiKey,
|
|
969
|
-
baseUrl: config.baseUrl
|
|
970
|
-
});
|
|
971
|
-
console.log(`Fetching template ${id}...`);
|
|
972
|
-
const template = await client.templates.get(id);
|
|
973
|
-
const htmlContent = template.html_content ?? "";
|
|
974
|
-
saveTemplate(template.id, htmlContent);
|
|
975
|
-
const previewUrl = await getPreviewUrl(template.id);
|
|
976
|
-
if (!previewUrl) {
|
|
977
|
-
error("Failed to start preview server");
|
|
978
|
-
process.exit(1);
|
|
979
|
-
}
|
|
980
|
-
keepServerAlive();
|
|
981
|
-
console.log(`
|
|
982
|
-
Template: ${template.name}`);
|
|
983
|
-
console.log(`Preview URL: ${previewUrl}`);
|
|
984
|
-
if (options.open !== false) {
|
|
985
|
-
try {
|
|
986
|
-
const open = await import("open");
|
|
987
|
-
await open.default(previewUrl);
|
|
988
|
-
console.log("\nBrowser opened. Press Ctrl+C to stop the preview server.");
|
|
989
|
-
} catch {
|
|
990
|
-
console.log("\nCould not open browser automatically. Open the URL above manually.");
|
|
991
|
-
console.log("Press Ctrl+C to stop the preview server.");
|
|
992
|
-
}
|
|
993
|
-
} else {
|
|
994
|
-
console.log("\nOpen the URL above in your browser.");
|
|
995
|
-
console.log("Press Ctrl+C to stop the preview server.");
|
|
996
|
-
}
|
|
997
|
-
process.on("SIGINT", async () => {
|
|
998
|
-
console.log("\n\nStopping preview server...");
|
|
999
|
-
const server = getPreviewServer();
|
|
1000
|
-
await server.stop();
|
|
1001
|
-
console.log("Done.");
|
|
1002
|
-
process.exit(0);
|
|
1003
|
-
});
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
error(err instanceof Error ? err.message : "Failed to preview template");
|
|
1006
|
-
process.exit(1);
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
cmd.command("edit <id>").description(`Start a live editing session for a template.
|
|
174
|
+
It downloads the template with hot-reload preview, then use this command to save.`).option("--name <name>","Template name").option("--subject <subject>","Email subject").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read text content from file").option("--from <email>","Default from email").option("--reply-to <email>","Default reply-to email").option("--preview-text <text>","Preview text").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.subject&&(a.subject=e.subject),e.htmlFile){let s=await import('fs');a.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(a.html_content=e.html);if(e.textFile){let s=await import('fs');a.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(a.text_content=e.text);e.from&&(a.from_email=e.from),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText);let r=await i.templates.update(t,a);if(e.format==="json")l(r,"json");else {d(`Template updated: ${r.id}`);let s={ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Updated:r.updated_at};l(s,"table");}}catch(o){c(o instanceof Error?o.message:"Failed to update template"),process.exit(1);}}),n.command("delete <id>").description("Delete a template").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(t),d(`Template deleted: ${t}`);}catch(e){c(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),n.command("preview <id>").description("Preview a template in the browser (keeps server running until Ctrl+C)").option("--no-open","Do not automatically open browser").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl});console.log(`Fetching template ${t}...`);let a=await i.templates.get(t),r=a.html_content??"";J(a.id,r);let s=await z(a.id);if(s||(c("Failed to start preview server"),process.exit(1)),ye(),console.log(`
|
|
175
|
+
Template: ${a.name}`),console.log(`Preview URL: ${s}`),e.open!==!1)try{await(await import('open')).default(s),console.log(`
|
|
176
|
+
Browser opened. Press Ctrl+C to stop the preview server.`);}catch{console.log(`
|
|
177
|
+
Could not open browser automatically. Open the URL above manually.`),console.log("Press Ctrl+C to stop the preview server.");}else console.log(`
|
|
178
|
+
Open the URL above in your browser.`),console.log("Press Ctrl+C to stop the preview server.");process.on("SIGINT",async()=>{console.log(`
|
|
179
|
+
|
|
180
|
+
Stopping preview server...`),await he().stop(),console.log("Done."),process.exit(0);});}catch(o){c(o instanceof Error?o.message:"Failed to preview template"),process.exit(1);}}),n.command("edit <id>").description(`Start a live editing session for a template.
|
|
1010
181
|
|
|
1011
182
|
AGENTIC WORKFLOW (for AI agents, use --background):
|
|
1012
183
|
1. Run: emailr templates edit <id> --file ./template.html --background
|
|
@@ -1017,34 +188,12 @@ AGENTIC WORKFLOW (for AI agents, use --background):
|
|
|
1017
188
|
INTERACTIVE MODE (default):
|
|
1018
189
|
1. Run: emailr templates edit <id> --file ./template.html
|
|
1019
190
|
2. Edit the file - browser auto-refreshes on save
|
|
1020
|
-
3. Press Ctrl+C when done, then run update command`).option("--file <path>",
|
|
1021
|
-
try {
|
|
1022
|
-
const config = loadConfig();
|
|
1023
|
-
const client = new Emailr3({
|
|
1024
|
-
apiKey: config.apiKey,
|
|
1025
|
-
baseUrl: config.baseUrl
|
|
1026
|
-
});
|
|
1027
|
-
const filePath = path4.resolve(options.file);
|
|
1028
|
-
console.log(`Fetching template ${id}...`);
|
|
1029
|
-
const template = await client.templates.get(id);
|
|
1030
|
-
const htmlContent = template.html_content ?? "";
|
|
1031
|
-
fs4.writeFileSync(filePath, htmlContent, "utf-8");
|
|
1032
|
-
console.log(`Template saved to: ${filePath}`);
|
|
1033
|
-
if (options.background) {
|
|
1034
|
-
const { spawn: spawn2 } = await import("child_process");
|
|
1035
|
-
const os4 = await import("os");
|
|
1036
|
-
const pidFile = path4.join(os4.tmpdir(), "emailr-preview.pid");
|
|
1037
|
-
try {
|
|
1038
|
-
const existingPid = fs4.readFileSync(pidFile, "utf-8").trim();
|
|
1039
|
-
process.kill(parseInt(existingPid, 10), "SIGTERM");
|
|
1040
|
-
} catch {
|
|
1041
|
-
}
|
|
1042
|
-
const serverCode = `
|
|
191
|
+
3. Press Ctrl+C when done, then run update command`).option("--file <path>","Path to save the HTML file for editing","./template.html").option("--no-open","Do not automatically open browser").option("--background","Start preview server in background and exit (for AI agents)").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=N.resolve(e.file);console.log(`Fetching template ${t}...`);let r=await i.templates.get(t),s=r.html_content??"";if(S.writeFileSync(a,s,"utf-8"),console.log(`Template saved to: ${a}`),e.background){let{spawn:y}=await import('child_process'),M=await import('os'),H=N.join(M.tmpdir(),"emailr-preview.pid");try{let L=S.readFileSync(H,"utf-8").trim();process.kill(parseInt(L,10),"SIGTERM");}catch{}let x=`
|
|
1043
192
|
const http = require('http');
|
|
1044
193
|
const fs = require('fs');
|
|
1045
194
|
const path = require('path');
|
|
1046
195
|
const os = require('os');
|
|
1047
|
-
const filePath = ${JSON.stringify(
|
|
196
|
+
const filePath = ${JSON.stringify(a)};
|
|
1048
197
|
const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
|
|
1049
198
|
const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
|
|
1050
199
|
let clients = [];
|
|
@@ -1079,81 +228,16 @@ INTERACTIVE MODE (default):
|
|
|
1079
228
|
});
|
|
1080
229
|
});
|
|
1081
230
|
process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (match) {
|
|
1093
|
-
port2 = match[1];
|
|
1094
|
-
resolve();
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
setTimeout(resolve, 3e3);
|
|
1098
|
-
});
|
|
1099
|
-
child.unref();
|
|
1100
|
-
const previewUrl2 = `http://127.0.0.1:${port2}/`;
|
|
1101
|
-
console.log(`
|
|
1102
|
-
Template: ${template.name}`);
|
|
1103
|
-
console.log(`Template ID: ${template.id}`);
|
|
1104
|
-
console.log(`Live Preview: ${previewUrl2}`);
|
|
1105
|
-
console.log(`File: ${filePath}`);
|
|
1106
|
-
console.log(`
|
|
1107
|
-
Preview server running in background (PID: ${child.pid}).`);
|
|
1108
|
-
console.log(`Edit the file and see live updates in the browser.`);
|
|
1109
|
-
console.log(`
|
|
1110
|
-
When done:`);
|
|
1111
|
-
console.log(` emailr templates update ${id} --html-file ${options.file}`);
|
|
1112
|
-
console.log(` emailr templates stop-preview`);
|
|
1113
|
-
if (options.open !== false) {
|
|
1114
|
-
try {
|
|
1115
|
-
const open = await import("open");
|
|
1116
|
-
await open.default(previewUrl2);
|
|
1117
|
-
} catch {
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
process.exit(0);
|
|
1121
|
-
}
|
|
1122
|
-
const port = await startLivePreviewServer(filePath, () => {
|
|
1123
|
-
console.log("File changed - browser refreshed");
|
|
1124
|
-
});
|
|
1125
|
-
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
1126
|
-
console.log(`
|
|
1127
|
-
Template: ${template.name}`);
|
|
1128
|
-
console.log(`Template ID: ${template.id}`);
|
|
1129
|
-
console.log(`Live Preview: ${previewUrl}`);
|
|
1130
|
-
console.log(`File: ${filePath}`);
|
|
1131
|
-
console.log(`
|
|
1132
|
-
Watching for changes... Edit the file and see live updates.`);
|
|
1133
|
-
console.log(`When done, run: emailr templates update ${id} --html-file ${options.file}`);
|
|
1134
|
-
console.log(`
|
|
1135
|
-
Press Ctrl+C to stop.`);
|
|
1136
|
-
if (options.open !== false) {
|
|
1137
|
-
try {
|
|
1138
|
-
const open = await import("open");
|
|
1139
|
-
await open.default(previewUrl);
|
|
1140
|
-
} catch {
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
process.on("SIGINT", async () => {
|
|
1144
|
-
console.log("\n\nStopping live preview server...");
|
|
1145
|
-
await stopLivePreviewServer();
|
|
1146
|
-
console.log("Done.");
|
|
1147
|
-
console.log(`
|
|
1148
|
-
To save changes: emailr templates update ${id} --html-file ${options.file}`);
|
|
1149
|
-
process.exit(0);
|
|
1150
|
-
});
|
|
1151
|
-
} catch (err) {
|
|
1152
|
-
error(err instanceof Error ? err.message : "Failed to edit template");
|
|
1153
|
-
process.exit(1);
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
cmd.command("draft").description(`Start a live drafting session for a new template.
|
|
231
|
+
`,_=y("node",["-e",x],{detached:!0,stdio:["ignore","pipe","ignore"]}),K="";await new Promise(L=>{_.stdout?.on("data",Ke=>{let ne=Ke.toString().match(/PORT:(\d+)/);ne&&(K=ne[1],L());}),setTimeout(L,3e3);}),_.unref();let ae=`http://127.0.0.1:${K}/`;if(console.log(`
|
|
232
|
+
Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${ae}`),console.log(`File: ${a}`),console.log(`
|
|
233
|
+
Preview server running in background (PID: ${_.pid}).`),console.log("Edit the file and see live updates in the browser."),console.log(`
|
|
234
|
+
When done:`),console.log(` emailr templates update ${t} --html-file ${e.file}`),console.log(" emailr templates stop-preview"),e.open!==!1)try{await(await import('open')).default(ae);}catch{}process.exit(0);}let g=`http://127.0.0.1:${await Y(a,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
|
|
235
|
+
Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${g}`),console.log(`File: ${a}`),console.log(`
|
|
236
|
+
Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${t} --html-file ${e.file}`),console.log(`
|
|
237
|
+
Press Ctrl+C to stop.`),e.open!==!1)try{await(await import('open')).default(g);}catch{}process.on("SIGINT",async()=>{console.log(`
|
|
238
|
+
|
|
239
|
+
Stopping live preview server...`),await Q(),console.log("Done."),console.log(`
|
|
240
|
+
To save changes: emailr templates update ${t} --html-file ${e.file}`),process.exit(0);});}catch(o){c(o instanceof Error?o.message:"Failed to edit template"),process.exit(1);}}),n.command("draft").description(`Start a live drafting session for a new template.
|
|
1157
241
|
|
|
1158
242
|
AGENTIC WORKFLOW (for AI agents, use --background):
|
|
1159
243
|
1. Run: emailr templates draft --file ./new-template.html --background
|
|
@@ -1164,14 +248,7 @@ AGENTIC WORKFLOW (for AI agents, use --background):
|
|
|
1164
248
|
INTERACTIVE MODE (default):
|
|
1165
249
|
1. Run: emailr templates draft --file ./new-template.html
|
|
1166
250
|
2. Edit the file - browser auto-refreshes on save
|
|
1167
|
-
3. Press Ctrl+C when done, then run create command`).option("--file <path>",
|
|
1168
|
-
try {
|
|
1169
|
-
const filePath = path4.resolve(options.file);
|
|
1170
|
-
let htmlContent;
|
|
1171
|
-
if (options.blank) {
|
|
1172
|
-
htmlContent = "";
|
|
1173
|
-
} else {
|
|
1174
|
-
htmlContent = `<!DOCTYPE html>
|
|
251
|
+
3. Press Ctrl+C when done, then run create command`).option("--file <path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--background","Start preview server in background and exit (for AI agents)").action(async t=>{try{let e=N.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
|
|
1175
252
|
<html>
|
|
1176
253
|
<head>
|
|
1177
254
|
<meta charset="UTF-8">
|
|
@@ -1206,25 +283,12 @@ INTERACTIVE MODE (default):
|
|
|
1206
283
|
<p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
|
|
1207
284
|
</div>
|
|
1208
285
|
</body>
|
|
1209
|
-
</html
|
|
1210
|
-
}
|
|
1211
|
-
fs4.writeFileSync(filePath, htmlContent, "utf-8");
|
|
1212
|
-
console.log(`Template draft created: ${filePath}`);
|
|
1213
|
-
if (options.background) {
|
|
1214
|
-
const { spawn: spawn2 } = await import("child_process");
|
|
1215
|
-
const os4 = await import("os");
|
|
1216
|
-
const pidFile = path4.join(os4.tmpdir(), "emailr-preview.pid");
|
|
1217
|
-
try {
|
|
1218
|
-
const existingPid = fs4.readFileSync(pidFile, "utf-8").trim();
|
|
1219
|
-
process.kill(parseInt(existingPid, 10), "SIGTERM");
|
|
1220
|
-
} catch {
|
|
1221
|
-
}
|
|
1222
|
-
const serverCode = `
|
|
286
|
+
</html>`,S.writeFileSync(e,o,"utf-8"),console.log(`Template draft created: ${e}`),t.background){let{spawn:r}=await import('child_process'),s=await import('os'),f=N.join(s.tmpdir(),"emailr-preview.pid");try{let x=S.readFileSync(f,"utf-8").trim();process.kill(parseInt(x,10),"SIGTERM");}catch{}let g=`
|
|
1223
287
|
const http = require('http');
|
|
1224
288
|
const fs = require('fs');
|
|
1225
289
|
const path = require('path');
|
|
1226
290
|
const os = require('os');
|
|
1227
|
-
const filePath = ${JSON.stringify(
|
|
291
|
+
const filePath = ${JSON.stringify(e)};
|
|
1228
292
|
const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
|
|
1229
293
|
const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
|
|
1230
294
|
let clients = [];
|
|
@@ -1259,920 +323,17 @@ INTERACTIVE MODE (default):
|
|
|
1259
323
|
});
|
|
1260
324
|
});
|
|
1261
325
|
process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
port2 = match[1];
|
|
1273
|
-
resolve();
|
|
1274
|
-
}
|
|
1275
|
-
});
|
|
1276
|
-
setTimeout(resolve, 3e3);
|
|
1277
|
-
});
|
|
1278
|
-
child.unref();
|
|
1279
|
-
const previewUrl2 = `http://127.0.0.1:${port2}/`;
|
|
1280
|
-
console.log(`
|
|
1281
|
-
Live Preview: ${previewUrl2}`);
|
|
1282
|
-
console.log(`File: ${filePath}`);
|
|
1283
|
-
console.log(`
|
|
1284
|
-
Preview server running in background (PID: ${child.pid}).`);
|
|
1285
|
-
console.log(`Edit the file and see live updates in the browser.`);
|
|
1286
|
-
console.log(`
|
|
1287
|
-
When done:`);
|
|
1288
|
-
console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${options.file}`);
|
|
1289
|
-
console.log(` emailr templates stop-preview`);
|
|
1290
|
-
if (options.open !== false) {
|
|
1291
|
-
try {
|
|
1292
|
-
const open = await import("open");
|
|
1293
|
-
await open.default(previewUrl2);
|
|
1294
|
-
} catch {
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
process.exit(0);
|
|
1298
|
-
}
|
|
1299
|
-
const port = await startLivePreviewServer(filePath, () => {
|
|
1300
|
-
console.log("File changed - browser refreshed");
|
|
1301
|
-
});
|
|
1302
|
-
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
1303
|
-
console.log(`
|
|
1304
|
-
Live Preview: ${previewUrl}`);
|
|
1305
|
-
console.log(`File: ${filePath}`);
|
|
1306
|
-
console.log(`
|
|
1307
|
-
Watching for changes... Edit the file and see live updates.`);
|
|
1308
|
-
console.log(`
|
|
1309
|
-
When done, create the template:`);
|
|
1310
|
-
console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${options.file}`);
|
|
1311
|
-
console.log(`
|
|
1312
|
-
Press Ctrl+C to stop.`);
|
|
1313
|
-
if (options.open !== false) {
|
|
1314
|
-
try {
|
|
1315
|
-
const open = await import("open");
|
|
1316
|
-
await open.default(previewUrl);
|
|
1317
|
-
} catch {
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
process.on("SIGINT", async () => {
|
|
1321
|
-
console.log("\n\nStopping live preview server...");
|
|
1322
|
-
await stopLivePreviewServer();
|
|
1323
|
-
console.log("Done.");
|
|
1324
|
-
console.log(`
|
|
1325
|
-
To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${options.file}`);
|
|
1326
|
-
process.exit(0);
|
|
1327
|
-
});
|
|
1328
|
-
} catch (err) {
|
|
1329
|
-
error(err instanceof Error ? err.message : "Failed to start draft session");
|
|
1330
|
-
process.exit(1);
|
|
1331
|
-
}
|
|
1332
|
-
});
|
|
1333
|
-
cmd.command("stop-preview").description("Stop any running background preview server").action(async () => {
|
|
1334
|
-
const os4 = await import("os");
|
|
1335
|
-
const pidFile = path4.join(os4.tmpdir(), "emailr-preview.pid");
|
|
1336
|
-
try {
|
|
1337
|
-
const pid = fs4.readFileSync(pidFile, "utf-8").trim();
|
|
1338
|
-
process.kill(parseInt(pid, 10), "SIGTERM");
|
|
1339
|
-
fs4.unlinkSync(pidFile);
|
|
1340
|
-
success("Preview server stopped");
|
|
1341
|
-
} catch {
|
|
1342
|
-
success("No preview server running");
|
|
1343
|
-
}
|
|
1344
|
-
});
|
|
1345
|
-
return cmd;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// src/commands/domains.ts
|
|
1349
|
-
import { Command as Command4 } from "commander";
|
|
1350
|
-
import { Emailr as Emailr4 } from "emailr";
|
|
1351
|
-
function createDomainsCommand() {
|
|
1352
|
-
const cmd = new Command4("domains").description("Manage sending domains");
|
|
1353
|
-
cmd.command("list").description("List all domains").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1354
|
-
try {
|
|
1355
|
-
const config = loadConfig();
|
|
1356
|
-
const client = new Emailr4({
|
|
1357
|
-
apiKey: config.apiKey,
|
|
1358
|
-
baseUrl: config.baseUrl
|
|
1359
|
-
});
|
|
1360
|
-
const domains = await client.domains.list();
|
|
1361
|
-
if (options.format === "json") {
|
|
1362
|
-
output(domains, "json");
|
|
1363
|
-
} else {
|
|
1364
|
-
const formatted = domains.map((d) => ({
|
|
1365
|
-
ID: d.id,
|
|
1366
|
-
Domain: d.domain,
|
|
1367
|
-
Status: d.status,
|
|
1368
|
-
DKIM: d.dkim_verified,
|
|
1369
|
-
SPF: d.spf_verified,
|
|
1370
|
-
DMARC: d.dmarc_verified,
|
|
1371
|
-
Created: d.created_at
|
|
1372
|
-
}));
|
|
1373
|
-
output(formatted, "table");
|
|
1374
|
-
}
|
|
1375
|
-
} catch (err) {
|
|
1376
|
-
error(err instanceof Error ? err.message : "Failed to list domains");
|
|
1377
|
-
process.exit(1);
|
|
1378
|
-
}
|
|
1379
|
-
});
|
|
1380
|
-
cmd.command("get <id>").description("Get a domain by ID").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1381
|
-
try {
|
|
1382
|
-
const config = loadConfig();
|
|
1383
|
-
const client = new Emailr4({
|
|
1384
|
-
apiKey: config.apiKey,
|
|
1385
|
-
baseUrl: config.baseUrl
|
|
1386
|
-
});
|
|
1387
|
-
const domain = await client.domains.get(id);
|
|
1388
|
-
output(domain, options.format);
|
|
1389
|
-
} catch (err) {
|
|
1390
|
-
error(err instanceof Error ? err.message : "Failed to get domain");
|
|
1391
|
-
process.exit(1);
|
|
1392
|
-
}
|
|
1393
|
-
});
|
|
1394
|
-
cmd.command("add <domain>").description("Add a new domain").option("--receiving-subdomain <subdomain>", "Subdomain for receiving emails").option("--format <format>", "Output format (json|table)", "table").action(async (domainName, options) => {
|
|
1395
|
-
try {
|
|
1396
|
-
const config = loadConfig();
|
|
1397
|
-
const client = new Emailr4({
|
|
1398
|
-
apiKey: config.apiKey,
|
|
1399
|
-
baseUrl: config.baseUrl
|
|
1400
|
-
});
|
|
1401
|
-
const request = {
|
|
1402
|
-
domain: domainName
|
|
1403
|
-
};
|
|
1404
|
-
if (options.receivingSubdomain) {
|
|
1405
|
-
request.receivingSubdomain = options.receivingSubdomain;
|
|
1406
|
-
}
|
|
1407
|
-
const domain = await client.domains.add(request);
|
|
1408
|
-
if (options.format === "json") {
|
|
1409
|
-
output(domain, "json");
|
|
1410
|
-
} else {
|
|
1411
|
-
success(`Domain added: ${domain.domain}`);
|
|
1412
|
-
info("Add the following DNS records to verify your domain:");
|
|
1413
|
-
console.log("");
|
|
1414
|
-
if (domain.dns_records) {
|
|
1415
|
-
const records = [
|
|
1416
|
-
{ Type: "DKIM", ...formatDnsRecord(domain.dns_records.dkim) },
|
|
1417
|
-
{ Type: "SPF", ...formatDnsRecord(domain.dns_records.spf) },
|
|
1418
|
-
{ Type: "DMARC", ...formatDnsRecord(domain.dns_records.dmarc) }
|
|
1419
|
-
];
|
|
1420
|
-
output(records, "table");
|
|
1421
|
-
}
|
|
1422
|
-
console.log("");
|
|
1423
|
-
info(`Run 'emailr domains verify ${domain.id}' after adding DNS records`);
|
|
1424
|
-
}
|
|
1425
|
-
} catch (err) {
|
|
1426
|
-
error(err instanceof Error ? err.message : "Failed to add domain");
|
|
1427
|
-
process.exit(1);
|
|
1428
|
-
}
|
|
1429
|
-
});
|
|
1430
|
-
cmd.command("verify <id>").description("Verify a domain").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1431
|
-
try {
|
|
1432
|
-
const config = loadConfig();
|
|
1433
|
-
const client = new Emailr4({
|
|
1434
|
-
apiKey: config.apiKey,
|
|
1435
|
-
baseUrl: config.baseUrl
|
|
1436
|
-
});
|
|
1437
|
-
const result = await client.domains.verify(id);
|
|
1438
|
-
if (options.format === "json") {
|
|
1439
|
-
output(result, "json");
|
|
1440
|
-
} else {
|
|
1441
|
-
if (result.verified) {
|
|
1442
|
-
success("Domain verified successfully!");
|
|
1443
|
-
} else {
|
|
1444
|
-
info(`Domain status: ${result.status}`);
|
|
1445
|
-
if (result.dkim_status) {
|
|
1446
|
-
info(`DKIM status: ${result.dkim_status}`);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
} catch (err) {
|
|
1451
|
-
error(err instanceof Error ? err.message : "Failed to verify domain");
|
|
1452
|
-
process.exit(1);
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
cmd.command("check-dns <id>").description("Check DNS records for a domain").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1456
|
-
try {
|
|
1457
|
-
const config = loadConfig();
|
|
1458
|
-
const client = new Emailr4({
|
|
1459
|
-
apiKey: config.apiKey,
|
|
1460
|
-
baseUrl: config.baseUrl
|
|
1461
|
-
});
|
|
1462
|
-
const result = await client.domains.checkDns(id);
|
|
1463
|
-
if (options.format === "json") {
|
|
1464
|
-
output(result, "json");
|
|
1465
|
-
} else {
|
|
1466
|
-
const records = Object.entries(result).map(([name, status]) => ({
|
|
1467
|
-
Record: name,
|
|
1468
|
-
Verified: status.verified,
|
|
1469
|
-
Expected: status.expected || "-",
|
|
1470
|
-
Found: status.found?.join(", ") || "-"
|
|
1471
|
-
}));
|
|
1472
|
-
output(records, "table");
|
|
1473
|
-
}
|
|
1474
|
-
} catch (err) {
|
|
1475
|
-
error(err instanceof Error ? err.message : "Failed to check DNS");
|
|
1476
|
-
process.exit(1);
|
|
1477
|
-
}
|
|
1478
|
-
});
|
|
1479
|
-
cmd.command("delete <id>").description("Delete a domain").action(async (id) => {
|
|
1480
|
-
try {
|
|
1481
|
-
const config = loadConfig();
|
|
1482
|
-
const client = new Emailr4({
|
|
1483
|
-
apiKey: config.apiKey,
|
|
1484
|
-
baseUrl: config.baseUrl
|
|
1485
|
-
});
|
|
1486
|
-
await client.domains.delete(id);
|
|
1487
|
-
success(`Domain deleted: ${id}`);
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
error(err instanceof Error ? err.message : "Failed to delete domain");
|
|
1490
|
-
process.exit(1);
|
|
1491
|
-
}
|
|
1492
|
-
});
|
|
1493
|
-
return cmd;
|
|
1494
|
-
}
|
|
1495
|
-
function formatDnsRecord(record) {
|
|
1496
|
-
return {
|
|
1497
|
-
"Record Type": record.type,
|
|
1498
|
-
Name: record.name,
|
|
1499
|
-
Value: record.value.length > 50 ? record.value.substring(0, 47) + "..." : record.value,
|
|
1500
|
-
...record.priority !== void 0 && { Priority: record.priority }
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
// src/commands/config.ts
|
|
1505
|
-
import { Command as Command5 } from "commander";
|
|
1506
|
-
function createConfigCommand() {
|
|
1507
|
-
const cmd = new Command5("config").description("Manage CLI configuration");
|
|
1508
|
-
cmd.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
|
|
1509
|
-
try {
|
|
1510
|
-
const validKeys = ["api-key", "base-url", "format"];
|
|
1511
|
-
const normalizedKey = key.toLowerCase();
|
|
1512
|
-
if (!validKeys.includes(normalizedKey)) {
|
|
1513
|
-
error(`Invalid config key: ${key}`);
|
|
1514
|
-
info(`Valid keys: ${validKeys.join(", ")}`);
|
|
1515
|
-
process.exit(1);
|
|
1516
|
-
}
|
|
1517
|
-
const keyMap = {
|
|
1518
|
-
"api-key": "apiKey",
|
|
1519
|
-
"base-url": "baseUrl",
|
|
1520
|
-
"format": "format"
|
|
1521
|
-
};
|
|
1522
|
-
const configKey = keyMap[normalizedKey];
|
|
1523
|
-
if (normalizedKey === "format" && !["json", "table"].includes(value)) {
|
|
1524
|
-
error(`Invalid format value: ${value}`);
|
|
1525
|
-
info("Valid formats: json, table");
|
|
1526
|
-
process.exit(1);
|
|
1527
|
-
}
|
|
1528
|
-
saveConfig({ [configKey]: value });
|
|
1529
|
-
success(`Configuration saved: ${key} = ${normalizedKey === "api-key" ? "***" : value}`);
|
|
1530
|
-
info(`Config file: ${getConfigPath()}`);
|
|
1531
|
-
} catch (err) {
|
|
1532
|
-
error(err instanceof Error ? err.message : "Failed to save configuration");
|
|
1533
|
-
process.exit(1);
|
|
1534
|
-
}
|
|
1535
|
-
});
|
|
1536
|
-
cmd.command("get <key>").description("Get a configuration value").action(async (key) => {
|
|
1537
|
-
try {
|
|
1538
|
-
const validKeys = ["api-key", "base-url", "format"];
|
|
1539
|
-
const normalizedKey = key.toLowerCase();
|
|
1540
|
-
if (!validKeys.includes(normalizedKey)) {
|
|
1541
|
-
error(`Invalid config key: ${key}`);
|
|
1542
|
-
info(`Valid keys: ${validKeys.join(", ")}`);
|
|
1543
|
-
process.exit(1);
|
|
1544
|
-
}
|
|
1545
|
-
const keyMap = {
|
|
1546
|
-
"api-key": "apiKey",
|
|
1547
|
-
"base-url": "baseUrl",
|
|
1548
|
-
"format": "format"
|
|
1549
|
-
};
|
|
1550
|
-
const configKey = keyMap[normalizedKey];
|
|
1551
|
-
const value = getConfigValue(configKey);
|
|
1552
|
-
if (value) {
|
|
1553
|
-
if (normalizedKey === "api-key") {
|
|
1554
|
-
console.log(value.substring(0, 8) + "..." + value.substring(value.length - 4));
|
|
1555
|
-
} else {
|
|
1556
|
-
console.log(value);
|
|
1557
|
-
}
|
|
1558
|
-
} else {
|
|
1559
|
-
info(`${key} is not set`);
|
|
1560
|
-
}
|
|
1561
|
-
} catch (err) {
|
|
1562
|
-
error(err instanceof Error ? err.message : "Failed to get configuration");
|
|
1563
|
-
process.exit(1);
|
|
1564
|
-
}
|
|
1565
|
-
});
|
|
1566
|
-
cmd.command("list").description("List all configuration values").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1567
|
-
try {
|
|
1568
|
-
const config = loadConfig();
|
|
1569
|
-
const configData = {
|
|
1570
|
-
"api-key": config.apiKey ? config.apiKey.substring(0, 8) + "..." + config.apiKey.substring(config.apiKey.length - 4) : "(not set)",
|
|
1571
|
-
"base-url": config.baseUrl || "(default)",
|
|
1572
|
-
"format": config.format || "table"
|
|
1573
|
-
};
|
|
1574
|
-
if (options.format === "json") {
|
|
1575
|
-
output(configData, "json");
|
|
1576
|
-
} else {
|
|
1577
|
-
const rows = Object.entries(configData).map(([key, value]) => ({
|
|
1578
|
-
Key: key,
|
|
1579
|
-
Value: value
|
|
1580
|
-
}));
|
|
1581
|
-
output(rows, "table");
|
|
1582
|
-
}
|
|
1583
|
-
console.log("");
|
|
1584
|
-
info(`Config file: ${getConfigPath()}`);
|
|
1585
|
-
} catch (err) {
|
|
1586
|
-
if (err instanceof Error && err.message.includes("No API key configured")) {
|
|
1587
|
-
info("No configuration found.");
|
|
1588
|
-
info(`Run 'emailr config set api-key <your-api-key>' to get started.`);
|
|
1589
|
-
} else {
|
|
1590
|
-
error(err instanceof Error ? err.message : "Failed to list configuration");
|
|
1591
|
-
process.exit(1);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
});
|
|
1595
|
-
cmd.command("path").description("Show the configuration file path").action(() => {
|
|
1596
|
-
console.log(getConfigPath());
|
|
1597
|
-
});
|
|
1598
|
-
cmd.command("init").description("Initialize configuration interactively").option("--api-key <key>", "API key to use").option("--base-url <url>", "Base URL for API").action(async (options) => {
|
|
1599
|
-
try {
|
|
1600
|
-
if (options.apiKey) {
|
|
1601
|
-
saveConfig({
|
|
1602
|
-
apiKey: options.apiKey,
|
|
1603
|
-
baseUrl: options.baseUrl
|
|
1604
|
-
});
|
|
1605
|
-
success("Configuration initialized!");
|
|
1606
|
-
info(`Config file: ${getConfigPath()}`);
|
|
1607
|
-
} else {
|
|
1608
|
-
info("Initialize your Emailr CLI configuration:");
|
|
1609
|
-
console.log("");
|
|
1610
|
-
info("Run with --api-key flag:");
|
|
1611
|
-
console.log(" emailr config init --api-key <your-api-key>");
|
|
1612
|
-
console.log("");
|
|
1613
|
-
info("Or set environment variable:");
|
|
1614
|
-
console.log(" export EMAILR_API_KEY=<your-api-key>");
|
|
1615
|
-
}
|
|
1616
|
-
} catch (err) {
|
|
1617
|
-
error(err instanceof Error ? err.message : "Failed to initialize configuration");
|
|
1618
|
-
process.exit(1);
|
|
1619
|
-
}
|
|
1620
|
-
});
|
|
1621
|
-
return cmd;
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
// src/commands/broadcasts.ts
|
|
1625
|
-
import { Command as Command6 } from "commander";
|
|
1626
|
-
import { Emailr as Emailr5 } from "emailr";
|
|
1627
|
-
function createBroadcastsCommand() {
|
|
1628
|
-
const cmd = new Command6("broadcasts").description("Manage broadcast campaigns");
|
|
1629
|
-
cmd.command("list").description("List all broadcasts").option("--status <status>", "Filter by status (draft, scheduled, sending, sent)").option("--limit <number>", "Number of broadcasts to return", "20").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1630
|
-
try {
|
|
1631
|
-
const config = loadConfig();
|
|
1632
|
-
const client = new Emailr5({
|
|
1633
|
-
apiKey: config.apiKey,
|
|
1634
|
-
baseUrl: config.baseUrl
|
|
1635
|
-
});
|
|
1636
|
-
const broadcasts = await client.broadcasts.list({
|
|
1637
|
-
status: options.status,
|
|
1638
|
-
limit: parseInt(options.limit)
|
|
1639
|
-
});
|
|
1640
|
-
if (options.format === "json") {
|
|
1641
|
-
output(broadcasts, "json");
|
|
1642
|
-
} else {
|
|
1643
|
-
if (broadcasts.length === 0) {
|
|
1644
|
-
console.log("No broadcasts found.");
|
|
1645
|
-
return;
|
|
1646
|
-
}
|
|
1647
|
-
const tableData = broadcasts.map((b) => ({
|
|
1648
|
-
ID: b.id,
|
|
1649
|
-
Name: b.name,
|
|
1650
|
-
Subject: b.subject.substring(0, 30) + (b.subject.length > 30 ? "..." : ""),
|
|
1651
|
-
Status: b.status,
|
|
1652
|
-
Recipients: b.total_recipients || 0,
|
|
1653
|
-
"Sent": b.sent_count || 0,
|
|
1654
|
-
"Created": new Date(b.created_at).toLocaleDateString()
|
|
1655
|
-
}));
|
|
1656
|
-
output(tableData, "table");
|
|
1657
|
-
}
|
|
1658
|
-
} catch (err) {
|
|
1659
|
-
error(err instanceof Error ? err.message : "Failed to list broadcasts");
|
|
1660
|
-
process.exit(1);
|
|
1661
|
-
}
|
|
1662
|
-
});
|
|
1663
|
-
cmd.command("get <id>").description("Get broadcast details").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1664
|
-
try {
|
|
1665
|
-
const config = loadConfig();
|
|
1666
|
-
const client = new Emailr5({
|
|
1667
|
-
apiKey: config.apiKey,
|
|
1668
|
-
baseUrl: config.baseUrl
|
|
1669
|
-
});
|
|
1670
|
-
const broadcast = await client.broadcasts.get(id);
|
|
1671
|
-
if (options.format === "json") {
|
|
1672
|
-
output(broadcast, "json");
|
|
1673
|
-
} else {
|
|
1674
|
-
output({
|
|
1675
|
-
ID: broadcast.id,
|
|
1676
|
-
Name: broadcast.name,
|
|
1677
|
-
Subject: broadcast.subject,
|
|
1678
|
-
"From Email": broadcast.from_email,
|
|
1679
|
-
Status: broadcast.status,
|
|
1680
|
-
"Total Recipients": broadcast.total_recipients || 0,
|
|
1681
|
-
"Sent Count": broadcast.sent_count || 0,
|
|
1682
|
-
"Delivered": broadcast.delivered_count || 0,
|
|
1683
|
-
"Opened": broadcast.opened_count || 0,
|
|
1684
|
-
"Clicked": broadcast.clicked_count || 0,
|
|
1685
|
-
"Bounced": broadcast.bounced_count || 0,
|
|
1686
|
-
"Scheduled At": broadcast.scheduled_at || "N/A",
|
|
1687
|
-
"Started At": broadcast.started_at || "N/A",
|
|
1688
|
-
"Completed At": broadcast.completed_at || "N/A",
|
|
1689
|
-
"Created At": broadcast.created_at
|
|
1690
|
-
}, "table");
|
|
1691
|
-
}
|
|
1692
|
-
} catch (err) {
|
|
1693
|
-
error(err instanceof Error ? err.message : "Failed to get broadcast");
|
|
1694
|
-
process.exit(1);
|
|
1695
|
-
}
|
|
1696
|
-
});
|
|
1697
|
-
cmd.command("create").description("Create a new broadcast").requiredOption("--name <name>", "Broadcast name").requiredOption("--subject <subject>", "Email subject").requiredOption("--from <email>", "Sender email address").option("--template <id>", "Template ID to use").option("--segment <id>", "Segment ID to target").option("--html <html>", "HTML content").option("--text <text>", "Plain text content").option("--schedule <datetime>", "Schedule time (ISO 8601)").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1698
|
-
try {
|
|
1699
|
-
const config = loadConfig();
|
|
1700
|
-
const client = new Emailr5({
|
|
1701
|
-
apiKey: config.apiKey,
|
|
1702
|
-
baseUrl: config.baseUrl
|
|
1703
|
-
});
|
|
1704
|
-
const broadcast = await client.broadcasts.create({
|
|
1705
|
-
name: options.name,
|
|
1706
|
-
subject: options.subject,
|
|
1707
|
-
from_email: options.from,
|
|
1708
|
-
template_id: options.template,
|
|
1709
|
-
segment_id: options.segment,
|
|
1710
|
-
html_content: options.html,
|
|
1711
|
-
text_content: options.text,
|
|
1712
|
-
scheduled_at: options.schedule
|
|
1713
|
-
});
|
|
1714
|
-
if (options.format === "json") {
|
|
1715
|
-
output(broadcast, "json");
|
|
1716
|
-
} else {
|
|
1717
|
-
success("Broadcast created successfully!");
|
|
1718
|
-
output({
|
|
1719
|
-
ID: broadcast.id,
|
|
1720
|
-
Name: broadcast.name,
|
|
1721
|
-
Status: broadcast.status,
|
|
1722
|
-
"Scheduled At": broadcast.scheduled_at || "Not scheduled"
|
|
1723
|
-
}, "table");
|
|
1724
|
-
}
|
|
1725
|
-
} catch (err) {
|
|
1726
|
-
error(err instanceof Error ? err.message : "Failed to create broadcast");
|
|
1727
|
-
process.exit(1);
|
|
1728
|
-
}
|
|
1729
|
-
});
|
|
1730
|
-
cmd.command("send <id>").description("Send a broadcast immediately").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1731
|
-
try {
|
|
1732
|
-
const config = loadConfig();
|
|
1733
|
-
const client = new Emailr5({
|
|
1734
|
-
apiKey: config.apiKey,
|
|
1735
|
-
baseUrl: config.baseUrl
|
|
1736
|
-
});
|
|
1737
|
-
const result = await client.broadcasts.send(id);
|
|
1738
|
-
if (options.format === "json") {
|
|
1739
|
-
output(result, "json");
|
|
1740
|
-
} else {
|
|
1741
|
-
success("Broadcast sent successfully!");
|
|
1742
|
-
output({
|
|
1743
|
-
Success: result.success,
|
|
1744
|
-
Sent: result.sent,
|
|
1745
|
-
Total: result.total
|
|
1746
|
-
}, "table");
|
|
1747
|
-
}
|
|
1748
|
-
} catch (err) {
|
|
1749
|
-
error(err instanceof Error ? err.message : "Failed to send broadcast");
|
|
1750
|
-
process.exit(1);
|
|
1751
|
-
}
|
|
1752
|
-
});
|
|
1753
|
-
cmd.command("schedule <id>").description("Schedule a broadcast").requiredOption("--at <datetime>", "Schedule time (ISO 8601)").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1754
|
-
try {
|
|
1755
|
-
const config = loadConfig();
|
|
1756
|
-
const client = new Emailr5({
|
|
1757
|
-
apiKey: config.apiKey,
|
|
1758
|
-
baseUrl: config.baseUrl
|
|
1759
|
-
});
|
|
1760
|
-
const broadcast = await client.broadcasts.schedule(id, options.at);
|
|
1761
|
-
if (options.format === "json") {
|
|
1762
|
-
output(broadcast, "json");
|
|
1763
|
-
} else {
|
|
1764
|
-
success("Broadcast scheduled successfully!");
|
|
1765
|
-
output({
|
|
1766
|
-
ID: broadcast.id,
|
|
1767
|
-
Name: broadcast.name,
|
|
1768
|
-
Status: broadcast.status,
|
|
1769
|
-
"Scheduled At": broadcast.scheduled_at
|
|
1770
|
-
}, "table");
|
|
1771
|
-
}
|
|
1772
|
-
} catch (err) {
|
|
1773
|
-
error(err instanceof Error ? err.message : "Failed to schedule broadcast");
|
|
1774
|
-
process.exit(1);
|
|
1775
|
-
}
|
|
1776
|
-
});
|
|
1777
|
-
cmd.command("cancel <id>").description("Cancel a scheduled broadcast").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1778
|
-
try {
|
|
1779
|
-
const config = loadConfig();
|
|
1780
|
-
const client = new Emailr5({
|
|
1781
|
-
apiKey: config.apiKey,
|
|
1782
|
-
baseUrl: config.baseUrl
|
|
1783
|
-
});
|
|
1784
|
-
const result = await client.broadcasts.cancel(id);
|
|
1785
|
-
if (options.format === "json") {
|
|
1786
|
-
output(result, "json");
|
|
1787
|
-
} else {
|
|
1788
|
-
success("Broadcast cancelled successfully!");
|
|
1789
|
-
}
|
|
1790
|
-
} catch (err) {
|
|
1791
|
-
error(err instanceof Error ? err.message : "Failed to cancel broadcast");
|
|
1792
|
-
process.exit(1);
|
|
1793
|
-
}
|
|
1794
|
-
});
|
|
1795
|
-
cmd.command("delete <id>").description("Delete a broadcast").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1796
|
-
try {
|
|
1797
|
-
const config = loadConfig();
|
|
1798
|
-
const client = new Emailr5({
|
|
1799
|
-
apiKey: config.apiKey,
|
|
1800
|
-
baseUrl: config.baseUrl
|
|
1801
|
-
});
|
|
1802
|
-
const result = await client.broadcasts.delete(id);
|
|
1803
|
-
if (options.format === "json") {
|
|
1804
|
-
output(result, "json");
|
|
1805
|
-
} else {
|
|
1806
|
-
success("Broadcast deleted successfully!");
|
|
1807
|
-
}
|
|
1808
|
-
} catch (err) {
|
|
1809
|
-
error(err instanceof Error ? err.message : "Failed to delete broadcast");
|
|
1810
|
-
process.exit(1);
|
|
1811
|
-
}
|
|
1812
|
-
});
|
|
1813
|
-
return cmd;
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
// src/commands/webhooks.ts
|
|
1817
|
-
import { Command as Command7 } from "commander";
|
|
1818
|
-
import { Emailr as Emailr6 } from "emailr";
|
|
1819
|
-
function createWebhooksCommand() {
|
|
1820
|
-
const cmd = new Command7("webhooks").description("Manage webhooks");
|
|
1821
|
-
cmd.command("list").description("List all webhooks").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1822
|
-
try {
|
|
1823
|
-
const config = loadConfig();
|
|
1824
|
-
const client = new Emailr6({
|
|
1825
|
-
apiKey: config.apiKey,
|
|
1826
|
-
baseUrl: config.baseUrl
|
|
1827
|
-
});
|
|
1828
|
-
const webhooks = await client.webhooks.list();
|
|
1829
|
-
if (options.format === "json") {
|
|
1830
|
-
output(webhooks, "json");
|
|
1831
|
-
} else {
|
|
1832
|
-
if (webhooks.length === 0) {
|
|
1833
|
-
console.log("No webhooks found.");
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
1836
|
-
const tableData = webhooks.map((w) => ({
|
|
1837
|
-
ID: w.id,
|
|
1838
|
-
Name: w.name,
|
|
1839
|
-
URL: w.url.substring(0, 40) + (w.url.length > 40 ? "..." : ""),
|
|
1840
|
-
Events: w.events.join(", "),
|
|
1841
|
-
Active: w.active ? "Yes" : "No"
|
|
1842
|
-
}));
|
|
1843
|
-
output(tableData, "table");
|
|
1844
|
-
}
|
|
1845
|
-
} catch (err) {
|
|
1846
|
-
error(err instanceof Error ? err.message : "Failed to list webhooks");
|
|
1847
|
-
process.exit(1);
|
|
1848
|
-
}
|
|
1849
|
-
});
|
|
1850
|
-
cmd.command("get <id>").description("Get webhook details").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1851
|
-
try {
|
|
1852
|
-
const config = loadConfig();
|
|
1853
|
-
const client = new Emailr6({
|
|
1854
|
-
apiKey: config.apiKey,
|
|
1855
|
-
baseUrl: config.baseUrl
|
|
1856
|
-
});
|
|
1857
|
-
const webhook = await client.webhooks.get(id);
|
|
1858
|
-
if (options.format === "json") {
|
|
1859
|
-
output(webhook, "json");
|
|
1860
|
-
} else {
|
|
1861
|
-
output({
|
|
1862
|
-
ID: webhook.id,
|
|
1863
|
-
Name: webhook.name,
|
|
1864
|
-
URL: webhook.url,
|
|
1865
|
-
Events: webhook.events.join(", "),
|
|
1866
|
-
Active: webhook.active ? "Yes" : "No",
|
|
1867
|
-
Secret: webhook.secret,
|
|
1868
|
-
"Created At": webhook.created_at
|
|
1869
|
-
}, "table");
|
|
1870
|
-
}
|
|
1871
|
-
} catch (err) {
|
|
1872
|
-
error(err instanceof Error ? err.message : "Failed to get webhook");
|
|
1873
|
-
process.exit(1);
|
|
1874
|
-
}
|
|
1875
|
-
});
|
|
1876
|
-
cmd.command("create").description("Create a new webhook").requiredOption("--name <name>", "Webhook name").requiredOption("--url <url>", "Webhook URL").requiredOption("--events <events>", "Events to subscribe to (comma-separated)").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1877
|
-
try {
|
|
1878
|
-
const config = loadConfig();
|
|
1879
|
-
const client = new Emailr6({
|
|
1880
|
-
apiKey: config.apiKey,
|
|
1881
|
-
baseUrl: config.baseUrl
|
|
1882
|
-
});
|
|
1883
|
-
const events = options.events.split(",").map((e) => e.trim());
|
|
1884
|
-
const webhook = await client.webhooks.create({
|
|
1885
|
-
name: options.name,
|
|
1886
|
-
url: options.url,
|
|
1887
|
-
events
|
|
1888
|
-
});
|
|
1889
|
-
if (options.format === "json") {
|
|
1890
|
-
output(webhook, "json");
|
|
1891
|
-
} else {
|
|
1892
|
-
success("Webhook created successfully!");
|
|
1893
|
-
output({
|
|
1894
|
-
ID: webhook.id,
|
|
1895
|
-
Name: webhook.name,
|
|
1896
|
-
URL: webhook.url,
|
|
1897
|
-
Secret: webhook.secret
|
|
1898
|
-
}, "table");
|
|
1899
|
-
}
|
|
1900
|
-
} catch (err) {
|
|
1901
|
-
error(err instanceof Error ? err.message : "Failed to create webhook");
|
|
1902
|
-
process.exit(1);
|
|
1903
|
-
}
|
|
1904
|
-
});
|
|
1905
|
-
cmd.command("update <id>").description("Update a webhook").option("--name <name>", "New webhook name").option("--url <url>", "New webhook URL").option("--events <events>", "New events (comma-separated)").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1906
|
-
try {
|
|
1907
|
-
const config = loadConfig();
|
|
1908
|
-
const client = new Emailr6({
|
|
1909
|
-
apiKey: config.apiKey,
|
|
1910
|
-
baseUrl: config.baseUrl
|
|
1911
|
-
});
|
|
1912
|
-
const updateData = {};
|
|
1913
|
-
if (options.name) updateData.name = options.name;
|
|
1914
|
-
if (options.url) updateData.url = options.url;
|
|
1915
|
-
if (options.events) {
|
|
1916
|
-
updateData.events = options.events.split(",").map((e) => e.trim());
|
|
1917
|
-
}
|
|
1918
|
-
const webhook = await client.webhooks.update(id, updateData);
|
|
1919
|
-
if (options.format === "json") {
|
|
1920
|
-
output(webhook, "json");
|
|
1921
|
-
} else {
|
|
1922
|
-
success("Webhook updated successfully!");
|
|
1923
|
-
output({
|
|
1924
|
-
ID: webhook.id,
|
|
1925
|
-
Name: webhook.name,
|
|
1926
|
-
URL: webhook.url
|
|
1927
|
-
}, "table");
|
|
1928
|
-
}
|
|
1929
|
-
} catch (err) {
|
|
1930
|
-
error(err instanceof Error ? err.message : "Failed to update webhook");
|
|
1931
|
-
process.exit(1);
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
cmd.command("enable <id>").description("Enable a webhook").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1935
|
-
try {
|
|
1936
|
-
const config = loadConfig();
|
|
1937
|
-
const client = new Emailr6({
|
|
1938
|
-
apiKey: config.apiKey,
|
|
1939
|
-
baseUrl: config.baseUrl
|
|
1940
|
-
});
|
|
1941
|
-
const result = await client.webhooks.enable(id);
|
|
1942
|
-
if (options.format === "json") {
|
|
1943
|
-
output(result, "json");
|
|
1944
|
-
} else {
|
|
1945
|
-
success("Webhook enabled successfully!");
|
|
1946
|
-
}
|
|
1947
|
-
} catch (err) {
|
|
1948
|
-
error(err instanceof Error ? err.message : "Failed to enable webhook");
|
|
1949
|
-
process.exit(1);
|
|
1950
|
-
}
|
|
1951
|
-
});
|
|
1952
|
-
cmd.command("disable <id>").description("Disable a webhook").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1953
|
-
try {
|
|
1954
|
-
const config = loadConfig();
|
|
1955
|
-
const client = new Emailr6({
|
|
1956
|
-
apiKey: config.apiKey,
|
|
1957
|
-
baseUrl: config.baseUrl
|
|
1958
|
-
});
|
|
1959
|
-
const result = await client.webhooks.disable(id);
|
|
1960
|
-
if (options.format === "json") {
|
|
1961
|
-
output(result, "json");
|
|
1962
|
-
} else {
|
|
1963
|
-
success("Webhook disabled successfully!");
|
|
1964
|
-
}
|
|
1965
|
-
} catch (err) {
|
|
1966
|
-
error(err instanceof Error ? err.message : "Failed to disable webhook");
|
|
1967
|
-
process.exit(1);
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
cmd.command("delete <id>").description("Delete a webhook").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1971
|
-
try {
|
|
1972
|
-
const config = loadConfig();
|
|
1973
|
-
const client = new Emailr6({
|
|
1974
|
-
apiKey: config.apiKey,
|
|
1975
|
-
baseUrl: config.baseUrl
|
|
1976
|
-
});
|
|
1977
|
-
const result = await client.webhooks.delete(id);
|
|
1978
|
-
if (options.format === "json") {
|
|
1979
|
-
output(result, "json");
|
|
1980
|
-
} else {
|
|
1981
|
-
success("Webhook deleted successfully!");
|
|
1982
|
-
}
|
|
1983
|
-
} catch (err) {
|
|
1984
|
-
error(err instanceof Error ? err.message : "Failed to delete webhook");
|
|
1985
|
-
process.exit(1);
|
|
1986
|
-
}
|
|
1987
|
-
});
|
|
1988
|
-
return cmd;
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
// src/commands/segments.ts
|
|
1992
|
-
import { Command as Command8 } from "commander";
|
|
1993
|
-
import { Emailr as Emailr7 } from "emailr";
|
|
1994
|
-
function createSegmentsCommand() {
|
|
1995
|
-
const cmd = new Command8("segments").description("Manage contact segments");
|
|
1996
|
-
cmd.command("list").description("List all segments").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1997
|
-
try {
|
|
1998
|
-
const config = loadConfig();
|
|
1999
|
-
const client = new Emailr7({
|
|
2000
|
-
apiKey: config.apiKey,
|
|
2001
|
-
baseUrl: config.baseUrl
|
|
2002
|
-
});
|
|
2003
|
-
const segments = await client.segments.list();
|
|
2004
|
-
if (options.format === "json") {
|
|
2005
|
-
output(segments, "json");
|
|
2006
|
-
} else {
|
|
2007
|
-
if (segments.length === 0) {
|
|
2008
|
-
console.log("No segments found.");
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
const tableData = segments.map((s) => ({
|
|
2012
|
-
ID: s.id,
|
|
2013
|
-
Name: s.name,
|
|
2014
|
-
Description: (s.description || "").substring(0, 30) + ((s.description?.length || 0) > 30 ? "..." : ""),
|
|
2015
|
-
"Created At": new Date(s.created_at).toLocaleDateString()
|
|
2016
|
-
}));
|
|
2017
|
-
output(tableData, "table");
|
|
2018
|
-
}
|
|
2019
|
-
} catch (err) {
|
|
2020
|
-
error(err instanceof Error ? err.message : "Failed to list segments");
|
|
2021
|
-
process.exit(1);
|
|
2022
|
-
}
|
|
2023
|
-
});
|
|
2024
|
-
cmd.command("get <id>").description("Get segment details").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
2025
|
-
try {
|
|
2026
|
-
const config = loadConfig();
|
|
2027
|
-
const client = new Emailr7({
|
|
2028
|
-
apiKey: config.apiKey,
|
|
2029
|
-
baseUrl: config.baseUrl
|
|
2030
|
-
});
|
|
2031
|
-
const segment = await client.segments.get(id);
|
|
2032
|
-
if (options.format === "json") {
|
|
2033
|
-
output(segment, "json");
|
|
2034
|
-
} else {
|
|
2035
|
-
output({
|
|
2036
|
-
ID: segment.id,
|
|
2037
|
-
Name: segment.name,
|
|
2038
|
-
Description: segment.description || "N/A",
|
|
2039
|
-
Conditions: JSON.stringify(segment.conditions),
|
|
2040
|
-
"Created At": segment.created_at,
|
|
2041
|
-
"Updated At": segment.updated_at
|
|
2042
|
-
}, "table");
|
|
2043
|
-
}
|
|
2044
|
-
} catch (err) {
|
|
2045
|
-
error(err instanceof Error ? err.message : "Failed to get segment");
|
|
2046
|
-
process.exit(1);
|
|
2047
|
-
}
|
|
2048
|
-
});
|
|
2049
|
-
cmd.command("create").description("Create a new segment").requiredOption("--name <name>", "Segment name").requiredOption("--conditions <json>", "Segment conditions as JSON").option("--description <description>", "Segment description").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
2050
|
-
try {
|
|
2051
|
-
const config = loadConfig();
|
|
2052
|
-
const client = new Emailr7({
|
|
2053
|
-
apiKey: config.apiKey,
|
|
2054
|
-
baseUrl: config.baseUrl
|
|
2055
|
-
});
|
|
2056
|
-
let conditions;
|
|
2057
|
-
try {
|
|
2058
|
-
conditions = JSON.parse(options.conditions);
|
|
2059
|
-
} catch {
|
|
2060
|
-
error("Invalid JSON for --conditions");
|
|
2061
|
-
process.exit(1);
|
|
2062
|
-
}
|
|
2063
|
-
const segment = await client.segments.create({
|
|
2064
|
-
name: options.name,
|
|
2065
|
-
description: options.description,
|
|
2066
|
-
conditions
|
|
2067
|
-
});
|
|
2068
|
-
if (options.format === "json") {
|
|
2069
|
-
output(segment, "json");
|
|
2070
|
-
} else {
|
|
2071
|
-
success("Segment created successfully!");
|
|
2072
|
-
output({
|
|
2073
|
-
ID: segment.id,
|
|
2074
|
-
Name: segment.name
|
|
2075
|
-
}, "table");
|
|
2076
|
-
}
|
|
2077
|
-
} catch (err) {
|
|
2078
|
-
error(err instanceof Error ? err.message : "Failed to create segment");
|
|
2079
|
-
process.exit(1);
|
|
2080
|
-
}
|
|
2081
|
-
});
|
|
2082
|
-
cmd.command("update <id>").description("Update a segment").option("--name <name>", "New segment name").option("--description <description>", "New description").option("--conditions <json>", "New conditions as JSON").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
2083
|
-
try {
|
|
2084
|
-
const config = loadConfig();
|
|
2085
|
-
const client = new Emailr7({
|
|
2086
|
-
apiKey: config.apiKey,
|
|
2087
|
-
baseUrl: config.baseUrl
|
|
2088
|
-
});
|
|
2089
|
-
const updateData = {};
|
|
2090
|
-
if (options.name) updateData.name = options.name;
|
|
2091
|
-
if (options.description) updateData.description = options.description;
|
|
2092
|
-
if (options.conditions) {
|
|
2093
|
-
try {
|
|
2094
|
-
updateData.conditions = JSON.parse(options.conditions);
|
|
2095
|
-
} catch {
|
|
2096
|
-
error("Invalid JSON for --conditions");
|
|
2097
|
-
process.exit(1);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
const segment = await client.segments.update(id, updateData);
|
|
2101
|
-
if (options.format === "json") {
|
|
2102
|
-
output(segment, "json");
|
|
2103
|
-
} else {
|
|
2104
|
-
success("Segment updated successfully!");
|
|
2105
|
-
output({
|
|
2106
|
-
ID: segment.id,
|
|
2107
|
-
Name: segment.name
|
|
2108
|
-
}, "table");
|
|
2109
|
-
}
|
|
2110
|
-
} catch (err) {
|
|
2111
|
-
error(err instanceof Error ? err.message : "Failed to update segment");
|
|
2112
|
-
process.exit(1);
|
|
2113
|
-
}
|
|
2114
|
-
});
|
|
2115
|
-
cmd.command("delete <id>").description("Delete a segment").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
2116
|
-
try {
|
|
2117
|
-
const config = loadConfig();
|
|
2118
|
-
const client = new Emailr7({
|
|
2119
|
-
apiKey: config.apiKey,
|
|
2120
|
-
baseUrl: config.baseUrl
|
|
2121
|
-
});
|
|
2122
|
-
const result = await client.segments.delete(id);
|
|
2123
|
-
if (options.format === "json") {
|
|
2124
|
-
output(result, "json");
|
|
2125
|
-
} else {
|
|
2126
|
-
success("Segment deleted successfully!");
|
|
2127
|
-
}
|
|
2128
|
-
} catch (err) {
|
|
2129
|
-
error(err instanceof Error ? err.message : "Failed to delete segment");
|
|
2130
|
-
process.exit(1);
|
|
2131
|
-
}
|
|
2132
|
-
});
|
|
2133
|
-
cmd.command("count <id>").description("Get the number of contacts in a segment").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
2134
|
-
try {
|
|
2135
|
-
const config = loadConfig();
|
|
2136
|
-
const client = new Emailr7({
|
|
2137
|
-
apiKey: config.apiKey,
|
|
2138
|
-
baseUrl: config.baseUrl
|
|
2139
|
-
});
|
|
2140
|
-
const result = await client.segments.getContactsCount(id);
|
|
2141
|
-
if (options.format === "json") {
|
|
2142
|
-
output(result, "json");
|
|
2143
|
-
} else {
|
|
2144
|
-
console.log(`Segment contains ${result.count} contacts.`);
|
|
2145
|
-
}
|
|
2146
|
-
} catch (err) {
|
|
2147
|
-
error(err instanceof Error ? err.message : "Failed to get segment count");
|
|
2148
|
-
process.exit(1);
|
|
2149
|
-
}
|
|
2150
|
-
});
|
|
2151
|
-
return cmd;
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
// src/commands/login.ts
|
|
2155
|
-
import { Command as Command9 } from "commander";
|
|
2156
|
-
|
|
2157
|
-
// src/server/callback.ts
|
|
2158
|
-
import http3 from "http";
|
|
2159
|
-
import { URL } from "url";
|
|
2160
|
-
function parseCallbackParams(url) {
|
|
2161
|
-
try {
|
|
2162
|
-
const fullUrl = url.startsWith("http") ? url : `http://localhost${url}`;
|
|
2163
|
-
const parsed = new URL(fullUrl);
|
|
2164
|
-
return {
|
|
2165
|
-
key: parsed.searchParams.get("key") ?? void 0,
|
|
2166
|
-
state: parsed.searchParams.get("state") ?? void 0,
|
|
2167
|
-
error: parsed.searchParams.get("error") ?? void 0,
|
|
2168
|
-
message: parsed.searchParams.get("message") ?? void 0
|
|
2169
|
-
};
|
|
2170
|
-
} catch {
|
|
2171
|
-
return {};
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
function generateSuccessHtml() {
|
|
2175
|
-
return `<!DOCTYPE html>
|
|
326
|
+
`,y=r("node",["-e",g],{detached:!0,stdio:["ignore","pipe","ignore"]}),M="";await new Promise(x=>{y.stdout?.on("data",_=>{let K=_.toString().match(/PORT:(\d+)/);K&&(M=K[1],x());}),setTimeout(x,3e3);}),y.unref();let H=`http://127.0.0.1:${M}/`;if(console.log(`
|
|
327
|
+
Live Preview: ${H}`),console.log(`File: ${e}`),console.log(`
|
|
328
|
+
Preview server running in background (PID: ${y.pid}).`),console.log("Edit the file and see live updates in the browser."),console.log(`
|
|
329
|
+
When done:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),console.log(" emailr templates stop-preview"),t.open!==!1)try{await(await import('open')).default(H);}catch{}process.exit(0);}let a=`http://127.0.0.1:${await Y(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
|
|
330
|
+
Live Preview: ${a}`),console.log(`File: ${e}`),console.log(`
|
|
331
|
+
Watching for changes... Edit the file and see live updates.`),console.log(`
|
|
332
|
+
When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),console.log(`
|
|
333
|
+
Press Ctrl+C to stop.`),t.open!==!1)try{await(await import('open')).default(a);}catch{}process.on("SIGINT",async()=>{console.log(`
|
|
334
|
+
|
|
335
|
+
Stopping live preview server...`),await Q(),console.log("Done."),console.log(`
|
|
336
|
+
To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});}catch(e){c(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),n.command("stop-preview").description("Stop any running background preview server").action(async()=>{let t=await import('os'),e=N.join(t.tmpdir(),"emailr-preview.pid");try{let o=S.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),S.unlinkSync(e),d("Preview server stopped");}catch{d("No preview server running");}}),n}function Ce(){let n=new Command("domains").description("Manage sending domains");return n.command("list").description("List all domains").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(t.format==="json")l(i,"json");else {let a=i.map(r=>({ID:r.id,Domain:r.domain,Status:r.status,DKIM:r.dkim_verified,SPF:r.spf_verified,DMARC:r.dmarc_verified,Created:r.created_at}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),n.command("get <id>").description("Get a domain by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.get(t);l(a,e.format);}catch(o){c(o instanceof Error?o.message:"Failed to get domain"),process.exit(1);}}),n.command("add <domain>").description("Add a new domain").option("--receiving-subdomain <subdomain>","Subdomain for receiving emails").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={domain:t};e.receivingSubdomain&&(a.receivingSubdomain=e.receivingSubdomain);let r=await i.domains.add(a);if(e.format==="json")l(r,"json");else {if(d(`Domain added: ${r.domain}`),p("Add the following DNS records to verify your domain:"),console.log(""),r.dns_records){let s=[{Type:"DKIM",...X(r.dns_records.dkim)},{Type:"SPF",...X(r.dns_records.spf)},{Type:"DMARC",...X(r.dns_records.dmarc)}];l(s,"table");}console.log(""),p(`Run 'emailr domains verify ${r.id}' after adding DNS records`);}}catch(o){c(o instanceof Error?o.message:"Failed to add domain"),process.exit(1);}}),n.command("verify <id>").description("Verify a domain").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.verify(t);e.format==="json"?l(a,"json"):a.verified?d("Domain verified successfully!"):(p(`Domain status: ${a.status}`),a.dkim_status&&p(`DKIM status: ${a.dkim_status}`));}catch(o){c(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),n.command("check-dns <id>").description("Check DNS records for a domain").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.checkDns(t);if(e.format==="json")l(a,"json");else {let r=Object.entries(a).map(([s,f])=>({Record:s,Verified:f.verified,Expected:f.expected||"-",Found:f.found?.join(", ")||"-"}));l(r,"table");}}catch(o){c(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),n.command("delete <id>").description("Delete a domain").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(t),d(`Domain deleted: ${t}`);}catch(e){c(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),n}function X(n){return {"Record Type":n.type,Name:n.name,Value:n.value.length>50?n.value.substring(0,47)+"...":n.value,...n.priority!==void 0&&{Priority:n.priority}}}function ke(){let n=new Command("config").description("Manage CLI configuration");return n.command("set <key> <value>").description("Set a configuration value").action(async(t,e)=>{try{let o=["api-key","base-url","format"],i=t.toLowerCase();o.includes(i)||(c(`Invalid config key: ${t}`),p(`Valid keys: ${o.join(", ")}`),process.exit(1));let r={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[i];i==="format"&&!["json","table"].includes(e)&&(c(`Invalid format value: ${e}`),p("Valid formats: json, table"),process.exit(1)),R({[r]:e}),d(`Configuration saved: ${t} = ${i==="api-key"?"***":e}`),p(`Config file: ${w()}`);}catch(o){c(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),n.command("get <key>").description("Get a configuration value").action(async t=>{try{let e=["api-key","base-url","format"],o=t.toLowerCase();e.includes(o)||(c(`Invalid config key: ${t}`),p(`Valid keys: ${e.join(", ")}`),process.exit(1));let a={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],r=re(a);r?console.log(o==="api-key"?r.substring(0,8)+"..."+r.substring(r.length-4):r):p(`${t} is not set`);}catch(e){c(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),n.command("list").description("List all configuration values").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o={"api-key":e.apiKey?e.apiKey.substring(0,8)+"..."+e.apiKey.substring(e.apiKey.length-4):"(not set)","base-url":e.baseUrl||"(default)",format:e.format||"table"};if(t.format==="json")l(o,"json");else {let i=Object.entries(o).map(([a,r])=>({Key:a,Value:r}));l(i,"table");}console.log(""),p(`Config file: ${w()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(p("No configuration found."),p("Run 'emailr config set api-key <your-api-key>' to get started.")):(c(e instanceof Error?e.message:"Failed to list configuration"),process.exit(1));}}),n.command("path").description("Show the configuration file path").action(()=>{console.log(w());}),n.command("init").description("Initialize configuration interactively").option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async t=>{try{t.apiKey?(R({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),p(`Config file: ${w()}`)):(p("Initialize your Emailr CLI configuration:"),console.log(""),p("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),p("Or set environment variable:"),console.log(" export EMAILR_API_KEY=<your-api-key>"));}catch(e){c(e instanceof Error?e.message:"Failed to initialize configuration"),process.exit(1);}}),n}function je(){let n=new Command("broadcasts").description("Manage broadcast campaigns");return n.command("list").description("List all broadcasts").option("--status <status>","Filter by status (draft, scheduled, sending, sent)").option("--limit <number>","Number of broadcasts to return","20").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.list({status:t.status,limit:parseInt(t.limit)});if(t.format==="json")l(i,"json");else {if(i.length===0){console.log("No broadcasts found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,Subject:r.subject.substring(0,30)+(r.subject.length>30?"...":""),Status:r.status,Recipients:r.total_recipients||0,Sent:r.sent_count||0,Created:new Date(r.created_at).toLocaleDateString()}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),n.command("get <id>").description("Get broadcast details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.get(t);e.format==="json"?l(a,"json"):l({ID:a.id,Name:a.name,Subject:a.subject,"From Email":a.from_email,Status:a.status,"Total Recipients":a.total_recipients||0,"Sent Count":a.sent_count||0,Delivered:a.delivered_count||0,Opened:a.opened_count||0,Clicked:a.clicked_count||0,Bounced:a.bounced_count||0,"Scheduled At":a.scheduled_at||"N/A","Started At":a.started_at||"N/A","Completed At":a.completed_at||"N/A","Created At":a.created_at},"table");}catch(o){c(o instanceof Error?o.message:"Failed to get broadcast"),process.exit(1);}}),n.command("create").description("Create a new broadcast").requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--template <id>","Template ID to use").option("--segment <id>","Segment ID to target").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.create({name:t.name,subject:t.subject,from_email:t.from,template_id:t.template,segment_id:t.segment,html_content:t.html,text_content:t.text,scheduled_at:t.schedule});t.format==="json"?l(i,"json"):(d("Broadcast created successfully!"),l({ID:i.id,Name:i.name,Status:i.status,"Scheduled At":i.scheduled_at||"Not scheduled"},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),n.command("send <id>").description("Send a broadcast immediately").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.send(t);e.format==="json"?l(a,"json"):(d("Broadcast sent successfully!"),l({Success:a.success,Sent:a.sent,Total:a.total},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to send broadcast"),process.exit(1);}}),n.command("schedule <id>").description("Schedule a broadcast").requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.schedule(t,e.at);e.format==="json"?l(a,"json"):(d("Broadcast scheduled successfully!"),l({ID:a.id,Name:a.name,Status:a.status,"Scheduled At":a.scheduled_at},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to schedule broadcast"),process.exit(1);}}),n.command("cancel <id>").description("Cancel a scheduled broadcast").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.cancel(t);e.format==="json"?l(a,"json"):d("Broadcast cancelled successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to cancel broadcast"),process.exit(1);}}),n.command("delete <id>").description("Delete a broadcast").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.delete(t);e.format==="json"?l(a,"json"):d("Broadcast deleted successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to delete broadcast"),process.exit(1);}}),n}function Ee(){let n=new Command("webhooks").description("Manage webhooks");return n.command("list").description("List all webhooks").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(t.format==="json")l(i,"json");else {if(i.length===0){console.log("No webhooks found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,URL:r.url.substring(0,40)+(r.url.length>40?"...":""),Events:r.events.join(", "),Active:r.active?"Yes":"No"}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),n.command("get <id>").description("Get webhook details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.get(t);e.format==="json"?l(a,"json"):l({ID:a.id,Name:a.name,URL:a.url,Events:a.events.join(", "),Active:a.active?"Yes":"No",Secret:a.secret,"Created At":a.created_at},"table");}catch(o){c(o instanceof Error?o.message:"Failed to get webhook"),process.exit(1);}}),n.command("create").description("Create a new webhook").requiredOption("--name <name>","Webhook name").requiredOption("--url <url>","Webhook URL").requiredOption("--events <events>","Events to subscribe to (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i=t.events.split(",").map(r=>r.trim()),a=await o.webhooks.create({name:t.name,url:t.url,events:i});t.format==="json"?l(a,"json"):(d("Webhook created successfully!"),l({ID:a.id,Name:a.name,URL:a.url,Secret:a.secret},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),n.command("update <id>").description("Update a webhook").option("--name <name>","New webhook name").option("--url <url>","New webhook URL").option("--events <events>","New events (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name),e.url&&(a.url=e.url),e.events&&(a.events=e.events.split(",").map(s=>s.trim()));let r=await i.webhooks.update(t,a);e.format==="json"?l(r,"json"):(d("Webhook updated successfully!"),l({ID:r.id,Name:r.name,URL:r.url},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to update webhook"),process.exit(1);}}),n.command("enable <id>").description("Enable a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.enable(t);e.format==="json"?l(a,"json"):d("Webhook enabled successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to enable webhook"),process.exit(1);}}),n.command("disable <id>").description("Disable a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.disable(t);e.format==="json"?l(a,"json"):d("Webhook disabled successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to disable webhook"),process.exit(1);}}),n.command("delete <id>").description("Delete a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.delete(t);e.format==="json"?l(a,"json"):d("Webhook deleted successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),n}function Pe(){let n=new Command("segments").description("Manage contact segments");return n.command("list").description("List all segments").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).segments.list();if(t.format==="json")l(i,"json");else {if(i.length===0){console.log("No segments found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,Description:(r.description||"").substring(0,30)+((r.description?.length||0)>30?"...":""),"Created At":new Date(r.created_at).toLocaleDateString()}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),n.command("get <id>").description("Get segment details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.get(t);e.format==="json"?l(a,"json"):l({ID:a.id,Name:a.name,Description:a.description||"N/A",Conditions:JSON.stringify(a.conditions),"Created At":a.created_at,"Updated At":a.updated_at},"table");}catch(o){c(o instanceof Error?o.message:"Failed to get segment"),process.exit(1);}}),n.command("create").description("Create a new segment").requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i;try{i=JSON.parse(t.conditions);}catch{c("Invalid JSON for --conditions"),process.exit(1);}let a=await o.segments.create({name:t.name,description:t.description,conditions:i});t.format==="json"?l(a,"json"):(d("Segment created successfully!"),l({ID:a.id,Name:a.name},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create segment"),process.exit(1);}}),n.command("update <id>").description("Update a segment").option("--name <name>","New segment name").option("--description <description>","New description").option("--conditions <json>","New conditions as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.description&&(a.description=e.description),e.conditions)try{a.conditions=JSON.parse(e.conditions);}catch{c("Invalid JSON for --conditions"),process.exit(1);}let r=await i.segments.update(t,a);e.format==="json"?l(r,"json"):(d("Segment updated successfully!"),l({ID:r.id,Name:r.name},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),n.command("delete <id>").description("Delete a segment").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.delete(t);e.format==="json"?l(a,"json"):d("Segment deleted successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to delete segment"),process.exit(1);}}),n.command("count <id>").description("Get the number of contacts in a segment").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.getContactsCount(t);e.format==="json"?l(a,"json"):console.log(`Segment contains ${a.count} contacts.`);}catch(o){c(o instanceof Error?o.message:"Failed to get segment count"),process.exit(1);}}),n}function st(n){try{let t=n.startsWith("http")?n:`http://localhost${n}`,e=new URL(t);return {key:e.searchParams.get("key")??void 0,state:e.searchParams.get("state")??void 0,error:e.searchParams.get("error")??void 0,message:e.searchParams.get("message")??void 0}}catch{return {}}}function lt(){return `<!DOCTYPE html>
|
|
2176
337
|
<html lang="en">
|
|
2177
338
|
<head>
|
|
2178
339
|
<meta charset="UTF-8">
|
|
@@ -2218,11 +379,7 @@ function generateSuccessHtml() {
|
|
|
2218
379
|
<p>You can close this window and return to your terminal.</p>
|
|
2219
380
|
</div>
|
|
2220
381
|
</body>
|
|
2221
|
-
</html
|
|
2222
|
-
}
|
|
2223
|
-
function generateErrorHtml(errorMessage) {
|
|
2224
|
-
const escapedMessage = errorMessage.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2225
|
-
return `<!DOCTYPE html>
|
|
382
|
+
</html>`}function Z(n){return `<!DOCTYPE html>
|
|
2226
383
|
<html lang="en">
|
|
2227
384
|
<head>
|
|
2228
385
|
<meta charset="UTF-8">
|
|
@@ -2273,230 +430,11 @@ function generateErrorHtml(errorMessage) {
|
|
|
2273
430
|
<div class="icon">\u2717</div>
|
|
2274
431
|
<h1>Login Failed</h1>
|
|
2275
432
|
<p>Something went wrong during authentication.</p>
|
|
2276
|
-
<div class="error-message">${
|
|
433
|
+
<div class="error-message">${n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}</div>
|
|
2277
434
|
<p style="margin-top: 1rem;">Please close this window and try again.</p>
|
|
2278
435
|
</div>
|
|
2279
436
|
</body>
|
|
2280
|
-
</html
|
|
2281
|
-
}
|
|
2282
|
-
function createCallbackServer() {
|
|
2283
|
-
let server = null;
|
|
2284
|
-
let port = 0;
|
|
2285
|
-
let callbackPromiseResolve = null;
|
|
2286
|
-
let timeoutId = null;
|
|
2287
|
-
let expectedState = null;
|
|
2288
|
-
return {
|
|
2289
|
-
async start() {
|
|
2290
|
-
return new Promise((resolve, reject) => {
|
|
2291
|
-
server = http3.createServer((req, res) => {
|
|
2292
|
-
if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
|
|
2293
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2294
|
-
res.end("Not Found");
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
|
-
const params = parseCallbackParams(req.url);
|
|
2298
|
-
if (expectedState && params.state !== expectedState) {
|
|
2299
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
2300
|
-
res.end(generateErrorHtml("Security verification failed. State parameter mismatch."));
|
|
2301
|
-
if (callbackPromiseResolve) {
|
|
2302
|
-
callbackPromiseResolve({
|
|
2303
|
-
success: false,
|
|
2304
|
-
error: "State parameter mismatch"
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
|
-
return;
|
|
2308
|
-
}
|
|
2309
|
-
if (params.error) {
|
|
2310
|
-
const errorMessage = params.message || params.error;
|
|
2311
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
2312
|
-
res.end(generateErrorHtml(errorMessage));
|
|
2313
|
-
if (callbackPromiseResolve) {
|
|
2314
|
-
callbackPromiseResolve({
|
|
2315
|
-
success: false,
|
|
2316
|
-
error: errorMessage
|
|
2317
|
-
});
|
|
2318
|
-
}
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
if (params.key) {
|
|
2322
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
2323
|
-
res.end(generateSuccessHtml());
|
|
2324
|
-
if (callbackPromiseResolve) {
|
|
2325
|
-
callbackPromiseResolve({
|
|
2326
|
-
success: true,
|
|
2327
|
-
apiKey: params.key
|
|
2328
|
-
});
|
|
2329
|
-
}
|
|
2330
|
-
return;
|
|
2331
|
-
}
|
|
2332
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
2333
|
-
res.end(generateErrorHtml("Invalid callback: missing required parameters."));
|
|
2334
|
-
if (callbackPromiseResolve) {
|
|
2335
|
-
callbackPromiseResolve({
|
|
2336
|
-
success: false,
|
|
2337
|
-
error: "Invalid callback: missing required parameters"
|
|
2338
|
-
});
|
|
2339
|
-
}
|
|
2340
|
-
});
|
|
2341
|
-
server.listen(0, "127.0.0.1", () => {
|
|
2342
|
-
const address = server.address();
|
|
2343
|
-
if (address && typeof address === "object") {
|
|
2344
|
-
port = address.port;
|
|
2345
|
-
resolve({
|
|
2346
|
-
port,
|
|
2347
|
-
url: `http://127.0.0.1:${port}/callback`
|
|
2348
|
-
});
|
|
2349
|
-
} else {
|
|
2350
|
-
reject(new Error("Failed to get server address"));
|
|
2351
|
-
}
|
|
2352
|
-
});
|
|
2353
|
-
server.on("error", (err) => {
|
|
2354
|
-
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
2355
|
-
});
|
|
2356
|
-
});
|
|
2357
|
-
},
|
|
2358
|
-
async waitForCallback(state, timeoutMs) {
|
|
2359
|
-
expectedState = state;
|
|
2360
|
-
return new Promise((resolve) => {
|
|
2361
|
-
callbackPromiseResolve = resolve;
|
|
2362
|
-
timeoutId = setTimeout(() => {
|
|
2363
|
-
if (callbackPromiseResolve) {
|
|
2364
|
-
callbackPromiseResolve({
|
|
2365
|
-
success: false,
|
|
2366
|
-
error: "Login timed out. Please try again."
|
|
2367
|
-
});
|
|
2368
|
-
}
|
|
2369
|
-
}, timeoutMs);
|
|
2370
|
-
});
|
|
2371
|
-
},
|
|
2372
|
-
async stop() {
|
|
2373
|
-
if (timeoutId) {
|
|
2374
|
-
clearTimeout(timeoutId);
|
|
2375
|
-
timeoutId = null;
|
|
2376
|
-
}
|
|
2377
|
-
if (server) {
|
|
2378
|
-
return new Promise((resolve) => {
|
|
2379
|
-
server.close(() => {
|
|
2380
|
-
server = null;
|
|
2381
|
-
resolve();
|
|
2382
|
-
});
|
|
2383
|
-
});
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
// src/utils/state.ts
|
|
2390
|
-
import crypto from "crypto";
|
|
2391
|
-
function generateState() {
|
|
2392
|
-
return crypto.randomBytes(32).toString("hex");
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
// src/utils/browser.ts
|
|
2396
|
-
import { exec } from "child_process";
|
|
2397
|
-
function openBrowser(url) {
|
|
2398
|
-
return new Promise((resolve) => {
|
|
2399
|
-
const platform = process.platform;
|
|
2400
|
-
let command;
|
|
2401
|
-
switch (platform) {
|
|
2402
|
-
case "darwin":
|
|
2403
|
-
command = `open "${url}"`;
|
|
2404
|
-
break;
|
|
2405
|
-
case "win32":
|
|
2406
|
-
command = `start "" "${url}"`;
|
|
2407
|
-
break;
|
|
2408
|
-
default:
|
|
2409
|
-
command = `xdg-open "${url}"`;
|
|
2410
|
-
break;
|
|
2411
|
-
}
|
|
2412
|
-
exec(command, (error2) => {
|
|
2413
|
-
if (error2) {
|
|
2414
|
-
resolve(false);
|
|
2415
|
-
} else {
|
|
2416
|
-
resolve(true);
|
|
2417
|
-
}
|
|
2418
|
-
});
|
|
2419
|
-
});
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
// src/commands/login.ts
|
|
2423
|
-
var DEFAULT_TIMEOUT_SECONDS = 120;
|
|
2424
|
-
var WEB_APP_BASE_URL = process.env.EMAILR_WEB_URL || "https://app.emailr.dev";
|
|
2425
|
-
function buildAuthorizationUrl(state, callbackPort) {
|
|
2426
|
-
const callbackUrl = `http://127.0.0.1:${callbackPort}/callback`;
|
|
2427
|
-
const params = new URLSearchParams({
|
|
2428
|
-
state,
|
|
2429
|
-
callback_url: callbackUrl
|
|
2430
|
-
});
|
|
2431
|
-
return `${WEB_APP_BASE_URL}/cli/authorize?${params.toString()}`;
|
|
2432
|
-
}
|
|
2433
|
-
function createLoginCommand() {
|
|
2434
|
-
const cmd = new Command9("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>", "Timeout in seconds", String(DEFAULT_TIMEOUT_SECONDS)).option("--no-browser", "Don't automatically open the browser").action(async (options) => {
|
|
2435
|
-
await executeLogin({
|
|
2436
|
-
timeout: parseInt(options.timeout, 10) || DEFAULT_TIMEOUT_SECONDS,
|
|
2437
|
-
noBrowser: options.browser === false
|
|
2438
|
-
});
|
|
2439
|
-
});
|
|
2440
|
-
return cmd;
|
|
2441
|
-
}
|
|
2442
|
-
async function executeLogin(options) {
|
|
2443
|
-
const callbackServer = createCallbackServer();
|
|
2444
|
-
const timeoutMs = (options.timeout || DEFAULT_TIMEOUT_SECONDS) * 1e3;
|
|
2445
|
-
try {
|
|
2446
|
-
info("Starting authentication server...");
|
|
2447
|
-
const { port, url } = await callbackServer.start();
|
|
2448
|
-
const state = generateState();
|
|
2449
|
-
const authUrl = buildAuthorizationUrl(state, port);
|
|
2450
|
-
console.log("");
|
|
2451
|
-
info("Authorization URL:");
|
|
2452
|
-
console.log(` ${authUrl}`);
|
|
2453
|
-
console.log("");
|
|
2454
|
-
if (!options.noBrowser) {
|
|
2455
|
-
const browserOpened = await openBrowser(authUrl);
|
|
2456
|
-
if (browserOpened) {
|
|
2457
|
-
info("Browser opened. Please complete authentication in your browser.");
|
|
2458
|
-
} else {
|
|
2459
|
-
warn("Could not open browser automatically.");
|
|
2460
|
-
info("Please open the URL above in your browser to continue.");
|
|
2461
|
-
}
|
|
2462
|
-
} else {
|
|
2463
|
-
info("Please open the URL above in your browser to continue.");
|
|
2464
|
-
}
|
|
2465
|
-
console.log("");
|
|
2466
|
-
info(`Waiting for authentication (timeout: ${options.timeout || DEFAULT_TIMEOUT_SECONDS}s)...`);
|
|
2467
|
-
const result = await callbackServer.waitForCallback(state, timeoutMs);
|
|
2468
|
-
if (result.success && result.apiKey) {
|
|
2469
|
-
saveConfig({ apiKey: result.apiKey });
|
|
2470
|
-
console.log("");
|
|
2471
|
-
success("Login successful!");
|
|
2472
|
-
info(`API key saved to: ${getConfigPath()}`);
|
|
2473
|
-
info("You can now use the Emailr CLI.");
|
|
2474
|
-
} else {
|
|
2475
|
-
console.log("");
|
|
2476
|
-
error(result.error || "Authentication failed.");
|
|
2477
|
-
info("Please try again or use manual configuration:");
|
|
2478
|
-
console.log(" emailr config set api-key <your-api-key>");
|
|
2479
|
-
process.exit(1);
|
|
2480
|
-
}
|
|
2481
|
-
} catch (err) {
|
|
2482
|
-
console.log("");
|
|
2483
|
-
error(err instanceof Error ? err.message : "An unexpected error occurred.");
|
|
2484
|
-
info("Please try again or use manual configuration:");
|
|
2485
|
-
console.log(" emailr config set api-key <your-api-key>");
|
|
2486
|
-
process.exit(1);
|
|
2487
|
-
} finally {
|
|
2488
|
-
await callbackServer.stop();
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// src/commands/agent.ts
|
|
2493
|
-
import { Command as Command10 } from "commander";
|
|
2494
|
-
import { spawn, execSync } from "child_process";
|
|
2495
|
-
import fs5 from "fs";
|
|
2496
|
-
import path5 from "path";
|
|
2497
|
-
import os3 from "os";
|
|
2498
|
-
var OPENCODE_SKILL_DIR = path5.join(os3.homedir(), ".config", "opencode", "skills", "emailr-cli");
|
|
2499
|
-
var SKILL_MD = `---
|
|
437
|
+
</html>`}function Te(){let n=null,t=0,e=null,o=null,i=null;return {async start(){return new Promise((a,r)=>{n=Be.createServer((s,f)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){f.writeHead(404,{"Content-Type":"text/plain"}),f.end("Not Found");return}let g=st(s.url);if(i&&g.state!==i){f.writeHead(400,{"Content-Type":"text/html"}),f.end(Z("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(g.error){let y=g.message||g.error;f.writeHead(200,{"Content-Type":"text/html"}),f.end(Z(y)),e&&e({success:false,error:y});return}if(g.key){f.writeHead(200,{"Content-Type":"text/html"}),f.end(lt()),e&&e({success:true,apiKey:g.key});return}f.writeHead(400,{"Content-Type":"text/html"}),f.end(Z("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),n.listen(0,"127.0.0.1",()=>{let s=n.address();s&&typeof s=="object"?(t=s.port,a({port:t,url:`http://127.0.0.1:${t}/callback`})):r(new Error("Failed to get server address"));}),n.on("error",s=>{r(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(a,r){return i=a,new Promise(s=>{e=s,o=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},r);})},async stop(){if(o&&(clearTimeout(o),o=null),n)return new Promise(a=>{n.close(()=>{n=null,a();});})}}}function Ie(){return ct.randomBytes(32).toString("hex")}function Fe(n){return new Promise(t=>{let e=process.platform,o;switch(e){case "darwin":o=`open "${n}"`;break;case "win32":o=`start "" "${n}"`;break;default:o=`xdg-open "${n}"`;break}exec(o,i=>{t(!i);});})}var B=120,pt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function ft(n,t){let e=`http://127.0.0.1:${t}/callback`,o=new URLSearchParams({state:n,callback_url:e});return `${pt}/consent/authorize?${o.toString()}`}function Ue(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String(B)).option("--no-browser","Don't automatically open the browser").action(async t=>{await ut({timeout:parseInt(t.timeout,10)||B,noBrowser:t.browser===false});})}async function ut(n){let t=Te(),e=(n.timeout||B)*1e3;try{p("Starting authentication server...");let{port:o,url:i}=await t.start(),a=Ie(),r=ft(a,o);console.log(""),p("Authorization URL:"),console.log(` ${r}`),console.log(""),n.noBrowser?p("Please open the URL above in your browser to continue."):await Fe(r)?p("Browser opened. Please complete authentication in your browser."):(v("Could not open browser automatically."),p("Please open the URL above in your browser to continue.")),console.log(""),p(`Waiting for authentication (timeout: ${n.timeout||B}s)...`);let s=await t.waitForCallback(a,e);s.success&&s.apiKey?(R({apiKey:s.apiKey}),console.log(""),d("Login successful!"),p(`API key saved to: ${w()}`),p("You can now use the Emailr CLI.")):(console.log(""),c(s.error||"Authentication failed."),p("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1));}catch(o){console.log(""),c(o instanceof Error?o.message:"An unexpected error occurred."),p("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1);}finally{await t.stop();}}var te=N.join(W.homedir(),".config","opencode","skills","emailr-cli"),yt=`---
|
|
2500
438
|
name: emailr-cli
|
|
2501
439
|
description: Operate the Emailr CLI to send emails, manage contacts, templates, domains, broadcasts, webhooks, and segments. Includes LIVE PREVIEW editing for templates with hot-reload.
|
|
2502
440
|
---
|
|
@@ -2536,41 +474,7 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
|
|
|
2536
474
|
- Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
|
|
2537
475
|
- Update: \`emailr templates update <id> --html-file ./template.html\`
|
|
2538
476
|
- Delete: \`emailr templates delete <id>\`
|
|
2539
|
-
`;
|
|
2540
|
-
function isOpenCodeInstalled() {
|
|
2541
|
-
try {
|
|
2542
|
-
execSync("which opencode", { stdio: "ignore" });
|
|
2543
|
-
return true;
|
|
2544
|
-
} catch {
|
|
2545
|
-
return false;
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
function installOpenCode() {
|
|
2549
|
-
console.log("Installing OpenCode AI agent...");
|
|
2550
|
-
try {
|
|
2551
|
-
execSync("npm install -g opencode-ai@latest", { stdio: "inherit" });
|
|
2552
|
-
return true;
|
|
2553
|
-
} catch {
|
|
2554
|
-
if (process.platform === "darwin") {
|
|
2555
|
-
try {
|
|
2556
|
-
execSync("brew install anomalyco/tap/opencode", { stdio: "inherit" });
|
|
2557
|
-
return true;
|
|
2558
|
-
} catch {
|
|
2559
|
-
return false;
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
return false;
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
function ensureSkillInstalled() {
|
|
2566
|
-
if (!fs5.existsSync(OPENCODE_SKILL_DIR)) {
|
|
2567
|
-
fs5.mkdirSync(OPENCODE_SKILL_DIR, { recursive: true });
|
|
2568
|
-
}
|
|
2569
|
-
const skillPath = path5.join(OPENCODE_SKILL_DIR, "SKILL.md");
|
|
2570
|
-
fs5.writeFileSync(skillPath, SKILL_MD, "utf-8");
|
|
2571
|
-
}
|
|
2572
|
-
function createAgentCommand() {
|
|
2573
|
-
const cmd = new Command10("agent").description(`Launch an AI agent with Emailr CLI expertise
|
|
477
|
+
`;function wt(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function vt(){console.log("Installing OpenCode AI agent...");try{return execSync("npm install -g opencode-ai@latest",{stdio:"inherit"}),!0}catch{if(process.platform==="darwin")try{return execSync("brew install anomalyco/tap/opencode",{stdio:"inherit"}),!0}catch{return false}return false}}function xt(){S.existsSync(te)||S.mkdirSync(te,{recursive:true});let n=N.join(te,"SKILL.md");S.writeFileSync(n,yt,"utf-8");}function _e(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
|
|
2574
478
|
|
|
2575
479
|
This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
|
|
2576
480
|
The agent can help you:
|
|
@@ -2578,65 +482,7 @@ The agent can help you:
|
|
|
2578
482
|
- Manage contacts, broadcasts, and segments
|
|
2579
483
|
- Configure domains and webhooks
|
|
2580
484
|
|
|
2581
|
-
The agent runs in your terminal with full access to the emailr CLI.`).option("--install",
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
if (!installed) {
|
|
2586
|
-
error("Failed to install OpenCode. Please install manually:");
|
|
2587
|
-
console.log(" npm install -g opencode-ai@latest");
|
|
2588
|
-
console.log(" # or");
|
|
2589
|
-
console.log(" brew install anomalyco/tap/opencode");
|
|
2590
|
-
process.exit(1);
|
|
2591
|
-
}
|
|
2592
|
-
} else {
|
|
2593
|
-
error("OpenCode AI agent is not installed.");
|
|
2594
|
-
console.log("\nInstall it with one of:");
|
|
2595
|
-
console.log(" emailr agent --install");
|
|
2596
|
-
console.log(" npm install -g opencode-ai@latest");
|
|
2597
|
-
console.log(" brew install anomalyco/tap/opencode");
|
|
2598
|
-
process.exit(1);
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
try {
|
|
2602
|
-
ensureSkillInstalled();
|
|
2603
|
-
success("Emailr CLI skill loaded");
|
|
2604
|
-
} catch (err) {
|
|
2605
|
-
warn(`Could not install skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
2606
|
-
}
|
|
2607
|
-
const args = [];
|
|
2608
|
-
if (options.model) {
|
|
2609
|
-
args.push("--model", options.model);
|
|
2610
|
-
}
|
|
2611
|
-
console.log("\nStarting Emailr AI Agent...");
|
|
2612
|
-
console.log("The agent has the emailr-cli skill loaded.");
|
|
2613
|
-
console.log("Ask it to help with emails, templates, contacts, or broadcasts.\n");
|
|
2614
|
-
const opencode = spawn("opencode", args, {
|
|
2615
|
-
stdio: "inherit",
|
|
2616
|
-
env: process.env
|
|
2617
|
-
});
|
|
2618
|
-
opencode.on("error", (err) => {
|
|
2619
|
-
error(`Failed to start agent: ${err.message}`);
|
|
2620
|
-
process.exit(1);
|
|
2621
|
-
});
|
|
2622
|
-
opencode.on("exit", (code) => {
|
|
2623
|
-
process.exit(code ?? 0);
|
|
2624
|
-
});
|
|
2625
|
-
});
|
|
2626
|
-
return cmd;
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
// src/index.ts
|
|
2630
|
-
var program = new Command11();
|
|
2631
|
-
program.name("emailr").description("Emailr CLI - Send emails and manage your email infrastructure").version("1.0.0");
|
|
2632
|
-
program.addCommand(createSendCommand());
|
|
2633
|
-
program.addCommand(createContactsCommand());
|
|
2634
|
-
program.addCommand(createTemplatesCommand());
|
|
2635
|
-
program.addCommand(createDomainsCommand());
|
|
2636
|
-
program.addCommand(createBroadcastsCommand());
|
|
2637
|
-
program.addCommand(createWebhooksCommand());
|
|
2638
|
-
program.addCommand(createSegmentsCommand());
|
|
2639
|
-
program.addCommand(createConfigCommand());
|
|
2640
|
-
program.addCommand(createLoginCommand());
|
|
2641
|
-
program.addCommand(createAgentCommand());
|
|
2642
|
-
program.parse();
|
|
485
|
+
The agent runs in your terminal with full access to the emailr CLI.`).option("--install","Install OpenCode if not already installed").option("--model <model>","Model to use (e.g., anthropic/claude-sonnet-4)").action(async t=>{wt()||(t.install?vt()||(c("Failed to install OpenCode. Please install manually:"),console.log(" npm install -g opencode-ai@latest"),console.log(" # or"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)):(c("OpenCode AI agent is not installed."),console.log(`
|
|
486
|
+
Install it with one of:`),console.log(" emailr agent --install"),console.log(" npm install -g opencode-ai@latest"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)));try{xt(),d("Emailr CLI skill loaded");}catch(i){v(`Could not install skill: ${i instanceof Error?i.message:String(i)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
|
|
487
|
+
Starting Emailr AI Agent...`),console.log("The agent has the emailr-cli skill loaded."),console.log(`Ask it to help with emails, templates, contacts, or broadcasts.
|
|
488
|
+
`);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",i=>{c(`Failed to start agent: ${i.message}`),process.exit(1);}),o.on("exit",i=>{process.exit(i??0);});})}var u=new Command;u.name("emailr").description("Emailr CLI - Send emails and manage your email infrastructure").version("1.0.0");u.addCommand(le());u.addCommand(ce());u.addCommand(Se());u.addCommand(Ce());u.addCommand(je());u.addCommand(Ee());u.addCommand(Pe());u.addCommand(ke());u.addCommand(Ue());u.addCommand(_e());u.parse();
|