@agenticmail/cli 0.8.14 → 0.8.16
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 +194 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -90,23 +90,198 @@ function formatEmailDate(input, now = /* @__PURE__ */ new Date()) {
|
|
|
90
90
|
return `${rel} \u2014 ${abs}`;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// src/ui/
|
|
93
|
+
// src/ui/markdown.ts
|
|
94
94
|
var ESC = "\x1B[";
|
|
95
95
|
var RESET = `${ESC}0m`;
|
|
96
|
+
var BOLD_OFF = `${ESC}22m`;
|
|
97
|
+
var ITALIC_OFF = `${ESC}23m`;
|
|
98
|
+
var STRIKE_OFF = `${ESC}29m`;
|
|
99
|
+
var FG_OFF = `${ESC}39m`;
|
|
100
|
+
var BG_OFF = `${ESC}49m`;
|
|
101
|
+
var md = {
|
|
102
|
+
bold: (s) => `${ESC}1m${s}${BOLD_OFF}`,
|
|
103
|
+
italic: (s) => `${ESC}3m${s}${ITALIC_OFF}`,
|
|
104
|
+
// Bold + italic combo for ***text*** — apply both, reset both targeted.
|
|
105
|
+
boldItalic: (s) => `${ESC}1m${ESC}3m${s}${ITALIC_OFF}${BOLD_OFF}`,
|
|
106
|
+
strike: (s) => `${ESC}9m${s}${STRIKE_OFF}`,
|
|
107
|
+
dim: (s) => `${ESC}2m${s}${ESC}22m`,
|
|
108
|
+
underline: (s) => `${ESC}4m${s}${ESC}24m`,
|
|
109
|
+
// Inline code: subtle dark background + orange foreground, surrounded
|
|
110
|
+
// by thin-space padding so it visually pops as a token.
|
|
111
|
+
code: (s) => `${ESC}48;5;236m${ESC}38;5;208m ${s} ${BG_OFF}${FG_OFF}`,
|
|
112
|
+
// Block code: cyan foreground, no background — keeps multi-line
|
|
113
|
+
// indentation legible and doesn't paint the whole screen on long
|
|
114
|
+
// snippets the way a background would.
|
|
115
|
+
blockCode: (s) => `${ESC}38;5;39m${s}${FG_OFF}`,
|
|
116
|
+
// Headings: brand pink, bold. We use the same `38;5;205` we use for
|
|
117
|
+
// the project logo so the whole shell stays on one accent color.
|
|
118
|
+
heading: (s) => `${ESC}1m${ESC}38;5;205m${s}${FG_OFF}${BOLD_OFF}`,
|
|
119
|
+
// Highlight: yellow background. Used for ==text== (GFM-ish).
|
|
120
|
+
highlight: (s) => `${ESC}48;5;226m${ESC}30m${s}${BG_OFF}${FG_OFF}`,
|
|
121
|
+
// Link URL: dim parens. The visible text stays at normal weight so
|
|
122
|
+
// the eye still reads it as link copy.
|
|
123
|
+
link: (text, url) => `${text} ${md.dim("(" + url + ")")}`
|
|
124
|
+
};
|
|
125
|
+
function decodeEntities(s) {
|
|
126
|
+
return s.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/…/g, "\u2026").replace(/—/g, "\u2014").replace(/–/g, "\u2013");
|
|
127
|
+
}
|
|
128
|
+
function renderInlineMarkdown(text) {
|
|
129
|
+
let masked = decodeEntities(text);
|
|
130
|
+
const codeSpans = [];
|
|
131
|
+
masked = masked.replace(/`([^`\n]+)`/g, (_match, code) => {
|
|
132
|
+
codeSpans.push(code);
|
|
133
|
+
return `\0CODE${codeSpans.length - 1}\0`;
|
|
134
|
+
});
|
|
135
|
+
masked = masked.replace(/\*\*\*([^*\n]+)\*\*\*/g, (_m, s) => md.boldItalic(s));
|
|
136
|
+
masked = masked.replace(/___([^_\n]+)___/g, (_m, s) => md.boldItalic(s));
|
|
137
|
+
masked = masked.replace(/\*\*([^*\n]+)\*\*/g, (_m, s) => md.bold(s));
|
|
138
|
+
masked = masked.replace(/__([^_\n]+)__/g, (_m, s) => md.bold(s));
|
|
139
|
+
masked = masked.replace(/(^|[^\w*])\*([^*\n]+)\*(?!\w)/g, (_m, lead, s) => `${lead}${md.italic(s)}`);
|
|
140
|
+
masked = masked.replace(/(^|[^\w_])_([^_\n]+)_(?!\w)/g, (_m, lead, s) => `${lead}${md.italic(s)}`);
|
|
141
|
+
masked = masked.replace(/~~([^~\n]+)~~/g, (_m, s) => md.strike(s));
|
|
142
|
+
masked = masked.replace(/==([^=\n]+)==/g, (_m, s) => md.highlight(s));
|
|
143
|
+
masked = masked.replace(/!\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g, (_m, alt, u) => {
|
|
144
|
+
const label = alt ? `\u{1F5BC} ${alt}` : "\u{1F5BC} image";
|
|
145
|
+
return `[${label}] ${md.dim("(" + u + ")")}`;
|
|
146
|
+
});
|
|
147
|
+
masked = masked.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_m, t, u) => md.link(t, u));
|
|
148
|
+
masked = masked.replace(/<(https?:\/\/[^>\s]+)>/g, (_m, u) => md.dim(u));
|
|
149
|
+
masked = masked.replace(/\u0000CODE(\d+)\u0000/g, (_m, idx) => md.code(codeSpans[Number(idx)] ?? ""));
|
|
150
|
+
return masked;
|
|
151
|
+
}
|
|
152
|
+
var TABLE_SEPARATOR_RE = /^\s*\|?(\s*:?-{3,}:?\s*\|)+(\s*:?-{3,}:?\s*)?\s*$/;
|
|
153
|
+
var TABLE_ROW_RE = /^\s*\|.*\|\s*$/;
|
|
154
|
+
function createMarkdownLineRenderer() {
|
|
155
|
+
let inCodeBlock = false;
|
|
156
|
+
let inIndentedCode = false;
|
|
157
|
+
return {
|
|
158
|
+
renderLine(line) {
|
|
159
|
+
const fence = line.match(/^\s*```([\w+-]*)\s*$/);
|
|
160
|
+
if (fence) {
|
|
161
|
+
inCodeBlock = !inCodeBlock;
|
|
162
|
+
return md.dim(fence[1] ? `\u25BE ${fence[1]}` : "\u25BE code");
|
|
163
|
+
}
|
|
164
|
+
if (inCodeBlock) {
|
|
165
|
+
return md.blockCode(line);
|
|
166
|
+
}
|
|
167
|
+
if (/^( {4,}|\t)/.test(line) && !/^(?:\s{4,}|\t)(?:[-*+]|\d+[.)])\s/.test(line)) {
|
|
168
|
+
inIndentedCode = true;
|
|
169
|
+
return md.blockCode(line.replace(/^( {4}|\t)/, ""));
|
|
170
|
+
}
|
|
171
|
+
if (inIndentedCode && line.trim() !== "") {
|
|
172
|
+
inIndentedCode = false;
|
|
173
|
+
}
|
|
174
|
+
if (TABLE_SEPARATOR_RE.test(line) && line.includes("|")) {
|
|
175
|
+
const rendered = line.replace(/-{3,}/g, (m) => "\u2500".repeat(m.length)).replace(/\|/g, md.dim("\u2502")).replace(/:/g, md.dim(":"));
|
|
176
|
+
return rendered;
|
|
177
|
+
}
|
|
178
|
+
if (TABLE_ROW_RE.test(line)) {
|
|
179
|
+
const cells = line.trim().slice(1, -1).split("|").map((c3) => renderInlineMarkdown(c3.trim()));
|
|
180
|
+
return `${md.dim("\u2502")} ${cells.join(` ${md.dim("\u2502")} `)} ${md.dim("\u2502")}`;
|
|
181
|
+
}
|
|
182
|
+
if (/^\s*(?:-{3,}|_{3,}|\*{3,})\s*$/.test(line)) {
|
|
183
|
+
return md.dim("\u2500".repeat(40));
|
|
184
|
+
}
|
|
185
|
+
const heading2 = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
|
|
186
|
+
if (heading2) {
|
|
187
|
+
const level = heading2[1].length;
|
|
188
|
+
const text = renderInlineMarkdown(heading2[2]);
|
|
189
|
+
if (level === 1) return md.heading(`\u258C ${text}`);
|
|
190
|
+
if (level === 2) return md.heading(`\u258C ${text}`);
|
|
191
|
+
return md.heading(text);
|
|
192
|
+
}
|
|
193
|
+
const task = line.match(/^(\s*)([-*+])\s+\[([ xX])\]\s+(.*)$/);
|
|
194
|
+
if (task) {
|
|
195
|
+
const indent = task[1];
|
|
196
|
+
const checked = task[3] !== " ";
|
|
197
|
+
const content = renderInlineMarkdown(task[4]);
|
|
198
|
+
const box = checked ? md.dim("\u2611") : "\u2610";
|
|
199
|
+
return checked ? `${indent}${box} ${md.dim(md.strike(content))}` : `${indent}${box} ${content}`;
|
|
200
|
+
}
|
|
201
|
+
const bullet = line.match(/^(\s*)([-*+])\s+(.*)$/);
|
|
202
|
+
if (bullet) {
|
|
203
|
+
const indent = bullet[1];
|
|
204
|
+
const content = renderInlineMarkdown(bullet[3]);
|
|
205
|
+
return `${indent}${md.dim("\u2022")} ${content}`;
|
|
206
|
+
}
|
|
207
|
+
const numbered = line.match(/^(\s*)(\d+)[.)]\s+(.*)$/);
|
|
208
|
+
if (numbered) {
|
|
209
|
+
const indent = numbered[1];
|
|
210
|
+
const num = numbered[2];
|
|
211
|
+
const content = renderInlineMarkdown(numbered[3]);
|
|
212
|
+
return `${indent}${md.dim(num + ".")} ${content}`;
|
|
213
|
+
}
|
|
214
|
+
return renderInlineMarkdown(line);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/ui/email-card.ts
|
|
220
|
+
var ESC2 = "\x1B[";
|
|
221
|
+
var RESET2 = `${ESC2}0m`;
|
|
96
222
|
var ansi = {
|
|
97
|
-
reset:
|
|
98
|
-
bold: (s) => `${
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
223
|
+
reset: RESET2,
|
|
224
|
+
bold: (s) => `${ESC2}1m${s}${RESET2}`,
|
|
225
|
+
italic: (s) => `${ESC2}3m${s}${RESET2}`,
|
|
226
|
+
dim: (s) => `${ESC2}90m${s}${RESET2}`,
|
|
227
|
+
cyan: (s) => `${ESC2}36m${s}${RESET2}`,
|
|
228
|
+
magenta: (s) => `${ESC2}35m${s}${RESET2}`,
|
|
229
|
+
yellow: (s) => `${ESC2}33m${s}${RESET2}`,
|
|
230
|
+
green: (s) => `${ESC2}32m${s}${RESET2}`,
|
|
231
|
+
red: (s) => `${ESC2}31m${s}${RESET2}`,
|
|
105
232
|
// The brand pink we use for the project logo. `38;5;205` is xterm-256
|
|
106
233
|
// hot pink — close to the bow in the logo and readable on both dark
|
|
107
234
|
// and light terminals.
|
|
108
|
-
pink: (s) => `${
|
|
235
|
+
pink: (s) => `${ESC2}38;5;205m${s}${RESET2}`
|
|
109
236
|
};
|
|
237
|
+
var ON_WROTE_RE = /^((?:>\s*)*)On (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z), (.+?) wrote:$/;
|
|
238
|
+
function splitQuotePrefix(line) {
|
|
239
|
+
let i = 0;
|
|
240
|
+
let depth = 0;
|
|
241
|
+
while (i < line.length) {
|
|
242
|
+
if (line[i] === ">") {
|
|
243
|
+
depth++;
|
|
244
|
+
i++;
|
|
245
|
+
while (i < line.length && line[i] === " ") i++;
|
|
246
|
+
} else {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return { depth, content: line.slice(i) };
|
|
251
|
+
}
|
|
252
|
+
var QUOTE_COLORS = [
|
|
253
|
+
ansi.cyan,
|
|
254
|
+
ansi.magenta,
|
|
255
|
+
ansi.yellow
|
|
256
|
+
];
|
|
257
|
+
function renderQuoteStripe(depth) {
|
|
258
|
+
if (depth <= 0) return "";
|
|
259
|
+
const bars = [];
|
|
260
|
+
for (let i = 0; i < depth; i++) {
|
|
261
|
+
const color = QUOTE_COLORS[i] ?? ansi.dim;
|
|
262
|
+
bars.push(color("\u258E"));
|
|
263
|
+
}
|
|
264
|
+
return bars.join("") + " ";
|
|
265
|
+
}
|
|
266
|
+
function renderBodyLine(line, now, mdRenderer) {
|
|
267
|
+
if (line.trim() === "") return line;
|
|
268
|
+
const m = line.match(ON_WROTE_RE);
|
|
269
|
+
if (m) {
|
|
270
|
+
const prefixRaw = m[1] ?? "";
|
|
271
|
+
const iso = m[2];
|
|
272
|
+
const sender = m[3];
|
|
273
|
+
const parsed = new Date(iso);
|
|
274
|
+
const dateStr = Number.isNaN(parsed.getTime()) ? iso : formatEmailDate(parsed, now);
|
|
275
|
+
const depth2 = (prefixRaw.match(/>/g) ?? []).length;
|
|
276
|
+
const stripe = renderQuoteStripe(depth2);
|
|
277
|
+
const inner = `On ${dateStr}, ${sender} wrote:`;
|
|
278
|
+
return `${stripe}${ansi.dim(ansi.italic(inner))}`;
|
|
279
|
+
}
|
|
280
|
+
const { depth, content } = splitQuotePrefix(line);
|
|
281
|
+
const rendered = mdRenderer.renderLine(content);
|
|
282
|
+
if (depth === 0) return rendered;
|
|
283
|
+
return `${renderQuoteStripe(depth)}${rendered}`;
|
|
284
|
+
}
|
|
110
285
|
function formatAddress(a) {
|
|
111
286
|
if (!a) return "";
|
|
112
287
|
if (a.name && a.address) return `${a.name} <${a.address}>`;
|
|
@@ -175,8 +350,9 @@ function renderEmailCard(msg, opts = {}) {
|
|
|
175
350
|
let body = msg.text ?? "";
|
|
176
351
|
if (!body && msg.html) body = stripHtmlForTerminal(msg.html);
|
|
177
352
|
if (body) {
|
|
353
|
+
const mdRenderer = createMarkdownLineRenderer();
|
|
178
354
|
for (const line of body.split("\n")) {
|
|
179
|
-
out.push(` ${line}`);
|
|
355
|
+
out.push(` ${renderBodyLine(line, now, mdRenderer)}`);
|
|
180
356
|
}
|
|
181
357
|
} else {
|
|
182
358
|
out.push(` ${ansi.dim("(no body content)")}`);
|
|
@@ -207,8 +383,14 @@ var c = {
|
|
|
207
383
|
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
208
384
|
yellow: (s) => `\x1B[33m${s}\x1B[0m`,
|
|
209
385
|
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
386
|
+
blue: (s) => `\x1B[34m${s}\x1B[0m`,
|
|
387
|
+
magenta: (s) => `\x1B[35m${s}\x1B[0m`,
|
|
210
388
|
dim: (s) => `\x1B[90m${s}\x1B[0m`,
|
|
211
|
-
bold: (s) => `\x1B[1m${s}\x1B[0m
|
|
389
|
+
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
390
|
+
// Brand pink — xterm-256 hot-pink background (205) with white text (97).
|
|
391
|
+
// Mirrors the pinkBg helper in cli.ts; the shell uses it for the "🎀
|
|
392
|
+
// AgenticMail is running" welcome banner and any future brand marks.
|
|
393
|
+
pinkBg: (s) => `\x1B[48;5;205m\x1B[97m${s}\x1B[0m`
|
|
212
394
|
};
|
|
213
395
|
var dotColors = [
|
|
214
396
|
(s) => `\x1B[35m${s}\x1B[0m`,
|
package/package.json
CHANGED