@agenticmail/cli 0.8.13 → 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.
- package/dist/cli.js +215 -36
- 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(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/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
|
-
|
|
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 ?
|
|
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:")} ${
|
|
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