@agenticmail/cli 0.8.12 → 0.8.14

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 +215 -36
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -17,6 +17,191 @@ 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`,
@@ -392,40 +577,7 @@ async function interactiveShell(options) {
392
577
  return null;
393
578
  }
394
579
  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("");
580
+ process.stdout.write(renderEmailCard(msg, { width: tw() }));
429
581
  }
430
582
  async function showInboxPreview(apiKey, agentName) {
431
583
  try {
@@ -446,7 +598,7 @@ async function interactiveShell(options) {
446
598
  const msg = messages[i];
447
599
  const fromAddr = msg.from?.[0] || {};
448
600
  const from = fromAddr.name ? `${fromAddr.name} <${fromAddr.address}>` : fromAddr.address || msg.from || "?";
449
- const date = msg.date ? new Date(msg.date).toLocaleString() : "";
601
+ const date = msg.date ? formatEmailDate(msg.date) : "";
450
602
  const dot = dotColors[i % dotColors.length]("\u25CF");
451
603
  log(` ${dot} ${c.dim("#" + String(msg.uid).padEnd(5))} ${c.bold((msg.subject || "(no subject)").slice(0, 48))}`);
452
604
  log(` ${" ".repeat(8)} ${c.dim(from)} ${c.dim(date)}`);
@@ -1052,6 +1204,7 @@ async function interactiveShell(options) {
1052
1204
  nav.push(`${c.bold("[\u2191\u2193]")} select`);
1053
1205
  nav.push(`${c.bold("[Enter]")} read`);
1054
1206
  nav.push(`${c.bold("[v]")} ${inboxPreviews ? "hide" : "show"} previews`);
1207
+ nav.push(`${c.bold("[r]")} refresh`);
1055
1208
  nav.push(`${c.bold("[Esc]")} back`);
1056
1209
  log(` ${c.dim(pageLabel)} ${c.dim("\u2500")} ${nav.join(" ")}`);
1057
1210
  log("");
@@ -1130,6 +1283,32 @@ async function interactiveShell(options) {
1130
1283
  const pageData = await fetchPage(page * PAGE_SIZE);
1131
1284
  process.stdout.write("\x1B[2J\x1B[H");
1132
1285
  renderPage(pageData.messages || [], total, page);
1286
+ } else if (_s === "r" || _s === "R" || // bare letter — primary keybind, matches navigator convention
1287
+ key.ctrl && key.name === "r" || // Ctrl+R for browser-muscle-memory users
1288
+ key.name === "f5") {
1289
+ process.stdout.write("\x1B[2J\x1B[H");
1290
+ log(` ${c.dim("Refreshing inbox\u2026")}`);
1291
+ try {
1292
+ const pageData = await fetchPage(page * PAGE_SIZE);
1293
+ const fresh = pageData.messages || [];
1294
+ if (selected >= fresh.length) selected = Math.max(0, fresh.length - 1);
1295
+ const newTotal = pageData.total ?? fresh.length;
1296
+ const newTotalPages = Math.max(1, Math.ceil(newTotal / PAGE_SIZE));
1297
+ if (page >= newTotalPages) {
1298
+ page = newTotalPages - 1;
1299
+ selected = 0;
1300
+ const fallback = await fetchPage(page * PAGE_SIZE);
1301
+ process.stdout.write("\x1B[2J\x1B[H");
1302
+ renderPage(fallback.messages || [], newTotal, page);
1303
+ } else {
1304
+ process.stdout.write("\x1B[2J\x1B[H");
1305
+ renderPage(fresh, newTotal, page);
1306
+ }
1307
+ } catch (err) {
1308
+ process.stdout.write("\x1B[2J\x1B[H");
1309
+ renderPage(currentMessages, total, page);
1310
+ fail(`Refresh failed: ${errMsg(err)}`);
1311
+ }
1133
1312
  }
1134
1313
  } catch (err) {
1135
1314
  fail(`Error: ${errMsg(err)}`);
@@ -3801,7 +3980,7 @@ ${c.dim(boxChar.bl + boxChar.h.repeat(bWidth) + boxChar.br)}`);
3801
3980
  ok(`Verification code found: ${c.bold(c.green(data.code))}`);
3802
3981
  log(` ${c.dim("From:")} ${data.from}`);
3803
3982
  log(` ${c.dim("Message:")} ${data.body}`);
3804
- log(` ${c.dim("Received:")} ${new Date(data.receivedAt).toLocaleString()}`);
3983
+ log(` ${c.dim("Received:")} ${formatEmailDate(data.receivedAt)}`);
3805
3984
  } else {
3806
3985
  info("No verification codes found in the last 30 minutes.");
3807
3986
  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.12",
3
+ "version": "0.8.14",
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",