emailr-cli 1.5.2 → 1.5.3

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 +17 -17
  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 C from'fs';import W from'os';import R 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=[R.join(W.homedir(),".emailrrc"),R.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(C.existsSync(n))try{let t=C.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)+`
10
+ Or run: emailr config set api-key <your-api-key>`)}function w(){let n=R.join(W.homedir(),".config","emailr");return R.join(n,"config.json")}function D(n){let t=w(),e=R.dirname(t);C.existsSync(e)||C.mkdirSync(e,{recursive:true});let o={};if(C.existsSync(t))try{o=JSON.parse(C.readFileSync(t,"utf-8"));}catch{}let i={...o,...n};C.writeFileSync(t,JSON.stringify(i,null,2)+`
11
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>
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 R.join(W.homedir(),".config","emailr","templates")}function qe(){let n=de();C.existsSync(n)||C.mkdirSync(n,{recursive:true});}function G(n){return R.join(de(),`${n}.html`)}function J(n,t){qe();let e=G(n);C.writeFileSync(e,t,"utf-8");}function pe(n){let t=G(n);return C.existsSync(t)?C.readFileSync(t,"utf-8"):null}function fe(n){let t=G(n);return C.existsSync(t)}var b=null,k=null,A=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>
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 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 A&&k!==null?k:new Promise((n,t)=>{b=Be.createServer(ze()),b.listen(0,"127.0.0.1",()=>{let e=b.address();e&&typeof e=="object"?(k=e.port,A=true,b.unref(),n(k)):t(new Error("Failed to get server address"));}),b.on("error",e=>{A=false,k=null,b=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return k},isRunning(){return A},async stop(){if(b)return new Promise(n=>{b.close(()=>{b=null,k=null,A=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 j=null,V=null,q=null,F=[],ve=`
142
142
  <script>
143
143
  (function() {
144
144
  const evtSource = new EventSource('/__live-reload');
@@ -152,11 +152,11 @@ 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 Qe(n){return n.includes("</body>")?n.replace("</body>",`${ve}</body>`):n+ve}function Xe(){F.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 Y(n,t){let e=R.resolve(n);return new Promise((o,i)=>{j=Be.createServer((a,r)=>{if(a.url==="/__live-reload"){r.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),r.write(`data: connected
158
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
+ `),F.push(r),a.on("close",()=>{F=F.filter(s=>s!==r);});return}if(a.method==="GET"){try{let s=C.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");}),j.listen(0,"127.0.0.1",()=>{let a=j.address();if(a&&typeof a=="object"){V=a.port;let r=null;q=C.watch(e,s=>{s==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{Xe(),t?.();},100));}),o(V);}else i(new Error("Failed to get server address"));}),j.on("error",a=>{i(new Error(`Failed to start server: ${a.message}`));});})}async function Q(){if(q&&(q.close(),q=null),F.forEach(n=>{try{n.end();}catch{}}),F=[],j)return new Promise(n=>{j.close(()=>{j=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
160
160
 
161
161
  AGENTIC LIVE EDITING:
162
162
  draft Create new template with live preview
@@ -188,7 +188,7 @@ AGENTIC WORKFLOW (for AI agents, use --background):
188
188
  INTERACTIVE MODE (default):
189
189
  1. Run: emailr templates edit <id> --file ./template.html
190
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=`
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=R.resolve(e.file);console.log(`Fetching template ${t}...`);let r=await i.templates.get(t),s=r.html_content??"";if(C.writeFileSync(a,s,"utf-8"),console.log(`Template saved to: ${a}`),e.background){let{spawn:y}=await import('child_process'),x=await import('os'),H=R.join(x.tmpdir(),"emailr-preview.pid");try{let N=C.readFileSync(H,"utf-8").trim();process.kill(parseInt(N,10),"SIGTERM");}catch{}let S=`
192
192
  const http = require('http');
193
193
  const fs = require('fs');
194
194
  const path = require('path');
@@ -228,9 +228,9 @@ INTERACTIVE MODE (default):
228
228
  });
229
229
  });
230
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(`
231
+ `,K=y("node",["-e",S],{detached:!0,stdio:["ignore","pipe","ignore"]}),L="";await new Promise(N=>{K.stdout?.on("data",Ke=>{let ne=Ke.toString().match(/PORT:(\d+)/);ne&&(L=ne[1],N());}),setTimeout(N,3e3);}),K.unref();let ae=`http://127.0.0.1:${L}/`;if(console.log(`
232
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(`
233
+ Preview server running in background (PID: ${K.pid}).`),console.log("Edit the file and see live updates in the browser."),console.log(`
234
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
235
  Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${g}`),console.log(`File: ${a}`),console.log(`
236
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(`
@@ -248,7 +248,7 @@ AGENTIC WORKFLOW (for AI agents, use --background):
248
248
  INTERACTIVE MODE (default):
249
249
  1. Run: emailr templates draft --file ./new-template.html
250
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>
251
+ 3. Press Ctrl+C when done, then run create command`).option("--file <path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--background","Start preview server in background and exit (for AI agents)").action(async t=>{try{let e=R.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
252
252
  <html>
253
253
  <head>
254
254
  <meta charset="UTF-8">
@@ -283,7 +283,7 @@ INTERACTIVE MODE (default):
283
283
  <p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
284
284
  </div>
285
285
  </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=`
286
+ </html>`,C.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=R.join(s.tmpdir(),"emailr-preview.pid");try{let S=C.readFileSync(f,"utf-8").trim();process.kill(parseInt(S,10),"SIGTERM");}catch{}let g=`
287
287
  const http = require('http');
288
288
  const fs = require('fs');
289
289
  const path = require('path');
@@ -323,7 +323,7 @@ INTERACTIVE MODE (default):
323
323
  });
324
324
  });
325
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(`
326
+ `,y=r("node",["-e",g],{detached:!0,stdio:["ignore","pipe","ignore"]}),x="";await new Promise(S=>{y.stdout?.on("data",K=>{let L=K.toString().match(/PORT:(\d+)/);L&&(x=L[1],S());}),setTimeout(S,3e3);}),y.unref();let H=`http://127.0.0.1:${x}/`;if(console.log(`
327
327
  Live Preview: ${H}`),console.log(`File: ${e}`),console.log(`
328
328
  Preview server running in background (PID: ${y.pid}).`),console.log("Edit the file and see live updates in the browser."),console.log(`
329
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(`
@@ -333,7 +333,7 @@ When done, create the template:`),console.log(` emailr templates create --name
333
333
  Press Ctrl+C to stop.`),t.open!==!1)try{await(await import('open')).default(a);}catch{}process.on("SIGINT",async()=>{console.log(`
334
334
 
335
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>
336
+ To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});}catch(e){c(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),n.command("stop-preview").description("Stop any running background preview server").action(async()=>{let t=await import('os'),e=R.join(t.tmpdir(),"emailr-preview.pid");try{let o=C.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),C.unlinkSync(e),d("Preview server stopped");}catch{d("No preview server running");}}),n}function Ce(){let n=new Command("domains").description("Manage sending domains");return n.command("list").description("List all domains").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(t.format==="json")l(i,"json");else {let a=i.map(r=>({ID:r.id,Domain:r.domain,Status:r.status,DKIM:r.dkim_verified,SPF:r.spf_verified,DMARC:r.dmarc_verified,Created:r.created_at}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),n.command("get <id>").description("Get a domain by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.get(t);l(a,e.format);}catch(o){c(o instanceof Error?o.message:"Failed to get domain"),process.exit(1);}}),n.command("add <domain>").description("Add a new domain").option("--receiving-subdomain <subdomain>","Subdomain for receiving emails").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={domain:t};e.receivingSubdomain&&(a.receivingSubdomain=e.receivingSubdomain);let r=await i.domains.add(a);if(e.format==="json")l(r,"json");else {if(d(`Domain added: ${r.domain}`),p("Add the following DNS records to verify your domain:"),console.log(""),r.dns_records){let s=[{Type:"DKIM",...X(r.dns_records.dkim)},{Type:"SPF",...X(r.dns_records.spf)},{Type:"DMARC",...X(r.dns_records.dmarc)}];l(s,"table");}console.log(""),p(`Run 'emailr domains verify ${r.id}' after adding DNS records`);}}catch(o){c(o instanceof Error?o.message:"Failed to add domain"),process.exit(1);}}),n.command("verify <id>").description("Verify a domain").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.verify(t);e.format==="json"?l(a,"json"):a.verified?d("Domain verified successfully!"):(p(`Domain status: ${a.status}`),a.dkim_status&&p(`DKIM status: ${a.dkim_status}`));}catch(o){c(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),n.command("check-dns <id>").description("Check DNS records for a domain").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.checkDns(t);if(e.format==="json")l(a,"json");else {let r=Object.entries(a).map(([s,f])=>({Record:s,Verified:f.verified,Expected:f.expected||"-",Found:f.found?.join(", ")||"-"}));l(r,"table");}}catch(o){c(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),n.command("delete <id>").description("Delete a domain").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(t),d(`Domain deleted: ${t}`);}catch(e){c(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),n}function X(n){return {"Record Type":n.type,Name:n.name,Value:n.value.length>50?n.value.substring(0,47)+"...":n.value,...n.priority!==void 0&&{Priority:n.priority}}}function ke(){let n=new Command("config").description("Manage CLI configuration");return n.command("set <key> <value>").description("Set a configuration value").action(async(t,e)=>{try{let o=["api-key","base-url","format"],i=t.toLowerCase();o.includes(i)||(c(`Invalid config key: ${t}`),p(`Valid keys: ${o.join(", ")}`),process.exit(1));let r={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[i];i==="format"&&!["json","table"].includes(e)&&(c(`Invalid format value: ${e}`),p("Valid formats: json, table"),process.exit(1)),D({[r]:e}),d(`Configuration saved: ${t} = ${i==="api-key"?"***":e}`),p(`Config file: ${w()}`);}catch(o){c(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),n.command("get <key>").description("Get a configuration value").action(async t=>{try{let e=["api-key","base-url","format"],o=t.toLowerCase();e.includes(o)||(c(`Invalid config key: ${t}`),p(`Valid keys: ${e.join(", ")}`),process.exit(1));let a={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],r=re(a);r?console.log(o==="api-key"?r.substring(0,8)+"..."+r.substring(r.length-4):r):p(`${t} is not set`);}catch(e){c(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),n.command("list").description("List all configuration values").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o={"api-key":e.apiKey?e.apiKey.substring(0,8)+"..."+e.apiKey.substring(e.apiKey.length-4):"(not set)","base-url":e.baseUrl||"(default)",format:e.format||"table"};if(t.format==="json")l(o,"json");else {let i=Object.entries(o).map(([a,r])=>({Key:a,Value:r}));l(i,"table");}console.log(""),p(`Config file: ${w()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(p("No configuration found."),p("Run 'emailr config set api-key <your-api-key>' to get started.")):(c(e instanceof Error?e.message:"Failed to list configuration"),process.exit(1));}}),n.command("path").description("Show the configuration file path").action(()=>{console.log(w());}),n.command("init").description("Initialize configuration interactively").option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async t=>{try{t.apiKey?(D({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),p(`Config file: ${w()}`)):(p("Initialize your Emailr CLI configuration:"),console.log(""),p("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),p("Or set environment variable:"),console.log(" export EMAILR_API_KEY=<your-api-key>"));}catch(e){c(e instanceof Error?e.message:"Failed to initialize configuration"),process.exit(1);}}),n}function je(){let n=new Command("broadcasts").description("Manage broadcast campaigns");return n.command("list").description("List all broadcasts").option("--status <status>","Filter by status (draft, scheduled, sending, sent)").option("--limit <number>","Number of broadcasts to return","20").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.list({status:t.status,limit:parseInt(t.limit)});if(t.format==="json")l(i,"json");else {if(i.length===0){console.log("No broadcasts found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,Subject:r.subject.substring(0,30)+(r.subject.length>30?"...":""),Status:r.status,Recipients:r.total_recipients||0,Sent:r.sent_count||0,Created:new Date(r.created_at).toLocaleDateString()}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),n.command("get <id>").description("Get broadcast details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.get(t);e.format==="json"?l(a,"json"):l({ID:a.id,Name:a.name,Subject:a.subject,"From Email":a.from_email,Status:a.status,"Total Recipients":a.total_recipients||0,"Sent Count":a.sent_count||0,Delivered:a.delivered_count||0,Opened:a.opened_count||0,Clicked:a.clicked_count||0,Bounced:a.bounced_count||0,"Scheduled At":a.scheduled_at||"N/A","Started At":a.started_at||"N/A","Completed At":a.completed_at||"N/A","Created At":a.created_at},"table");}catch(o){c(o instanceof Error?o.message:"Failed to get broadcast"),process.exit(1);}}),n.command("create").description("Create a new broadcast").requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--template <id>","Template ID to use").option("--segment <id>","Segment ID to target").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.create({name:t.name,subject:t.subject,from_email:t.from,template_id:t.template,segment_id:t.segment,html_content:t.html,text_content:t.text,scheduled_at:t.schedule});t.format==="json"?l(i,"json"):(d("Broadcast created successfully!"),l({ID:i.id,Name:i.name,Status:i.status,"Scheduled At":i.scheduled_at||"Not scheduled"},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),n.command("send <id>").description("Send a broadcast immediately").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.send(t);e.format==="json"?l(a,"json"):(d("Broadcast sent successfully!"),l({Success:a.success,Sent:a.sent,Total:a.total},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to send broadcast"),process.exit(1);}}),n.command("schedule <id>").description("Schedule a broadcast").requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.schedule(t,e.at);e.format==="json"?l(a,"json"):(d("Broadcast scheduled successfully!"),l({ID:a.id,Name:a.name,Status:a.status,"Scheduled At":a.scheduled_at},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to schedule broadcast"),process.exit(1);}}),n.command("cancel <id>").description("Cancel a scheduled broadcast").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.cancel(t);e.format==="json"?l(a,"json"):d("Broadcast cancelled successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to cancel broadcast"),process.exit(1);}}),n.command("delete <id>").description("Delete a broadcast").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.delete(t);e.format==="json"?l(a,"json"):d("Broadcast deleted successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to delete broadcast"),process.exit(1);}}),n}function Ee(){let n=new Command("webhooks").description("Manage webhooks");return n.command("list").description("List all webhooks").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(t.format==="json")l(i,"json");else {if(i.length===0){console.log("No webhooks found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,URL:r.url.substring(0,40)+(r.url.length>40?"...":""),Events:r.events.join(", "),Active:r.active?"Yes":"No"}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),n.command("get <id>").description("Get webhook details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.get(t);e.format==="json"?l(a,"json"):l({ID:a.id,Name:a.name,URL:a.url,Events:a.events.join(", "),Active:a.active?"Yes":"No",Secret:a.secret,"Created At":a.created_at},"table");}catch(o){c(o instanceof Error?o.message:"Failed to get webhook"),process.exit(1);}}),n.command("create").description("Create a new webhook").requiredOption("--name <name>","Webhook name").requiredOption("--url <url>","Webhook URL").requiredOption("--events <events>","Events to subscribe to (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i=t.events.split(",").map(r=>r.trim()),a=await o.webhooks.create({name:t.name,url:t.url,events:i});t.format==="json"?l(a,"json"):(d("Webhook created successfully!"),l({ID:a.id,Name:a.name,URL:a.url,Secret:a.secret},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),n.command("update <id>").description("Update a webhook").option("--name <name>","New webhook name").option("--url <url>","New webhook URL").option("--events <events>","New events (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name),e.url&&(a.url=e.url),e.events&&(a.events=e.events.split(",").map(s=>s.trim()));let r=await i.webhooks.update(t,a);e.format==="json"?l(r,"json"):(d("Webhook updated successfully!"),l({ID:r.id,Name:r.name,URL:r.url},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to update webhook"),process.exit(1);}}),n.command("enable <id>").description("Enable a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.enable(t);e.format==="json"?l(a,"json"):d("Webhook enabled successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to enable webhook"),process.exit(1);}}),n.command("disable <id>").description("Disable a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.disable(t);e.format==="json"?l(a,"json"):d("Webhook disabled successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to disable webhook"),process.exit(1);}}),n.command("delete <id>").description("Delete a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.delete(t);e.format==="json"?l(a,"json"):d("Webhook deleted successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),n}function Pe(){let n=new Command("segments").description("Manage contact segments");return n.command("list").description("List all segments").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).segments.list();if(t.format==="json")l(i,"json");else {if(i.length===0){console.log("No segments found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,Description:(r.description||"").substring(0,30)+((r.description?.length||0)>30?"...":""),"Created At":new Date(r.created_at).toLocaleDateString()}));l(a,"table");}}catch(e){c(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),n.command("get <id>").description("Get segment details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.get(t);e.format==="json"?l(a,"json"):l({ID:a.id,Name:a.name,Description:a.description||"N/A",Conditions:JSON.stringify(a.conditions),"Created At":a.created_at,"Updated At":a.updated_at},"table");}catch(o){c(o instanceof Error?o.message:"Failed to get segment"),process.exit(1);}}),n.command("create").description("Create a new segment").requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i;try{i=JSON.parse(t.conditions);}catch{c("Invalid JSON for --conditions"),process.exit(1);}let a=await o.segments.create({name:t.name,description:t.description,conditions:i});t.format==="json"?l(a,"json"):(d("Segment created successfully!"),l({ID:a.id,Name:a.name},"table"));}catch(e){c(e instanceof Error?e.message:"Failed to create segment"),process.exit(1);}}),n.command("update <id>").description("Update a segment").option("--name <name>","New segment name").option("--description <description>","New description").option("--conditions <json>","New conditions as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.description&&(a.description=e.description),e.conditions)try{a.conditions=JSON.parse(e.conditions);}catch{c("Invalid JSON for --conditions"),process.exit(1);}let r=await i.segments.update(t,a);e.format==="json"?l(r,"json"):(d("Segment updated successfully!"),l({ID:r.id,Name:r.name},"table"));}catch(o){c(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),n.command("delete <id>").description("Delete a segment").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.delete(t);e.format==="json"?l(a,"json"):d("Segment deleted successfully!");}catch(o){c(o instanceof Error?o.message:"Failed to delete segment"),process.exit(1);}}),n.command("count <id>").description("Get the number of contacts in a segment").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.getContactsCount(t);e.format==="json"?l(a,"json"):console.log(`Segment contains ${a.count} contacts.`);}catch(o){c(o instanceof Error?o.message:"Failed to get segment count"),process.exit(1);}}),n}function st(n){try{let t=n.startsWith("http")?n:`http://localhost${n}`,e=new URL(t);return {key:e.searchParams.get("key")??void 0,code:e.searchParams.get("code")??void 0,state:e.searchParams.get("state")??void 0,error:e.searchParams.get("error")??void 0,message:e.searchParams.get("message")??void 0}}catch{return {}}}function lt(){return `<!DOCTYPE html>
337
337
  <html lang="en">
338
338
  <head>
339
339
  <meta charset="UTF-8">
@@ -434,7 +434,7 @@ To create template: emailr templates create --name "Template Name" --subject "Em
434
434
  <p style="margin-top: 1rem;">Please close this window and try again.</p>
435
435
  </div>
436
436
  </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=`---
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 x=g.message||g.error;f.writeHead(200,{"Content-Type":"text/html"}),f.end(Z(x)),e&&e({success:false,error:x});return}let y=g.key||g.code;if(y){f.writeHead(200,{"Content-Type":"text/html"}),f.end(lt()),e&&e({success:true,apiKey:y});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?(D({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=R.join(W.homedir(),".config","opencode","skills","emailr-cli"),yt=`---
438
438
  name: emailr-cli
439
439
  description: Operate the Emailr CLI to send emails, manage contacts, templates, domains, broadcasts, webhooks, and segments. Includes LIVE PREVIEW editing for templates with hot-reload.
440
440
  ---
@@ -474,7 +474,7 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
474
474
  - Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
475
475
  - Update: \`emailr templates update <id> --html-file ./template.html\`
476
476
  - 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
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(){C.existsSync(te)||C.mkdirSync(te,{recursive:true});let n=R.join(te,"SKILL.md");C.writeFileSync(n,yt,"utf-8");}function _e(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
478
478
 
479
479
  This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
480
480
  The agent can help you:
@@ -485,4 +485,4 @@ The agent can help you:
485
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
486
  Install it with one of:`),console.log(" emailr agent --install"),console.log(" npm install -g opencode-ai@latest"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)));try{xt(),d("Emailr CLI skill loaded");}catch(i){v(`Could not install skill: ${i instanceof Error?i.message:String(i)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
487
487
  Starting Emailr AI Agent...`),console.log("The agent has the emailr-cli skill loaded."),console.log(`Ask it to help with emails, templates, contacts, or broadcasts.
488
- `);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",i=>{c(`Failed to start agent: ${i.message}`),process.exit(1);}),o.on("exit",i=>{process.exit(i??0);});})}var u=new Command;u.name("emailr").description("Emailr CLI - Send emails and manage your email infrastructure").version("1.0.0");u.addCommand(le());u.addCommand(ce());u.addCommand(Se());u.addCommand(Ce());u.addCommand(je());u.addCommand(Ee());u.addCommand(Pe());u.addCommand(ke());u.addCommand(Ue());u.addCommand(_e());u.parse();
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.5.3");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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailr-cli",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "Command-line interface for the Emailr email API",
5
5
  "type": "module",
6
6
  "bin": {