emailr-cli 1.5.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +150 -136
  2. 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 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
+ 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 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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function We(n){return n===null||n.trim()===""}function Ge(n){return `<!DOCTYPE html>
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}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 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=`
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 Qe(n){return n.includes("</body>")?n.replace("</body>",`${ve}</body>`):n+ve}function Xe(){I.forEach(n=>{try{n.write(`data: reload
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 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
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
- `),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
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")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
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")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
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")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(`
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);});}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.
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
- AGENTIC WORKFLOW (for AI agents, use --background):
183
- 1. Run: emailr templates edit <id> --file ./template.html --background
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
- INTERACTIVE MODE (default):
189
- 1. Run: emailr templates edit <id> --file ./template.html
190
- 2. Edit the file - browser auto-refreshes on save
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=`
192
- const http = require('http');
193
- const fs = require('fs');
194
- const path = require('path');
195
- const os = require('os');
196
- const filePath = ${JSON.stringify(a)};
197
- const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
198
- const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
199
- let clients = [];
200
- const script = '<script>(function(){const e=new EventSource("/__live-reload");e.onmessage=function(d){if(d.data==="reload")location.reload()};})();</script>';
201
- const server = http.createServer((req, res) => {
202
- if (req.url === '/__live-reload') {
203
- res.writeHead(200, {'Content-Type':'text/event-stream','Cache-Control':'no-cache','Connection':'keep-alive'});
204
- res.write('data: connected\\n\\n');
205
- clients.push(res);
206
- req.on('close', () => { clients = clients.filter(c => c !== res); });
207
- return;
208
- }
209
- try {
210
- let html = fs.readFileSync(filePath, 'utf-8');
211
- html = html.includes('</body>') ? html.replace('</body>', script + '</body>') : html + script;
212
- res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
213
- res.end(html);
214
- } catch (e) {
215
- res.writeHead(500);
216
- res.end('Error: ' + e.message);
217
- }
218
- });
219
- server.listen(0, '127.0.0.1', () => {
220
- const port = server.address().port;
221
- fs.writeFileSync(pidFile, String(process.pid));
222
- fs.writeFileSync(portFile, String(port));
223
- console.log('PORT:' + port);
224
- let timer;
225
- fs.watch(filePath, () => {
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
- process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
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(`
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
- 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.
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=N.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>`,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=`
287
- const http = require('http');
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"]}),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(`
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 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>
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 Z(n){return `<!DOCTYPE html>
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=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=`---
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 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
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=>{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(`
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=>{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();
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailr-cli",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Command-line interface for the Emailr email API",
5
5
  "type": "module",
6
6
  "bin": {