@agenticmail/cli 0.8.13 → 0.8.15

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/cli.js +222 -37
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -17,13 +17,204 @@ import {
17
17
  import { createInterface, emitKeypressEvents } from "readline";
18
18
  import { readFileSync, writeFileSync, existsSync } from "fs";
19
19
  import { basename } from "path";
20
+
21
+ // src/ui/time-format.ts
22
+ function toDate(input) {
23
+ if (input == null) return null;
24
+ if (input instanceof Date) return Number.isNaN(input.getTime()) ? null : input;
25
+ if (typeof input === "number") {
26
+ const d = new Date(input);
27
+ return Number.isNaN(d.getTime()) ? null : d;
28
+ }
29
+ if (typeof input === "string") {
30
+ const trimmed = input.trim();
31
+ if (!trimmed) return null;
32
+ const d = new Date(trimmed);
33
+ return Number.isNaN(d.getTime()) ? null : d;
34
+ }
35
+ return null;
36
+ }
37
+ function sameLocalDay(a, b) {
38
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
39
+ }
40
+ function localDayDiff(a, b) {
41
+ const da = new Date(a.getFullYear(), a.getMonth(), a.getDate());
42
+ const db = new Date(b.getFullYear(), b.getMonth(), b.getDate());
43
+ return Math.round((da.getTime() - db.getTime()) / (24 * 60 * 60 * 1e3));
44
+ }
45
+ function relativeTime(input, now = /* @__PURE__ */ new Date()) {
46
+ const d = toDate(input);
47
+ if (!d) return "?";
48
+ const deltaMs = now.getTime() - d.getTime();
49
+ if (deltaMs < 0) {
50
+ if (-deltaMs < 3e4) return "just now";
51
+ }
52
+ const absMs = Math.abs(deltaMs);
53
+ const sec = absMs / 1e3;
54
+ const min = sec / 60;
55
+ const hr2 = min / 60;
56
+ if (deltaMs >= 0 && deltaMs < 45e3) return "just now";
57
+ if (deltaMs >= 0 && deltaMs < 9e4) return "a minute ago";
58
+ if (deltaMs >= 0 && min < 60) return `${Math.round(min)} minutes ago`;
59
+ if (deltaMs >= 0 && min < 90) return "an hour ago";
60
+ if (deltaMs >= 0 && sameLocalDay(d, now) && hr2 < 24) return `${Math.round(hr2)} hours ago`;
61
+ const days = localDayDiff(now, d);
62
+ if (days === 1) return "yesterday";
63
+ if (days > 1 && days < 7) {
64
+ return d.toLocaleDateString(void 0, { weekday: "long" });
65
+ }
66
+ if (d.getFullYear() === now.getFullYear()) {
67
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
68
+ }
69
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
70
+ }
71
+ function absoluteLocal(input, now = /* @__PURE__ */ new Date()) {
72
+ const d = toDate(input);
73
+ if (!d) return "?";
74
+ const sameYear = d.getFullYear() === now.getFullYear();
75
+ const opts = {
76
+ weekday: "short",
77
+ month: "short",
78
+ day: "numeric",
79
+ hour: "numeric",
80
+ minute: "2-digit"
81
+ };
82
+ if (!sameYear) opts.year = "numeric";
83
+ return d.toLocaleString(void 0, opts);
84
+ }
85
+ function formatEmailDate(input, now = /* @__PURE__ */ new Date()) {
86
+ const d = toDate(input);
87
+ if (!d) return "?";
88
+ const rel = relativeTime(d, now);
89
+ const abs = absoluteLocal(d, now);
90
+ return `${rel} \u2014 ${abs}`;
91
+ }
92
+
93
+ // src/ui/email-card.ts
94
+ var ESC = "\x1B[";
95
+ var RESET = `${ESC}0m`;
96
+ var ansi = {
97
+ reset: RESET,
98
+ bold: (s) => `${ESC}1m${s}${RESET}`,
99
+ dim: (s) => `${ESC}90m${s}${RESET}`,
100
+ cyan: (s) => `${ESC}36m${s}${RESET}`,
101
+ magenta: (s) => `${ESC}35m${s}${RESET}`,
102
+ yellow: (s) => `${ESC}33m${s}${RESET}`,
103
+ green: (s) => `${ESC}32m${s}${RESET}`,
104
+ red: (s) => `${ESC}31m${s}${RESET}`,
105
+ // The brand pink we use for the project logo. `38;5;205` is xterm-256
106
+ // hot pink — close to the bow in the logo and readable on both dark
107
+ // and light terminals.
108
+ pink: (s) => `${ESC}38;5;205m${s}${RESET}`
109
+ };
110
+ function formatAddress(a) {
111
+ if (!a) return "";
112
+ if (a.name && a.address) return `${a.name} <${a.address}>`;
113
+ return a.address ?? a.name ?? "";
114
+ }
115
+ function formatAddressList(addrs) {
116
+ if (!addrs || addrs.length === 0) return "";
117
+ return addrs.map(formatAddress).filter(Boolean).join(", ");
118
+ }
119
+ function rule(width) {
120
+ const w = Math.max(10, width);
121
+ return ansi.pink("\u2500".repeat(w));
122
+ }
123
+ function stripHtmlForTerminal(html) {
124
+ return html.replace(/<\/(p|div|br|li|h[1-6]|tr)[^>]*>/gi, "\n").replace(/<br\s*\/?\s*>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\n{3,}/g, "\n\n").trim();
125
+ }
126
+ function securityLines(msg) {
127
+ const s = msg.security;
128
+ if (!s) return [];
129
+ const lines = [];
130
+ if (s.isSpam) {
131
+ const cat = s.topCategory ? ` [${s.topCategory}]` : "";
132
+ const score = typeof s.spamScore === "number" ? ` score=${s.spamScore.toFixed(1)}` : "";
133
+ lines.push(` ${ansi.red("\u26A0 SPAM")} ${ansi.dim(cat + score)}`);
134
+ } else if (s.isWarning) {
135
+ const cat = s.topCategory ? ` [${s.topCategory}]` : "";
136
+ const score = typeof s.spamScore === "number" ? ` score=${s.spamScore.toFixed(1)}` : "";
137
+ lines.push(` ${ansi.yellow("\u26A0 Suspicious")} ${ansi.dim(cat + score)}`);
138
+ }
139
+ if (s.sanitized) {
140
+ lines.push(` ${ansi.yellow("\u26A0 Content sanitised")} ${ansi.dim("(invisible characters or hidden HTML were stripped)")}`);
141
+ }
142
+ return lines;
143
+ }
144
+ function renderEmailCard(msg, opts = {}) {
145
+ const width = opts.width && opts.width > 10 ? opts.width : 80;
146
+ const now = opts.now ?? /* @__PURE__ */ new Date();
147
+ const out = [];
148
+ out.push("");
149
+ out.push(rule(width));
150
+ out.push("");
151
+ out.push(` ${ansi.bold(ansi.pink(msg.subject ?? "(no subject)"))}`);
152
+ out.push("");
153
+ out.push(rule(width));
154
+ out.push("");
155
+ const fromStr = formatAddressList(msg.from) || "?";
156
+ const toStr = formatAddressList(msg.to) || "?";
157
+ out.push(` ${ansi.dim("From:")} ${ansi.cyan(fromStr)}`);
158
+ out.push(` ${ansi.dim("To:")} ${toStr}`);
159
+ const ccStr = formatAddressList(msg.cc);
160
+ if (ccStr) out.push(` ${ansi.dim("Cc:")} ${ccStr}`);
161
+ const bccStr = formatAddressList(msg.bcc);
162
+ if (bccStr) out.push(` ${ansi.dim("Bcc:")} ${bccStr}`);
163
+ if (msg.date != null) {
164
+ out.push(` ${ansi.dim("Date:")} ${ansi.magenta(formatEmailDate(msg.date, now))}`);
165
+ }
166
+ if (msg.uid != null) {
167
+ out.push(` ${ansi.dim("UID:")} ${msg.uid}`);
168
+ }
169
+ if (msg.inReplyTo) {
170
+ out.push(` ${ansi.dim("In reply to:")} ${ansi.dim(msg.inReplyTo)}`);
171
+ }
172
+ out.push("");
173
+ out.push(rule(width));
174
+ out.push("");
175
+ let body = msg.text ?? "";
176
+ if (!body && msg.html) body = stripHtmlForTerminal(msg.html);
177
+ if (body) {
178
+ for (const line of body.split("\n")) {
179
+ out.push(` ${line}`);
180
+ }
181
+ } else {
182
+ out.push(` ${ansi.dim("(no body content)")}`);
183
+ }
184
+ out.push("");
185
+ const attachments = msg.attachments ?? [];
186
+ const secLines = securityLines(msg);
187
+ if (attachments.length > 0 || secLines.length > 0) {
188
+ out.push(rule(width));
189
+ out.push("");
190
+ for (const att of attachments) {
191
+ const size = typeof att.size === "number" ? ` ${ansi.dim(`(${Math.round(att.size / 1024)}KB)`)}` : "";
192
+ const type = att.contentType ? ` ${ansi.dim(att.contentType)}` : "";
193
+ out.push(` ${ansi.yellow("\u{1F4CE}")} ${att.filename ?? "(unnamed)"}${type}${size}`);
194
+ }
195
+ if (attachments.length > 0 && secLines.length > 0) out.push("");
196
+ for (const line of secLines) out.push(line);
197
+ out.push("");
198
+ }
199
+ out.push(rule(width));
200
+ out.push("");
201
+ return out.join("\n");
202
+ }
203
+
204
+ // src/shell.ts
20
205
  var c = {
21
206
  green: (s) => `\x1B[32m${s}\x1B[0m`,
22
207
  red: (s) => `\x1B[31m${s}\x1B[0m`,
23
208
  yellow: (s) => `\x1B[33m${s}\x1B[0m`,
24
209
  cyan: (s) => `\x1B[36m${s}\x1B[0m`,
210
+ blue: (s) => `\x1B[34m${s}\x1B[0m`,
211
+ magenta: (s) => `\x1B[35m${s}\x1B[0m`,
25
212
  dim: (s) => `\x1B[90m${s}\x1B[0m`,
26
- bold: (s) => `\x1B[1m${s}\x1B[0m`
213
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
214
+ // Brand pink — xterm-256 hot-pink background (205) with white text (97).
215
+ // Mirrors the pinkBg helper in cli.ts; the shell uses it for the "🎀
216
+ // AgenticMail is running" welcome banner and any future brand marks.
217
+ pinkBg: (s) => `\x1B[48;5;205m\x1B[97m${s}\x1B[0m`
27
218
  };
28
219
  var dotColors = [
29
220
  (s) => `\x1B[35m${s}\x1B[0m`,
@@ -392,40 +583,7 @@ async function interactiveShell(options) {
392
583
  return null;
393
584
  }
394
585
  function renderEmailMessage(msg) {
395
- log("");
396
- log(hr());
397
- log("");
398
- log(` ${c.bold(msg.subject || "(no subject)")}`);
399
- log("");
400
- const fromStr = (msg.from || []).map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ") || "?";
401
- const toStr = (msg.to || []).map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ") || "?";
402
- log(` ${c.dim("From:")} ${c.cyan(fromStr)}`);
403
- log(` ${c.dim("To:")} ${toStr}`);
404
- if (msg.date) log(` ${c.dim("Date:")} ${new Date(msg.date).toLocaleString()}`);
405
- if (msg.cc && msg.cc.length > 0) {
406
- const ccStr = msg.cc.map((a) => a.address).join(", ");
407
- log(` ${c.dim("CC:")} ${ccStr}`);
408
- }
409
- log("");
410
- log(hr());
411
- log("");
412
- const body = msg.text || msg.html?.replace(/<[^>]*>/g, "") || "";
413
- if (body) {
414
- for (const line of body.split("\n")) {
415
- log(` ${line}`);
416
- }
417
- } else {
418
- info("(no body content)");
419
- }
420
- if (msg.attachments && msg.attachments.length > 0) {
421
- log("");
422
- log(` ${c.dim("Attachments:")}`);
423
- for (const att of msg.attachments) {
424
- const size = att.size ? ` ${c.dim(`(${Math.round(att.size / 1024)}KB)`)}` : "";
425
- log(` ${c.yellow("\u{1F4CE}")} ${att.filename}${size}`);
426
- }
427
- }
428
- log("");
586
+ process.stdout.write(renderEmailCard(msg, { width: tw() }));
429
587
  }
430
588
  async function showInboxPreview(apiKey, agentName) {
431
589
  try {
@@ -446,7 +604,7 @@ async function interactiveShell(options) {
446
604
  const msg = messages[i];
447
605
  const fromAddr = msg.from?.[0] || {};
448
606
  const from = fromAddr.name ? `${fromAddr.name} <${fromAddr.address}>` : fromAddr.address || msg.from || "?";
449
- const date = msg.date ? new Date(msg.date).toLocaleString() : "";
607
+ const date = msg.date ? formatEmailDate(msg.date) : "";
450
608
  const dot = dotColors[i % dotColors.length]("\u25CF");
451
609
  log(` ${dot} ${c.dim("#" + String(msg.uid).padEnd(5))} ${c.bold((msg.subject || "(no subject)").slice(0, 48))}`);
452
610
  log(` ${" ".repeat(8)} ${c.dim(from)} ${c.dim(date)}`);
@@ -1052,6 +1210,7 @@ async function interactiveShell(options) {
1052
1210
  nav.push(`${c.bold("[\u2191\u2193]")} select`);
1053
1211
  nav.push(`${c.bold("[Enter]")} read`);
1054
1212
  nav.push(`${c.bold("[v]")} ${inboxPreviews ? "hide" : "show"} previews`);
1213
+ nav.push(`${c.bold("[r]")} refresh`);
1055
1214
  nav.push(`${c.bold("[Esc]")} back`);
1056
1215
  log(` ${c.dim(pageLabel)} ${c.dim("\u2500")} ${nav.join(" ")}`);
1057
1216
  log("");
@@ -1130,6 +1289,32 @@ async function interactiveShell(options) {
1130
1289
  const pageData = await fetchPage(page * PAGE_SIZE);
1131
1290
  process.stdout.write("\x1B[2J\x1B[H");
1132
1291
  renderPage(pageData.messages || [], total, page);
1292
+ } else if (_s === "r" || _s === "R" || // bare letter — primary keybind, matches navigator convention
1293
+ key.ctrl && key.name === "r" || // Ctrl+R for browser-muscle-memory users
1294
+ key.name === "f5") {
1295
+ process.stdout.write("\x1B[2J\x1B[H");
1296
+ log(` ${c.dim("Refreshing inbox\u2026")}`);
1297
+ try {
1298
+ const pageData = await fetchPage(page * PAGE_SIZE);
1299
+ const fresh = pageData.messages || [];
1300
+ if (selected >= fresh.length) selected = Math.max(0, fresh.length - 1);
1301
+ const newTotal = pageData.total ?? fresh.length;
1302
+ const newTotalPages = Math.max(1, Math.ceil(newTotal / PAGE_SIZE));
1303
+ if (page >= newTotalPages) {
1304
+ page = newTotalPages - 1;
1305
+ selected = 0;
1306
+ const fallback = await fetchPage(page * PAGE_SIZE);
1307
+ process.stdout.write("\x1B[2J\x1B[H");
1308
+ renderPage(fallback.messages || [], newTotal, page);
1309
+ } else {
1310
+ process.stdout.write("\x1B[2J\x1B[H");
1311
+ renderPage(fresh, newTotal, page);
1312
+ }
1313
+ } catch (err) {
1314
+ process.stdout.write("\x1B[2J\x1B[H");
1315
+ renderPage(currentMessages, total, page);
1316
+ fail(`Refresh failed: ${errMsg(err)}`);
1317
+ }
1133
1318
  }
1134
1319
  } catch (err) {
1135
1320
  fail(`Error: ${errMsg(err)}`);
@@ -3801,7 +3986,7 @@ ${c.dim(boxChar.bl + boxChar.h.repeat(bWidth) + boxChar.br)}`);
3801
3986
  ok(`Verification code found: ${c.bold(c.green(data.code))}`);
3802
3987
  log(` ${c.dim("From:")} ${data.from}`);
3803
3988
  log(` ${c.dim("Message:")} ${data.body}`);
3804
- log(` ${c.dim("Received:")} ${new Date(data.receivedAt).toLocaleString()}`);
3989
+ log(` ${c.dim("Received:")} ${formatEmailDate(data.receivedAt)}`);
3805
3990
  } else {
3806
3991
  info("No verification codes found in the last 30 minutes.");
3807
3992
  info("Make sure Google Voice SMS forwarding is enabled and use /inbox to check for forwarded SMS emails.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/cli",
3
- "version": "0.8.13",
3
+ "version": "0.8.15",
4
4
  "description": "Email and SMS infrastructure for AI agents — the first platform to give agents real email addresses and phone numbers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",