emailr-cli 1.6.0 → 1.7.0

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 +30 -30
  2. package/package.json +2 -2
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 v,{readFileSync}from'fs';import W from'os';import N from'path';import re from'cli-table3';import S from'chalk';import Je from'http';import {URL}from'url';import ct from'crypto';import {spawn,execSync,exec}from'child_process';var Re=[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 i of Re)if(v.existsSync(i))try{let t=v.readFileSync(i,"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 v,{readFileSync}from'fs';import B from'os';import N from'path';import re from'cli-table3';import S from'chalk';import Je from'http';import {URL}from'url';import ct from'crypto';import {spawn,execSync,exec}from'child_process';var Re=[N.join(B.homedir(),".emailrrc"),N.join(B.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 i of Re)if(v.existsSync(i))try{let t=v.readFileSync(i,"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,8 +7,8 @@ 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 I(){let i=N.join(W.homedir(),".config","emailr");return N.join(i,"config.json")}function G(i){let t=I(),e=N.dirname(t);v.existsSync(e)||v.mkdirSync(e,{recursive:true});let a={};if(v.existsSync(t))try{a=JSON.parse(v.readFileSync(t,"utf-8"));}catch{}let r={...a,...i};v.writeFileSync(t,JSON.stringify(r,null,2)+`
11
- `);}function ne(i){try{return m()[i]?.toString()}catch{return}}function c(i,t="table"){t==="json"?console.log(JSON.stringify(i,null,2)):Me(i);}function Me(i){Array.isArray(i)?Le(i):typeof i=="object"&&i!==null?De(i):console.log(i);}function Le(i){if(i.length===0){console.log(S.gray("No results"));return}let t=i[0];if(typeof t!="object"||t===null){i.forEach(r=>console.log(r));return}let e=Object.keys(t),a=new re({head:e.map(r=>S.cyan(r)),style:{head:[],border:[]}});for(let r of i){let o=e.map(n=>{let s=r[n];return se(s)});a.push(o);}console.log(a.toString());}function De(i){let t=new re({style:{head:[],border:[]}});for(let[e,a]of Object.entries(i))t.push([S.cyan(e),se(a)]);console.log(t.toString());}function se(i){return i==null?S.gray("-"):typeof i=="boolean"?i?S.green("\u2713"):S.red("\u2717"):typeof i=="object"?JSON.stringify(i):String(i)}function d(i){console.log(S.green("\u2713"),i);}function l(i){console.error(S.red("\u2717"),i);}function k(i){console.warn(S.yellow("\u26A0"),i);}function u(i){console.log(S.blue("\u2139"),i);}function ce(){return new Command("send").description(`Send an email
10
+ Or run: emailr config set api-key <your-api-key>`)}function k(){let i=N.join(B.homedir(),".config","emailr");return N.join(i,"config.json")}function G(i){let t=k(),e=N.dirname(t);v.existsSync(e)||v.mkdirSync(e,{recursive:true});let a={};if(v.existsSync(t))try{a=JSON.parse(v.readFileSync(t,"utf-8"));}catch{}let r={...a,...i};v.writeFileSync(t,JSON.stringify(r,null,2)+`
11
+ `);}function ne(i){try{return m()[i]?.toString()}catch{return}}function c(i,t="table"){t==="json"?console.log(JSON.stringify(i,null,2)):Me(i);}function Me(i){Array.isArray(i)?Le(i):typeof i=="object"&&i!==null?De(i):console.log(i);}function Le(i){if(i.length===0){console.log(S.gray("No results"));return}let t=i[0];if(typeof t!="object"||t===null){i.forEach(r=>console.log(r));return}let e=Object.keys(t),a=new re({head:e.map(r=>S.cyan(r)),style:{head:[],border:[]}});for(let r of i){let o=e.map(n=>{let s=r[n];return se(s)});a.push(o);}console.log(a.toString());}function De(i){let t=new re({style:{head:[],border:[]}});for(let[e,a]of Object.entries(i))t.push([S.cyan(e),se(a)]);console.log(t.toString());}function se(i){return i==null?S.gray("-"):typeof i=="boolean"?i?S.green("\u2713"):S.red("\u2717"):typeof i=="object"?JSON.stringify(i):String(i)}function d(i){console.log(S.green("\u2713"),i);}function l(i){console.error(S.red("\u2717"),i);}function I(i){console.warn(S.yellow("\u26A0"),i);}function u(i){console.log(S.blue("\u2139"),i);}function ce(){return new Command("send").description(`Send an email
12
12
 
13
13
  USAGE
14
14
  emailr send --to <email_address> [options]
@@ -284,7 +284,7 @@ EXAMPLES
284
284
  emailr contacts list --format json
285
285
 
286
286
  # Combine filters with pagination
287
- emailr contacts list --subscribed --limit 100 --offset 0`).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(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};t.subscribed&&(r.subscribed=!0),t.unsubscribed&&(r.subscribed=!1);let o=await a.contacts.list(r);if(t.format==="json")c(o,"json");else {let n=o.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(n,"table"),console.log(`
287
+ emailr contacts list --subscribed --limit 100 --offset 0`).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("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};if(t.subscribed&&(r.subscribed=!0),t.unsubscribed&&(r.subscribed=!1),t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let o=await a.contacts.list(r);if(t.format==="json")c(o,"json");else {let n=o.contacts.map(s=>({ID:s.id,Email:s.email,Name:[s.first_name,s.last_name].filter(Boolean).join(" ")||"-",Subscribed:s.subscribed,Tags:s.tags?.join(", ")||"-",Created:s.created_at}));c(n,"table"),console.log(`
288
288
  Total: ${o.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list contacts"),process.exit(1);}}),i.command("get <contact_id>").description(`Get a contact by ID
289
289
 
290
290
  USAGE
@@ -354,7 +354,7 @@ EXAMPLES
354
354
  emailr contacts create --email "user@example.com" --subscribed false
355
355
 
356
356
  # Get JSON output for scripting
357
- emailr contacts create --email "user@example.com" --format json`).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(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={email:t.email};if(t.firstName&&(r.first_name=t.firstName),t.lastName&&(r.last_name=t.lastName),t.subscribed!==void 0&&(r.subscribed=t.subscribed),t.metadata)try{r.metadata=JSON.parse(t.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}let o=await a.contacts.create(r);t.format==="json"?c(o,"json"):(d(`Contact created: ${o.id}`),c(o,"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create contact"),process.exit(1);}}),i.command("update <contact_id>").description(`Update a contact
357
+ emailr contacts create --email "user@example.com" --format json`).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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={email:t.email};if(t.firstName&&(r.first_name=t.firstName),t.lastName&&(r.last_name=t.lastName),t.subscribed!==void 0&&(r.subscribed=t.subscribed),t.metadata)try{r.metadata=JSON.parse(t.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}t.tags&&(r.tags=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let o=await a.contacts.create(r);t.format==="json"?c(o,"json"):(d(`Contact created: ${o.id}`),c(o,"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create contact"),process.exit(1);}}),i.command("update <contact_id>").description(`Update a contact
358
358
 
359
359
  USAGE
360
360
  emailr contacts update <contact_id> [options]
@@ -402,7 +402,7 @@ EXAMPLES
402
402
  emailr contacts update con_abc123 --metadata '{"plan": "enterprise", "upgraded": true}'
403
403
 
404
404
  # Get JSON output
405
- emailr contacts update con_abc123 --first-name "Jane" --format json`).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 a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.email&&(o.email=e.email),e.firstName&&(o.first_name=e.firstName),e.lastName&&(o.last_name=e.lastName),e.subscribed&&(o.subscribed=!0),e.unsubscribed&&(o.subscribed=!1),e.metadata)try{o.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}let n=await r.contacts.update(t,o);e.format==="json"?c(n,"json"):(d(`Contact updated: ${n.id}`),c(n,"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
405
+ emailr contacts update con_abc123 --first-name "Jane" --format json`).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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.email&&(o.email=e.email),e.firstName&&(o.first_name=e.firstName),e.lastName&&(o.last_name=e.lastName),e.subscribed&&(o.subscribed=!0),e.unsubscribed&&(o.subscribed=!1),e.metadata)try{o.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}e.tags&&(o.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.contacts.update(t,o);e.format==="json"?c(n,"json"):(d(`Contact updated: ${n.id}`),c(n,"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
406
406
 
407
407
  USAGE
408
408
  emailr contacts delete <contact_id>
@@ -419,7 +419,7 @@ EXAMPLES
419
419
  emailr contacts delete con_abc123
420
420
 
421
421
  WARNING
422
- This action is permanent and cannot be undone.`).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);}}),i}function pe(){return N.join(W.homedir(),".config","emailr","templates")}function qe(){let i=pe();v.existsSync(i)||v.mkdirSync(i,{recursive:true});}function B(i){return N.join(pe(),`${i}.html`)}function X(i,t){qe();let e=B(i);v.writeFileSync(e,t,"utf-8");}function ue(i){let t=B(i);return v.existsSync(t)?v.readFileSync(t,"utf-8"):null}function fe(i){let t=B(i);return v.existsSync(t)}var w=null,C=null,H=false;function he(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function $e(i){return i===null||i.trim()===""}function We(i){return `<!DOCTYPE html>
422
+ This action is permanent and cannot be undone.`).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);}}),i}function pe(){return N.join(B.homedir(),".config","emailr","templates")}function qe(){let i=pe();v.existsSync(i)||v.mkdirSync(i,{recursive:true});}function W(i){return N.join(pe(),`${i}.html`)}function X(i,t){qe();let e=W(i);v.writeFileSync(e,t,"utf-8");}function ue(i){let t=W(i);return v.existsSync(t)?v.readFileSync(t,"utf-8"):null}function fe(i){let t=W(i);return v.existsSync(t)}var w=null,C=null,H=false;function he(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function $e(i){return i===null||i.trim()===""}function Be(i){return `<!DOCTYPE html>
423
423
  <html lang="en">
424
424
  <head>
425
425
  <meta charset="UTF-8">
@@ -548,7 +548,7 @@ WARNING
548
548
  <p style="margin-top: 1rem;">Try creating or retrieving the template first using the CLI.</p>
549
549
  </div>
550
550
  </body>
551
- </html>`}function Be(i){let t=i.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function Xe(){return (i,t)=>{if(i.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=i.url||"/",a=Be(e);if(!a){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!fe(a)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(a));return}let r=ue(a);if(r===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(a));return}if($e(r)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(We(a));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r);}}var ge={async start(){return H&&C!==null?C:new Promise((i,t)=>{w=Je.createServer(Xe()),w.listen(0,"127.0.0.1",()=>{let e=w.address();e&&typeof e=="object"?(C=e.port,H=true,w.unref(),i(C)):t(new Error("Failed to get server address"));}),w.on("error",e=>{H=false,C=null,w=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return C},isRunning(){return H},async stop(){if(w)return new Promise(i=>{w.close(()=>{w=null,C=null,H=false,i();});})}};function we(){return ge}async function V(i){try{return `http://127.0.0.1:${await ge.start()}/preview/${i}`}catch{return null}}function ye(){w&&w.ref();}var A=null,z=null,J=null,R=[],ve=`
551
+ </html>`}function We(i){let t=i.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function Xe(){return (i,t)=>{if(i.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=i.url||"/",a=We(e);if(!a){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!fe(a)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(a));return}let r=ue(a);if(r===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(a));return}if($e(r)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(Be(a));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r);}}var ge={async start(){return H&&C!==null?C:new Promise((i,t)=>{w=Je.createServer(Xe()),w.listen(0,"127.0.0.1",()=>{let e=w.address();e&&typeof e=="object"?(C=e.port,H=true,w.unref(),i(C)):t(new Error("Failed to get server address"));}),w.on("error",e=>{H=false,C=null,w=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return C},isRunning(){return H},async stop(){if(w)return new Promise(i=>{w.close(()=>{w=null,C=null,H=false,i();});})}};function we(){return ge}async function V(i){try{return `http://127.0.0.1:${await ge.start()}/preview/${i}`}catch{return null}}function ye(){w&&w.ref();}var P=null,z=null,J=null,R=[],ve=`
552
552
  <script>
553
553
  (function() {
554
554
  const evtSource = new EventSource('/__live-reload');
@@ -564,9 +564,9 @@ WARNING
564
564
  </script>
565
565
  `;function Ye(i){return i.includes("</body>")?i.replace("</body>",`${ve}</body>`):i+ve}function Ze(){R.forEach(i=>{try{i.write(`data: reload
566
566
 
567
- `);}catch{}});}async function Y(i,t){let e=N.resolve(i);return new Promise((a,r)=>{A=Je.createServer((o,n)=>{if(o.url==="/__live-reload"){n.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),n.write(`data: connected
567
+ `);}catch{}});}async function Y(i,t){let e=N.resolve(i);return new Promise((a,r)=>{P=Je.createServer((o,n)=>{if(o.url==="/__live-reload"){n.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),n.write(`data: connected
568
568
 
569
- `),R.push(n),o.on("close",()=>{R=R.filter(s=>s!==n);});return}if(o.method==="GET"){try{let s=v.readFileSync(e,"utf-8"),p=Ye(s);n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(p);}catch(s){n.writeHead(500,{"Content-Type":"text/plain"}),n.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}n.writeHead(405,{"Content-Type":"text/plain"}),n.end("Method Not Allowed");}),A.listen(0,"127.0.0.1",()=>{let o=A.address();if(o&&typeof o=="object"){z=o.port;let n=null;J=v.watch(e,s=>{s==="change"&&(n&&clearTimeout(n),n=setTimeout(()=>{Ze(),t?.();},100));}),a(z);}else r(new Error("Failed to get server address"));}),A.on("error",o=>{r(new Error(`Failed to start server: ${o.message}`));});})}async function Z(){if(J&&(J.close(),J=null),R.forEach(i=>{try{i.end();}catch{}}),R=[],A)return new Promise(i=>{A.close(()=>{A=null,z=null,i();});})}async function Te(i){try{let t=i.html_content??"";X(i.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(i.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 Oe(){let i=new Command("templates").description(`Manage email templates
569
+ `),R.push(n),o.on("close",()=>{R=R.filter(s=>s!==n);});return}if(o.method==="GET"){try{let s=v.readFileSync(e,"utf-8"),p=Ye(s);n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(p);}catch(s){n.writeHead(500,{"Content-Type":"text/plain"}),n.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}n.writeHead(405,{"Content-Type":"text/plain"}),n.end("Method Not Allowed");}),P.listen(0,"127.0.0.1",()=>{let o=P.address();if(o&&typeof o=="object"){z=o.port;let n=null;J=v.watch(e,s=>{s==="change"&&(n&&clearTimeout(n),n=setTimeout(()=>{Ze(),t?.();},100));}),a(z);}else r(new Error("Failed to get server address"));}),P.on("error",o=>{r(new Error(`Failed to start server: ${o.message}`));});})}async function Z(){if(J&&(J.close(),J=null),R.forEach(i=>{try{i.end();}catch{}}),R=[],P)return new Promise(i=>{P.close(()=>{P=null,z=null,i();});})}async function Te(i){try{let t=i.html_content??"";X(i.id,t);}catch(t){return I(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await V(i.id);return t===null?(I("Could not start preview server"),null):t}catch(t){return I(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function Oe(){let i=new Command("templates").description(`Manage email templates
570
570
 
571
571
  USAGE
572
572
  emailr templates <subcommand> [options]
@@ -666,8 +666,8 @@ EXAMPLES
666
666
  emailr templates list --page 2 --limit 10
667
667
 
668
668
  # Get JSON output for scripting
669
- emailr templates list --format json`).option("--limit <count>","Number of templates to return","20").option("--page <page_number>","Page number for pagination","1").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),r=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(r,"json");else {let o=r.map(n=>({ID:n.id,Name:n.name,Subject:n.subject,Variables:n.variables?.join(", ")||"-",Created:n.created_at}));c(o,"table"),console.log(`
670
- Total: ${r.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list templates"),process.exit(1);}}),i.command("get <template_id>").description(`Get a template by ID
669
+ emailr templates list --format json`).option("--limit <count>","Number of templates to return","20").option("--page <page_number>","Page number for pagination","1").option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={limit:parseInt(t.limit,10),page:parseInt(t.page,10)};if(t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let o=await a.templates.list(r);if(t.format==="json")c(o,"json");else {let n=o.map(s=>({ID:s.id,Name:s.name,Subject:s.subject,Variables:s.variables?.join(", ")||"-",Tags:s.tags?.join(", ")||"-",Created:s.created_at}));c(n,"table"),console.log(`
670
+ Total: ${o.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list templates"),process.exit(1);}}),i.command("get <template_id>").description(`Get a template by ID
671
671
 
672
672
  USAGE
673
673
  emailr templates get <template_id> [options]
@@ -820,7 +820,7 @@ EXAMPLES
820
820
 
821
821
  TIP
822
822
  For live preview while building, use "emailr templates draft" first.
823
- It creates a local file with hot-reload preview, then use this command to save.`).requiredOption("--name <template_name>","Template name").requiredOption("--subject <subject_line>","Email subject line").option("--html <html_content>","Inline HTML content").option("--text <text_content>","Plain text content").option("--html-file <file_path>","Read HTML content from file").option("--text-file <file_path>","Read text content from file").option("--from <email_address>","Default from email address").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={name:t.name,subject:t.subject};if(t.htmlFile){let s=await import('fs');r.html_content=s.readFileSync(t.htmlFile,"utf-8");}else t.html&&(r.html_content=t.html);if(t.textFile){let s=await import('fs');r.text_content=s.readFileSync(t.textFile,"utf-8");}else t.text&&(r.text_content=t.text);t.from&&(r.from_email=t.from),t.replyTo&&(r.reply_to=t.replyTo),t.previewText&&(r.preview_text=t.previewText);let o=await a.templates.create(r),n=await Te({id:o.id,html_content:o.html_content??void 0});if(t.format==="json")c({...o,preview_url:n},"json");else {d(`Template created: ${o.id}`);let s={ID:o.id,Name:o.name,Subject:o.subject,Variables:o.variables?.join(", ")||"-"};n&&(s["Preview URL"]=n),c(s,"table"),n&&console.log(`
823
+ It creates a local file with hot-reload preview, then use this command to save.`).requiredOption("--name <template_name>","Template name").requiredOption("--subject <subject_line>","Email subject line").option("--html <html_content>","Inline HTML content").option("--text <text_content>","Plain text content").option("--html-file <file_path>","Read HTML content from file").option("--text-file <file_path>","Read text content from file").option("--from <email_address>","Default from email address").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={name:t.name,subject:t.subject};if(t.htmlFile){let s=await import('fs');r.html_content=s.readFileSync(t.htmlFile,"utf-8");}else t.html&&(r.html_content=t.html);if(t.textFile){let s=await import('fs');r.text_content=s.readFileSync(t.textFile,"utf-8");}else t.text&&(r.text_content=t.text);t.from&&(r.from_email=t.from),t.replyTo&&(r.reply_to=t.replyTo),t.previewText&&(r.preview_text=t.previewText),t.tags&&(r.tags=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let o=await a.templates.create(r),n=await Te({id:o.id,html_content:o.html_content??void 0});if(t.format==="json")c({...o,preview_url:n},"json");else {d(`Template created: ${o.id}`);let s={ID:o.id,Name:o.name,Subject:o.subject,Variables:o.variables?.join(", ")||"-",Tags:o.tags?.join(", ")||"-"};n&&(s["Preview URL"]=n),c(s,"table"),n&&console.log(`
824
824
  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);}}),i.command("update <template_id>").description(`Update an existing email template
825
825
 
826
826
  USAGE
@@ -873,7 +873,7 @@ AGENTIC WORKFLOW
873
873
  2. Edit locally or with AI assistance
874
874
  3. Push preview: emailr templates push-preview <id> --html-file template.html
875
875
  4. Share URL with AI agent, iterate on feedback
876
- 5. Publish: emailr templates update <id> --html-file template.html`).option("--name <template_name>","New template name").option("--subject <subject_line>","New email subject line").option("--html <html_content>","New inline HTML content").option("--text <text_content>","New plain text content").option("--html-file <file_path>","Read new HTML content from file").option("--text-file <file_path>","Read new text content from file").option("--from <email_address>","New default from email address").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.name&&(o.name=e.name),e.subject&&(o.subject=e.subject),e.htmlFile){let s=await import('fs');o.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(o.html_content=e.html);if(e.textFile){let s=await import('fs');o.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(o.text_content=e.text);e.from&&(o.from_email=e.from),e.replyTo&&(o.reply_to=e.replyTo),e.previewText&&(o.preview_text=e.previewText);let n=await r.templates.update(t,o);if(e.format==="json")c(n,"json");else {d(`Template updated: ${n.id}`);let s={ID:n.id,Name:n.name,Subject:n.subject,Variables:n.variables?.join(", ")||"-",Updated:n.updated_at};c(s,"table");}}catch(a){l(a instanceof Error?a.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
876
+ 5. Publish: emailr templates update <id> --html-file template.html`).option("--name <template_name>","New template name").option("--subject <subject_line>","New email subject line").option("--html <html_content>","New inline HTML content").option("--text <text_content>","New plain text content").option("--html-file <file_path>","Read new HTML content from file").option("--text-file <file_path>","Read new text content from file").option("--from <email_address>","New default from email address").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.name&&(o.name=e.name),e.subject&&(o.subject=e.subject),e.htmlFile){let s=await import('fs');o.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(o.html_content=e.html);if(e.textFile){let s=await import('fs');o.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(o.text_content=e.text);e.from&&(o.from_email=e.from),e.replyTo&&(o.reply_to=e.replyTo),e.previewText&&(o.preview_text=e.previewText),e.tags&&(o.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.templates.update(t,o);if(e.format==="json")c(n,"json");else {d(`Template updated: ${n.id}`);let s={ID:n.id,Name:n.name,Subject:n.subject,Variables:n.variables?.join(", ")||"-",Tags:n.tags?.join(", ")||"-",Updated:n.updated_at};c(s,"table");}}catch(a){l(a instanceof Error?a.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
877
877
 
878
878
  USAGE
879
879
  emailr templates delete <template_id>
@@ -1578,7 +1578,7 @@ EXAMPLES
1578
1578
 
1579
1579
  NOTE
1580
1580
  Environment variables (EMAILR_API_KEY, EMAILR_BASE_URL) take precedence
1581
- over config file values at runtime.`).action(async(t,e)=>{try{let a=["api-key","base-url","format"],r=t.toLowerCase();a.includes(r)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${a.join(", ")}`),process.exit(1));let n={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[r];r==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),u("Valid formats: json, table"),process.exit(1)),G({[n]:e}),d(`Configuration saved: ${t} = ${r==="api-key"?"***":e}`),u(`Config file: ${I()}`);}catch(a){l(a instanceof Error?a.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1581
+ over config file values at runtime.`).action(async(t,e)=>{try{let a=["api-key","base-url","format"],r=t.toLowerCase();a.includes(r)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${a.join(", ")}`),process.exit(1));let n={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[r];r==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),u("Valid formats: json, table"),process.exit(1)),G({[n]:e}),d(`Configuration saved: ${t} = ${r==="api-key"?"***":e}`),u(`Config file: ${k()}`);}catch(a){l(a instanceof Error?a.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1582
1582
 
1583
1583
  USAGE
1584
1584
  emailr config get <key>
@@ -1634,7 +1634,7 @@ EXAMPLES
1634
1634
 
1635
1635
  SEE ALSO
1636
1636
  emailr config get Get a single configuration value
1637
- emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a={"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(a,"json");else {let r=Object.entries(a).map(([o,n])=>({Key:o,Value:n}));c(r,"table");}console.log(""),u(`Config file: ${I()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(u("No configuration found."),u("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));}}),i.command("path").description(`Show the configuration file path
1637
+ emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a={"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(a,"json");else {let r=Object.entries(a).map(([o,n])=>({Key:o,Value:n}));c(r,"table");}console.log(""),u(`Config file: ${k()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(u("No configuration found."),u("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));}}),i.command("path").description(`Show the configuration file path
1638
1638
 
1639
1639
  USAGE
1640
1640
  emailr config path
@@ -1661,7 +1661,7 @@ EXAMPLES
1661
1661
  open $(emailr config path)
1662
1662
 
1663
1663
  # View config file contents
1664
- cat $(emailr config path)`).action(()=>{console.log(I());}),i.command("init").description(`Initialize configuration interactively
1664
+ cat $(emailr config path)`).action(()=>{console.log(k());}),i.command("init").description(`Initialize configuration interactively
1665
1665
 
1666
1666
  USAGE
1667
1667
  emailr config init [options]
@@ -1694,7 +1694,7 @@ ALTERNATIVE: ENVIRONMENT VARIABLES
1694
1694
 
1695
1695
  SEE ALSO
1696
1696
  emailr config set Set individual configuration values
1697
- emailr config list View current configuration`).option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async t=>{try{t.apiKey?(G({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),u(`Config file: ${I()}`)):(u("Initialize your Emailr CLI configuration:"),console.log(""),u("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),u("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);}}),i}function _e(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1697
+ emailr config list View current configuration`).option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async t=>{try{t.apiKey?(G({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),u(`Config file: ${k()}`)):(u("Initialize your Emailr CLI configuration:"),console.log(""),u("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),u("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);}}),i}function _e(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1698
1698
 
1699
1699
  USAGE
1700
1700
  emailr broadcasts <subcommand> [options]
@@ -1842,7 +1842,7 @@ EXAMPLES
1842
1842
  emailr broadcasts list --format json
1843
1843
 
1844
1844
  # Pipe JSON to jq for processing
1845
- emailr broadcasts list --format json | jq '.[] | select(.status == "scheduled")'`).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(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.list({status:t.status,limit:parseInt(t.limit)});if(t.format==="json")c(r,"json");else {if(r.length===0){console.log("No broadcasts found.");return}let o=r.map(n=>({ID:n.id,Name:n.name,Subject:n.subject.substring(0,30)+(n.subject.length>30?"...":""),Status:n.status,Recipients:n.total_recipients||0,Sent:n.sent_count||0,Created:new Date(n.created_at).toLocaleDateString()}));c(o,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),i.command("get <broadcast_id>").description(`Get broadcast details
1845
+ emailr broadcasts list --format json | jq '.[] | select(.status == "scheduled")'`).option("--status <status>","Filter by status (draft, scheduled, sending, sent)").option("--limit <number>","Number of broadcasts to return","20").option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={status:t.status,limit:parseInt(t.limit)};if(t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let o=await a.broadcasts.list(r);if(t.format==="json")c(o,"json");else {if(o.length===0){console.log("No broadcasts found.");return}let n=o.map(s=>({ID:s.id,Name:s.name,Subject:s.subject.substring(0,30)+(s.subject.length>30?"...":""),Status:s.status,Tags:s.tags?.join(", ")||"-",Recipients:s.total_recipients||0,Sent:s.sent_count||0,Created:new Date(s.created_at).toLocaleDateString()}));c(n,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),i.command("get <broadcast_id>").description(`Get broadcast details
1846
1846
 
1847
1847
  USAGE
1848
1848
  emailr broadcasts get <broadcast_id> [options]
@@ -1877,7 +1877,7 @@ EXAMPLES
1877
1877
  emailr broadcasts get brd_abc123 --format json
1878
1878
 
1879
1879
  # Pipe JSON to jq for processing
1880
- emailr broadcasts get brd_abc123 --format json | jq '{sent: .sent_count, opened: .opened_count}'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,Subject:o.subject,"From Email":o.from_email,Status:o.status,"Total Recipients":o.total_recipients||0,"Sent Count":o.sent_count||0,Delivered:o.delivered_count||0,Opened:o.opened_count||0,Clicked:o.clicked_count||0,Bounced:o.bounced_count||0,"Scheduled At":o.scheduled_at||"N/A","Started At":o.started_at||"N/A","Completed At":o.completed_at||"N/A","Created At":o.created_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get broadcast"),process.exit(1);}}),i.command("create").description(`Create a new broadcast
1880
+ emailr broadcasts get brd_abc123 --format json | jq '{sent: .sent_count, opened: .opened_count}'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,Subject:o.subject,"From Email":o.from_email,Status:o.status,Tags:o.tags?.join(", ")||"-","Total Recipients":o.total_recipients||0,"Sent Count":o.sent_count||0,Delivered:o.delivered_count||0,Opened:o.opened_count||0,Clicked:o.clicked_count||0,Bounced:o.bounced_count||0,"Scheduled At":o.scheduled_at||"N/A","Started At":o.started_at||"N/A","Completed At":o.completed_at||"N/A","Created At":o.created_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get broadcast"),process.exit(1);}}),i.command("create").description(`Create a new broadcast
1881
1881
 
1882
1882
  USAGE
1883
1883
  emailr broadcasts create --name <name> --subject <subject> --from <email> [options]
@@ -1953,7 +1953,7 @@ EXAMPLES
1953
1953
 
1954
1954
  SEE ALSO
1955
1955
  emailr templates Create and manage email templates
1956
- emailr segments Create and manage contact segments`).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(),r=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(r,"json"):(d("Broadcast created successfully!"),c({ID:r.id,Name:r.name,Status:r.status,"Scheduled At":r.scheduled_at||"Not scheduled"},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),i.command("send <broadcast_id>").description(`Send a broadcast immediately
1956
+ emailr segments Create and manage contact segments`).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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={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.tags&&(r.tags=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let o=await a.broadcasts.create(r);t.format==="json"?c(o,"json"):(d("Broadcast created successfully!"),c({ID:o.id,Name:o.name,Status:o.status,Tags:o.tags?.join(", ")||"-","Scheduled At":o.scheduled_at||"Not scheduled"},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),i.command("send <broadcast_id>").description(`Send a broadcast immediately
1957
1957
 
1958
1958
  USAGE
1959
1959
  emailr broadcasts send <broadcast_id> [options]
@@ -2092,7 +2092,7 @@ EXAMPLES
2092
2092
 
2093
2093
  WARNING
2094
2094
  This action is permanent and cannot be undone.
2095
- All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.delete(t);e.format==="json"?c(o,"json"):d("Broadcast deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete broadcast"),process.exit(1);}}),i}function Ie(){let i=new Command("webhooks").description(`Manage webhooks
2095
+ All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.delete(t);e.format==="json"?c(o,"json"):d("Broadcast deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete broadcast"),process.exit(1);}}),i}function ke(){let i=new Command("webhooks").description(`Manage webhooks
2096
2096
 
2097
2097
  USAGE
2098
2098
  emailr webhooks <subcommand> [options]
@@ -2476,7 +2476,7 @@ EXAMPLES
2476
2476
 
2477
2477
  WARNING
2478
2478
  This action is permanent and cannot be undone.
2479
- Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).webhooks.delete(t);e.format==="json"?c(o,"json"):d("Webhook deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete webhook"),process.exit(1);}}),i}function ke(){let i=new Command("segments").description(`Manage contact segments
2479
+ Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).webhooks.delete(t);e.format==="json"?c(o,"json"):d("Webhook deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete webhook"),process.exit(1);}}),i}function Ie(){let i=new Command("segments").description(`Manage contact segments
2480
2480
 
2481
2481
  USAGE
2482
2482
  emailr segments <subcommand> [options]
@@ -2615,7 +2615,7 @@ EXAMPLES
2615
2615
  emailr segments list --format json
2616
2616
 
2617
2617
  # Pipe JSON to jq for processing
2618
- emailr segments list --format json | jq '.[].name'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).segments.list();if(t.format==="json")c(r,"json");else {if(r.length===0){console.log("No segments found.");return}let o=r.map(n=>({ID:n.id,Name:n.name,Description:(n.description||"").substring(0,30)+((n.description?.length||0)>30?"...":""),"Created At":new Date(n.created_at).toLocaleDateString()}));c(o,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),i.command("get <segment_id>").description(`Get segment details
2618
+ emailr segments list --format json | jq '.[].name'`).option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={};if(t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let o=await a.segments.list(r);if(t.format==="json")c(o,"json");else {if(o.length===0){console.log("No segments found.");return}let n=o.map(s=>({ID:s.id,Name:s.name,Description:(s.description||"").substring(0,30)+((s.description?.length||0)>30?"...":""),Tags:s.tags?.join(", ")||"-","Created At":new Date(s.created_at).toLocaleDateString()}));c(n,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),i.command("get <segment_id>").description(`Get segment details
2619
2619
 
2620
2620
  USAGE
2621
2621
  emailr segments get <segment_id> [options]
@@ -2642,7 +2642,7 @@ EXAMPLES
2642
2642
  emailr segments get seg_abc123 --format json
2643
2643
 
2644
2644
  # Pipe JSON to jq to view conditions
2645
- emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).segments.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,Description:o.description||"N/A",Conditions:JSON.stringify(o.conditions),"Created At":o.created_at,"Updated At":o.updated_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get segment"),process.exit(1);}}),i.command("create").description(`Create a new segment
2645
+ emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).segments.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,Description:o.description||"N/A",Conditions:JSON.stringify(o.conditions),Tags:o.tags?.join(", ")||"-","Created At":o.created_at,"Updated At":o.updated_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get segment"),process.exit(1);}}),i.command("create").description(`Create a new segment
2646
2646
 
2647
2647
  USAGE
2648
2648
  emailr segments create --name <segment_name> --conditions <json> [options]
@@ -2691,7 +2691,7 @@ EXAMPLES
2691
2691
  # Get JSON output for scripting
2692
2692
  emailr segments create --name "Test" \\
2693
2693
  --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]' \\
2694
- --format json`).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(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r;try{r=JSON.parse(t.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let o=await a.segments.create({name:t.name,description:t.description,conditions:r});t.format==="json"?c(o,"json"):(d("Segment created successfully!"),c({ID:o.id,Name:o.name},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create segment"),process.exit(1);}}),i.command("update <segment_id>").description(`Update a segment
2694
+ --format json`).requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r;try{r=JSON.parse(t.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let o={name:t.name,description:t.description,conditions:r};t.tags&&(o.tags=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await a.segments.create(o);t.format==="json"?c(n,"json"):(d("Segment created successfully!"),c({ID:n.id,Name:n.name,Tags:n.tags?.join(", ")||"-"},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create segment"),process.exit(1);}}),i.command("update <segment_id>").description(`Update a segment
2695
2695
 
2696
2696
  USAGE
2697
2697
  emailr segments update <segment_id> [options]
@@ -2736,7 +2736,7 @@ EXAMPLES
2736
2736
  --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2737
2737
 
2738
2738
  # Get JSON output
2739
- emailr segments update seg_abc123 --name "Updated" --format json`).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 a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.name&&(o.name=e.name),e.description&&(o.description=e.description),e.conditions)try{o.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let n=await r.segments.update(t,o);e.format==="json"?c(n,"json"):(d("Segment updated successfully!"),c({ID:n.id,Name:n.name},"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2739
+ emailr segments update seg_abc123 --name "Updated" --format json`).option("--name <name>","New segment name").option("--description <description>","New description").option("--conditions <json>","New conditions as JSON").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.name&&(o.name=e.name),e.description&&(o.description=e.description),e.conditions)try{o.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}e.tags&&(o.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.segments.update(t,o);e.format==="json"?c(n,"json"):(d("Segment updated successfully!"),c({ID:n.id,Name:n.name,Tags:n.tags?.join(", ")||"-"},"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2740
2740
 
2741
2741
  USAGE
2742
2742
  emailr segments delete <segment_id>
@@ -2893,7 +2893,7 @@ TIP
2893
2893
  <p style="margin-top: 1rem;">Please close this window and try again.</p>
2894
2894
  </div>
2895
2895
  </body>
2896
- </html>`}function je(){let i=null,t=0,e=null,a=null,r=null;return {async start(){return new Promise((o,n)=>{i=Je.createServer((s,p)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){p.writeHead(404,{"Content-Type":"text/plain"}),p.end("Not Found");return}let b=st(s.url);if(r&&b.state!==r){p.writeHead(400,{"Content-Type":"text/html"}),p.end(ee("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(b.error){let h=b.message||b.error;p.writeHead(200,{"Content-Type":"text/html"}),p.end(ee(h)),e&&e({success:false,error:h});return}let T=b.key||b.code;if(T){p.writeHead(200,{"Content-Type":"text/html"}),p.end(lt()),e&&e({success:true,apiKey:T});return}p.writeHead(400,{"Content-Type":"text/html"}),p.end(ee("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),i.listen(0,"127.0.0.1",()=>{let s=i.address();s&&typeof s=="object"?(t=s.port,o({port:t,url:`http://127.0.0.1:${t}/callback`})):n(new Error("Failed to get server address"));}),i.on("error",s=>{n(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(o,n){return r=o,new Promise(s=>{e=s,a=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},n);})},async stop(){if(a&&(clearTimeout(a),a=null),i)return new Promise(o=>{i.close(()=>{i=null,o();});})}}}function Ce(){return ct.randomBytes(32).toString("hex")}function Ae(i){return new Promise(t=>{let e=process.platform,a;switch(e){case "darwin":a=`open "${i}"`;break;case "win32":a=`start "" "${i}"`;break;default:a=`xdg-open "${i}"`;break}exec(a,r=>{t(!r);});})}var $=120,pt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function ut(i,t){let e=`http://127.0.0.1:${t}/callback`,a=new URLSearchParams({state:i,callback_url:e});return `${pt}/consent/authorize?${a.toString()}`}function Pe(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String($)).option("--no-browser","Don't automatically open the browser").action(async t=>{await ft({timeout:parseInt(t.timeout,10)||$,noBrowser:t.browser===false});})}async function ft(i){let t=je(),e=(i.timeout||$)*1e3;try{u("Starting authentication server...");let{port:a,url:r}=await t.start(),o=Ce(),n=ut(o,a);console.log(""),u("Authorization URL:"),console.log(` ${n}`),console.log(""),i.noBrowser?u("Please open the URL above in your browser to continue."):await Ae(n)?u("Browser opened. Please complete authentication in your browser."):(k("Could not open browser automatically."),u("Please open the URL above in your browser to continue.")),console.log(""),u(`Waiting for authentication (timeout: ${i.timeout||$}s)...`);let s=await t.waitForCallback(o,e);s.success&&s.apiKey?(G({apiKey:s.apiKey}),console.log(""),d("Login successful!"),u(`API key saved to: ${I()}`),u("You can now use the Emailr CLI.")):(console.log(""),l(s.error||"Authentication failed."),u("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1));}catch(a){console.log(""),l(a instanceof Error?a.message:"An unexpected error occurred."),u("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 ae=N.join(W.homedir(),".config","opencode","skills","emailr-cli"),wt=`---
2896
+ </html>`}function je(){let i=null,t=0,e=null,a=null,r=null;return {async start(){return new Promise((o,n)=>{i=Je.createServer((s,p)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){p.writeHead(404,{"Content-Type":"text/plain"}),p.end("Not Found");return}let b=st(s.url);if(r&&b.state!==r){p.writeHead(400,{"Content-Type":"text/html"}),p.end(ee("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(b.error){let h=b.message||b.error;p.writeHead(200,{"Content-Type":"text/html"}),p.end(ee(h)),e&&e({success:false,error:h});return}let T=b.key||b.code;if(T){p.writeHead(200,{"Content-Type":"text/html"}),p.end(lt()),e&&e({success:true,apiKey:T});return}p.writeHead(400,{"Content-Type":"text/html"}),p.end(ee("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),i.listen(0,"127.0.0.1",()=>{let s=i.address();s&&typeof s=="object"?(t=s.port,o({port:t,url:`http://127.0.0.1:${t}/callback`})):n(new Error("Failed to get server address"));}),i.on("error",s=>{n(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(o,n){return r=o,new Promise(s=>{e=s,a=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},n);})},async stop(){if(a&&(clearTimeout(a),a=null),i)return new Promise(o=>{i.close(()=>{i=null,o();});})}}}function Ce(){return ct.randomBytes(32).toString("hex")}function Pe(i){return new Promise(t=>{let e=process.platform,a;switch(e){case "darwin":a=`open "${i}"`;break;case "win32":a=`start "" "${i}"`;break;default:a=`xdg-open "${i}"`;break}exec(a,r=>{t(!r);});})}var $=120,pt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function ut(i,t){let e=`http://127.0.0.1:${t}/callback`,a=new URLSearchParams({state:i,callback_url:e});return `${pt}/consent/authorize?${a.toString()}`}function Ae(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String($)).option("--no-browser","Don't automatically open the browser").action(async t=>{await ft({timeout:parseInt(t.timeout,10)||$,noBrowser:t.browser===false});})}async function ft(i){let t=je(),e=(i.timeout||$)*1e3;try{u("Starting authentication server...");let{port:a,url:r}=await t.start(),o=Ce(),n=ut(o,a);console.log(""),u("Authorization URL:"),console.log(` ${n}`),console.log(""),i.noBrowser?u("Please open the URL above in your browser to continue."):await Pe(n)?u("Browser opened. Please complete authentication in your browser."):(I("Could not open browser automatically."),u("Please open the URL above in your browser to continue.")),console.log(""),u(`Waiting for authentication (timeout: ${i.timeout||$}s)...`);let s=await t.waitForCallback(o,e);s.success&&s.apiKey?(G({apiKey:s.apiKey}),console.log(""),d("Login successful!"),u(`API key saved to: ${k()}`),u("You can now use the Emailr CLI.")):(console.log(""),l(s.error||"Authentication failed."),u("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1));}catch(a){console.log(""),l(a instanceof Error?a.message:"An unexpected error occurred."),u("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 ae=N.join(B.homedir(),".config","opencode","skills","emailr-cli"),wt=`---
2897
2897
  name: emailr-cli
2898
2898
  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.
2899
2899
  ---
@@ -2942,7 +2942,7 @@ The agent can help you:
2942
2942
  - Configure domains and webhooks
2943
2943
 
2944
2944
  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?St()||(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(`
2945
- 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(r){k(`Could not install skill: ${r instanceof Error?r.message:String(r)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
2945
+ 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(r){I(`Could not install skill: ${r instanceof Error?r.message:String(r)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
2946
2946
  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.
2947
2947
  `);let a=spawn("opencode",e,{stdio:"inherit",env:process.env});a.on("error",r=>{l(`Failed to start agent: ${r.message}`),process.exit(1);}),a.on("exit",r=>{process.exit(r??0);});})}var g=new Command;g.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
2948
2948
 
@@ -3030,4 +3030,4 @@ AGENTIC WORKFLOW
3030
3030
 
3031
3031
  MORE INFORMATION
3032
3032
  Run 'emailr <command> --help' for detailed help on any command.
3033
- Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.5.4");g.addCommand(ce());g.addCommand(me());g.addCommand(Oe());g.addCommand(Ee());g.addCommand(_e());g.addCommand(Ie());g.addCommand(ke());g.addCommand(Ne());g.addCommand(Pe());g.addCommand(xe());g.parse();
3033
+ Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.5.4");g.addCommand(ce());g.addCommand(me());g.addCommand(Oe());g.addCommand(Ee());g.addCommand(_e());g.addCommand(ke());g.addCommand(Ie());g.addCommand(Ne());g.addCommand(Ae());g.addCommand(xe());g.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailr-cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Command-line interface for the Emailr email API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "chalk": "^5.3.0",
24
24
  "cli-table3": "^0.6.3",
25
25
  "commander": "^12.0.0",
26
- "emailr": "^1.2.0",
26
+ "emailr": "^1.3.0",
27
27
  "open": "^11.0.0"
28
28
  },
29
29
  "devDependencies": {