emailr-cli 1.9.0 → 1.11.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 +451 -134
  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 O,{readFileSync}from'fs';import X from'os';import E from'path';import ce from'cli-table3';import T from'chalk';import tt from'http';import {URL}from'url';import St from'crypto';import {spawn,execSync,exec}from'child_process';var Be=[E.join(X.homedir(),".emailrrc"),E.join(X.homedir(),".config","emailr","config.json")];function c(){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 Be)if(O.existsSync(i))try{let t=O.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 x,{readFileSync}from'fs';import V from'os';import E from'path';import de from'cli-table3';import T from'chalk';import at from'http';import {URL}from'url';import vt from'crypto';import {spawn,execSync,exec}from'child_process';var We=[E.join(V.homedir(),".emailrrc"),E.join(V.homedir(),".config","emailr","config.json")];function d(){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 We)if(x.existsSync(i))try{let a=x.readFileSync(i,"utf-8"),e=JSON.parse(a);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=E.join(X.homedir(),".config","emailr");return E.join(i,"config.json")}function G(i){let t=I(),e=E.dirname(t);O.existsSync(e)||O.mkdirSync(e,{recursive:true});let o={};if(O.existsSync(t))try{o=JSON.parse(O.readFileSync(t,"utf-8"));}catch{}let r={...o,...i};O.writeFileSync(t,JSON.stringify(r,null,2)+`
11
- `);}function me(i){try{return c()[i]?.toString()}catch{return}}function m(i,t="table"){t==="json"?console.log(JSON.stringify(i,null,2)):Ve(i);}function Ve(i){Array.isArray(i)?Xe(i):typeof i=="object"&&i!==null?We(i):console.log(i);}function Xe(i){if(i.length===0){console.log(T.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),o=new ce({head:e.map(r=>T.cyan(r)),style:{head:[],border:[]}});for(let r of i){let a=e.map(n=>{let s=r[n];return de(s)});o.push(a);}console.log(o.toString());}function We(i){let t=new ce({style:{head:[],border:[]}});for(let[e,o]of Object.entries(i))t.push([T.cyan(e),de(o)]);console.log(t.toString());}function de(i){return i==null?T.gray("-"):typeof i=="boolean"?i?T.green("\u2713"):T.red("\u2717"):typeof i=="object"?JSON.stringify(i):String(i)}function p(i){console.log(T.green("\u2713"),i);}function l(i){console.error(T.red("\u2717"),i);}function N(i){console.warn(T.yellow("\u26A0"),i);}function u(i){console.log(T.blue("\u2139"),i);}function ue(){return new Command("send").description(`Send an email
10
+ Or run: emailr config set api-key <your-api-key>`)}function I(){let i=E.join(V.homedir(),".config","emailr");return E.join(i,"config.json")}function K(i){let a=I(),e=E.dirname(a);x.existsSync(e)||x.mkdirSync(e,{recursive:true});let o={};if(x.existsSync(a))try{o=JSON.parse(x.readFileSync(a,"utf-8"));}catch{}let n={...o,...i};x.writeFileSync(a,JSON.stringify(n,null,2)+`
11
+ `);}function ce(i){try{return d()[i]?.toString()}catch{return}}function m(i,a="table"){a==="json"?console.log(JSON.stringify(i,null,2)):Xe(i);}function Xe(i){Array.isArray(i)?Ve(i):typeof i=="object"&&i!==null?ze(i):console.log(i);}function Ve(i){if(i.length===0){console.log(T.gray("No results"));return}let a=i[0];if(typeof a!="object"||a===null){i.forEach(n=>console.log(n));return}let e=Object.keys(a),o=new de({head:e.map(n=>T.cyan(n)),style:{head:[],border:[]}});for(let n of i){let t=e.map(r=>{let s=n[r];return pe(s)});o.push(t);}console.log(o.toString());}function ze(i){let a=new de({style:{head:[],border:[]}});for(let[e,o]of Object.entries(i))a.push([T.cyan(e),pe(o)]);console.log(a.toString());}function pe(i){return i==null?T.gray("-"):typeof i=="boolean"?i?T.green("\u2713"):T.red("\u2717"):typeof i=="object"?JSON.stringify(i):String(i)}function p(i){console.log(T.green("\u2713"),i);}function l(i){console.error(T.red("\u2717"),i);}function N(i){console.warn(T.yellow("\u26A0"),i);}function u(i){console.log(T.blue("\u2139"),i);}function fe(){return new Command("send").description(`Send an email
12
12
 
13
13
  USAGE
14
14
  emailr send --to <email_address> [options]
@@ -156,7 +156,7 @@ EXAMPLES
156
156
  SEE ALSO
157
157
  emailr templates Manage email templates
158
158
  emailr contacts Manage contacts
159
- emailr broadcasts Send bulk emails to segments`).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 (inline)").option("--text <text>","Plain text content (inline)").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read plain text content from file").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=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r=t.to.split(",").map(s=>s.trim()),a={to:r.length===1?r[0]:r};if(t.from&&(a.from=t.from),t.subject&&(a.subject=t.subject),t.htmlFile)try{a.html=readFileSync(t.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${t.htmlFile}`),process.exit(1);}else t.html&&(a.html=t.html);if(t.textFile)try{a.text=readFileSync(t.textFile,"utf-8");}catch{l(`Failed to read text file: ${t.textFile}`),process.exit(1);}else t.text&&(a.text=t.text);if(t.template&&(a.template_id=t.template),t.templateData)try{a.template_data=JSON.parse(t.templateData);}catch{l("Invalid JSON for --template-data"),process.exit(1);}if(t.cc){let s=t.cc.split(",").map(d=>d.trim());a.cc=s.length===1?s[0]:s;}if(t.bcc){let s=t.bcc.split(",").map(d=>d.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 n=await o.emails.send(a);t.format==="json"?m(n,"json"):(p("Email sent successfully!"),m({"Message ID":n.message_id,Recipients:n.recipients,Status:n.status,...n.scheduled_at&&{"Scheduled At":n.scheduled_at}},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to send email"),process.exit(1);}})}function fe(){let i=new Command("contacts").description(`Manage contacts
159
+ emailr broadcasts Send bulk emails to segments`).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 (inline)").option("--text <text>","Plain text content (inline)").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read plain text content from file").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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n=a.to.split(",").map(s=>s.trim()),t={to:n.length===1?n[0]:n};if(a.from&&(t.from=a.from),a.subject&&(t.subject=a.subject),a.htmlFile)try{t.html=readFileSync(a.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${a.htmlFile}`),process.exit(1);}else a.html&&(t.html=a.html);if(a.textFile)try{t.text=readFileSync(a.textFile,"utf-8");}catch{l(`Failed to read text file: ${a.textFile}`),process.exit(1);}else a.text&&(t.text=a.text);if(a.template&&(t.template_id=a.template),a.templateData)try{t.template_data=JSON.parse(a.templateData);}catch{l("Invalid JSON for --template-data"),process.exit(1);}if(a.cc){let s=a.cc.split(",").map(c=>c.trim());t.cc=s.length===1?s[0]:s;}if(a.bcc){let s=a.bcc.split(",").map(c=>c.trim());t.bcc=s.length===1?s[0]:s;}a.replyTo&&(t.reply_to_email=a.replyTo),a.schedule&&(t.scheduled_at=a.schedule);let r=await o.emails.send(t);a.format==="json"?m(r,"json"):(p("Email sent successfully!"),m({"Message ID":r.message_id,Recipients:r.recipients,Status:r.status,...r.scheduled_at&&{"Scheduled At":r.scheduled_at}},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to send email"),process.exit(1);}})}function be(){let i=new Command("contacts").description(`Manage contacts
160
160
 
161
161
  USAGE
162
162
  emailr contacts <subcommand> [options]
@@ -286,8 +286,8 @@ EXAMPLES
286
286
  emailr contacts list --format json
287
287
 
288
288
  # Combine filters with pagination
289
- 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("--search <query>","Search by email, first name, or last name").option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=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.search&&(r.search=t.search),t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let a=await o.contacts.list(r);if(t.format==="json")m(a,"json");else {let n=a.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}));m(n,"table"),console.log(`
290
- Total: ${a.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
+ 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("--search <query>","Search by email, first name, or last name").option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={limit:parseInt(a.limit,10),offset:parseInt(a.offset,10)};if(a.subscribed&&(n.subscribed=!0),a.unsubscribed&&(n.subscribed=!1),a.search&&(n.search=a.search),a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let t=await o.contacts.list(n);if(a.format==="json")m(t,"json");else {let r=t.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}));m(r,"table"),console.log(`
290
+ Total: ${t.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
291
291
 
292
292
  USAGE
293
293
  emailr contacts get <contact_id> [options]
@@ -314,7 +314,7 @@ EXAMPLES
314
314
  emailr contacts get con_abc123 --format json
315
315
 
316
316
  # Pipe JSON to jq for processing
317
- emailr contacts get con_abc123 --format json | jq '.metadata'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).contacts.get(t);m(a,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get contact"),process.exit(1);}}),i.command("create").description(`Create a new contact
317
+ emailr contacts get con_abc123 --format json | jq '.metadata'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).contacts.get(a);m(t,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get contact"),process.exit(1);}}),i.command("create").description(`Create a new contact
318
318
 
319
319
  USAGE
320
320
  emailr contacts create --email <email_address> [options]
@@ -357,7 +357,7 @@ EXAMPLES
357
357
  emailr contacts create --email "user@example.com" --subscribed false
358
358
 
359
359
  # Get JSON output for scripting
360
- 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=c(),o=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 a=await o.contacts.create(r);t.format==="json"?m(a,"json"):(p(`Contact created: ${a.id}`),m(a,"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
360
+ 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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={email:a.email};if(a.firstName&&(n.first_name=a.firstName),a.lastName&&(n.last_name=a.lastName),a.subscribed!==void 0&&(n.subscribed=a.subscribed),a.metadata)try{n.metadata=JSON.parse(a.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}a.tags&&(n.tags=a.tags.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean));let t=await o.contacts.create(n);a.format==="json"?m(t,"json"):(p(`Contact created: ${t.id}`),m(t,"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
361
361
 
362
362
  USAGE
363
363
  emailr contacts update <contact_id> [options]
@@ -406,7 +406,7 @@ EXAMPLES
406
406
  emailr contacts update con_abc123 --metadata '{"plan": "enterprise", "upgraded": true}'
407
407
 
408
408
  # Get JSON output
409
- 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 o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.email&&(a.email=e.email),e.firstName&&(a.first_name=e.firstName),e.lastName&&(a.last_name=e.lastName),e.subscribed&&(a.subscribed=!0),e.unsubscribed&&(a.subscribed=!1),e.metadata)try{a.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.contacts.update(t,a);e.format==="json"?m(n,"json"):(p(`Contact updated: ${n.id}`),m(n,"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
409
+ 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(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={};if(e.email&&(t.email=e.email),e.firstName&&(t.first_name=e.firstName),e.lastName&&(t.last_name=e.lastName),e.subscribed&&(t.subscribed=!0),e.unsubscribed&&(t.subscribed=!1),e.metadata)try{t.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}e.tags&&(t.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await n.contacts.update(a,t);e.format==="json"?m(r,"json"):(p(`Contact updated: ${r.id}`),m(r,"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
410
410
 
411
411
  USAGE
412
412
  emailr contacts delete <contact_id>
@@ -423,7 +423,7 @@ EXAMPLES
423
423
  emailr contacts delete con_abc123
424
424
 
425
425
  WARNING
426
- This action is permanent and cannot be undone.`).action(async t=>{try{let e=c();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(t),p(`Contact deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),i}function he(){return E.join(X.homedir(),".config","emailr","templates")}function et(){let i=he();O.existsSync(i)||O.mkdirSync(i,{recursive:true});}function W(i){return E.join(he(),`${i}.html`)}function z(i,t){et();let e=W(i);O.writeFileSync(e,t,"utf-8");}function ge(i){let t=W(i);return O.existsSync(t)?O.readFileSync(t,"utf-8"):null}function we(i){let t=W(i);return O.existsSync(t)}var v=null,P=null,q=false;function Se(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function at(i){return i===null||i.trim()===""}function ot(i){return `<!DOCTYPE html>
426
+ This action is permanent and cannot be undone.`).action(async a=>{try{let e=d();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(a),p(`Contact deleted: ${a}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),i}function ge(){return E.join(V.homedir(),".config","emailr","templates")}function tt(){let i=ge();x.existsSync(i)||x.mkdirSync(i,{recursive:true});}function z(i){return E.join(ge(),`${i}.html`)}function Y(i,a){tt();let e=z(i);x.writeFileSync(e,a,"utf-8");}function we(i){let a=z(i);return x.existsSync(a)?x.readFileSync(a,"utf-8"):null}function ye(i){let a=z(i);return x.existsSync(a)}var v=null,k=null,q=false;function ve(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function ot(i){return i===null||i.trim()===""}function it(i){return `<!DOCTYPE html>
427
427
  <html lang="en">
428
428
  <head>
429
429
  <meta charset="UTF-8">
@@ -489,14 +489,14 @@ WARNING
489
489
  <div class="icon">\u{1F4ED}</div>
490
490
  <h1>No Content Available</h1>
491
491
  <p>This template exists but has no HTML content to display.</p>
492
- <div class="template-id">${Se(i)}</div>
492
+ <div class="template-id">${ve(i)}</div>
493
493
  <div class="hint">
494
494
  <p><strong>To add content:</strong></p>
495
495
  <p>Update the template using the CLI with the <code>--html</code> option or provide HTML content when creating the template.</p>
496
496
  </div>
497
497
  </div>
498
498
  </body>
499
- </html>`}function ye(i){return `<!DOCTYPE html>
499
+ </html>`}function Se(i){return `<!DOCTYPE html>
500
500
  <html lang="en">
501
501
  <head>
502
502
  <meta charset="UTF-8">
@@ -548,11 +548,11 @@ WARNING
548
548
  <div class="icon">404</div>
549
549
  <h1>Template Not Found</h1>
550
550
  <p>The requested template could not be found in local storage.</p>
551
- <div class="template-id">${Se(i)}</div>
551
+ <div class="template-id">${ve(i)}</div>
552
552
  <p style="margin-top: 1rem;">Try creating or retrieving the template first using the CLI.</p>
553
553
  </div>
554
554
  </body>
555
- </html>`}function it(i){let t=i.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function nt(){return (i,t)=>{if(i.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=i.url||"/",o=it(e);if(!o){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!we(o)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ye(o));return}let r=ge(o);if(r===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ye(o));return}if(at(r)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(ot(o));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r);}}var ve={async start(){return q&&P!==null?P:new Promise((i,t)=>{v=tt.createServer(nt()),v.listen(0,"127.0.0.1",()=>{let e=v.address();e&&typeof e=="object"?(P=e.port,q=true,v.unref(),i(P)):t(new Error("Failed to get server address"));}),v.on("error",e=>{q=false,P=null,v=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return P},isRunning(){return q},async stop(){if(v)return new Promise(i=>{v.close(()=>{v=null,P=null,q=false,i();});})}};function Te(){return ve}async function Y(i){try{return `http://127.0.0.1:${await ve.start()}/preview/${i}`}catch{return null}}function Oe(){v&&v.ref();}var A=null,Z=null,B=null,M=[],Ee=`
555
+ </html>`}function rt(i){let a=i.match(/^\/preview\/([^/]+)$/);return a?a[1]:null}function nt(){return (i,a)=>{if(i.method!=="GET"){a.writeHead(405,{"Content-Type":"text/plain"}),a.end("Method Not Allowed");return}let e=i.url||"/",o=rt(e);if(!o){a.writeHead(404,{"Content-Type":"text/plain"}),a.end("Not Found");return}if(!ye(o)){a.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),a.end(Se(o));return}let n=we(o);if(n===null){a.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),a.end(Se(o));return}if(ot(n)){a.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),a.end(it(o));return}a.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),a.end(n);}}var Te={async start(){return q&&k!==null?k:new Promise((i,a)=>{v=at.createServer(nt()),v.listen(0,"127.0.0.1",()=>{let e=v.address();e&&typeof e=="object"?(k=e.port,q=true,v.unref(),i(k)):a(new Error("Failed to get server address"));}),v.on("error",e=>{q=false,k=null,v=null,a(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return k},isRunning(){return q},async stop(){if(v)return new Promise(i=>{v.close(()=>{v=null,k=null,q=false,i();});})}};function xe(){return Te}async function Z(i){try{return `http://127.0.0.1:${await Te.start()}/preview/${i}`}catch{return null}}function Oe(){v&&v.ref();}var P=null,Q=null,W=null,M=[],_e=`
556
556
  <script>
557
557
  (function() {
558
558
  const evtSource = new EventSource('/__live-reload');
@@ -566,11 +566,11 @@ WARNING
566
566
  };
567
567
  })();
568
568
  </script>
569
- `;function lt(i){return i.includes("</body>")?i.replace("</body>",`${Ee}</body>`):i+Ee}function mt(){M.forEach(i=>{try{i.write(`data: reload
569
+ `;function mt(i){return i.includes("</body>")?i.replace("</body>",`${_e}</body>`):i+_e}function ct(){M.forEach(i=>{try{i.write(`data: reload
570
570
 
571
- `);}catch{}});}async function Q(i,t){let e=E.resolve(i);return new Promise((o,r)=>{A=tt.createServer((a,n)=>{if(a.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
571
+ `);}catch{}});}async function ee(i,a){let e=E.resolve(i);return new Promise((o,n)=>{P=at.createServer((t,r)=>{if(t.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
572
572
 
573
- `),M.push(n),a.on("close",()=>{M=M.filter(s=>s!==n);});return}if(a.method==="GET"){try{let s=O.readFileSync(e,"utf-8"),d=lt(s);n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(d);}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 a=A.address();if(a&&typeof a=="object"){Z=a.port;let n=null;B=O.watch(e,s=>{s==="change"&&(n&&clearTimeout(n),n=setTimeout(()=>{mt(),t?.();},100));}),o(Z);}else r(new Error("Failed to get server address"));}),A.on("error",a=>{r(new Error(`Failed to start server: ${a.message}`));});})}async function ee(){if(B&&(B.close(),B=null),M.forEach(i=>{try{i.end();}catch{}}),M=[],A)return new Promise(i=>{A.close(()=>{A=null,Z=null,i();});})}async function _e(i){try{let t=i.html_content??"";z(i.id,t);}catch(t){return N(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await Y(i.id);return t===null?(N("Could not start preview server"),null):t}catch(t){return N(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function je(){let i=new Command("templates").description(`Manage email templates
573
+ `),M.push(r),t.on("close",()=>{M=M.filter(s=>s!==r);});return}if(t.method==="GET"){try{let s=x.readFileSync(e,"utf-8"),c=mt(s);r.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r.end(c);}catch(s){r.writeHead(500,{"Content-Type":"text/plain"}),r.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}r.writeHead(405,{"Content-Type":"text/plain"}),r.end("Method Not Allowed");}),P.listen(0,"127.0.0.1",()=>{let t=P.address();if(t&&typeof t=="object"){Q=t.port;let r=null;W=x.watch(e,s=>{s==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{ct(),a?.();},100));}),o(Q);}else n(new Error("Failed to get server address"));}),P.on("error",t=>{n(new Error(`Failed to start server: ${t.message}`));});})}async function te(){if(W&&(W.close(),W=null),M.forEach(i=>{try{i.end();}catch{}}),M=[],P)return new Promise(i=>{P.close(()=>{P=null,Q=null,i();});})}async function je(i){try{let a=i.html_content??"";Y(i.id,a);}catch(a){return N(`Could not save template for preview: ${a instanceof Error?a.message:String(a)}`),null}try{let a=await Z(i.id);return a===null?(N("Could not start preview server"),null):a}catch(a){return N(`Could not generate preview URL: ${a instanceof Error?a.message:String(a)}`),null}}function Ie(){let i=new Command("templates").description(`Manage email templates
574
574
 
575
575
  USAGE
576
576
  emailr templates <subcommand> [options]
@@ -671,8 +671,8 @@ EXAMPLES
671
671
  emailr templates list --page 2 --limit 10
672
672
 
673
673
  # Get JSON output for scripting
674
- 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=c(),o=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 a=await o.templates.list(r);if(t.format==="json")m(a,"json");else {let n=a.map(s=>({ID:s.id,Name:s.name,Subject:s.subject,Variables:s.variables?.join(", ")||"-",Tags:s.tags?.join(", ")||"-",Created:s.created_at}));m(n,"table"),console.log(`
675
- Total: ${a.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
674
+ 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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={limit:parseInt(a.limit,10),page:parseInt(a.page,10)};if(a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let t=await o.templates.list(n);if(a.format==="json")m(t,"json");else {let r=t.map(s=>({ID:s.id,Name:s.name,Subject:s.subject,Variables:s.variables?.join(", ")||"-",Tags:s.tags?.join(", ")||"-",Created:s.created_at}));m(r,"table"),console.log(`
675
+ Total: ${t.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
676
676
 
677
677
  USAGE
678
678
  emailr templates get <template_id> [options]
@@ -700,7 +700,7 @@ EXAMPLES
700
700
  emailr templates get tpl_abc123 --format json
701
701
 
702
702
  # Pipe JSON to jq for processing
703
- emailr templates get tpl_abc123 --format json | jq '.html_content'`).option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.get(t),n=await _e({id:a.id,html_content:a.html_content??void 0}),s=a.preview_html?`${o.baseUrl}/preview/${a.id}`:null;if(e.format==="json")m({...a,preview_url:s??n},"json");else {let d={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Created:a.created_at};s?d["Preview URL"]=s:n&&(d["Local Preview"]=n),m(d,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),i.command("fetch <template_id>").description(`Download template HTML to file or stdout
703
+ emailr templates get tpl_abc123 --format json | jq '.html_content'`).option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.get(a),r=await je({id:t.id,html_content:t.html_content??void 0}),s=t.preview_html?`${o.baseUrl}/preview/${t.id}`:null;if(e.format==="json")m({...t,preview_url:s??r},"json");else {let c={ID:t.id,Name:t.name,Subject:t.subject,Variables:t.variables?.join(", ")||"-",Created:t.created_at};s?c["Preview URL"]=s:r&&(c["Local Preview"]=r),m(c,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),i.command("fetch <template_id>").description(`Download template HTML to file or stdout
704
704
 
705
705
  USAGE
706
706
  emailr templates fetch <template_id> [options]
@@ -734,8 +734,8 @@ AGENTIC WORKFLOW
734
734
  This is step 1 of the agentic workflow:
735
735
  1. Fetch: emailr templates fetch <id> --output template.html
736
736
  2. Edit locally or with AI assistance
737
- 3. Push preview: emailr templates push-preview <id> --html-file template.html`).option("--output <file_path>","Write HTML to file (default: stdout)").option("--preview","Fetch preview HTML instead of published HTML").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.fetch(t,{preview:e.preview??!1});if(a||(e.preview?(l("No preview HTML exists for this template"),console.log(`
738
- Run 'emailr templates push-preview <id> --html-file <path>' to create a preview.`)):l("Template has no HTML content"),process.exit(1)),e.output){let n=E.resolve(e.output);O.writeFileSync(n,a,"utf-8"),p(`HTML saved to: ${n}`);}else console.log(a);}catch(o){l(o instanceof Error?o.message:"Failed to fetch template"),process.exit(1);}}),i.command("push-preview <template_id>").description(`Upload HTML to template's preview for sharing with AI agents
737
+ 3. Push preview: emailr templates push-preview <id> --html-file template.html`).option("--output <file_path>","Write HTML to file (default: stdout)").option("--preview","Fetch preview HTML instead of published HTML").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.fetch(a,{preview:e.preview??!1});if(t||(e.preview?(l("No preview HTML exists for this template"),console.log(`
738
+ Run 'emailr templates push-preview <id> --html-file <path>' to create a preview.`)):l("Template has no HTML content"),process.exit(1)),e.output){let r=E.resolve(e.output);x.writeFileSync(r,t,"utf-8"),p(`HTML saved to: ${r}`);}else console.log(t);}catch(o){l(o instanceof Error?o.message:"Failed to fetch template"),process.exit(1);}}),i.command("push-preview <template_id>").description(`Upload HTML to template's preview for sharing with AI agents
739
739
 
740
740
  USAGE
741
741
  emailr templates push-preview <template_id> --html-file <file_path>
@@ -778,8 +778,8 @@ AGENTIC WORKFLOW
778
778
  2. Edit locally or with AI assistance
779
779
  3. Push preview: emailr templates push-preview <id> --html-file template.html
780
780
  4. Share the preview URL with your AI agent for feedback
781
- 5. Repeat steps 2-4 until satisfied`).option("--html-file <file_path>","Read HTML content from file").option("--html <html_content>","Inline HTML content").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{!e.htmlFile&&!e.html&&(l("Either --html-file or --html is required"),console.log(`
782
- Usage:`),console.log(" emailr templates push-preview <id> --html-file <path>"),console.log(" emailr templates push-preview <id> --html <content>"),process.exit(1)),e.htmlFile&&e.html&&(l("Cannot use both --html-file and --html. Choose one."),process.exit(1));let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a;if(e.htmlFile){let d=E.resolve(e.htmlFile);try{a=O.readFileSync(d,"utf-8");}catch{l(`Failed to read file: ${d}`),process.exit(1);}}else a=e.html;let n=await r.templates.pushPreview(t,a),s="updated";e.format==="json"?m({template_id:t,preview_url:n.preview_url,status:s},"json"):(p("Preview uploaded successfully"),m({"Template ID":t,"Preview URL":n.preview_url,Status:s},"table"),console.log(`
781
+ 5. Repeat steps 2-4 until satisfied`).option("--html-file <file_path>","Read HTML content from file").option("--html <html_content>","Inline HTML content").option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{!e.htmlFile&&!e.html&&(l("Either --html-file or --html is required"),console.log(`
782
+ Usage:`),console.log(" emailr templates push-preview <id> --html-file <path>"),console.log(" emailr templates push-preview <id> --html <content>"),process.exit(1)),e.htmlFile&&e.html&&(l("Cannot use both --html-file and --html. Choose one."),process.exit(1));let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t;if(e.htmlFile){let c=E.resolve(e.htmlFile);try{t=x.readFileSync(c,"utf-8");}catch{l(`Failed to read file: ${c}`),process.exit(1);}}else t=e.html;let r=await n.templates.pushPreview(a,t),s="updated";e.format==="json"?m({template_id:a,preview_url:r.preview_url,status:s},"json"):(p("Preview uploaded successfully"),m({"Template ID":a,"Preview URL":r.preview_url,Status:s},"table"),console.log(`
783
783
  Share this URL with your AI agent for feedback.`));}catch(o){l(o instanceof Error?o.message:"Failed to push preview"),process.exit(1);}}),i.command("create").description(`Create a new email template
784
784
 
785
785
  USAGE
@@ -828,7 +828,7 @@ EXAMPLES
828
828
 
829
829
  TIP
830
830
  For live preview while building, use "emailr templates draft" first.
831
- 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("--from-name <name>","Sender display name").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--inbox-id <inbox_id>","Inbox ID for sender identity defaults").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=c(),o=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.fromName&&(r.from_name=t.fromName),t.replyTo&&(r.reply_to=t.replyTo),t.previewText&&(r.preview_text=t.previewText),t.inboxId&&(r.inbox_id=t.inboxId),t.tags&&(r.tags=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let a=await o.templates.create(r),n=await _e({id:a.id,html_content:a.html_content??void 0});if(t.format==="json")m({...a,preview_url:n},"json");else {p(`Template created: ${a.id}`);let s={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Tags:a.tags?.join(", ")||"-"};n&&(s["Preview URL"]=n),m(s,"table"),n&&console.log(`
831
+ 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("--from-name <name>","Sender display name").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--inbox-id <inbox_id>","Inbox ID for sender identity defaults").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={name:a.name,subject:a.subject};if(a.htmlFile){let s=await import('fs');n.html_content=s.readFileSync(a.htmlFile,"utf-8");}else a.html&&(n.html_content=a.html);if(a.textFile){let s=await import('fs');n.text_content=s.readFileSync(a.textFile,"utf-8");}else a.text&&(n.text_content=a.text);a.from&&(n.from_email=a.from),a.fromName&&(n.from_name=a.fromName),a.replyTo&&(n.reply_to=a.replyTo),a.previewText&&(n.preview_text=a.previewText),a.inboxId&&(n.inbox_id=a.inboxId),a.tags&&(n.tags=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let t=await o.templates.create(n),r=await je({id:t.id,html_content:t.html_content??void 0});if(a.format==="json")m({...t,preview_url:r},"json");else {p(`Template created: ${t.id}`);let s={ID:t.id,Name:t.name,Subject:t.subject,Variables:t.variables?.join(", ")||"-",Tags:t.tags?.join(", ")||"-"};r&&(s["Preview URL"]=r),m(s,"table"),r&&console.log(`
832
832
  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
833
833
 
834
834
  USAGE
@@ -884,7 +884,7 @@ AGENTIC WORKFLOW
884
884
  2. Edit locally or with AI assistance
885
885
  3. Push preview: emailr templates push-preview <id> --html-file template.html
886
886
  4. Share URL with AI agent, iterate on feedback
887
- 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("--from-name <name>","New sender display name").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--inbox-id <inbox_id>",'Inbox ID for sender identity (use "none" to clear)').option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.subject&&(a.subject=e.subject),e.htmlFile){let s=await import('fs');a.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(a.html_content=e.html);if(e.textFile){let s=await import('fs');a.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(a.text_content=e.text);e.from&&(a.from_email=e.from),e.fromName&&(a.from_name=e.fromName),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText),e.inboxId&&(a.inbox_id=e.inboxId==="none"?null:e.inboxId),e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.templates.update(t,a);if(e.format==="json")m(n,"json");else {p(`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};m(s,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
887
+ 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("--from-name <name>","New sender display name").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--inbox-id <inbox_id>",'Inbox ID for sender identity (use "none" to clear)').option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={};if(e.name&&(t.name=e.name),e.subject&&(t.subject=e.subject),e.htmlFile){let s=await import('fs');t.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(t.html_content=e.html);if(e.textFile){let s=await import('fs');t.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(t.text_content=e.text);e.from&&(t.from_email=e.from),e.fromName&&(t.from_name=e.fromName),e.replyTo&&(t.reply_to=e.replyTo),e.previewText&&(t.preview_text=e.previewText),e.inboxId&&(t.inbox_id=e.inboxId==="none"?null:e.inboxId),e.tags&&(t.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await n.templates.update(a,t);if(e.format==="json")m(r,"json");else {p(`Template updated: ${r.id}`);let s={ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Tags:r.tags?.join(", ")||"-",Updated:r.updated_at};m(s,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
888
888
 
889
889
  USAGE
890
890
  emailr templates delete <template_id>
@@ -901,7 +901,7 @@ EXAMPLES
901
901
  emailr templates delete tpl_abc123
902
902
 
903
903
  WARNING
904
- This action is permanent and cannot be undone.`).action(async t=>{try{let e=c();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(t),p(`Template deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),i.command("preview <template_id>").description(`Preview a template in the browser
904
+ This action is permanent and cannot be undone.`).action(async a=>{try{let e=d();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(a),p(`Template deleted: ${a}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),i.command("preview <template_id>").description(`Preview a template in the browser
905
905
 
906
906
  USAGE
907
907
  emailr templates preview <template_id> [options]
@@ -930,12 +930,12 @@ EXAMPLES
930
930
 
931
931
  SEE ALSO
932
932
  emailr templates edit Live editing with hot-reload
933
- emailr templates stop-preview Stop background preview server`).option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl});console.log(`Fetching template ${t}...`);let a=await r.templates.get(t),n=a.html_content??"";if(z(a.id,n),e.foreground){let g=await Y(a.id);if(g||(l("Failed to start preview server"),process.exit(1)),Oe(),console.log(`
934
- Template: ${a.name}`),console.log(`Preview URL: ${g}`),e.open!==!1)try{await(await import('open')).default(g),console.log(`
933
+ emailr templates stop-preview Stop background preview server`).option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl});console.log(`Fetching template ${a}...`);let t=await n.templates.get(a),r=t.html_content??"";if(Y(t.id,r),e.foreground){let w=await Z(t.id);if(w||(l("Failed to start preview server"),process.exit(1)),Oe(),console.log(`
934
+ Template: ${t.name}`),console.log(`Preview URL: ${w}`),e.open!==!1)try{await(await import('open')).default(w),console.log(`
935
935
  Browser opened.`);}catch{console.log(`
936
936
  Could not open browser automatically. Open the URL above manually.`);}process.on("SIGINT",async()=>{console.log(`
937
937
 
938
- Stopping preview server...`),await Te().stop(),console.log("Done."),process.exit(0);});return}let s=E.join(process.cwd(),`.template-preview-${a.id}.html`);O.writeFileSync(s,n,"utf-8");let{spawn:d}=await import('child_process'),b=await import('os'),S=E.join(b.tmpdir(),"emailr-preview.pid");try{let g=O.readFileSync(S,"utf-8").trim();process.kill(parseInt(g,10),"SIGTERM");}catch{}let f=`
938
+ Stopping preview server...`),await xe().stop(),console.log("Done."),process.exit(0);});return}let s=E.join(process.cwd(),`.template-preview-${t.id}.html`);x.writeFileSync(s,r,"utf-8");let{spawn:c}=await import('child_process'),b=await import('os'),g=E.join(b.tmpdir(),"emailr-preview.pid");try{let w=x.readFileSync(g,"utf-8").trim();process.kill(parseInt(w,10),"SIGTERM");}catch{}let f=`
939
939
  const http = require('http');
940
940
  const fs = require('fs');
941
941
  const path = require('path');
@@ -960,8 +960,8 @@ Stopping preview server...`),await Te().stop(),console.log("Done."),process.exit
960
960
  console.log('PORT:' + port);
961
961
  });
962
962
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); fs.unlinkSync(filePath); } catch {} process.exit(0); });
963
- `,h=d("node",["-e",f],{detached:!0,stdio:["ignore","pipe","ignore"]}),w="";await new Promise(g=>{h.stdout?.on("data",j=>{let $=j.toString().match(/PORT:(\d+)/);$&&(w=$[1],g());}),setTimeout(g,3e3);}),h.unref();let _=`http://127.0.0.1:${w}/`;if(console.log(`
964
- Template: ${a.name}`),console.log(`Preview URL: ${_}`),console.log(`
963
+ `,h=c("node",["-e",f],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(w=>{h.stdout?.on("data",j=>{let B=j.toString().match(/PORT:(\d+)/);B&&(S=B[1],w());}),setTimeout(w,3e3);}),h.unref();let _=`http://127.0.0.1:${S}/`;if(console.log(`
964
+ Template: ${t.name}`),console.log(`Preview URL: ${_}`),console.log(`
965
965
  To stop: emailr templates stop-preview`),e.open!==!1)try{await(await import('open')).default(_);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to preview template"),process.exit(1);}}),i.command("edit <template_id>").description(`Start a live editing session for a template with hot-reload
966
966
 
967
967
  USAGE
@@ -1002,17 +1002,17 @@ EXAMPLES
1002
1002
  SEE ALSO
1003
1003
  emailr templates draft Draft a new template with live preview
1004
1004
  emailr templates update Save changes to the template
1005
- emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for editing","./template.html").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=E.resolve(e.file);console.log(`Fetching template ${t}...`);let n=await r.templates.get(t),s=n.html_content??"";if(O.writeFileSync(a,s,"utf-8"),console.log(`Template saved to: ${a}`),e.foreground){let j=`http://127.0.0.1:${await Q(a,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1006
- Template: ${n.name}`),console.log(`Template ID: ${n.id}`),console.log(`Live Preview: ${j}`),console.log(`File: ${a}`),console.log(`
1007
- Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${t} --html-file ${e.file}`),e.open!==!1)try{await(await import('open')).default(j);}catch{}process.on("SIGINT",async()=>{console.log(`
1005
+ emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for editing","./template.html").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t=E.resolve(e.file);console.log(`Fetching template ${a}...`);let r=await n.templates.get(a),s=r.html_content??"";if(x.writeFileSync(t,s,"utf-8"),console.log(`Template saved to: ${t}`),e.foreground){let j=`http://127.0.0.1:${await ee(t,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1006
+ Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${j}`),console.log(`File: ${t}`),console.log(`
1007
+ Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${a} --html-file ${e.file}`),e.open!==!1)try{await(await import('open')).default(j);}catch{}process.on("SIGINT",async()=>{console.log(`
1008
1008
 
1009
- Stopping live preview server...`),await ee(),console.log("Done."),console.log(`
1010
- To save changes: emailr templates update ${t} --html-file ${e.file}`),process.exit(0);});return}let{spawn:d}=await import('child_process'),b=await import('os'),S=E.join(b.tmpdir(),"emailr-preview.pid");try{let g=O.readFileSync(S,"utf-8").trim();process.kill(parseInt(g,10),"SIGTERM");}catch{}let f=`
1009
+ Stopping live preview server...`),await te(),console.log("Done."),console.log(`
1010
+ To save changes: emailr templates update ${a} --html-file ${e.file}`),process.exit(0);});return}let{spawn:c}=await import('child_process'),b=await import('os'),g=E.join(b.tmpdir(),"emailr-preview.pid");try{let w=x.readFileSync(g,"utf-8").trim();process.kill(parseInt(w,10),"SIGTERM");}catch{}let f=`
1011
1011
  const http = require('http');
1012
1012
  const fs = require('fs');
1013
1013
  const path = require('path');
1014
1014
  const os = require('os');
1015
- const filePath = ${JSON.stringify(a)};
1015
+ const filePath = ${JSON.stringify(t)};
1016
1016
  const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
1017
1017
  const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
1018
1018
  let clients = [];
@@ -1047,10 +1047,10 @@ To save changes: emailr templates update ${t} --html-file ${e.file}`),process.ex
1047
1047
  });
1048
1048
  });
1049
1049
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
1050
- `,h=d("node",["-e",f],{detached:!0,stdio:["ignore","pipe","ignore"]}),w="";await new Promise(g=>{h.stdout?.on("data",j=>{let le=j.toString().match(/PORT:(\d+)/);le&&(w=le[1],g());}),setTimeout(g,3e3);}),h.unref();let _=`http://127.0.0.1:${w}/`;if(console.log(`
1051
- Template: ${n.name}`),console.log(`Template ID: ${n.id}`),console.log(`Live Preview: ${_}`),console.log(`File: ${a}`),console.log(`
1050
+ `,h=c("node",["-e",f],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(w=>{h.stdout?.on("data",j=>{let me=j.toString().match(/PORT:(\d+)/);me&&(S=me[1],w());}),setTimeout(w,3e3);}),h.unref();let _=`http://127.0.0.1:${S}/`;if(console.log(`
1051
+ Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${_}`),console.log(`File: ${t}`),console.log(`
1052
1052
  Edit the file and see live updates in the browser.`),console.log(`
1053
- 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(_);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to edit template"),process.exit(1);}}),i.command("draft").description(`Start a live drafting session for a new template with hot-reload
1053
+ When done:`),console.log(` emailr templates update ${a} --html-file ${e.file}`),console.log(" emailr templates stop-preview"),e.open!==!1)try{await(await import('open')).default(_);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to edit template"),process.exit(1);}}),i.command("draft").description(`Start a live drafting session for a new template with hot-reload
1054
1054
 
1055
1055
  USAGE
1056
1056
  emailr templates draft [options]
@@ -1092,7 +1092,7 @@ EXAMPLES
1092
1092
  SEE ALSO
1093
1093
  emailr templates edit Edit an existing template with live preview
1094
1094
  emailr templates create Create the template from your draft
1095
- emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--foreground","Keep process running in foreground (blocks terminal)").action(async t=>{try{let e=E.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
1095
+ emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--foreground","Keep process running in foreground (blocks terminal)").action(async a=>{try{let e=E.resolve(a.file),o;if(a.blank?o="":o=`<!DOCTYPE html>
1096
1096
  <html>
1097
1097
  <head>
1098
1098
  <meta charset="UTF-8">
@@ -1127,13 +1127,13 @@ SEE ALSO
1127
1127
  <p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
1128
1128
  </div>
1129
1129
  </body>
1130
- </html>`,O.writeFileSync(e,o,"utf-8"),console.log(`Template draft created: ${e}`),t.foreground){let h=`http://127.0.0.1:${await Q(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1130
+ </html>`,x.writeFileSync(e,o,"utf-8"),console.log(`Template draft created: ${e}`),a.foreground){let h=`http://127.0.0.1:${await ee(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1131
1131
  Live Preview: ${h}`),console.log(`File: ${e}`),console.log(`
1132
1132
  Watching for changes... Edit the file and see live updates.`),console.log(`
1133
- When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),t.open!==!1)try{await(await import('open')).default(h);}catch{}process.on("SIGINT",async()=>{console.log(`
1133
+ When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${a.file}`),a.open!==!1)try{await(await import('open')).default(h);}catch{}process.on("SIGINT",async()=>{console.log(`
1134
1134
 
1135
- Stopping live preview server...`),await ee(),console.log("Done."),console.log(`
1136
- To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});return}let{spawn:r}=await import('child_process'),a=await import('os'),n=E.join(a.tmpdir(),"emailr-preview.pid");try{let f=O.readFileSync(n,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let s=`
1135
+ Stopping live preview server...`),await te(),console.log("Done."),console.log(`
1136
+ To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${a.file}`),process.exit(0);});return}let{spawn:n}=await import('child_process'),t=await import('os'),r=E.join(t.tmpdir(),"emailr-preview.pid");try{let f=x.readFileSync(r,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let s=`
1137
1137
  const http = require('http');
1138
1138
  const fs = require('fs');
1139
1139
  const path = require('path');
@@ -1173,10 +1173,10 @@ To create template: emailr templates create --name "Template Name" --subject "Em
1173
1173
  });
1174
1174
  });
1175
1175
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
1176
- `,d=r("node",["-e",s],{detached:!0,stdio:["ignore","pipe","ignore"]}),b="";await new Promise(f=>{d.stdout?.on("data",h=>{let w=h.toString().match(/PORT:(\d+)/);w&&(b=w[1],f());}),setTimeout(f,3e3);}),d.unref();let S=`http://127.0.0.1:${b}/`;if(console.log(`
1177
- Live Preview: ${S}`),console.log(`File: ${e}`),console.log(`
1176
+ `,c=n("node",["-e",s],{detached:!0,stdio:["ignore","pipe","ignore"]}),b="";await new Promise(f=>{c.stdout?.on("data",h=>{let S=h.toString().match(/PORT:(\d+)/);S&&(b=S[1],f());}),setTimeout(f,3e3);}),c.unref();let g=`http://127.0.0.1:${b}/`;if(console.log(`
1177
+ Live Preview: ${g}`),console.log(`File: ${e}`),console.log(`
1178
1178
  Edit the file and see live updates in the browser.`),console.log(`
1179
- 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(S);}catch{}process.exit(0);}catch(e){l(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),i.command("stop-preview").description(`Stop any running background preview server
1179
+ When done:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${a.file}`),console.log(" emailr templates stop-preview"),a.open!==!1)try{await(await import('open')).default(g);}catch{}process.exit(0);}catch(e){l(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),i.command("stop-preview").description(`Stop any running background preview server
1180
1180
 
1181
1181
  USAGE
1182
1182
  emailr templates stop-preview
@@ -1193,7 +1193,7 @@ EXAMPLES
1193
1193
  SEE ALSO
1194
1194
  emailr templates edit Start live editing session
1195
1195
  emailr templates draft Start live drafting session
1196
- emailr templates preview Preview a template in browser`).action(async()=>{let t=await import('os'),e=E.join(t.tmpdir(),"emailr-preview.pid");try{let o=O.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),O.unlinkSync(e),p("Preview server stopped");}catch{p("No preview server running");}}),i}function Ie(){let i=new Command("domains").description(`Manage sending domains
1196
+ emailr templates preview Preview a template in browser`).action(async()=>{let a=await import('os'),e=E.join(a.tmpdir(),"emailr-preview.pid");try{let o=x.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),x.unlinkSync(e),p("Preview server stopped");}catch{p("No preview server running");}}),i}function Ne(){let i=new Command("domains").description(`Manage sending domains
1197
1197
 
1198
1198
  USAGE
1199
1199
  emailr domains <subcommand> [options]
@@ -1301,7 +1301,7 @@ EXAMPLES
1301
1301
  emailr domains list --format json | jq '.[] | select(.status == "verified")'
1302
1302
 
1303
1303
  # Count domains
1304
- emailr domains list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(t.format==="json")m(r,"json");else {let a=r.map(n=>({ID:n.id,Domain:n.domain,Status:n.status,DKIM:n.dkim_verified,SPF:n.spf_verified,DMARC:n.dmarc_verified,Created:n.created_at}));m(a,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),i.command("get <domain_id>").description(`Get domain details
1304
+ emailr domains list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(a.format==="json")m(n,"json");else {let t=n.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}));m(t,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),i.command("get <domain_id>").description(`Get domain details
1305
1305
 
1306
1306
  USAGE
1307
1307
  emailr domains get <domain_id> [options]
@@ -1328,7 +1328,7 @@ EXAMPLES
1328
1328
  emailr domains get dom_abc123 --format json
1329
1329
 
1330
1330
  # Extract DNS records
1331
- emailr domains get dom_abc123 --format json | jq '.dns_records'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.get(t);m(a,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get domain"),process.exit(1);}}),i.command("add <domain_name>").description(`Add a new domain
1331
+ emailr domains get dom_abc123 --format json | jq '.dns_records'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.get(a);m(t,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get domain"),process.exit(1);}}),i.command("add <domain_name>").description(`Add a new domain
1332
1332
 
1333
1333
  USAGE
1334
1334
  emailr domains add <domain_name> [options]
@@ -1381,7 +1381,7 @@ NEXT STEPS
1381
1381
  1. Copy the DNS records shown in the output
1382
1382
  2. Add them to your DNS provider (Cloudflare, Route53, etc.)
1383
1383
  3. Wait for DNS propagation (up to 48 hours)
1384
- 4. Run: emailr domains verify <domain_id>`).option("--receiving-subdomain <subdomain>","Subdomain for receiving emails").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={domain:t};e.receivingSubdomain&&(a.receivingSubdomain=e.receivingSubdomain);let n=await r.domains.add(a);if(e.format==="json")m(n,"json");else {if(p(`Domain added: ${n.domain}`),u("Add the following DNS records to verify your domain:"),console.log(""),n.dns_records){let s=[{Type:"DKIM",...te(n.dns_records.dkim)},{Type:"SPF",...te(n.dns_records.spf)},{Type:"DMARC",...te(n.dns_records.dmarc)}];m(s,"table");}console.log(""),u(`Run 'emailr domains verify ${n.id}' after adding DNS records`);}}catch(o){l(o instanceof Error?o.message:"Failed to add domain"),process.exit(1);}}),i.command("verify <domain_id>").description(`Verify a domain
1384
+ 4. Run: emailr domains verify <domain_id>`).option("--receiving-subdomain <subdomain>","Subdomain for receiving emails").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={domain:a};e.receivingSubdomain&&(t.receivingSubdomain=e.receivingSubdomain);let r=await n.domains.add(t);if(e.format==="json")m(r,"json");else {if(p(`Domain added: ${r.domain}`),u("Add the following DNS records to verify your domain:"),console.log(""),r.dns_records){let s=[{Type:"DKIM",...ae(r.dns_records.dkim)},{Type:"SPF",...ae(r.dns_records.spf)},{Type:"DMARC",...ae(r.dns_records.dmarc)}];m(s,"table");}console.log(""),u(`Run 'emailr domains verify ${r.id}' after adding DNS records`);}}catch(o){l(o instanceof Error?o.message:"Failed to add domain"),process.exit(1);}}),i.command("verify <domain_id>").description(`Verify a domain
1385
1385
 
1386
1386
  USAGE
1387
1387
  emailr domains verify <domain_id> [options]
@@ -1419,7 +1419,7 @@ TROUBLESHOOTING
1419
1419
  1. Run 'check-dns' to see which records are missing
1420
1420
  2. Verify records are correctly configured with your DNS provider
1421
1421
  3. Wait for DNS propagation (can take up to 48 hours)
1422
- 4. Try verification again`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.verify(t);e.format==="json"?m(a,"json"):a.verified?p("Domain verified successfully!"):(u(`Domain status: ${a.status}`),a.dkim_status&&u(`DKIM status: ${a.dkim_status}`));}catch(o){l(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),i.command("check-dns <domain_id>").description(`Check DNS records for a domain
1422
+ 4. Try verification again`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.verify(a);e.format==="json"?m(t,"json"):t.verified?p("Domain verified successfully!"):(u(`Domain status: ${t.status}`),t.dkim_status&&u(`DKIM status: ${t.dkim_status}`));}catch(o){l(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),i.command("check-dns <domain_id>").description(`Check DNS records for a domain
1423
1423
 
1424
1424
  USAGE
1425
1425
  emailr domains check-dns <domain_id> [options]
@@ -1466,7 +1466,7 @@ COMMON ISSUES
1466
1466
 
1467
1467
  Multiple records found:
1468
1468
  - Remove duplicate TXT records
1469
- - Keep only the Emailr-specific record`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.checkDns(t);if(e.format==="json")m(a,"json");else {let n=Object.entries(a).map(([s,d])=>({Record:s,Verified:d.verified,Expected:d.expected||"-",Found:d.found?.join(", ")||"-"}));m(n,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),i.command("delete <domain_id>").description(`Delete a domain
1469
+ - Keep only the Emailr-specific record`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.checkDns(a);if(e.format==="json")m(t,"json");else {let r=Object.entries(t).map(([s,c])=>({Record:s,Verified:c.verified,Expected:c.expected||"-",Found:c.found?.join(", ")||"-"}));m(r,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),i.command("delete <domain_id>").description(`Delete a domain
1470
1470
 
1471
1471
  USAGE
1472
1472
  emailr domains delete <domain_id> [options]
@@ -1496,7 +1496,7 @@ WARNING
1496
1496
  This action is permanent and cannot be undone.
1497
1497
  - Emails from this domain will fail to send
1498
1498
  - DNS records can be removed from your DNS provider
1499
- - Re-add the domain if you need to restore functionality`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(t),p(`Domain deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),i}function te(i){return {"Record Type":i.type,Name:i.name,Value:i.value.length>50?i.value.substring(0,47)+"...":i.value,...i.priority!==void 0&&{Priority:i.priority}}}function Ne(){let i=new Command("config").description(`Manage CLI configuration
1499
+ - Re-add the domain if you need to restore functionality`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(a),p(`Domain deleted: ${a}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),i}function ae(i){return {"Record Type":i.type,Name:i.name,Value:i.value.length>50?i.value.substring(0,47)+"...":i.value,...i.priority!==void 0&&{Priority:i.priority}}}function Ce(){let i=new Command("config").description(`Manage CLI configuration
1500
1500
 
1501
1501
  USAGE
1502
1502
  emailr config <subcommand> [options]
@@ -1589,7 +1589,7 @@ EXAMPLES
1589
1589
 
1590
1590
  NOTE
1591
1591
  Environment variables (EMAILR_API_KEY, EMAILR_BASE_URL) take precedence
1592
- over config file values at runtime.`).action(async(t,e)=>{try{let o=["api-key","base-url","format"],r=t.toLowerCase();o.includes(r)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${o.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}),p(`Configuration saved: ${t} = ${r==="api-key"?"***":e}`),u(`Config file: ${I()}`);}catch(o){l(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1592
+ over config file values at runtime.`).action(async(a,e)=>{try{let o=["api-key","base-url","format"],n=a.toLowerCase();o.includes(n)||(l(`Invalid config key: ${a}`),u(`Valid keys: ${o.join(", ")}`),process.exit(1));let r={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[n];n==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),u("Valid formats: json, table"),process.exit(1)),K({[r]:e}),p(`Configuration saved: ${a} = ${n==="api-key"?"***":e}`),u(`Config file: ${I()}`);}catch(o){l(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1593
1593
 
1594
1594
  USAGE
1595
1595
  emailr config get <key>
@@ -1617,7 +1617,7 @@ EXAMPLES
1617
1617
  emailr config get format
1618
1618
 
1619
1619
  SEE ALSO
1620
- emailr config list Show all configuration values`).action(async t=>{try{let e=["api-key","base-url","format"],o=t.toLowerCase();e.includes(o)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${e.join(", ")}`),process.exit(1));let a={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],n=me(a);n?console.log(o==="api-key"?n.substring(0,8)+"..."+n.substring(n.length-4):n):u(`${t} is not set`);}catch(e){l(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),i.command("list").description(`List all configuration values
1620
+ emailr config list Show all configuration values`).action(async a=>{try{let e=["api-key","base-url","format"],o=a.toLowerCase();e.includes(o)||(l(`Invalid config key: ${a}`),u(`Valid keys: ${e.join(", ")}`),process.exit(1));let t={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],r=ce(t);r?console.log(o==="api-key"?r.substring(0,8)+"..."+r.substring(r.length-4):r):u(`${a} is not set`);}catch(e){l(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),i.command("list").description(`List all configuration values
1621
1621
 
1622
1622
  USAGE
1623
1623
  emailr config list [options]
@@ -1645,7 +1645,7 @@ EXAMPLES
1645
1645
 
1646
1646
  SEE ALSO
1647
1647
  emailr config get Get a single configuration value
1648
- emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),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")m(o,"json");else {let r=Object.entries(o).map(([a,n])=>({Key:a,Value:n}));m(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
1648
+ emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),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(a.format==="json")m(o,"json");else {let n=Object.entries(o).map(([t,r])=>({Key:t,Value:r}));m(n,"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
1649
1649
 
1650
1650
  USAGE
1651
1651
  emailr config path
@@ -1705,7 +1705,7 @@ ALTERNATIVE: ENVIRONMENT VARIABLES
1705
1705
 
1706
1706
  SEE ALSO
1707
1707
  emailr config set Set individual configuration values
1708
- 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}),p("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 Ce(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1708
+ emailr config list View current configuration`).option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async a=>{try{a.apiKey?(K({apiKey:a.apiKey,baseUrl:a.baseUrl}),p("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 Ue(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1709
1709
 
1710
1710
  USAGE
1711
1711
  emailr broadcasts <subcommand> [options]
@@ -1855,7 +1855,7 @@ EXAMPLES
1855
1855
  emailr broadcasts list --format json
1856
1856
 
1857
1857
  # Pipe JSON to jq for processing
1858
- 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=c(),o=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 a=await o.broadcasts.list(r);if(t.format==="json")m(a,"json");else {if(a.length===0){console.log("No broadcasts found.");return}let n=a.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()}));m(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
1858
+ 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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={status:a.status,limit:parseInt(a.limit)};if(a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let t=await o.broadcasts.list(n);if(a.format==="json")m(t,"json");else {if(t.length===0){console.log("No broadcasts found.");return}let r=t.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()}));m(r,"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
1859
1859
 
1860
1860
  USAGE
1861
1861
  emailr broadcasts get <broadcast_id> [options]
@@ -1890,7 +1890,7 @@ EXAMPLES
1890
1890
  emailr broadcasts get brd_abc123 --format json
1891
1891
 
1892
1892
  # Pipe JSON to jq for processing
1893
- 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 o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.get(t);e.format==="json"?m(a,"json"):m({ID:a.id,Name:a.name,Subject:a.subject,"From Email":a.from_email,"From Name":a.from_name||"-",Status:a.status,Tags:a.tags?.join(", ")||"-","Total Recipients":a.total_recipients||0,"Sent Count":a.sent_count||0,Delivered:a.delivered_count||0,Opened:a.opened_count||0,Clicked:a.clicked_count||0,Bounced:a.bounced_count||0,"Scheduled At":a.scheduled_at||"N/A","Started At":a.started_at||"N/A","Completed At":a.completed_at||"N/A","Created At":a.created_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get broadcast"),process.exit(1);}}),i.command("create").description(`Create a new broadcast
1893
+ emailr broadcasts get brd_abc123 --format json | jq '{sent: .sent_count, opened: .opened_count}'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.get(a);e.format==="json"?m(t,"json"):m({ID:t.id,Name:t.name,Subject:t.subject,"From Email":t.from_email,"From Name":t.from_name||"-",Status:t.status,Tags:t.tags?.join(", ")||"-","Total Recipients":t.total_recipients||0,"Sent Count":t.sent_count||0,Delivered:t.delivered_count||0,Opened:t.opened_count||0,Clicked:t.clicked_count||0,Bounced:t.bounced_count||0,"Scheduled At":t.scheduled_at||"N/A","Started At":t.started_at||"N/A","Completed At":t.completed_at||"N/A","Created At":t.created_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get broadcast"),process.exit(1);}}),i.command("create").description(`Create a new broadcast
1894
1894
 
1895
1895
  USAGE
1896
1896
  emailr broadcasts create --name <name> --subject <subject> --from <email> [options]
@@ -1971,7 +1971,7 @@ EXAMPLES
1971
1971
 
1972
1972
  SEE ALSO
1973
1973
  emailr templates Create and manage email templates
1974
- emailr segments Create and manage contact segments`).requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--reply-to <email>","Reply-To email address").option("--preview-text <text>","Preview text (preheader)").option("--inbox-id <inbox_id>","Inbox ID for sender identity defaults").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=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={name:t.name,subject:t.subject,from_email:t.from,from_name:t.fromName,reply_to:t.replyTo,preview_text:t.previewText,inbox_id:t.inboxId,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 a=await o.broadcasts.create(r);t.format==="json"?m(a,"json"):(p("Broadcast created successfully!"),m({ID:a.id,Name:a.name,Status:a.status,Tags:a.tags?.join(", ")||"-","Scheduled At":a.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
1974
+ emailr segments Create and manage contact segments`).requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--reply-to <email>","Reply-To email address").option("--preview-text <text>","Preview text (preheader)").option("--inbox-id <inbox_id>","Inbox ID for sender identity defaults").option("--inbox-ids <ids>","Comma-separated inbox IDs for inbox rotation").option("--sending-speed <speed>","Sending speed: auto | slow | normal | instant (default: auto)").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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={name:a.name,subject:a.subject,from_email:a.from,from_name:a.fromName,reply_to:a.replyTo,preview_text:a.previewText,inbox_id:a.inboxId,template_id:a.template,segment_id:a.segment,html_content:a.html,text_content:a.text,scheduled_at:a.schedule};a.inboxIds&&(n.inbox_ids=a.inboxIds.split(",").map(r=>r.trim()).filter(Boolean)),a.sendingSpeed&&(n.sending_speed=a.sendingSpeed),a.tags&&(n.tags=a.tags.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean));let t=await o.broadcasts.create(n);a.format==="json"?m(t,"json"):(p("Broadcast created successfully!"),m({ID:t.id,Name:t.name,Status:t.status,Tags:t.tags?.join(", ")||"-","Scheduled At":t.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
1975
1975
 
1976
1976
  USAGE
1977
1977
  emailr broadcasts send <broadcast_id> [options]
@@ -2004,7 +2004,7 @@ EXAMPLES
2004
2004
  NOTE
2005
2005
  Sending is asynchronous. The command returns when sending starts,
2006
2006
  not when all emails are delivered. Use 'emailr broadcasts get' to
2007
- check delivery progress.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.send(t);e.format==="json"?m(a,"json"):(p("Broadcast sent successfully!"),m({Success:a.success,Sent:a.sent,Total:a.total},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to send broadcast"),process.exit(1);}}),i.command("schedule <broadcast_id>").description(`Schedule a broadcast for future delivery
2007
+ check delivery progress.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.send(a);e.format==="json"?m(t,"json"):(p("Broadcast sent successfully!"),m({Success:t.success,Sent:t.sent,Total:t.total},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to send broadcast"),process.exit(1);}}),i.command("schedule <broadcast_id>").description(`Schedule a broadcast for future delivery
2008
2008
 
2009
2009
  USAGE
2010
2010
  emailr broadcasts schedule <broadcast_id> --at <datetime> [options]
@@ -2054,7 +2054,7 @@ EXAMPLES
2054
2054
  emailr broadcasts schedule brd_abc123 --at "2024-12-25T10:00:00Z" --format json
2055
2055
 
2056
2056
  SEE ALSO
2057
- emailr broadcasts cancel Cancel a scheduled broadcast`).requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.schedule(t,e.at);e.format==="json"?m(a,"json"):(p("Broadcast scheduled successfully!"),m({ID:a.id,Name:a.name,Status:a.status,"Scheduled At":a.scheduled_at},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to schedule broadcast"),process.exit(1);}}),i.command("cancel <broadcast_id>").description(`Cancel a scheduled broadcast
2057
+ emailr broadcasts cancel Cancel a scheduled broadcast`).requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.schedule(a,e.at);e.format==="json"?m(t,"json"):(p("Broadcast scheduled successfully!"),m({ID:t.id,Name:t.name,Status:t.status,"Scheduled At":t.scheduled_at},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to schedule broadcast"),process.exit(1);}}),i.command("cancel <broadcast_id>").description(`Cancel a scheduled broadcast
2058
2058
 
2059
2059
  USAGE
2060
2060
  emailr broadcasts cancel <broadcast_id> [options]
@@ -2082,7 +2082,7 @@ EXAMPLES
2082
2082
  emailr broadcasts cancel brd_abc123 --format json
2083
2083
 
2084
2084
  NOTE
2085
- Broadcasts that are already 'sending' or 'sent' cannot be cancelled.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.cancel(t);e.format==="json"?m(a,"json"):p("Broadcast cancelled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to cancel broadcast"),process.exit(1);}}),i.command("update <broadcast_id>").description(`Update a broadcast
2085
+ Broadcasts that are already 'sending' or 'sent' cannot be cancelled.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.cancel(a);e.format==="json"?m(t,"json"):p("Broadcast cancelled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to cancel broadcast"),process.exit(1);}}),i.command("update <broadcast_id>").description(`Update a broadcast
2086
2086
 
2087
2087
  USAGE
2088
2088
  emailr broadcasts update <broadcast_id> [options]
@@ -2135,7 +2135,7 @@ EXAMPLES
2135
2135
  emailr broadcasts update brd_abc123 --name "Updated" --format json
2136
2136
 
2137
2137
  NOTE
2138
- Only broadcasts in 'draft' or 'scheduled' status can be updated.`).option("--name <name>","Broadcast name").option("--subject <subject>","Email subject").option("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--reply-to <email>","Reply-To email address").option("--preview-text <text>","Preview text (preheader)").option("--inbox-id <inbox_id>",'Inbox ID for sender identity (use "none" to clear)').option("--template <id>",'Template ID (use "none" to clear)').option("--segment <id>",'Segment ID (use "none" to clear)').option("--topic <id>",'Topic ID (use "none" to clear)').option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>",'Schedule time ISO 8601 (use "none" to clear)').option("--tags <tags>","Comma-separated tags (replaces existing)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name),e.subject&&(a.subject=e.subject),e.from&&(a.from_email=e.from),e.fromName&&(a.from_name=e.fromName),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText),e.html&&(a.html_content=e.html),e.text&&(a.text_content=e.text),e.inboxId&&(a.inbox_id=e.inboxId==="none"?null:e.inboxId),e.template&&(a.template_id=e.template==="none"?null:e.template),e.segment&&(a.segment_id=e.segment==="none"?null:e.segment),e.topic&&(a.topic_id=e.topic==="none"?null:e.topic),e.schedule&&(a.scheduled_at=e.schedule==="none"?null:e.schedule),e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean)),Object.keys(a).length===0&&(l("No update fields specified. Use --help to see available options."),process.exit(1));let n=await r.broadcasts.update(t,a);e.format==="json"?m(n,"json"):(p("Broadcast updated successfully!"),m({ID:n.id,Name:n.name,Subject:n.subject,Status:n.status,Tags:n.tags?.join(", ")||"-","Scheduled At":n.scheduled_at||"Not scheduled"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update broadcast"),process.exit(1);}}),i.command("delete <broadcast_id>").description(`Delete a broadcast
2138
+ Only broadcasts in 'draft' or 'scheduled' status can be updated.`).option("--name <name>","Broadcast name").option("--subject <subject>","Email subject").option("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--reply-to <email>","Reply-To email address").option("--preview-text <text>","Preview text (preheader)").option("--inbox-id <inbox_id>",'Inbox ID for sender identity (use "none" to clear)').option("--inbox-ids <ids>",'Comma-separated inbox IDs for rotation (use "none" to clear)').option("--sending-speed <speed>",'Sending speed: auto | slow | normal | instant (use "none" to clear)').option("--template <id>",'Template ID (use "none" to clear)').option("--segment <id>",'Segment ID (use "none" to clear)').option("--topic <id>",'Topic ID (use "none" to clear)').option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>",'Schedule time ISO 8601 (use "none" to clear)').option("--tags <tags>","Comma-separated tags (replaces existing)").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={};e.name&&(t.name=e.name),e.subject&&(t.subject=e.subject),e.from&&(t.from_email=e.from),e.fromName&&(t.from_name=e.fromName),e.replyTo&&(t.reply_to=e.replyTo),e.previewText&&(t.preview_text=e.previewText),e.html&&(t.html_content=e.html),e.text&&(t.text_content=e.text),e.inboxId&&(t.inbox_id=e.inboxId==="none"?null:e.inboxId),e.inboxIds&&(t.inbox_ids=e.inboxIds==="none"?null:e.inboxIds.split(",").map(s=>s.trim()).filter(Boolean)),e.sendingSpeed&&(t.sending_speed=e.sendingSpeed==="none"?null:e.sendingSpeed),e.template&&(t.template_id=e.template==="none"?null:e.template),e.segment&&(t.segment_id=e.segment==="none"?null:e.segment),e.topic&&(t.topic_id=e.topic==="none"?null:e.topic),e.schedule&&(t.scheduled_at=e.schedule==="none"?null:e.schedule),e.tags&&(t.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean)),Object.keys(t).length===0&&(l("No update fields specified. Use --help to see available options."),process.exit(1));let r=await n.broadcasts.update(a,t);e.format==="json"?m(r,"json"):(p("Broadcast updated successfully!"),m({ID:r.id,Name:r.name,Subject:r.subject,Status:r.status,Tags:r.tags?.join(", ")||"-","Scheduled At":r.scheduled_at||"Not scheduled"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update broadcast"),process.exit(1);}}),i.command("delete <broadcast_id>").description(`Delete a broadcast
2139
2139
 
2140
2140
  USAGE
2141
2141
  emailr broadcasts delete <broadcast_id> [options]
@@ -2163,7 +2163,7 @@ EXAMPLES
2163
2163
 
2164
2164
  WARNING
2165
2165
  This action is permanent and cannot be undone.
2166
- All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.delete(t);e.format==="json"?m(a,"json"):p("Broadcast deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete broadcast"),process.exit(1);}}),i}function Ue(){let i=new Command("webhooks").description(`Manage webhooks
2166
+ All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.delete(a);e.format==="json"?m(t,"json"):p("Broadcast deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete broadcast"),process.exit(1);}}),i}function ke(){let i=new Command("webhooks").description(`Manage webhooks
2167
2167
 
2168
2168
  USAGE
2169
2169
  emailr webhooks <subcommand> [options]
@@ -2316,7 +2316,7 @@ EXAMPLES
2316
2316
  emailr webhooks list --format json | jq '.[] | select(.active == true)'
2317
2317
 
2318
2318
  # Count webhooks
2319
- emailr webhooks list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(t.format==="json")m(r,"json");else {if(r.data.length===0){console.log("No webhooks found.");return}let a=r.data.map(n=>({ID:n.id,Name:n.name,URL:n.url.substring(0,40)+(n.url.length>40?"...":""),Events:n.events.join(", "),Active:n.active?"Yes":"No"}));m(a,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),i.command("get <webhook_id>").description(`Get webhook details
2319
+ emailr webhooks list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(a.format==="json")m(n,"json");else {if(n.data.length===0){console.log("No webhooks found.");return}let t=n.data.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"}));m(t,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),i.command("get <webhook_id>").description(`Get webhook details
2320
2320
 
2321
2321
  USAGE
2322
2322
  emailr webhooks get <webhook_id> [options]
@@ -2347,7 +2347,7 @@ EXAMPLES
2347
2347
  emailr webhooks get whk_abc123 --format json
2348
2348
 
2349
2349
  # Extract just the secret
2350
- emailr webhooks get whk_abc123 --format json | jq -r '.secret'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.get(t);e.format==="json"?m(a,"json"):m({ID:a.id,Name:a.name,URL:a.url,Events:a.events.join(", "),Active:a.active?"Yes":"No",Secret:a.secret,"Created At":a.created_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get webhook"),process.exit(1);}}),i.command("create").description(`Create a new webhook
2350
+ emailr webhooks get whk_abc123 --format json | jq -r '.secret'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.get(a);e.format==="json"?m(t,"json"):m({ID:t.id,Name:t.name,URL:t.url,Events:t.events.join(", "),Active:t.active?"Yes":"No",Secret:t.secret,"Created At":t.created_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get webhook"),process.exit(1);}}),i.command("create").description(`Create a new webhook
2351
2351
 
2352
2352
  USAGE
2353
2353
  emailr webhooks create --name <name> --url <url> --events <events> [options]
@@ -2421,7 +2421,7 @@ EXAMPLES
2421
2421
 
2422
2422
  NOTE
2423
2423
  Save the webhook secret returned by this command. You'll need it
2424
- to verify webhook signatures in your endpoint.`).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=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r=t.events.split(",").map(n=>n.trim()),a=await o.webhooks.create({name:t.name,url:t.url,events:r});t.format==="json"?m(a,"json"):(p("Webhook created successfully!"),m({ID:a.id,Name:a.name,URL:a.url,Secret:a.secret},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),i.command("update <webhook_id>").description(`Update a webhook
2424
+ to verify webhook signatures in your endpoint.`).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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n=a.events.split(",").map(r=>r.trim()),t=await o.webhooks.create({name:a.name,url:a.url,events:n});a.format==="json"?m(t,"json"):(p("Webhook created successfully!"),m({ID:t.id,Name:t.name,URL:t.url,Secret:t.secret},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),i.command("update <webhook_id>").description(`Update a webhook
2425
2425
 
2426
2426
  USAGE
2427
2427
  emailr webhooks update <webhook_id> [options]
@@ -2466,7 +2466,7 @@ EXAMPLES
2466
2466
  --events "email.delivered,email.bounced"
2467
2467
 
2468
2468
  # Get JSON output
2469
- emailr webhooks update whk_abc123 --name "New Name" --format json`).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=c(),r=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 n=await r.webhooks.update(t,a);e.format==="json"?m(n,"json"):(p("Webhook updated successfully!"),m({ID:n.id,Name:n.name,URL:n.url},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update webhook"),process.exit(1);}}),i.command("enable <webhook_id>").description(`Enable a webhook
2469
+ emailr webhooks update whk_abc123 --name "New Name" --format json`).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(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={};e.name&&(t.name=e.name),e.url&&(t.url=e.url),e.events&&(t.events=e.events.split(",").map(s=>s.trim()));let r=await n.webhooks.update(a,t);e.format==="json"?m(r,"json"):(p("Webhook updated successfully!"),m({ID:r.id,Name:r.name,URL:r.url},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update webhook"),process.exit(1);}}),i.command("enable <webhook_id>").description(`Enable a webhook
2470
2470
 
2471
2471
  USAGE
2472
2472
  emailr webhooks enable <webhook_id> [options]
@@ -2490,7 +2490,7 @@ EXAMPLES
2490
2490
  emailr webhooks enable whk_abc123
2491
2491
 
2492
2492
  # Get JSON output
2493
- emailr webhooks enable whk_abc123 --format json`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.enable(t);e.format==="json"?m(a,"json"):p("Webhook enabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to enable webhook"),process.exit(1);}}),i.command("disable <webhook_id>").description(`Disable a webhook
2493
+ emailr webhooks enable whk_abc123 --format json`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.enable(a);e.format==="json"?m(t,"json"):p("Webhook enabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to enable webhook"),process.exit(1);}}),i.command("disable <webhook_id>").description(`Disable a webhook
2494
2494
 
2495
2495
  USAGE
2496
2496
  emailr webhooks disable <webhook_id> [options]
@@ -2519,7 +2519,7 @@ EXAMPLES
2519
2519
 
2520
2520
  NOTE
2521
2521
  Events are not queued while webhook is disabled. If you need to
2522
- temporarily stop processing, consider handling this in your endpoint.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.disable(t);e.format==="json"?m(a,"json"):p("Webhook disabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to disable webhook"),process.exit(1);}}),i.command("delete <webhook_id>").description(`Delete a webhook
2522
+ temporarily stop processing, consider handling this in your endpoint.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.disable(a);e.format==="json"?m(t,"json"):p("Webhook disabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to disable webhook"),process.exit(1);}}),i.command("delete <webhook_id>").description(`Delete a webhook
2523
2523
 
2524
2524
  USAGE
2525
2525
  emailr webhooks delete <webhook_id> [options]
@@ -2547,7 +2547,7 @@ EXAMPLES
2547
2547
 
2548
2548
  WARNING
2549
2549
  This action is permanent and cannot be undone.
2550
- Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.delete(t);e.format==="json"?m(a,"json"):p("Webhook deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),i}function Pe(){let i=new Command("segments").description(`Manage contact segments
2550
+ Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.delete(a);e.format==="json"?m(t,"json"):p("Webhook deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),i}function Pe(){let i=new Command("segments").description(`Manage contact segments
2551
2551
 
2552
2552
  USAGE
2553
2553
  emailr segments <subcommand> [options]
@@ -2687,7 +2687,7 @@ EXAMPLES
2687
2687
  emailr segments list --format json
2688
2688
 
2689
2689
  # Pipe JSON to jq for processing
2690
- 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=c(),o=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 a=await o.segments.list(r);if(t.format==="json")m(a,"json");else {if(a.length===0){console.log("No segments found.");return}let n=a.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()}));m(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
2690
+ 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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={};if(a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let t=await o.segments.list(n);if(a.format==="json")m(t,"json");else {if(t.length===0){console.log("No segments found.");return}let r=t.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()}));m(r,"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
2691
2691
 
2692
2692
  USAGE
2693
2693
  emailr segments get <segment_id> [options]
@@ -2714,7 +2714,7 @@ EXAMPLES
2714
2714
  emailr segments get seg_abc123 --format json
2715
2715
 
2716
2716
  # Pipe JSON to jq to view conditions
2717
- emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.get(t);e.format==="json"?m(a,"json"):m({ID:a.id,Name:a.name,Description:a.description||"N/A",Conditions:JSON.stringify(a.conditions),Tags:a.tags?.join(", ")||"-","Created At":a.created_at,"Updated At":a.updated_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get segment"),process.exit(1);}}),i.command("create").description(`Create a new segment
2717
+ emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.get(a);e.format==="json"?m(t,"json"):m({ID:t.id,Name:t.name,Description:t.description||"N/A",Conditions:JSON.stringify(t.conditions),Tags:t.tags?.join(", ")||"-","Created At":t.created_at,"Updated At":t.updated_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get segment"),process.exit(1);}}),i.command("create").description(`Create a new segment
2718
2718
 
2719
2719
  USAGE
2720
2720
  emailr segments create --name <segment_name> --conditions <json> [options]
@@ -2764,7 +2764,7 @@ EXAMPLES
2764
2764
  # Get JSON output for scripting
2765
2765
  emailr segments create --name "Test" \\
2766
2766
  --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]' \\
2767
- --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=c(),o=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 a={name:t.name,description:t.description,conditions:r};t.tags&&(a.tags=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await o.segments.create(a);t.format==="json"?m(n,"json"):(p("Segment created successfully!"),m({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
2767
+ --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 a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n;try{n=JSON.parse(a.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let t={name:a.name,description:a.description,conditions:n};a.tags&&(t.tags=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await o.segments.create(t);a.format==="json"?m(r,"json"):(p("Segment created successfully!"),m({ID:r.id,Name:r.name,Tags:r.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
2768
2768
 
2769
2769
  USAGE
2770
2770
  emailr segments update <segment_id> [options]
@@ -2810,7 +2810,7 @@ EXAMPLES
2810
2810
  --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2811
2811
 
2812
2812
  # Get JSON output
2813
- 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 o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.description&&(a.description=e.description),e.conditions)try{a.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.segments.update(t,a);e.format==="json"?m(n,"json"):(p("Segment updated successfully!"),m({ID:n.id,Name:n.name,Tags:n.tags?.join(", ")||"-"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2813
+ 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(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={};if(e.name&&(t.name=e.name),e.description&&(t.description=e.description),e.conditions)try{t.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}e.tags&&(t.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await n.segments.update(a,t);e.format==="json"?m(r,"json"):(p("Segment updated successfully!"),m({ID:r.id,Name:r.name,Tags:r.tags?.join(", ")||"-"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2814
2814
 
2815
2815
  USAGE
2816
2816
  emailr segments delete <segment_id>
@@ -2835,7 +2835,7 @@ EXAMPLES
2835
2835
 
2836
2836
  WARNING
2837
2837
  This action is permanent and cannot be undone.
2838
- Any broadcasts targeting this segment will need to be updated.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.delete(t);e.format==="json"?m(a,"json"):p("Segment deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete segment"),process.exit(1);}}),i.command("count <segment_id>").description(`Get the number of contacts in a segment
2838
+ Any broadcasts targeting this segment will need to be updated.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.delete(a);e.format==="json"?m(t,"json"):p("Segment deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete segment"),process.exit(1);}}),i.command("count <segment_id>").description(`Get the number of contacts in a segment
2839
2839
 
2840
2840
  USAGE
2841
2841
  emailr segments count <segment_id> [options]
@@ -2866,7 +2866,7 @@ EXAMPLES
2866
2866
 
2867
2867
  TIP
2868
2868
  Use this before sending broadcasts to verify your segment
2869
- targets the expected number of contacts.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.getContactsCount(t);e.format==="json"?m(a,"json"):console.log(`Segment contains ${a.count} contacts.`);}catch(o){l(o instanceof Error?o.message:"Failed to get segment count"),process.exit(1);}}),i}function wt(i){try{let t=i.startsWith("http")?i:`http://localhost${i}`,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 yt(){return `<!DOCTYPE html>
2869
+ targets the expected number of contacts.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.getContactsCount(a);e.format==="json"?m(t,"json"):console.log(`Segment contains ${t.count} contacts.`);}catch(o){l(o instanceof Error?o.message:"Failed to get segment count"),process.exit(1);}}),i}function yt(i){try{let a=i.startsWith("http")?i:`http://localhost${i}`,e=new URL(a);return {key:e.searchParams.get("key")??void 0,code:e.searchParams.get("code")??void 0,state:e.searchParams.get("state")??void 0,error:e.searchParams.get("error")??void 0,message:e.searchParams.get("message")??void 0}}catch{return {}}}function St(){return `<!DOCTYPE html>
2870
2870
  <html lang="en">
2871
2871
  <head>
2872
2872
  <meta charset="UTF-8">
@@ -2912,7 +2912,7 @@ TIP
2912
2912
  <p>You can close this window and return to your terminal.</p>
2913
2913
  </div>
2914
2914
  </body>
2915
- </html>`}function ae(i){return `<!DOCTYPE html>
2915
+ </html>`}function oe(i){return `<!DOCTYPE html>
2916
2916
  <html lang="en">
2917
2917
  <head>
2918
2918
  <meta charset="UTF-8">
@@ -2967,20 +2967,61 @@ TIP
2967
2967
  <p style="margin-top: 1rem;">Please close this window and try again.</p>
2968
2968
  </div>
2969
2969
  </body>
2970
- </html>`}function Ae(){let i=null,t=0,e=null,o=null,r=null;return {async start(){return new Promise((a,n)=>{i=tt.createServer((s,d)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){d.writeHead(404,{"Content-Type":"text/plain"}),d.end("Not Found");return}let b=wt(s.url);if(r&&b.state!==r){d.writeHead(400,{"Content-Type":"text/html"}),d.end(ae("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(b.error){let f=b.message||b.error;d.writeHead(200,{"Content-Type":"text/html"}),d.end(ae(f)),e&&e({success:false,error:f});return}let S=b.key||b.code;if(S){d.writeHead(200,{"Content-Type":"text/html"}),d.end(yt()),e&&e({success:true,apiKey:S});return}d.writeHead(400,{"Content-Type":"text/html"}),d.end(ae("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,a({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(a,n){return r=a,new Promise(s=>{e=s,o=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},n);})},async stop(){if(o&&(clearTimeout(o),o=null),i)return new Promise(a=>{i.close(()=>{i=null,a();});})}}}function ke(){return St.randomBytes(32).toString("hex")}function Re(i){return new Promise(t=>{let e=process.platform,o;switch(e){case "darwin":o=`open "${i}"`;break;case "win32":o=`start "" "${i}"`;break;default:o=`xdg-open "${i}"`;break}exec(o,r=>{t(!r);});})}var V=120,Ot=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function xt(i,t){let e=`http://127.0.0.1:${t}/callback`,o=new URLSearchParams({state:i,callback_url:e});return `${Ot}/consent/authorize?${o.toString()}`}function Me(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String(V)).option("--no-browser","Don't automatically open the browser").action(async t=>{await Et({timeout:parseInt(t.timeout,10)||V,noBrowser:t.browser===false});})}async function Et(i){let t=Ae(),e=(i.timeout||V)*1e3;try{u("Starting authentication server...");let{port:o,url:r}=await t.start(),a=ke(),n=xt(a,o);console.log(""),u("Authorization URL:"),console.log(` ${n}`),console.log(""),i.noBrowser?u("Please open the URL above in your browser to continue."):await Re(n)?u("Browser opened. Please complete authentication in your browser."):(N("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||V}s)...`);let s=await t.waitForCallback(a,e);s.success&&s.apiKey?(G({apiKey:s.apiKey}),console.log(""),p("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(o){console.log(""),l(o instanceof Error?o.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 ie=E.join(X.homedir(),".config","opencode","skills","emailr-cli"),Nt=`---
2970
+ </html>`}function Ae(){let i=null,a=0,e=null,o=null,n=null;return {async start(){return new Promise((t,r)=>{i=at.createServer((s,c)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){c.writeHead(404,{"Content-Type":"text/plain"}),c.end("Not Found");return}let b=yt(s.url);if(n&&b.state!==n){c.writeHead(400,{"Content-Type":"text/html"}),c.end(oe("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(b.error){let f=b.message||b.error;c.writeHead(200,{"Content-Type":"text/html"}),c.end(oe(f)),e&&e({success:false,error:f});return}let g=b.key||b.code;if(g){c.writeHead(200,{"Content-Type":"text/html"}),c.end(St()),e&&e({success:true,apiKey:g});return}c.writeHead(400,{"Content-Type":"text/html"}),c.end(oe("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"?(a=s.port,t({port:a,url:`http://127.0.0.1:${a}/callback`})):r(new Error("Failed to get server address"));}),i.on("error",s=>{r(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(t,r){return n=t,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),i)return new Promise(t=>{i.close(()=>{i=null,t();});})}}}function Re(){return vt.randomBytes(32).toString("hex")}function De(i){return new Promise(a=>{let e=process.platform,o;switch(e){case "darwin":o=`open "${i}"`;break;case "win32":o=`start "" "${i}"`;break;default:o=`xdg-open "${i}"`;break}exec(o,n=>{a(!n);});})}var X=120,Ot=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function Et(i,a){let e=`http://127.0.0.1:${a}/callback`,o=new URLSearchParams({state:i,callback_url:e});return `${Ot}/consent/authorize?${o.toString()}`}function Me(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String(X)).option("--no-browser","Don't automatically open the browser").action(async a=>{await _t({timeout:parseInt(a.timeout,10)||X,noBrowser:a.browser===false});})}async function _t(i){let a=Ae(),e=(i.timeout||X)*1e3;try{u("Starting authentication server...");let{port:o,url:n}=await a.start(),t=Re(),r=Et(t,o);console.log(""),u("Authorization URL:"),console.log(` ${r}`),console.log(""),i.noBrowser?u("Please open the URL above in your browser to continue."):await De(r)?u("Browser opened. Please complete authentication in your browser."):(N("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||X}s)...`);let s=await a.waitForCallback(t,e);s.success&&s.apiKey?(K({apiKey:s.apiKey}),console.log(""),p("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(o){console.log(""),l(o instanceof Error?o.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 a.stop();}}var re=E.join(V.homedir(),".config","opencode","skills","emailr-cli"),Ct=`---
2971
2971
  name: emailr-cli
2972
- 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.
2972
+ description: Operate the Emailr CLI to send emails, reply to threads, manage contacts, templates, inboxes, domains, broadcasts, webhooks, and segments. Includes thread reply auto-resolution and live preview editing for templates.
2973
2973
  ---
2974
2974
  # Emailr CLI
2975
2975
 
2976
2976
  ## Quick start
2977
2977
  - Ensure auth: \`emailr login\` (browser-based) or \`emailr config set api-key <token>\`.
2978
2978
  - Prefer machine-readable output with \`--format json\` on listing/get commands.
2979
+ - Run \`emailr skill\` for the full agent skill guide.
2980
+
2981
+ ## Thread Reply (KEY FEATURE)
2982
+ Reply to any thread with just the thread ID and HTML \u2014 from, to, subject, reply-to are all auto-resolved from the thread's inbox:
2983
+ \`\`\`bash
2984
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>" --format json
2985
+ emailr threads reply <thread-id> --html "<p>Done</p>" --cc "boss@co.com" --format json
2986
+ \`\`\`
2987
+
2988
+ ## Thread Drafts
2989
+ \`\`\`bash
2990
+ emailr threads draft save <thread-id> --html "<p>WIP</p>"
2991
+ emailr threads draft get <thread-id> --format json
2992
+ emailr threads draft delete <thread-id>
2993
+ \`\`\`
2994
+
2995
+ ## Thread Management
2996
+ \`\`\`bash
2997
+ emailr threads list --format json
2998
+ emailr threads list --label sent --inbox-id <id>
2999
+ emailr threads get <thread-id> --format json
3000
+ emailr threads label <thread-id> --add starred
3001
+ emailr threads label <thread-id> --remove spam,trash
3002
+ \`\`\`
3003
+ Labels: inbox, sent, starred, spam, trash, archived, all
3004
+
3005
+ ## Inbox Management
3006
+ \`\`\`bash
3007
+ emailr inboxes list --format json
3008
+ emailr inboxes create --name "Support" --username support --domain example.com
3009
+ emailr inboxes get <id> --format json
3010
+ emailr inboxes update <id> --name "New Name"
3011
+ emailr inboxes delete <id>
3012
+ \`\`\`
3013
+
3014
+ ## Inbound Emails
3015
+ \`\`\`bash
3016
+ emailr inbox list --format json
3017
+ emailr inbox get <email-id> --format json
3018
+ emailr inbox thread <email-id> --format json
3019
+ emailr inbox reply <email-id> --html "<p>Reply</p>"
3020
+ emailr inbox forward <email-id> --to other@x.com
3021
+ \`\`\`
2979
3022
 
2980
3023
  ## LIVE TEMPLATE EDITING (Agentic Workflow)
2981
3024
 
2982
- IMPORTANT: Always use \`--background\` flag so the command returns immediately and you can continue editing.
2983
-
2984
3025
  ### Edit existing template with live preview
2985
3026
  1. Start live editing: \`emailr templates edit <template_id> --file ./template.html --background\`
2986
3027
  2. Edit the file at ./template.html - browser auto-refreshes on every save
@@ -2993,21 +3034,56 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
2993
3034
  3. When satisfied: \`emailr templates create --name "Template Name" --subject "Subject" --html-file ./new-template.html\`
2994
3035
  4. Stop server: \`emailr templates stop-preview\`
2995
3036
 
3037
+ ### Template preview sharing
3038
+ 1. Fetch: \`emailr templates fetch <id> --output template.html\`
3039
+ 2. Edit locally
3040
+ 3. Push preview: \`emailr templates push-preview <id> --html-file template.html\`
3041
+ 4. Share preview URL for feedback
3042
+ 5. Publish: \`emailr templates update <id> --html-file template.html\`
3043
+
2996
3044
  ## Common commands
2997
3045
 
2998
3046
  ### Sending emails
2999
- - Plain text: \`emailr send --to user@example.com --from no-reply@example.com --subject "Hi" --text "Hello"\`
3000
- - HTML: \`emailr send --to user@example.com --from no-reply@example.com --subject "Hi" --html "<h1>Hello</h1>"\`
3001
- - With template: \`emailr send --to user@example.com --from no-reply@example.com --template tmpl_xxx --template-data '{"name":"Ada"}'\`
3002
-
3003
- ### Templates
3004
- - List: \`emailr templates list --format json\`
3005
- - Get: \`emailr templates get <id> --format json\`
3006
- - Preview: \`emailr templates preview <id>\`
3007
- - Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
3008
- - Update: \`emailr templates update <id> --html-file ./template.html\`
3009
- - Delete: \`emailr templates delete <id>\`
3010
- `;function Ct(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function Ut(){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 Pt(){O.existsSync(ie)||O.mkdirSync(ie,{recursive:true});let i=E.join(ie,"SKILL.md");O.writeFileSync(i,Nt,"utf-8");}function Le(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
3047
+ \`\`\`bash
3048
+ emailr send --to user@x.com --subject "Hi" --html "<h1>Hello</h1>"
3049
+ emailr send --to user@x.com --template tmpl_xxx --template-data '{"name":"Ada"}'
3050
+ emailr send --to user@x.com --html-file ./email.html --cc "other@x.com"
3051
+ \`\`\`
3052
+
3053
+ ### Contacts
3054
+ \`\`\`bash
3055
+ emailr contacts list --format json
3056
+ emailr contacts create --email user@x.com --first-name "Jo" --metadata '{"plan":"pro"}'
3057
+ emailr contacts update <id> --tags "vip,active"
3058
+ emailr contacts delete <id>
3059
+ \`\`\`
3060
+
3061
+ ### Segments
3062
+ \`\`\`bash
3063
+ emailr segments create --name "Active" --conditions '[{"field":"subscribed","operator":"equals","value":true}]'
3064
+ emailr segments count <id>
3065
+ \`\`\`
3066
+
3067
+ ### Broadcasts
3068
+ \`\`\`bash
3069
+ emailr broadcasts create --name "Newsletter" --subject "News" --from news@x.com --template tpl_abc --segment seg_xyz
3070
+ emailr broadcasts send <id>
3071
+ emailr broadcasts schedule <id> --at "2025-12-25T10:00:00Z"
3072
+ \`\`\`
3073
+
3074
+ ### Webhooks
3075
+ \`\`\`bash
3076
+ emailr webhooks create --name "Tracker" --url "https://x.com/hook" --events "email.delivered,email.bounced"
3077
+ emailr webhooks list --format json
3078
+ \`\`\`
3079
+
3080
+ ### Domains
3081
+ \`\`\`bash
3082
+ emailr domains add example.com
3083
+ emailr domains verify example.com
3084
+ emailr domains list
3085
+ \`\`\`
3086
+ `;function Ut(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function kt(){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 Pt(){x.existsSync(re)||x.mkdirSync(re,{recursive:true});let i=E.join(re,"SKILL.md");x.writeFileSync(i,Ct,"utf-8");}function Fe(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
3011
3087
 
3012
3088
  This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
3013
3089
  The agent can help you:
@@ -3015,10 +3091,10 @@ The agent can help you:
3015
3091
  - Manage contacts, broadcasts, and segments
3016
3092
  - Configure domains and webhooks
3017
3093
 
3018
- 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=>{Ct()||(t.install?Ut()||(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(`
3019
- 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{Pt(),p("Emailr CLI skill loaded");}catch(r){N(`Could not install skill: ${r instanceof Error?r.message:String(r)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
3094
+ 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 a=>{Ut()||(a.install?kt()||(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(`
3095
+ 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{Pt(),p("Emailr CLI skill loaded");}catch(n){N(`Could not install skill: ${n instanceof Error?n.message:String(n)}`);}let e=[];a.model&&e.push("--model",a.model),console.log(`
3020
3096
  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.
3021
- `);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",r=>{l(`Failed to start agent: ${r.message}`),process.exit(1);}),o.on("exit",r=>{process.exit(r??0);});})}function Ke(){let i=new Command("inbox").description(`Manage inbound emails (inbox)
3097
+ `);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",n=>{l(`Failed to start agent: ${n.message}`),process.exit(1);}),o.on("exit",n=>{process.exit(n??0);});})}function He(){let i=new Command("inbox").description(`Manage inbound emails (inbox)
3022
3098
 
3023
3099
  USAGE
3024
3100
  emailr inbox <subcommand> [options]
@@ -3098,7 +3174,7 @@ EXAMPLES
3098
3174
 
3099
3175
  OUTPUT FORMATS
3100
3176
  --format json Machine-readable JSON with data array and pagination
3101
- --format table Human-readable table with ID, From, Subject, To, Date (default)`).option("--limit <count>","Number of emails to return","20").option("--page <number>","Page number","1").option("--email <address>","Filter by sender email (partial match)").option("--domain <domain>","Filter by sender domain").option("--inbox-id <inbox_id>","Filter by inbox ID").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={status:"received",page:parseInt(t.page),limit:Math.min(parseInt(t.limit),100),email:t.email,domain:t.domain};t.inboxId&&(r.inbox_id=t.inboxId);let a=await o.emails.list(r);if(t.format==="json")m(a,"json");else {if(!a.data||a.data.length===0){u("No emails in inbox");return}let s=a.data.map(d=>({ID:d.id,From:d.from_email,Subject:Ge(d.subject||"(no subject)",40),To:d.to_email,Date:re(d.created_at),Attachments:d.attachments?.length||0}));m(s,"table"),u(`Page ${a.pagination.page} of ${a.pagination.pages} (${a.pagination.total} total)`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inbox"),process.exit(1);}}),i.command("get <id>").description(`View a received email
3177
+ --format table Human-readable table with ID, From, Subject, To, Date (default)`).option("--limit <count>","Number of emails to return","20").option("--page <number>","Page number","1").option("--email <address>","Filter by sender email (partial match)").option("--domain <domain>","Filter by sender domain").option("--inbox-id <inbox_id>","Filter by inbox ID").option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={status:"received",page:parseInt(a.page),limit:Math.min(parseInt(a.limit),100),email:a.email,domain:a.domain};a.inboxId&&(n.inbox_id=a.inboxId);let t=await o.emails.list(n);if(a.format==="json")m(t,"json");else {if(!t.data||t.data.length===0){u("No emails in inbox");return}let s=t.data.map(c=>({ID:c.id,From:c.from_email,Subject:Ke(c.subject||"(no subject)",40),To:c.to_email,Date:se(c.created_at),Attachments:c.attachments?.length||0}));m(s,"table"),u(`Page ${t.pagination.page} of ${t.pagination.pages} (${t.pagination.total} total)`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inbox"),process.exit(1);}}),i.command("get <id>").description(`View a received email
3102
3178
 
3103
3179
  USAGE
3104
3180
  emailr inbox get <email-id> [options]
@@ -3119,7 +3195,7 @@ EXAMPLES
3119
3195
 
3120
3196
  OUTPUT FORMATS
3121
3197
  --format json Full email object with all fields as JSON
3122
- --format table Key-value table with email metadata and content preview (default)`).option("--content <type>","Content to display: preview | text | html","preview").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).emails.get(t);if(e.format==="json")m(a,"json");else {if(m({ID:a.id,From:a.from_email,To:a.to_email,Subject:a.subject||"(no subject)",Date:re(a.created_at),Status:a.status,"Thread ID":a.thread_id||"-","Parent Email":a.parent_email_id||"-",Attachments:a.attachments?.length||0},"table"),console.log(""),e.content==="html")console.log(a.html_content||"(no HTML content)");else if(e.content==="text")console.log(a.text_content||"(no text content)");else {let s=a.text_content||a.html_content?.replace(/<[^>]*>/g,"")||"(no content)";console.log(s);}if(a.attachments&&a.attachments.length>0){console.log(""),u("Attachments:");let s=a.attachments.map(d=>({Filename:d.filename,Type:d.contentType||d.content_type||"-",Size:kt(d.size||0)}));m(s,"table");}}}catch(o){l(o instanceof Error?o.message:"Failed to get email"),process.exit(1);}}),i.command("thread <id>").description(`View conversation thread for an email
3198
+ --format table Key-value table with email metadata and content preview (default)`).option("--content <type>","Content to display: preview | text | html","preview").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).emails.get(a);if(e.format==="json")m(t,"json");else {if(m({ID:t.id,From:t.from_email,To:t.to_email,Subject:t.subject||"(no subject)",Date:se(t.created_at),Status:t.status,"Thread ID":t.thread_id||"-","Parent Email":t.parent_email_id||"-",Attachments:t.attachments?.length||0},"table"),console.log(""),e.content==="html")console.log(t.html_content||"(no HTML content)");else if(e.content==="text")console.log(t.text_content||"(no text content)");else {let s=t.text_content||t.html_content?.replace(/<[^>]*>/g,"")||"(no content)";console.log(s);}if(t.attachments&&t.attachments.length>0){console.log(""),u("Attachments:");let s=t.attachments.map(c=>({Filename:c.filename,Type:c.contentType||c.content_type||"-",Size:Rt(c.size||0)}));m(s,"table");}}}catch(o){l(o instanceof Error?o.message:"Failed to get email"),process.exit(1);}}),i.command("thread <id>").description(`View conversation thread for an email
3123
3199
 
3124
3200
  USAGE
3125
3201
  emailr inbox thread <email-id> [options]
@@ -3138,7 +3214,7 @@ EXAMPLES
3138
3214
 
3139
3215
  OUTPUT FORMATS
3140
3216
  --format json JSON array of all emails in the thread
3141
- --format table Chronological conversation view with direction indicators (default)`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=await r.emails.get(t),n=a.thread_id||a.id,s=[],d=new Set;if(s.push(a),d.add(a.id),a.thread_id&&a.thread_id!==a.id)try{let f=await r.emails.get(a.thread_id);d.has(f.id)||(s.push(f),d.add(f.id));}catch{}let b=await r.emails.list({limit:100});if(b.data)for(let f of b.data)d.has(f.id)||(f.thread_id===n||f.parent_email_id===a.id||f.id===a.parent_email_id)&&(s.push(f),d.add(f.id));if(s.sort((f,h)=>new Date(f.created_at).getTime()-new Date(h.created_at).getTime()),e.format==="json")m(s,"json");else {s.length<=1&&u("No other emails in this conversation"),u(`Conversation (${s.length} email${s.length!==1?"s":""}):`),console.log("");for(let f of s){let h=f.status==="received"?"\u2190 IN ":"\u2192 OUT",w=f.id===t?" \u25C0 (selected)":"";console.log(`${h} ${re(f.created_at)}${w}`),console.log(` From: ${f.from_email}`),console.log(` To: ${f.to_email}`),console.log(` Subject: ${f.subject||"(no subject)"}`);let _=f.text_content||f.html_content?.replace(/<[^>]*>/g,"")||"";_&&console.log(` ${Ge(_.trim(),120)}`),console.log("");}}await new Promise(f=>process.stdout.write("",()=>f())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to load thread"),process.exit(1);}}),i.command("reply <id>").description(`Reply to a received email
3217
+ --format table Chronological conversation view with direction indicators (default)`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t=await n.emails.get(a),r=t.thread_id||t.id,s=[],c=new Set;if(s.push(t),c.add(t.id),t.thread_id&&t.thread_id!==t.id)try{let f=await n.emails.get(t.thread_id);c.has(f.id)||(s.push(f),c.add(f.id));}catch{}let b=await n.emails.list({limit:100});if(b.data)for(let f of b.data)c.has(f.id)||(f.thread_id===r||f.parent_email_id===t.id||f.id===t.parent_email_id)&&(s.push(f),c.add(f.id));if(s.sort((f,h)=>new Date(f.created_at).getTime()-new Date(h.created_at).getTime()),e.format==="json")m(s,"json");else {s.length<=1&&u("No other emails in this conversation"),u(`Conversation (${s.length} email${s.length!==1?"s":""}):`),console.log("");for(let f of s){let h=f.status==="received"?"\u2190 IN ":"\u2192 OUT",S=f.id===a?" \u25C0 (selected)":"";console.log(`${h} ${se(f.created_at)}${S}`),console.log(` From: ${f.from_email}`),console.log(` To: ${f.to_email}`),console.log(` Subject: ${f.subject||"(no subject)"}`);let _=f.text_content||f.html_content?.replace(/<[^>]*>/g,"")||"";_&&console.log(` ${Ke(_.trim(),120)}`),console.log("");}}await new Promise(f=>process.stdout.write("",()=>f())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to load thread"),process.exit(1);}}),i.command("reply <id>").description(`Reply to a received email
3142
3218
 
3143
3219
  USAGE
3144
3220
  emailr inbox reply <email-id> [options]
@@ -3174,7 +3250,7 @@ EXAMPLES
3174
3250
 
3175
3251
  OUTPUT FORMATS
3176
3252
  --format json Machine-readable JSON with message_id and status
3177
- --format table Human-readable summary with Message ID, To, Subject, Status (default)`).option("--html <content>","HTML content for reply").option("--text <content>","Plain text content for reply").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read plain text content from file").option("--from <email>","Override sender address").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--subject <subject>","Override reply subject").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=await r.emails.get(t),n=e.html,s=e.text;if(e.htmlFile)try{n=readFileSync(e.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${e.htmlFile}`),process.exit(1);}if(e.textFile)try{s=readFileSync(e.textFile,"utf-8");}catch{l(`Failed to read text file: ${e.textFile}`),process.exit(1);}!n&&!s&&(l("Reply content is required. Use --html, --text, --html-file, or --text-file"),process.exit(1));let d=e.subject||(a.subject?.startsWith("Re:")?a.subject:`Re: ${a.subject||""}`),b={to:a.from_email,from:e.from||a.to_email,subject:d,html:n||void 0,text:s||n?.replace(/<[^>]*>/g,"")||void 0,replyTo:{in_reply_to:a.message_id||a.ses_message_id,thread_id:a.thread_id||a.id,parent_email_id:a.id}};if(e.cc){let h=e.cc.split(",").map(w=>w.trim());b.cc=h.length===1?h[0]:h;}if(e.bcc){let h=e.bcc.split(",").map(w=>w.trim());b.bcc=h.length===1?h[0]:h;}let S=await r.emails.send(b);e.format==="json"?m(S,"json"):(p("Reply sent successfully!"),m({"Message ID":S.message_id,To:a.from_email,Subject:d,Status:S.status},"table")),await new Promise(h=>process.stdout.write("",()=>h())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to send reply"),process.exit(1);}}),i.command("forward <id>").description(`Forward a received email
3253
+ --format table Human-readable summary with Message ID, To, Subject, Status (default)`).option("--html <content>","HTML content for reply").option("--text <content>","Plain text content for reply").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read plain text content from file").option("--from <email>","Override sender address").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--subject <subject>","Override reply subject").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t=await n.emails.get(a),r=e.html,s=e.text;if(e.htmlFile)try{r=readFileSync(e.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${e.htmlFile}`),process.exit(1);}if(e.textFile)try{s=readFileSync(e.textFile,"utf-8");}catch{l(`Failed to read text file: ${e.textFile}`),process.exit(1);}!r&&!s&&(l("Reply content is required. Use --html, --text, --html-file, or --text-file"),process.exit(1));let c=e.subject||(t.subject?.startsWith("Re:")?t.subject:`Re: ${t.subject||""}`),b={to:t.from_email,from:e.from||t.to_email,subject:c,html:r||void 0,text:s||r?.replace(/<[^>]*>/g,"")||void 0,replyTo:{in_reply_to:t.message_id||t.ses_message_id,thread_id:t.thread_id||t.id,parent_email_id:t.id}};if(e.cc){let h=e.cc.split(",").map(S=>S.trim());b.cc=h.length===1?h[0]:h;}if(e.bcc){let h=e.bcc.split(",").map(S=>S.trim());b.bcc=h.length===1?h[0]:h;}let g=await n.emails.send(b);e.format==="json"?m(g,"json"):(p("Reply sent successfully!"),m({"Message ID":g.message_id,To:t.from_email,Subject:c,Status:g.status},"table")),await new Promise(h=>process.stdout.write("",()=>h())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to send reply"),process.exit(1);}}),i.command("forward <id>").description(`Forward a received email
3178
3254
 
3179
3255
  USAGE
3180
3256
  emailr inbox forward <email-id> --to <recipients> [options]
@@ -3200,7 +3276,7 @@ EXAMPLES
3200
3276
 
3201
3277
  OUTPUT FORMATS
3202
3278
  --format json Machine-readable JSON with message_id and status
3203
- --format table Human-readable summary with Message ID, To, Recipients, Status (default)`).requiredOption("--to <emails>","Recipient email addresses (comma-separated)").option("--message <text>","Message to include with forwarded email").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=e.to.split(",").map(d=>d.trim()).filter(Boolean),n=await r.emails.forward({email_id:t,to:a.length===1?a[0]:a,message:e.message});e.format==="json"?m(n,"json"):(p("Email forwarded successfully!"),m({"Message ID":n.message_id,To:a.join(", "),Recipients:n.recipients,Status:n.status},"table")),await new Promise(d=>process.stdout.write("",()=>d())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to forward email"),process.exit(1);}}),i}function Ge(i,t){return i.length<=t?i:i.slice(0,t-1)+"\u2026"}function re(i){return new Date(i).toLocaleString()}function kt(i){return i<1024?i+" B":i<1024*1024?(i/1024).toFixed(1)+" KB":(i/(1024*1024)).toFixed(1)+" MB"}function qe(){let i=new Command("inboxes").description(`Manage inboxes
3279
+ --format table Human-readable summary with Message ID, To, Recipients, Status (default)`).requiredOption("--to <emails>","Recipient email addresses (comma-separated)").option("--message <text>","Message to include with forwarded email").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t=e.to.split(",").map(c=>c.trim()).filter(Boolean),r=await n.emails.forward({email_id:a,to:t.length===1?t[0]:t,message:e.message});e.format==="json"?m(r,"json"):(p("Email forwarded successfully!"),m({"Message ID":r.message_id,To:t.join(", "),Recipients:r.recipients,Status:r.status},"table")),await new Promise(c=>process.stdout.write("",()=>c())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to forward email"),process.exit(1);}}),i}function Ke(i,a){return i.length<=a?i:i.slice(0,a-1)+"\u2026"}function se(i){return new Date(i).toLocaleString()}function Rt(i){return i<1024?i+" B":i<1024*1024?(i/1024).toFixed(1)+" KB":(i/(1024*1024)).toFixed(1)+" MB"}function qe(){let i=new Command("inboxes").description(`Manage inboxes
3204
3280
 
3205
3281
  USAGE
3206
3282
  emailr inboxes <subcommand> [options]
@@ -3249,8 +3325,11 @@ EXAMPLES
3249
3325
  # Create a support inbox
3250
3326
  emailr inboxes create --name "Support" --username support --domain example.com
3251
3327
 
3328
+ # Create with custom reply-to
3329
+ emailr inboxes create --name "Support" --username support --domain example.com --reply-to replies@example.com
3330
+
3252
3331
  # Create and get JSON output
3253
- emailr inboxes create --name "Sales" --username sales --domain example.com --format json`).requiredOption("--name <name>","Inbox display name").requiredOption("--username <username>","Username for the email address").requiredOption("--domain <domain>","Domain for the email address").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=c(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).inboxes.create({name:t.name,username:t.username,domain:t.domain});t.format==="json"?m(r,"json"):(p(`Inbox created: ${r.id}`),m({ID:r.id,Name:r.name,"From Address":r.from_address,"Inbound Address":r.inbound_address,Created:r.created_at},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create inbox"),process.exit(1);}}),i.command("list").description(`List all inboxes
3332
+ emailr inboxes create --name "Sales" --username sales --domain example.com --format json`).requiredOption("--name <name>","Inbox display name").requiredOption("--username <username>","Username for the email address").requiredOption("--domain <domain>","Domain for the email address").option("--reply-to <email>","Custom reply-to address (defaults to username@mail.domain)").option("--format <format>","Output format: json | table","table").action(async a=>{try{let e=d(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={name:a.name,username:a.username,domain:a.domain};a.replyTo&&(n.reply_to=a.replyTo);let t=await o.inboxes.create(n);a.format==="json"?m(t,"json"):(p(`Inbox created: ${t.id}`),m({ID:t.id,Name:t.name,"From Address":t.from_address,"Reply To":t.reply_to,"Inbound Address":t.inbound_address,Created:t.created_at},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create inbox"),process.exit(1);}}),i.command("list").description(`List all inboxes
3254
3333
 
3255
3334
  USAGE
3256
3335
  emailr inboxes list [options]
@@ -3266,8 +3345,8 @@ EXAMPLES
3266
3345
  emailr inboxes list
3267
3346
 
3268
3347
  # Get JSON output
3269
- emailr inboxes list --format json`).option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=c(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).inboxes.list();if(t.format==="json")m(r,"json");else {if(r.length===0){console.log("No inboxes found.");return}let a=r.map(n=>({ID:n.id,Name:n.name,"From Address":n.from_address,"Inbound Address":n.inbound_address,Created:new Date(n.created_at).toLocaleDateString()}));m(a,"table"),console.log(`
3270
- Total: ${r.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inboxes"),process.exit(1);}}),i.command("get <inbox_id>").description(`Get an inbox by ID
3348
+ emailr inboxes list --format json`).option("--format <format>","Output format: json | table","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).inboxes.list();if(a.format==="json")m(n,"json");else {if(n.length===0){console.log("No inboxes found.");return}let t=n.map(r=>({ID:r.id,Name:r.name,"From Address":r.from_address,"Reply To":r.reply_to,"Inbound Address":r.inbound_address,Created:new Date(r.created_at).toLocaleDateString()}));m(t,"table"),console.log(`
3349
+ Total: ${n.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inboxes"),process.exit(1);}}),i.command("get <inbox_id>").description(`Get an inbox by ID
3271
3350
 
3272
3351
  USAGE
3273
3352
  emailr inboxes get <inbox_id> [options]
@@ -3286,7 +3365,7 @@ EXAMPLES
3286
3365
  emailr inboxes get inb_abc123
3287
3366
 
3288
3367
  # Get JSON output
3289
- emailr inboxes get inb_abc123 --format json`).option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).inboxes.get(t);e.format==="json"?m(a,"json"):m({ID:a.id,Name:a.name,Username:a.username,Domain:a.domain,"From Address":a.from_address,"Inbound Address":a.inbound_address,Created:a.created_at,Updated:a.updated_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get inbox"),process.exit(1);}}),i.command("update <inbox_id>").description(`Update an inbox
3368
+ emailr inboxes get inb_abc123 --format json`).option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).inboxes.get(a);e.format==="json"?m(t,"json"):m({ID:t.id,Name:t.name,Username:t.username,Domain:t.domain,"From Address":t.from_address,"Reply To":t.reply_to,"Inbound Address":t.inbound_address,Created:t.created_at,Updated:t.updated_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get inbox"),process.exit(1);}}),i.command("update <inbox_id>").description(`Update an inbox
3290
3369
 
3291
3370
  USAGE
3292
3371
  emailr inboxes update <inbox_id> --name <name> [options]
@@ -3306,8 +3385,14 @@ EXAMPLES
3306
3385
  # Update inbox name
3307
3386
  emailr inboxes update inb_abc123 --name "Customer Support"
3308
3387
 
3388
+ # Update reply-to address
3389
+ emailr inboxes update inb_abc123 --reply-to replies@example.com
3390
+
3391
+ # Reset reply-to to default
3392
+ emailr inboxes update inb_abc123 --reply-to none
3393
+
3309
3394
  # Get JSON output
3310
- emailr inboxes update inb_abc123 --name "New Name" --format json`).option("--name <name>","New inbox display name").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{e.name||(l("No update fields specified. Use --name to update the inbox name."),process.exit(1));let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name);let n=await r.inboxes.update(t,a);e.format==="json"?m(n,"json"):(p(`Inbox updated: ${n.id}`),m({ID:n.id,Name:n.name,"From Address":n.from_address,"Inbound Address":n.inbound_address,Updated:n.updated_at},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update inbox"),process.exit(1);}}),i.command("delete <inbox_id>").description(`Delete an inbox
3395
+ emailr inboxes update inb_abc123 --name "New Name" --format json`).option("--name <name>","New inbox display name").option("--reply-to <email>",'New reply-to address (use "none" to reset to default)').option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{!e.name&&!e.replyTo&&(l("No update fields specified. Use --name or --reply-to to update the inbox."),process.exit(1));let o=d(),n=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),t={};e.name&&(t.name=e.name),e.replyTo&&(t.reply_to=e.replyTo==="none"?null:e.replyTo);let r=await n.inboxes.update(a,t);e.format==="json"?m(r,"json"):(p(`Inbox updated: ${r.id}`),m({ID:r.id,Name:r.name,"From Address":r.from_address,"Reply To":r.reply_to,"Inbound Address":r.inbound_address,Updated:r.updated_at},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update inbox"),process.exit(1);}}),i.command("delete <inbox_id>").description(`Delete an inbox
3311
3396
 
3312
3397
  USAGE
3313
3398
  emailr inboxes delete <inbox_id> [options]
@@ -3330,48 +3415,269 @@ EXAMPLES
3330
3415
  emailr inboxes delete inb_abc123 --format json
3331
3416
 
3332
3417
  WARNING
3333
- This action is permanent and cannot be undone.`).option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).inboxes.delete(t);e.format==="json"?m(a,"json"):p(`Inbox deleted: ${t}`);}catch(o){l(o instanceof Error?o.message:"Failed to delete inbox"),process.exit(1);}}),i}function $e(){let i=new Command("threads").description(`Manage email threads and labels
3418
+ This action is permanent and cannot be undone.`).option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).inboxes.delete(a);e.format==="json"?m(t,"json"):p(`Inbox deleted: ${a}`);}catch(o){l(o instanceof Error?o.message:"Failed to delete inbox"),process.exit(1);}}),i}function Je(){let i=new Command("threads").description(`Manage email threads, labels, replies, and drafts
3334
3419
 
3335
3420
  USAGE
3336
3421
  emailr threads <subcommand> [options]
3337
3422
 
3338
3423
  DESCRIPTION
3339
- List, view, and manage email threads. Threads group related emails
3340
- into conversations. Use labels to organize threads (inbox, starred,
3341
- archived, spam, trash, etc).
3424
+ List, view, reply to, and manage email threads. Threads group related
3425
+ emails into conversations. Use labels to organize threads.
3342
3426
 
3343
3427
  SUBCOMMANDS
3344
3428
  list List threads filtered by label
3345
3429
  get <id> View a thread with all messages
3346
3430
  label <id> Add or remove labels from a thread
3431
+ reply <id> Reply to a thread (auto-resolves from/to/reply-to)
3432
+ draft save <id> Save a draft reply for a thread
3433
+ draft get <id> Get the saved draft for a thread
3434
+ draft delete <id> Delete the draft for a thread
3347
3435
 
3348
3436
  EXAMPLES
3437
+ # Reply to a thread (from/to/subject auto-resolved from inbox)
3438
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>"
3439
+
3440
+ # Reply with explicit CC
3441
+ emailr threads reply <thread-id> --html "<p>Done</p>" --cc "boss@co.com"
3442
+
3443
+ # Save a draft
3444
+ emailr threads draft save <thread-id> --html "<p>WIP reply</p>"
3445
+
3349
3446
  # List inbox threads
3350
3447
  emailr threads list
3351
3448
 
3352
- # List sent threads
3353
- emailr threads list --label sent
3449
+ # Star a thread
3450
+ emailr threads label <thread-id> --add starred`);i.command("list").description("List threads filtered by label").option("--label <label>","Filter by label (inbox, sent, starred, spam, trash, archived, all)","inbox").option("--limit <count>","Number of threads to return","20").option("--page <number>","Page number","1").option("--search <query>","Search by subject or sender").option("--inbox-id <id>","Filter by inbox ID").option("--format <format>","Output format (json|table)","table").action(async e=>{try{let o=d(),t=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).threads.list({label:e.label,page:parseInt(e.page),limit:Math.min(parseInt(e.limit),100),search:e.search,inbox_id:e.inboxId});if(e.format==="json")m(t,"json");else {if(!t.data||t.data.length===0){u(`No threads in ${e.label}`);return}let s=t.data.map(c=>({"Thread ID":c.thread_id.slice(0,8)+"\u2026",Subject:le(c.subject||"(no subject)",40),From:c.from_email,Messages:c.message_count,Labels:(c.labels||[]).join(", "),Updated:$e(c.updated_at)}));m(s,"table"),u(`Page ${t.pagination.page} of ${t.pagination.pages} (${t.pagination.total} total)`);}}catch(o){l(o instanceof Error?o.message:"Failed to list threads"),process.exit(1);}}),i.command("get <id>").description("View a thread with all messages").option("--format <format>","Output format (json|table)","table").action(async(e,o)=>{try{let n=d(),r=await new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}).threads.get(e);if(o.format==="json")m(r,"json");else {u(`Thread: ${r.subject||"(no subject)"}`),u(`Labels: ${r.labels.join(", ")||"none"}`),u(`Messages: ${r.messages.length}`),console.log("");for(let c of r.messages){let b=c.status==="received"?"\u2190 IN ":"\u2192 OUT";console.log(`${b} ${$e(c.created_at)}`),console.log(` From: ${c.from_email}`),console.log(` To: ${c.to_email}`),c.cc_emails?.length&&console.log(` Cc: ${c.cc_emails.join(", ")}`),console.log(` Labels: ${c.labels.join(", ")||"none"}`);let g=c.text_content||c.html_content?.replace(/<[^>]*>/g,"")||"";g&&console.log(` ${le(g.trim(),120)}`),console.log("");}}await new Promise(c=>process.stdout.write("",()=>c())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to get thread"),process.exit(1);}}),i.command("label <id>").description("Add or remove labels from a thread").option("--add <labels>","Labels to add (comma-separated)").option("--remove <labels>","Labels to remove (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(e,o)=>{try{!o.add&&!o.remove&&(l("Specify --add and/or --remove labels"),process.exit(1));let n=d(),t=new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}),r=o.add?o.add.split(",").map(g=>g.trim()):void 0,s=o.remove?o.remove.split(",").map(g=>g.trim()):void 0,c=await t.threads.updateLabels(e,{add:r,remove:s});o.format==="json"?m(c,"json"):(p(`Updated ${c.updated} email(s) in thread`),r&&u(`Added: ${r.join(", ")}`),s&&u(`Removed: ${s.join(", ")}`)),await new Promise(g=>process.stdout.write("",()=>g())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to update labels"),process.exit(1);}}),i.command("reply <id>").description("Reply to a thread (from/to/subject auto-resolved from inbox)").option("--html <content>","HTML content for the reply").option("--text <content>","Plain text content for the reply").option("--to <email>","Override recipient (auto-resolved if omitted)").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--from <email>","Override sender (auto-resolved from inbox if omitted)").option("--from-name <name>","Override sender name (auto-resolved from inbox if omitted)").option("--reply-to <email>","Override Reply-To (auto-resolved from inbox if omitted)").option("--format <format>","Output format (json|table)","table").action(async(e,o)=>{try{!o.html&&!o.text&&(l("Provide --html or --text for the reply body"),process.exit(1));let n=d(),t=new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}),r={};o.html&&(r.html=o.html),o.text&&(r.text=o.text),o.to&&(r.to=o.to),o.cc&&(r.cc=o.cc.split(",").map(b=>b.trim())),o.bcc&&(r.bcc=o.bcc.split(",").map(b=>b.trim())),o.from&&(r.from=o.from),o.fromName&&(r.from_name=o.fromName),o.replyTo&&(r.reply_to_email=o.replyTo);let s=await t.threads.reply(e,r);o.format==="json"?m(s,"json"):(p(`Reply sent to ${s.recipients} recipient(s)`),u(`Message ID: ${s.message_id}`)),await new Promise(b=>process.stdout.write("",()=>b())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to send reply"),process.exit(1);}});let a=i.command("draft").description("Manage draft replies for threads");return a.command("save <id>").description("Save a draft reply for a thread").option("--html <content>","HTML content").option("--text <content>","Plain text content").option("--to <email>","Recipient").option("--cc <emails>","CC recipients").option("--bcc <emails>","BCC recipients").option("--from <email>","Sender").option("--reply-to <email>","Reply-To address").option("--format <format>","Output format (json|table)","table").action(async(e,o)=>{try{let n=d(),t=new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}),r={};o.html&&(r.html=o.html),o.text&&(r.text=o.text),o.to&&(r.to=o.to),o.cc&&(r.cc=o.cc),o.bcc&&(r.bcc=o.bcc),o.from&&(r.from=o.from),o.replyTo&&(r.reply_to_email=o.replyTo);let s=await t.threads.saveDraft(e,r);o.format==="json"?m(s,"json"):(p(`Draft saved for thread ${e}`),u(`Draft ID: ${s.id}`)),await new Promise(b=>process.stdout.write("",()=>b())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to save draft"),process.exit(1);}}),a.command("get <id>").description("Get the saved draft for a thread").option("--format <format>","Output format (json|table)","table").action(async(e,o)=>{try{let n=d(),r=await new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}).threads.getDraft(e);o.format==="json"?m(r,"json"):(u(`Draft for thread: ${e}`),u(`To: ${r.to_email||"(auto)"}`),u(`From: ${r.from_email||"(auto)"}`),u(`Subject: ${r.subject||"(auto)"}`),r.html_content&&console.log(`
3451
+ Content:
3452
+ ${le(r.html_content.replace(/<[^>]*>/g,""),200)}`)),await new Promise(c=>process.stdout.write("",()=>c())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"No draft found"),process.exit(1);}}),a.command("delete <id>").description("Delete the draft for a thread").option("--format <format>","Output format (json|table)","table").action(async(e,o)=>{try{let n=d();await new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}).threads.deleteDraft(e),o.format==="json"?m({success:!0},"json"):p(`Draft deleted for thread ${e}`),await new Promise(s=>process.stdout.write("",()=>s())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to delete draft"),process.exit(1);}}),i}function le(i,a){return i.length<=a?i:i.slice(0,a-1)+"\u2026"}function $e(i){return new Date(i).toLocaleString()}var Ft=`# Emailr CLI \u2014 Agent Skill Guide
3354
3453
 
3355
- # List starred threads for a specific inbox
3356
- emailr threads list --label starred --inbox-id <uuid>
3454
+ ## Setup
3357
3455
 
3358
- # Search threads
3359
- emailr threads list --search "invoice"
3456
+ \`\`\`bash
3457
+ emailr config set api-key <your_api_key>
3458
+ emailr config list # verify
3459
+ \`\`\`
3360
3460
 
3361
- # View a thread
3362
- emailr threads get <thread-id>
3461
+ Or use environment variables: EMAILR_API_KEY, EMAILR_BASE_URL.
3363
3462
 
3364
- # Star a thread
3365
- emailr threads label <thread-id> --add starred
3463
+ ## Key Principle
3464
+
3465
+ Always use \`--format json\` for machine-readable output. Pipe through \`jq\` for field extraction.
3466
+
3467
+ ---
3468
+
3469
+ ## Commands Reference
3470
+
3471
+ ### send \u2014 Send transactional emails
3472
+ \`\`\`bash
3473
+ emailr send --to user@example.com --subject "Hello" --html "<p>Hi!</p>"
3474
+ emailr send --to user@example.com --template tpl_abc --template-data '{"name":"Jo"}'
3475
+ emailr send --to user@example.com --html-file ./email.html --text-file ./email.txt
3476
+ emailr send --to "a@x.com,b@x.com" --subject "Team" --html "<p>Update</p>" --cc "c@x.com"
3477
+ emailr send --to user@example.com --subject "Later" --html "<p>Hi</p>" --schedule "2025-12-25T10:00:00Z"
3478
+ \`\`\`
3479
+ Options: --to, --from, --subject, --html, --text, --html-file, --text-file, --template, --template-data, --cc, --bcc, --reply-to, --schedule, --format
3480
+
3481
+ ### threads \u2014 Manage email threads (conversations)
3482
+ \`\`\`bash
3483
+ emailr threads list # list inbox threads
3484
+ emailr threads list --label sent --inbox-id <id> # filter by label/inbox
3485
+ emailr threads get <thread-id> # view thread with all messages
3486
+ emailr threads get <thread-id> --format json # machine-readable
3487
+ emailr threads label <thread-id> --add starred # add labels
3488
+ emailr threads label <thread-id> --remove spam,trash # remove labels
3489
+ \`\`\`
3490
+ Labels: inbox, sent, starred, spam, trash, archived, all
3491
+
3492
+ #### Thread Reply (KEY FEATURE for agents)
3493
+ \`\`\`bash
3494
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>"
3495
+ emailr threads reply <thread-id> --html "<p>Done</p>" --cc "boss@co.com"
3496
+ \`\`\`
3497
+ **Auto-resolution**: The API automatically resolves from, from_name, reply_to, to, and subject from the thread's inbox. You only need to provide the thread ID and HTML content. Override with --from, --from-name, --reply-to, --to if needed.
3498
+
3499
+ #### Thread Drafts
3500
+ \`\`\`bash
3501
+ emailr threads draft save <thread-id> --html "<p>WIP</p>"
3502
+ emailr threads draft get <thread-id>
3503
+ emailr threads draft delete <thread-id>
3504
+ \`\`\`
3505
+
3506
+ ### inbox \u2014 View and manage inbound emails
3507
+ \`\`\`bash
3508
+ emailr inbox list # list received emails
3509
+ emailr inbox list --email sender@x.com --inbox-id <id> # filter
3510
+ emailr inbox get <email-id> # view email details
3511
+ emailr inbox get <email-id> --content html # view HTML content
3512
+ emailr inbox thread <email-id> # view conversation
3513
+ emailr inbox reply <email-id> --html "<p>Reply</p>" # reply to email
3514
+ emailr inbox forward <email-id> --to other@x.com # forward email
3515
+ \`\`\`
3516
+
3517
+ ### inboxes \u2014 Manage email inboxes
3518
+ \`\`\`bash
3519
+ emailr inboxes list
3520
+ emailr inboxes create --name "Support" --username support --domain example.com
3521
+ emailr inboxes get <inbox-id>
3522
+ emailr inboxes update <inbox-id> --name "New Name"
3523
+ emailr inboxes delete <inbox-id>
3524
+ \`\`\`
3525
+
3526
+ ### contacts \u2014 Manage email contacts
3527
+ \`\`\`bash
3528
+ emailr contacts list --format json
3529
+ emailr contacts list --subscribed --limit 50
3530
+ emailr contacts get <contact-id>
3531
+ emailr contacts create --email user@x.com --first-name "Jo" --metadata '{"plan":"pro"}'
3532
+ emailr contacts update <contact-id> --tags "vip,active"
3533
+ emailr contacts delete <contact-id>
3534
+ \`\`\`
3535
+
3536
+ ### templates \u2014 Create, edit, preview email templates
3537
+ \`\`\`bash
3538
+ emailr templates list --format json
3539
+ emailr templates get <id>
3540
+ emailr templates create --name "Welcome" --subject "Hi {{name}}" --html-file welcome.html
3541
+ emailr templates update <id> --html-file updated.html
3542
+ emailr templates delete <id>
3543
+ emailr templates fetch <id> --output template.html # download HTML
3544
+ emailr templates push-preview <id> --html-file t.html # upload preview for review
3545
+ emailr templates edit <id> --file ./t.html # live edit with hot-reload
3546
+ emailr templates draft --file ./new.html # draft new with live preview
3547
+ emailr templates stop-preview # stop preview server
3548
+ \`\`\`
3549
+
3550
+ ### segments \u2014 Manage contact segments
3551
+ \`\`\`bash
3552
+ emailr segments list
3553
+ emailr segments create --name "Active" --conditions '[{"field":"subscribed","operator":"equals","value":true}]'
3554
+ emailr segments get <id>
3555
+ emailr segments count <id> # count matching contacts
3556
+ emailr segments update <id> --conditions '[...]'
3557
+ emailr segments delete <id>
3558
+ \`\`\`
3559
+ Operators: equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, is_set, is_not_set
3560
+ Fields: email, first_name, last_name, subscribed, created_at, metadata.*
3561
+
3562
+ ### broadcasts \u2014 Send bulk email campaigns
3563
+ \`\`\`bash
3564
+ emailr broadcasts list --status draft
3565
+ emailr broadcasts create --name "Newsletter" --subject "Weekly" --from news@x.com --template tpl_abc --segment seg_xyz
3566
+ emailr broadcasts send <id> # send immediately
3567
+ emailr broadcasts schedule <id> --at "2025-12-25T10:00:00Z"
3568
+ emailr broadcasts cancel <id>
3569
+ emailr broadcasts get <id> # view delivery stats
3570
+ emailr broadcasts update <id> --subject "New Subject"
3571
+ emailr broadcasts delete <id>
3572
+ \`\`\`
3573
+
3574
+ ### webhooks \u2014 Configure event notifications
3575
+ \`\`\`bash
3576
+ emailr webhooks list
3577
+ emailr webhooks create --name "Tracker" --url "https://x.com/hook" --events "email.delivered,email.bounced"
3578
+ emailr webhooks get <id> # includes secret
3579
+ emailr webhooks update <id> --events "email.delivered"
3580
+ emailr webhooks enable <id>
3581
+ emailr webhooks disable <id>
3582
+ emailr webhooks delete <id>
3583
+ \`\`\`
3584
+ Events: email.sent, email.delivered, email.bounced, email.complained, email.opened, email.clicked, email.failed, email.delivery_delayed, contact.created, contact.updated, contact.deleted, domain.verified, domain.verification_failed
3585
+
3586
+ ### domains \u2014 Manage sending domains
3587
+ \`\`\`bash
3588
+ emailr domains add example.com
3589
+ emailr domains verify example.com
3590
+ emailr domains list
3591
+ \`\`\`
3592
+
3593
+ ### config \u2014 CLI configuration
3594
+ \`\`\`bash
3595
+ emailr config set api-key <key>
3596
+ emailr config set base-url https://custom.api.example.com
3597
+ emailr config list
3598
+ emailr config get api-key
3599
+ emailr config path
3600
+ emailr config init --api-key <key>
3601
+ \`\`\`
3602
+
3603
+ ### login \u2014 Browser-based authentication
3604
+ \`\`\`bash
3605
+ emailr login
3606
+ \`\`\`
3607
+
3608
+ ---
3609
+
3610
+ ## Agent Workflows
3611
+
3612
+ ### 1. Reply to a customer email (simplest)
3613
+ \`\`\`bash
3614
+ # Everything auto-resolved from thread's inbox
3615
+ emailr threads reply <thread-id> --html "<p>Thanks for reaching out!</p>" --format json
3616
+ \`\`\`
3617
+
3618
+ ### 2. Send a transactional email
3619
+ \`\`\`bash
3620
+ emailr send --to customer@x.com --subject "Order Confirmed" \\
3621
+ --html "<h1>Order #123</h1><p>Your order is confirmed.</p>" --format json
3622
+ \`\`\`
3623
+
3624
+ ### 3. Template editing workflow
3625
+ \`\`\`bash
3626
+ emailr templates fetch <id> --output template.html
3627
+ # edit template.html
3628
+ emailr templates push-preview <id> --html-file template.html
3629
+ # review preview URL, iterate
3630
+ emailr templates update <id> --html-file template.html
3631
+ \`\`\`
3632
+
3633
+ ### 4. Broadcast campaign
3634
+ \`\`\`bash
3635
+ emailr segments create --name "Active" --conditions '[{"field":"subscribed","operator":"equals","value":true}]' --format json
3636
+ # capture segment ID
3637
+ emailr broadcasts create --name "Campaign" --subject "News" --from news@x.com --template tpl_abc --segment seg_xyz --format json
3638
+ # capture broadcast ID
3639
+ emailr broadcasts send <broadcast-id> --format json
3640
+ \`\`\`
3641
+
3642
+ ### 5. Monitor inbox and respond
3643
+ \`\`\`bash
3644
+ emailr threads list --format json # get threads
3645
+ emailr threads get <thread-id> --format json # read conversation
3646
+ emailr threads reply <thread-id> --html "<p>Reply</p>" --format json
3647
+ \`\`\`
3366
3648
 
3367
- # Archive a thread (remove from inbox)
3368
- emailr threads label <thread-id> --add archived --remove inbox
3649
+ ---
3369
3650
 
3370
- # Move to trash
3371
- emailr threads label <thread-id> --add trash --remove inbox
3651
+ ## Tips
3372
3652
 
3373
- # Mark as spam
3374
- emailr threads label <thread-id> --add spam --remove inbox`);return i.command("list").description("List threads filtered by label").option("--label <label>","Filter by label (inbox, sent, starred, spam, trash, archived, all)","inbox").option("--limit <count>","Number of threads to return","20").option("--page <number>","Page number","1").option("--search <query>","Search by subject or sender").option("--inbox-id <id>","Filter by inbox ID").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).threads.list({label:t.label,page:parseInt(t.page),limit:Math.min(parseInt(t.limit),100),search:t.search,inbox_id:t.inboxId});if(t.format==="json")m(r,"json");else {if(!r.data||r.data.length===0){u(`No threads in ${t.label}`);return}let n=r.data.map(s=>({"Thread ID":s.thread_id.slice(0,8)+"\u2026",Subject:He(s.subject||"(no subject)",40),From:s.from_email,Messages:s.message_count,Labels:(s.labels||[]).join(", "),Updated:Je(s.updated_at)}));m(n,"table"),u(`Page ${r.pagination.page} of ${r.pagination.pages} (${r.pagination.total} total)`);}}catch(e){l(e instanceof Error?e.message:"Failed to list threads"),process.exit(1);}}),i.command("get <id>").description("View a thread with all messages").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).threads.get(t);if(e.format==="json")m(a,"json");else {u(`Thread: ${a.subject||"(no subject)"}`),u(`Labels: ${a.labels.join(", ")||"none"}`),u(`Messages: ${a.messages.length}`),console.log("");for(let s of a.messages){let d=s.status==="received"?"\u2190 IN ":"\u2192 OUT";console.log(`${d} ${Je(s.created_at)}`),console.log(` From: ${s.from_email}`),console.log(` To: ${s.to_email}`),s.cc_emails?.length&&console.log(` Cc: ${s.cc_emails.join(", ")}`),console.log(` Labels: ${s.labels.join(", ")||"none"}`);let b=s.text_content||s.html_content?.replace(/<[^>]*>/g,"")||"";b&&console.log(` ${He(b.trim(),120)}`),console.log("");}}await new Promise(s=>process.stdout.write("",()=>s())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to get thread"),process.exit(1);}}),i.command("label <id>").description("Add or remove labels from a thread").option("--add <labels>","Labels to add (comma-separated)").option("--remove <labels>","Labels to remove (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{!e.add&&!e.remove&&(l("Specify --add and/or --remove labels"),process.exit(1));let o=c(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=e.add?e.add.split(",").map(b=>b.trim()):void 0,n=e.remove?e.remove.split(",").map(b=>b.trim()):void 0,s=await r.threads.updateLabels(t,{add:a,remove:n});e.format==="json"?m(s,"json"):(p(`Updated ${s.updated} email(s) in thread`),a&&u(`Added: ${a.join(", ")}`),n&&u(`Removed: ${n.join(", ")}`)),await new Promise(b=>process.stdout.write("",()=>b())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to update labels"),process.exit(1);}}),i}function He(i,t){return i.length<=t?i:i.slice(0,t-1)+"\u2026"}function Je(i){return new Date(i).toLocaleString()}var y=new Command;y.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
3653
+ - All list/get commands support \`--format json\` for structured output
3654
+ - Thread reply auto-resolves from/to/subject \u2014 just provide thread ID + HTML
3655
+ - Use \`--format json | jq '.field'\` to extract specific fields
3656
+ - Drafts auto-save in the UI; use \`emailr threads draft save\` from CLI
3657
+ - Labels: inbox, sent, starred, spam, trash, archived
3658
+ - Broadcasts need a segment (audience) and template or inline HTML
3659
+ `;function Be(){return new Command("skill").description(`Output the Emailr CLI skill guide for AI agents
3660
+
3661
+ USAGE
3662
+ emailr skill
3663
+
3664
+ DESCRIPTION
3665
+ Prints a comprehensive markdown guide to stdout that teaches AI agents
3666
+ how to use all Emailr CLI commands effectively. This includes every
3667
+ command, key options, common workflows, and tips for machine-readable
3668
+ output.
3669
+
3670
+ Pipe to a file or feed directly to an AI agent's context.
3671
+
3672
+ EXAMPLES
3673
+ # Print skill to stdout
3674
+ emailr skill
3675
+
3676
+ # Save to file
3677
+ emailr skill > emailr-skill.md
3678
+
3679
+ # Feed to an AI agent context
3680
+ emailr skill | pbcopy`).action(()=>{process.stdout.write(Ft);})}var y=new Command;y.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
3375
3681
 
3376
3682
  USAGE
3377
3683
  emailr <command> [subcommand] [options]
@@ -3385,7 +3691,7 @@ COMMANDS
3385
3691
  send Send individual emails with HTML/text content or templates
3386
3692
  inbox View and manage received (inbound) emails
3387
3693
  inboxes Create and manage email inboxes
3388
- threads List, view, and manage email threads and labels
3694
+ threads List, view, reply to, and manage email threads and drafts
3389
3695
  templates Create, edit, preview, and publish email templates
3390
3696
  contacts Manage email contacts and their metadata
3391
3697
  segments Create and manage contact segments with conditions
@@ -3395,6 +3701,7 @@ COMMANDS
3395
3701
  config Configure CLI settings (API key, base URL)
3396
3702
  login Authenticate with your Emailr account
3397
3703
  agent Run the AI agent for automated email tasks
3704
+ skill Output the CLI skill guide for AI agents
3398
3705
 
3399
3706
  GETTING STARTED
3400
3707
  1. Set your API key:
@@ -3438,6 +3745,12 @@ EXAMPLES
3438
3745
  # Forward a received email
3439
3746
  emailr inbox forward <email-id> --to colleague@example.com
3440
3747
 
3748
+ # Reply to a thread (from/to/subject auto-resolved from inbox)
3749
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>"
3750
+
3751
+ # Save a draft reply
3752
+ emailr threads draft save <thread-id> --html "<p>WIP</p>"
3753
+
3441
3754
  # List all templates
3442
3755
  emailr templates list
3443
3756
 
@@ -3464,8 +3777,12 @@ EXAMPLES
3464
3777
  emailr contacts list --format json | jq '.[].email'
3465
3778
 
3466
3779
  AGENTIC WORKFLOW
3467
- For collaborating with AI agents on email templates:
3780
+ For AI agents: run 'emailr skill' to get a comprehensive guide.
3781
+
3782
+ Quick thread reply (from/to/subject auto-resolved):
3783
+ emailr threads reply <thread-id> --html "<p>Reply content</p>" --format json
3468
3784
 
3785
+ Template editing with AI agents:
3469
3786
  1. Fetch template: emailr templates fetch <id> --output template.html
3470
3787
  2. Edit locally or with AI assistance
3471
3788
  3. Push preview: emailr templates push-preview <id> --html-file template.html
@@ -3475,4 +3792,4 @@ AGENTIC WORKFLOW
3475
3792
 
3476
3793
  MORE INFORMATION
3477
3794
  Run 'emailr <command> --help' for detailed help on any command.
3478
- Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.9.0");y.addCommand(ue());y.addCommand(Ke());y.addCommand(qe());y.addCommand($e());y.addCommand(fe());y.addCommand(je());y.addCommand(Ie());y.addCommand(Ce());y.addCommand(Ue());y.addCommand(Pe());y.addCommand(Ne());y.addCommand(Me());y.addCommand(Le());y.parse();
3795
+ Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.10.0");y.addCommand(fe());y.addCommand(He());y.addCommand(qe());y.addCommand(Je());y.addCommand(be());y.addCommand(Ie());y.addCommand(Ne());y.addCommand(Ue());y.addCommand(ke());y.addCommand(Pe());y.addCommand(Ce());y.addCommand(Me());y.addCommand(Fe());y.addCommand(Be());y.parse();