emailr-cli 1.5.3 → 1.5.4
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 +150 -136
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command}from'commander';import {Emailr}from'emailr';import
|
|
2
|
+
import {Command}from'commander';import {Emailr}from'emailr';import E from'fs';import G from'os';import U from'path';import ie from'cli-table3';import v from'chalk';import qe from'http';import {URL}from'url';import ct from'crypto';import {spawn,execSync,exec}from'child_process';var _e=[U.join(G.homedir(),".emailrrc"),U.join(G.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 _e)if(E.existsSync(n))try{let t=E.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.
|
|
3
3
|
|
|
4
4
|
Set the EMAILR_API_KEY environment variable:
|
|
5
5
|
export EMAILR_API_KEY=your-api-key
|
|
@@ -7,9 +7,9 @@ Set the EMAILR_API_KEY environment variable:
|
|
|
7
7
|
Or create a config file at ~/.emailrrc:
|
|
8
8
|
{ "apiKey": "your-api-key" }
|
|
9
9
|
|
|
10
|
-
Or run: emailr config set api-key <your-api-key>`)}function
|
|
11
|
-
`);}function re(n){try{return m()[n]?.toString()}catch{return}}function
|
|
12
|
-
Total: ${a.total}`);}}catch(e){
|
|
10
|
+
Or run: emailr config set api-key <your-api-key>`)}function j(){let n=U.join(G.homedir(),".config","emailr");return U.join(n,"config.json")}function A(n){let t=j(),e=U.dirname(t);E.existsSync(e)||E.mkdirSync(e,{recursive:true});let o={};if(E.existsSync(t))try{o=JSON.parse(E.readFileSync(t,"utf-8"));}catch{}let i={...o,...n};E.writeFileSync(t,JSON.stringify(i,null,2)+`
|
|
11
|
+
`);}function re(n){try{return m()[n]?.toString()}catch{return}}function c(n,t="table"){t==="json"?console.log(JSON.stringify(n,null,2)):Le(n);}function Le(n){Array.isArray(n)?Re(n):typeof n=="object"&&n!==null?Ne(n):console.log(n);}function Re(n){if(n.length===0){console.log(v.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=>v.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 Ne(n){let t=new ie({style:{head:[],border:[]}});for(let[e,o]of Object.entries(n))t.push([v.cyan(e),se(o)]);console.log(t.toString());}function se(n){return n==null?v.gray("-"):typeof n=="boolean"?n?v.green("\u2713"):v.red("\u2717"):typeof n=="object"?JSON.stringify(n):String(n)}function d(n){console.log(v.green("\u2713"),n);}function l(n){console.error(v.red("\u2717"),n);}function k(n){console.warn(v.yellow("\u26A0"),n);}function p(n){console.log(v.blue("\u2139"),n);}function ce(){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{l("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"?c(r,"json"):(d("Email sent successfully!"),c({"Message ID":r.message_id,Recipients:r.recipients,Status:r.status,...r.scheduled_at&&{"Scheduled At":r.scheduled_at}},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to send email"),process.exit(1);}})}function le(){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")c(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}));c(r,"table"),console.log(`
|
|
12
|
+
Total: ${a.total}`);}}catch(e){l(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);c(a,e.format);}catch(o){l(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{l("Invalid JSON for --metadata"),process.exit(1);}let a=await o.contacts.create(i);t.format==="json"?c(a,"json"):(d(`Contact created: ${a.id}`),c(a,"table"));}catch(e){l(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{l("Invalid JSON for --metadata"),process.exit(1);}let r=await i.contacts.update(t,a);e.format==="json"?c(r,"json"):(d(`Contact updated: ${r.id}`),c(r,"table"));}catch(o){l(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){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),n}function de(){return U.join(G.homedir(),".config","emailr","templates")}function He(){let n=de();E.existsSync(n)||E.mkdirSync(n,{recursive:true});}function J(n){return U.join(de(),`${n}.html`)}function z(n,t){He();let e=J(n);E.writeFileSync(e,t,"utf-8");}function pe(n){let t=J(n);return E.existsSync(t)?E.readFileSync(t,"utf-8"):null}function fe(n){let t=J(n);return E.existsSync(t)}var y=null,T=null,H=false;function ge(n){return n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function Be(n){return n===null||n.trim()===""}function We(n){return `<!DOCTYPE html>
|
|
13
13
|
<html lang="en">
|
|
14
14
|
<head>
|
|
15
15
|
<meta charset="UTF-8">
|
|
@@ -138,7 +138,7 @@ Total: ${a.total}`);}}catch(e){c(e instanceof Error?e.message:"Failed to list co
|
|
|
138
138
|
<p style="margin-top: 1rem;">Try creating or retrieving the template first using the CLI.</p>
|
|
139
139
|
</div>
|
|
140
140
|
</body>
|
|
141
|
-
</html>`}function
|
|
141
|
+
</html>`}function Ge(n){let t=n.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function Je(){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=Ge(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(Be(i)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(We(o));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(i);}}var be={async start(){return H&&T!==null?T:new Promise((n,t)=>{y=qe.createServer(Je()),y.listen(0,"127.0.0.1",()=>{let e=y.address();e&&typeof e=="object"?(T=e.port,H=true,y.unref(),n(T)):t(new Error("Failed to get server address"));}),y.on("error",e=>{H=false,T=null,y=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return T},isRunning(){return H},async stop(){if(y)return new Promise(n=>{y.close(()=>{y=null,T=null,H=false,n();});})}};function he(){return be}async function V(n){try{return `http://127.0.0.1:${await be.start()}/preview/${n}`}catch{return null}}function ye(){y&&y.ref();}var P=null,Y=null,B=null,L=[],ve=`
|
|
142
142
|
<script>
|
|
143
143
|
(function() {
|
|
144
144
|
const evtSource = new EventSource('/__live-reload');
|
|
@@ -152,103 +152,118 @@ Total: ${a.total}`);}}catch(e){c(e instanceof Error?e.message:"Failed to list co
|
|
|
152
152
|
};
|
|
153
153
|
})();
|
|
154
154
|
</script>
|
|
155
|
-
`;function
|
|
155
|
+
`;function Ye(n){return n.includes("</body>")?n.replace("</body>",`${ve}</body>`):n+ve}function Qe(){L.forEach(n=>{try{n.write(`data: reload
|
|
156
156
|
|
|
157
|
-
`);}catch{}});}async function
|
|
157
|
+
`);}catch{}});}async function Q(n,t){let e=U.resolve(n);return new Promise((o,i)=>{P=qe.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
158
|
|
|
159
|
-
`),
|
|
159
|
+
`),L.push(r),a.on("close",()=>{L=L.filter(s=>s!==r);});return}if(a.method==="GET"){try{let s=E.readFileSync(e,"utf-8"),f=Ye(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");}),P.listen(0,"127.0.0.1",()=>{let a=P.address();if(a&&typeof a=="object"){Y=a.port;let r=null;B=E.watch(e,s=>{s==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{Qe(),t?.();},100));}),o(Y);}else i(new Error("Failed to get server address"));}),P.on("error",a=>{i(new Error(`Failed to start server: ${a.message}`));});})}async function X(){if(B&&(B.close(),B=null),L.forEach(n=>{try{n.end();}catch{}}),L=[],P)return new Promise(n=>{P.close(()=>{P=null,Y=null,n();});})}async function xe(n){try{let t=n.html_content??"";z(n.id,t);}catch(t){return k(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await V(n.id);return t===null?(k("Could not start preview server"),null):t}catch(t){return k(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function Se(){let n=new Command("templates").description(`Manage email templates
|
|
160
160
|
|
|
161
161
|
AGENTIC LIVE EDITING:
|
|
162
162
|
draft Create new template with live preview
|
|
163
163
|
edit <id> Edit existing template with live preview
|
|
164
164
|
|
|
165
165
|
Both commands save HTML to a local file and start a hot-reload server.
|
|
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")
|
|
167
|
-
Total: ${i.length}`);}}catch(e){
|
|
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")c(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}));c(a,"table"),console.log(`
|
|
167
|
+
Total: ${i.length}`);}}catch(e){l(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")c({...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),c(s,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),n.command("create").description(`Create a new template
|
|
168
168
|
|
|
169
169
|
TIP: For live preview while building, use "emailr templates draft" first.
|
|
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")
|
|
171
|
-
Open the Preview URL in your browser to view the rendered template.`);}}catch(e){
|
|
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")c({...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),c(s,"table"),r&&console.log(`
|
|
171
|
+
Open the Preview URL in your browser to view the rendered template.`);}}catch(e){l(e instanceof Error?e.message:"Failed to create template"),process.exit(1);}}),n.command("update <id>").description(`Update a template
|
|
172
172
|
|
|
173
173
|
TIP: For live preview while editing, use "emailr templates edit <id>" first.
|
|
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")
|
|
175
|
-
Template: ${a.name}`),console.log(`Preview URL: ${
|
|
176
|
-
Browser opened
|
|
177
|
-
Could not open browser automatically. Open the URL above manually.`)
|
|
178
|
-
Open the URL above in your browser.`),console.log("Press Ctrl+C to stop the preview server.");process.on("SIGINT",async()=>{console.log(`
|
|
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")c(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};c(s,"table");}}catch(o){l(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){l(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),n.command("preview <id>").description("Preview a template in the browser").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").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??"";if(z(a.id,r),e.foreground){let u=await V(a.id);if(u||(l("Failed to start preview server"),process.exit(1)),ye(),console.log(`
|
|
175
|
+
Template: ${a.name}`),console.log(`Preview URL: ${u}`),e.open!==!1)try{await(await import('open')).default(u),console.log(`
|
|
176
|
+
Browser opened.`);}catch{console.log(`
|
|
177
|
+
Could not open browser automatically. Open the URL above manually.`);}process.on("SIGINT",async()=>{console.log(`
|
|
179
178
|
|
|
180
|
-
Stopping preview server...`),await he().stop(),console.log("Done."),process.exit(0);});}
|
|
179
|
+
Stopping preview server...`),await he().stop(),console.log("Done."),process.exit(0);});return}let s=U.join(process.cwd(),`.template-preview-${a.id}.html`);E.writeFileSync(s,r,"utf-8");let{spawn:f}=await import('child_process'),g=await import('os'),x=U.join(g.tmpdir(),"emailr-preview.pid");try{let u=E.readFileSync(x,"utf-8").trim();process.kill(parseInt(u,10),"SIGTERM");}catch{}let b=`
|
|
180
|
+
const http = require('http');
|
|
181
|
+
const fs = require('fs');
|
|
182
|
+
const path = require('path');
|
|
183
|
+
const os = require('os');
|
|
184
|
+
const filePath = ${JSON.stringify(s)};
|
|
185
|
+
const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
|
|
186
|
+
const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
|
|
187
|
+
const server = http.createServer((req, res) => {
|
|
188
|
+
try {
|
|
189
|
+
const html = fs.readFileSync(filePath, 'utf-8');
|
|
190
|
+
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
|
|
191
|
+
res.end(html);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
res.writeHead(500);
|
|
194
|
+
res.end('Error: ' + e.message);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
server.listen(0, '127.0.0.1', () => {
|
|
198
|
+
const port = server.address().port;
|
|
199
|
+
fs.writeFileSync(pidFile, String(process.pid));
|
|
200
|
+
fs.writeFileSync(portFile, String(port));
|
|
201
|
+
console.log('PORT:' + port);
|
|
202
|
+
});
|
|
203
|
+
process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); fs.unlinkSync(filePath); } catch {} process.exit(0); });
|
|
204
|
+
`,w=f("node",["-e",b],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(u=>{w.stdout?.on("data",C=>{let q=C.toString().match(/PORT:(\d+)/);q&&(S=q[1],u());}),setTimeout(u,3e3);}),w.unref();let D=`http://127.0.0.1:${S}/`;if(console.log(`
|
|
205
|
+
Template: ${a.name}`),console.log(`Preview URL: ${D}`),console.log(`
|
|
206
|
+
To stop: emailr templates stop-preview`),e.open!==!1)try{await(await import('open')).default(D);}catch{}process.exit(0);}catch(o){l(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.
|
|
181
207
|
|
|
182
|
-
|
|
183
|
-
1. Run: emailr templates edit <id> --file ./template.html
|
|
208
|
+
WORKFLOW:
|
|
209
|
+
1. Run: emailr templates edit <id> --file ./template.html
|
|
184
210
|
2. Edit the file at ./template.html - browser auto-refreshes on save
|
|
185
211
|
3. When done: emailr templates update <id> --html-file ./template.html
|
|
186
|
-
4. Stop server: emailr templates stop-preview
|
|
212
|
+
4. Stop server: emailr templates stop-preview`).option("--file <path>","Path to save the HTML file for editing","./template.html").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=U.resolve(e.file);console.log(`Fetching template ${t}...`);let r=await i.templates.get(t),s=r.html_content??"";if(E.writeFileSync(a,s,"utf-8"),console.log(`Template saved to: ${a}`),e.foreground){let C=`http://127.0.0.1:${await Q(a,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
|
|
213
|
+
Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${C}`),console.log(`File: ${a}`),console.log(`
|
|
214
|
+
Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${t} --html-file ${e.file}`),e.open!==!1)try{await(await import('open')).default(C);}catch{}process.on("SIGINT",async()=>{console.log(`
|
|
187
215
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
clearTimeout(timer);
|
|
227
|
-
timer = setTimeout(() => clients.forEach(c => { try { c.write('data: reload\\n\\n'); } catch {} }), 100);
|
|
228
|
-
});
|
|
216
|
+
Stopping live preview server...`),await X(),console.log("Done."),console.log(`
|
|
217
|
+
To save changes: emailr templates update ${t} --html-file ${e.file}`),process.exit(0);});return}let{spawn:f}=await import('child_process'),g=await import('os'),x=U.join(g.tmpdir(),"emailr-preview.pid");try{let u=E.readFileSync(x,"utf-8").trim();process.kill(parseInt(u,10),"SIGTERM");}catch{}let b=`
|
|
218
|
+
const http = require('http');
|
|
219
|
+
const fs = require('fs');
|
|
220
|
+
const path = require('path');
|
|
221
|
+
const os = require('os');
|
|
222
|
+
const filePath = ${JSON.stringify(a)};
|
|
223
|
+
const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
|
|
224
|
+
const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
|
|
225
|
+
let clients = [];
|
|
226
|
+
const script = '<script>(function(){const e=new EventSource("/__live-reload");e.onmessage=function(d){if(d.data==="reload")location.reload()};})();</script>';
|
|
227
|
+
const server = http.createServer((req, res) => {
|
|
228
|
+
if (req.url === '/__live-reload') {
|
|
229
|
+
res.writeHead(200, {'Content-Type':'text/event-stream','Cache-Control':'no-cache','Connection':'keep-alive'});
|
|
230
|
+
res.write('data: connected\\n\\n');
|
|
231
|
+
clients.push(res);
|
|
232
|
+
req.on('close', () => { clients = clients.filter(c => c !== res); });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
let html = fs.readFileSync(filePath, 'utf-8');
|
|
237
|
+
html = html.includes('</body>') ? html.replace('</body>', script + '</body>') : html + script;
|
|
238
|
+
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
|
|
239
|
+
res.end(html);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
res.writeHead(500);
|
|
242
|
+
res.end('Error: ' + e.message);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
server.listen(0, '127.0.0.1', () => {
|
|
246
|
+
const port = server.address().port;
|
|
247
|
+
fs.writeFileSync(pidFile, String(process.pid));
|
|
248
|
+
fs.writeFileSync(portFile, String(port));
|
|
249
|
+
console.log('PORT:' + port);
|
|
250
|
+
let timer;
|
|
251
|
+
fs.watch(filePath, () => {
|
|
252
|
+
clearTimeout(timer);
|
|
253
|
+
timer = setTimeout(() => clients.forEach(c => { try { c.write('data: reload\\n\\n'); } catch {} }), 100);
|
|
229
254
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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(`
|
|
255
|
+
});
|
|
256
|
+
process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
|
|
257
|
+
`,w=f("node",["-e",b],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(u=>{w.stdout?.on("data",C=>{let ne=C.toString().match(/PORT:(\d+)/);ne&&(S=ne[1],u());}),setTimeout(u,3e3);}),w.unref();let D=`http://127.0.0.1:${S}/`;if(console.log(`
|
|
258
|
+
Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${D}`),console.log(`File: ${a}`),console.log(`
|
|
259
|
+
Edit the file and see live updates in the browser.`),console.log(`
|
|
260
|
+
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(D);}catch{}process.exit(0);}catch(o){l(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.
|
|
238
261
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
AGENTIC WORKFLOW (for AI agents, use --background):
|
|
243
|
-
1. Run: emailr templates draft --file ./new-template.html --background
|
|
262
|
+
WORKFLOW:
|
|
263
|
+
1. Run: emailr templates draft --file ./new-template.html
|
|
244
264
|
2. Edit the file at ./new-template.html - browser auto-refreshes on save
|
|
245
265
|
3. When done: emailr templates create --name "My Template" --subject "Subject" --html-file ./new-template.html
|
|
246
|
-
4. Stop server: emailr templates stop-preview
|
|
247
|
-
|
|
248
|
-
INTERACTIVE MODE (default):
|
|
249
|
-
1. Run: emailr templates draft --file ./new-template.html
|
|
250
|
-
2. Edit the file - browser auto-refreshes on save
|
|
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=R.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
|
|
266
|
+
4. Stop server: emailr templates stop-preview`).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("--foreground","Keep process running in foreground (blocks terminal)").action(async t=>{try{let e=U.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
|
|
252
267
|
<html>
|
|
253
268
|
<head>
|
|
254
269
|
<meta charset="UTF-8">
|
|
@@ -283,57 +298,56 @@ INTERACTIVE MODE (default):
|
|
|
283
298
|
<p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
|
|
284
299
|
</div>
|
|
285
300
|
</body>
|
|
286
|
-
</html>`,
|
|
287
|
-
|
|
288
|
-
const fs = require('fs');
|
|
289
|
-
const path = require('path');
|
|
290
|
-
const os = require('os');
|
|
291
|
-
const filePath = ${JSON.stringify(e)};
|
|
292
|
-
const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
|
|
293
|
-
const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
|
|
294
|
-
let clients = [];
|
|
295
|
-
const script = '<script>(function(){const e=new EventSource("/__live-reload");e.onmessage=function(d){if(d.data==="reload")location.reload()};})();</script>';
|
|
296
|
-
const server = http.createServer((req, res) => {
|
|
297
|
-
if (req.url === '/__live-reload') {
|
|
298
|
-
res.writeHead(200, {'Content-Type':'text/event-stream','Cache-Control':'no-cache','Connection':'keep-alive'});
|
|
299
|
-
res.write('data: connected\\n\\n');
|
|
300
|
-
clients.push(res);
|
|
301
|
-
req.on('close', () => { clients = clients.filter(c => c !== res); });
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
try {
|
|
305
|
-
let html = fs.readFileSync(filePath, 'utf-8');
|
|
306
|
-
html = html.includes('</body>') ? html.replace('</body>', script + '</body>') : html + script;
|
|
307
|
-
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
|
|
308
|
-
res.end(html);
|
|
309
|
-
} catch (e) {
|
|
310
|
-
res.writeHead(500);
|
|
311
|
-
res.end('Error: ' + e.message);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
server.listen(0, '127.0.0.1', () => {
|
|
315
|
-
const port = server.address().port;
|
|
316
|
-
fs.writeFileSync(pidFile, String(process.pid));
|
|
317
|
-
fs.writeFileSync(portFile, String(port));
|
|
318
|
-
console.log('PORT:' + port);
|
|
319
|
-
let timer;
|
|
320
|
-
fs.watch(filePath, () => {
|
|
321
|
-
clearTimeout(timer);
|
|
322
|
-
timer = setTimeout(() => clients.forEach(c => { try { c.write('data: reload\\n\\n'); } catch {} }), 100);
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
|
|
326
|
-
`,y=r("node",["-e",g],{detached:!0,stdio:["ignore","pipe","ignore"]}),x="";await new Promise(S=>{y.stdout?.on("data",K=>{let L=K.toString().match(/PORT:(\d+)/);L&&(x=L[1],S());}),setTimeout(S,3e3);}),y.unref();let H=`http://127.0.0.1:${x}/`;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(`
|
|
301
|
+
</html>`,E.writeFileSync(e,o,"utf-8"),console.log(`Template draft created: ${e}`),t.foreground){let w=`http://127.0.0.1:${await Q(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
|
|
302
|
+
Live Preview: ${w}`),console.log(`File: ${e}`),console.log(`
|
|
331
303
|
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(`
|
|
304
|
+
When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),t.open!==!1)try{await(await import('open')).default(w);}catch{}process.on("SIGINT",async()=>{console.log(`
|
|
334
305
|
|
|
335
|
-
Stopping live preview server...`),await
|
|
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=R.join(t.tmpdir(),"emailr-preview.pid");try{let o=C.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),C.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)),D({[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?(D({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,code:e.searchParams.get("code")??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>
|
|
306
|
+
Stopping live preview server...`),await X(),console.log("Done."),console.log(`
|
|
307
|
+
To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});return}let{spawn:i}=await import('child_process'),a=await import('os'),r=U.join(a.tmpdir(),"emailr-preview.pid");try{let b=E.readFileSync(r,"utf-8").trim();process.kill(parseInt(b,10),"SIGTERM");}catch{}let s=`
|
|
308
|
+
const http = require('http');
|
|
309
|
+
const fs = require('fs');
|
|
310
|
+
const path = require('path');
|
|
311
|
+
const os = require('os');
|
|
312
|
+
const filePath = ${JSON.stringify(e)};
|
|
313
|
+
const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
|
|
314
|
+
const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
|
|
315
|
+
let clients = [];
|
|
316
|
+
const script = '<script>(function(){const e=new EventSource("/__live-reload");e.onmessage=function(d){if(d.data==="reload")location.reload()};})();</script>';
|
|
317
|
+
const server = http.createServer((req, res) => {
|
|
318
|
+
if (req.url === '/__live-reload') {
|
|
319
|
+
res.writeHead(200, {'Content-Type':'text/event-stream','Cache-Control':'no-cache','Connection':'keep-alive'});
|
|
320
|
+
res.write('data: connected\\n\\n');
|
|
321
|
+
clients.push(res);
|
|
322
|
+
req.on('close', () => { clients = clients.filter(c => c !== res); });
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
let html = fs.readFileSync(filePath, 'utf-8');
|
|
327
|
+
html = html.includes('</body>') ? html.replace('</body>', script + '</body>') : html + script;
|
|
328
|
+
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
|
|
329
|
+
res.end(html);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
res.writeHead(500);
|
|
332
|
+
res.end('Error: ' + e.message);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
server.listen(0, '127.0.0.1', () => {
|
|
336
|
+
const port = server.address().port;
|
|
337
|
+
fs.writeFileSync(pidFile, String(process.pid));
|
|
338
|
+
fs.writeFileSync(portFile, String(port));
|
|
339
|
+
console.log('PORT:' + port);
|
|
340
|
+
let timer;
|
|
341
|
+
fs.watch(filePath, () => {
|
|
342
|
+
clearTimeout(timer);
|
|
343
|
+
timer = setTimeout(() => clients.forEach(c => { try { c.write('data: reload\\n\\n'); } catch {} }), 100);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
|
|
347
|
+
`,f=i("node",["-e",s],{detached:!0,stdio:["ignore","pipe","ignore"]}),g="";await new Promise(b=>{f.stdout?.on("data",w=>{let S=w.toString().match(/PORT:(\d+)/);S&&(g=S[1],b());}),setTimeout(b,3e3);}),f.unref();let x=`http://127.0.0.1:${g}/`;if(console.log(`
|
|
348
|
+
Live Preview: ${x}`),console.log(`File: ${e}`),console.log(`
|
|
349
|
+
Edit the file and see live updates in the browser.`),console.log(`
|
|
350
|
+
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(x);}catch{}process.exit(0);}catch(e){l(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=U.join(t.tmpdir(),"emailr-preview.pid");try{let o=E.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),E.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")c(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}));c(a,"table");}}catch(e){l(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);c(a,e.format);}catch(o){l(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")c(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",...Z(r.dns_records.dkim)},{Type:"SPF",...Z(r.dns_records.spf)},{Type:"DMARC",...Z(r.dns_records.dmarc)}];c(s,"table");}console.log(""),p(`Run 'emailr domains verify ${r.id}' after adding DNS records`);}}catch(o){l(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"?c(a,"json"):a.verified?d("Domain verified successfully!"):(p(`Domain status: ${a.status}`),a.dkim_status&&p(`DKIM status: ${a.dkim_status}`));}catch(o){l(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")c(a,"json");else {let r=Object.entries(a).map(([s,f])=>({Record:s,Verified:f.verified,Expected:f.expected||"-",Found:f.found?.join(", ")||"-"}));c(r,"table");}}catch(o){l(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){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),n}function Z(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 je(){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)||(l(`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)&&(l(`Invalid format value: ${e}`),p("Valid formats: json, table"),process.exit(1)),A({[r]:e}),d(`Configuration saved: ${t} = ${i==="api-key"?"***":e}`),p(`Config file: ${j()}`);}catch(o){l(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)||(l(`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){l(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")c(o,"json");else {let i=Object.entries(o).map(([a,r])=>({Key:a,Value:r}));c(i,"table");}console.log(""),p(`Config file: ${j()}`);}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.")):(l(e instanceof Error?e.message:"Failed to list configuration"),process.exit(1));}}),n.command("path").description("Show the configuration file path").action(()=>{console.log(j());}),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?(A({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),p(`Config file: ${j()}`)):(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){l(e instanceof Error?e.message:"Failed to initialize configuration"),process.exit(1);}}),n}function ke(){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")c(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()}));c(a,"table");}}catch(e){l(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"?c(a,"json"):c({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){l(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"?c(i,"json"):(d("Broadcast created successfully!"),c({ID:i.id,Name:i.name,Status:i.status,"Scheduled At":i.scheduled_at||"Not scheduled"},"table"));}catch(e){l(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"?c(a,"json"):(d("Broadcast sent successfully!"),c({Success:a.success,Sent:a.sent,Total:a.total},"table"));}catch(o){l(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"?c(a,"json"):(d("Broadcast scheduled successfully!"),c({ID:a.id,Name:a.name,Status:a.status,"Scheduled At":a.scheduled_at},"table"));}catch(o){l(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"?c(a,"json"):d("Broadcast cancelled successfully!");}catch(o){l(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"?c(a,"json"):d("Broadcast deleted successfully!");}catch(o){l(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")c(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"}));c(a,"table");}}catch(e){l(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"?c(a,"json"):c({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){l(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"?c(a,"json"):(d("Webhook created successfully!"),c({ID:a.id,Name:a.name,URL:a.url,Secret:a.secret},"table"));}catch(e){l(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"?c(r,"json"):(d("Webhook updated successfully!"),c({ID:r.id,Name:r.name,URL:r.url},"table"));}catch(o){l(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"?c(a,"json"):d("Webhook enabled successfully!");}catch(o){l(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"?c(a,"json"):d("Webhook disabled successfully!");}catch(o){l(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"?c(a,"json"):d("Webhook deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),n}function Fe(){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")c(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()}));c(a,"table");}}catch(e){l(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"?c(a,"json"):c({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){l(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{l("Invalid JSON for --conditions"),process.exit(1);}let a=await o.segments.create({name:t.name,description:t.description,conditions:i});t.format==="json"?c(a,"json"):(d("Segment created successfully!"),c({ID:a.id,Name:a.name},"table"));}catch(e){l(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{l("Invalid JSON for --conditions"),process.exit(1);}let r=await i.segments.update(t,a);e.format==="json"?c(r,"json"):(d("Segment updated successfully!"),c({ID:r.id,Name:r.name},"table"));}catch(o){l(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"?c(a,"json"):d("Segment deleted successfully!");}catch(o){l(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"?c(a,"json"):console.log(`Segment contains ${a.count} contacts.`);}catch(o){l(o instanceof Error?o.message:"Failed to get segment count"),process.exit(1);}}),n}function it(n){try{let t=n.startsWith("http")?n:`http://localhost${n}`,e=new URL(t);return {key:e.searchParams.get("key")??void 0,code:e.searchParams.get("code")??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 st(){return `<!DOCTYPE html>
|
|
337
351
|
<html lang="en">
|
|
338
352
|
<head>
|
|
339
353
|
<meta charset="UTF-8">
|
|
@@ -379,7 +393,7 @@ To create template: emailr templates create --name "Template Name" --subject "Em
|
|
|
379
393
|
<p>You can close this window and return to your terminal.</p>
|
|
380
394
|
</div>
|
|
381
395
|
</body>
|
|
382
|
-
</html>`}function
|
|
396
|
+
</html>`}function ee(n){return `<!DOCTYPE html>
|
|
383
397
|
<html lang="en">
|
|
384
398
|
<head>
|
|
385
399
|
<meta charset="UTF-8">
|
|
@@ -434,7 +448,7 @@ To create template: emailr templates create --name "Template Name" --subject "Em
|
|
|
434
448
|
<p style="margin-top: 1rem;">Please close this window and try again.</p>
|
|
435
449
|
</div>
|
|
436
450
|
</body>
|
|
437
|
-
</html>`}function Te(){let n=null,t=0,e=null,o=null,i=null;return {async start(){return new Promise((a,r)=>{n=
|
|
451
|
+
</html>`}function Te(){let n=null,t=0,e=null,o=null,i=null;return {async start(){return new Promise((a,r)=>{n=qe.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=it(s.url);if(i&&g.state!==i){f.writeHead(400,{"Content-Type":"text/html"}),f.end(ee("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(g.error){let b=g.message||g.error;f.writeHead(200,{"Content-Type":"text/html"}),f.end(ee(b)),e&&e({success:false,error:b});return}let x=g.key||g.code;if(x){f.writeHead(200,{"Content-Type":"text/html"}),f.end(st()),e&&e({success:true,apiKey:x});return}f.writeHead(400,{"Content-Type":"text/html"}),f.end(ee("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 Pe(){return ct.randomBytes(32).toString("hex")}function Ie(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 W=120,dt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function pt(n,t){let e=`http://127.0.0.1:${t}/callback`,o=new URLSearchParams({state:n,callback_url:e});return `${dt}/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(W)).option("--no-browser","Don't automatically open the browser").action(async t=>{await ft({timeout:parseInt(t.timeout,10)||W,noBrowser:t.browser===false});})}async function ft(n){let t=Te(),e=(n.timeout||W)*1e3;try{p("Starting authentication server...");let{port:o,url:i}=await t.start(),a=Pe(),r=pt(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 Ie(r)?p("Browser opened. Please complete authentication in your browser."):(k("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||W}s)...`);let s=await t.waitForCallback(a,e);s.success&&s.apiKey?(A({apiKey:s.apiKey}),console.log(""),d("Login successful!"),p(`API key saved to: ${j()}`),p("You can now use the Emailr CLI.")):(console.log(""),l(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(""),l(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 oe=U.join(G.homedir(),".config","opencode","skills","emailr-cli"),ht=`---
|
|
438
452
|
name: emailr-cli
|
|
439
453
|
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.
|
|
440
454
|
---
|
|
@@ -474,7 +488,7 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
|
|
|
474
488
|
- Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
|
|
475
489
|
- Update: \`emailr templates update <id> --html-file ./template.html\`
|
|
476
490
|
- Delete: \`emailr templates delete <id>\`
|
|
477
|
-
`;function
|
|
491
|
+
`;function yt(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function wt(){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 vt(){E.existsSync(oe)||E.mkdirSync(oe,{recursive:true});let n=U.join(oe,"SKILL.md");E.writeFileSync(n,ht,"utf-8");}function Ke(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
|
|
478
492
|
|
|
479
493
|
This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
|
|
480
494
|
The agent can help you:
|
|
@@ -482,7 +496,7 @@ The agent can help you:
|
|
|
482
496
|
- Manage contacts, broadcasts, and segments
|
|
483
497
|
- Configure domains and webhooks
|
|
484
498
|
|
|
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=>{
|
|
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{
|
|
499
|
+
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=>{yt()||(t.install?wt()||(l("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)):(l("OpenCode AI agent is not installed."),console.log(`
|
|
500
|
+
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{vt(),d("Emailr CLI skill loaded");}catch(i){k(`Could not install skill: ${i instanceof Error?i.message:String(i)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
|
|
487
501
|
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=>{
|
|
502
|
+
`);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",i=>{l(`Failed to start agent: ${i.message}`),process.exit(1);}),o.on("exit",i=>{process.exit(i??0);});})}var h=new Command;h.name("emailr").description("Emailr CLI - Send emails and manage your email infrastructure").version("1.5.3");h.addCommand(ce());h.addCommand(le());h.addCommand(Se());h.addCommand(Ce());h.addCommand(ke());h.addCommand(Ee());h.addCommand(Fe());h.addCommand(je());h.addCommand(Ue());h.addCommand(Ke());h.parse();
|