@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.
- package/dist/cli.js +222 -37
- 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(/ /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`,
|
|
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
|
-
|
|
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 ?
|
|
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:")} ${
|
|
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