@agenticmail/cli 0.8.24 → 0.8.25

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/README.md CHANGED
@@ -23,9 +23,14 @@ It bundles a setup wizard, API server launcher, and a full interactive shell wit
23
23
  >
24
24
  > Zero prompts, ~2 minutes, fully autonomous. Then tell the user to restart Claude Code. Full agent-facing runbook — including verification, common failures, and "what if Node < 22" — is at [`AGENTS.md`](https://github.com/agenticmail/agenticmail/blob/main/AGENTS.md) in the repo (the [agents.md](https://agents.md) convention).
25
25
 
26
- ## ✨ What's new in 0.8.24
27
-
28
- - **🌐 Gmail-style web UI, fully redesigned** — `agenticmail web` opens a proper two-column Gmail layout: left sidebar with Compose + folders (Inbox / Starred / Sent / Drafts / All Mail / Spam / Trash), content pane that swaps between list view (40 px Gmail-style rows) and message view via a hash router (`#/inbox`, `#/m/<uid>`). Every emoji replaced with an inline 24×24 vector icon library. Modular ES module JS under `public/js/` (14 files). Search supports `from:` / `subject:` operators, real-time SSE updates, browser notifications, full markdown rendering, compose + reply with `wake` as a first-class field.
26
+ ## ✨ What's new in 0.8.25
27
+
28
+ - **⏱ Workers can run for hours** — no aggressive timeout. Per-worker logs at `~/.agenticmail/worker-logs/`, heartbeats every 30 s, isolated cwd per worker. New MCP tool `tail_worker` reads the running log; `check_activity` shows last tool, turn count, and a `stale` flag instead of evicting long-running workers.
29
+ - **🤖 Autonomous-mode awareness via Stop hook** — long headless Claude Code runs now see teammate replies at every turn boundary. Closes the follow-up from 0.8.23.
30
+ - **🩹 Hook bin resolution fixed** — `agenticmail-mail-hook: command not found` errors gone; the hook is registered with an absolute path resolved at install time. Old installs auto-heal on the next `agenticmail claudecode`.
31
+ - **🐛 Web UI bug sweep** — flags-`.includes`-not-a-function crash, sidebar folders all hitting `/mail/inbox`, Cmd+C opening compose: all fixed.
32
+ - **📱 Mobile-responsive web UI** — off-canvas sidebar with hamburger toggle, full-screen compose, list rows that fold sender into the preview, message view that drops the desktop content cap.
33
+ - **🎀 Official logos** — Claude starburst (Wikipedia) + AgenticMail `@` mark replace the placeholder glyphs everywhere.
29
34
  - **`wake: ["alice", "bob"]`** on `send_email` / `reply_email` / `forward_email` / `template_send` / `manage_drafts(send)` tells the dispatcher to give a Claude turn only to named agents — the biggest token saver on large threads.
30
35
  - **`[FINAL]` / `[DONE]` / `[CLOSED]` / `[WRAP]` in a subject** closes a thread — the dispatcher stops waking workers on any further reply to it.
31
36
  - **`check_activity` MCP tool** — see which agents the dispatcher has woken right now and how long they've been running.
@@ -0,0 +1,2 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="73 220 158 165" fill="currentColor" aria-hidden="true"><path d="m 105.01,322.07 29.14,-16.35 0.49,-1.42 -0.49,-0.79 h -1.42 l -4.87,-0.3 -16.65,-0.45 -14.44,-0.6 -13.99,-0.75 -3.52,-0.75 -3.3,-4.35 0.34,-2.17 2.96,-1.99 4.24,0.37 9.37,0.64 14.06,0.97 10.2,0.6 15.11,1.57 h 2.4 l 0.34,-0.97 -0.82,-0.6 -0.64,-0.6 -14.55,-9.86 -15.75,-10.42 -8.25,-6 -4.46,-3.04 -2.25,-2.85 -0.97,-6.22 4.05,-4.46 5.44,0.37 1.39,0.37 5.51,4.24 11.77,9.11 15.37,11.32 2.25,1.87 0.9,-0.64 0.11,-0.45 -1.01,-1.69 -8.36,-15.11 -8.92,-15.37 -3.97,-6.37 -1.05,-3.82 c -0.37,-1.57 -0.64,-2.89 -0.64,-4.5 l 4.61,-6.26 2.55,-0.82 6.15,0.82 2.59,2.25 3.82,8.74 6.19,13.76 9.6,18.71 2.81,5.55 1.5,5.14 0.56,1.57 h 0.97 v -0.9 l 0.79,-10.54 1.46,-12.94 1.42,-16.65 0.49,-4.69 2.32,-5.62 4.61,-3.04 3.6,1.72 2.96,4.24 -0.41,2.74 -1.76,11.44 -3.45,17.92 -2.25,12 h 1.31 l 1.5,-1.5 6.07,-8.06 10.2,-12.75 4.5,-5.06 5.25,-5.59 3.37,-2.66 h 6.37 l 4.69,6.97 -2.1,7.2 -6.56,8.32 -5.44,7.05 -7.8,10.5 -4.87,8.4 0.45,0.67 1.16,-0.11 17.62,-3.75 9.52,-1.72 11.36,-1.95 5.14,2.4 0.56,2.44 -2.02,4.99 -12.15,3 -14.25,2.85 -21.22,5.02 -0.26,0.19 0.3,0.37 9.56,0.9 4.09,0.22 h 10.01 l 18.64,1.39 4.87,3.22 2.92,3.94 -0.49,3 -7.5,3.82 -10.12,-2.4 -23.62,-5.62 -8.1,-2.02 h -1.12 v 0.67 l 6.75,6.6 12.37,11.17 15.49,14.4 0.79,3.56 -1.99,2.81 -2.1,-0.3 -13.61,-10.24 -5.25,-4.61 -11.89,-10.01 h -0.79 v 1.05 l 2.74,4.01 14.47,21.75 0.75,6.67 -1.05,2.17 -3.75,1.31 -4.12,-0.75 -8.47,-11.89 -8.74,-13.39 -7.05,-12 -0.86,0.49 -4.16,44.81 -1.95,2.29 -4.5,1.72 -3.75,-2.85 -1.99,-4.61 1.99,-9.11 2.4,-11.89 1.95,-9.45 1.76,-11.74 1.05,-3.9 -0.07,-0.26 -0.86,0.11 -8.85,12.15 -13.46,18.19 -10.65,11.4 -2.55,1.01 -4.42,-2.29 0.41,-4.09 2.47,-3.64 14.74,-18.75 8.89,-11.62 5.74,-6.71 -0.04,-0.97 h -0.34 l -39.15,25.42 -6.97,0.9 -3,-2.81 0.37,-4.61 1.42,-1.5 11.77,-8.1 -0.04,0.04 z"
2
+ shape-rendering="optimizeQuality"/></svg>
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>AgenticMail</title>
7
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ec4899'%3E%3Cpath d='M12 12c-2-3-5-5-8-5-1 0-2 1-2 2s1 2 2 2c2 0 4 1 5 2-1 1-3 2-5 2-1 0-2 1-2 2s1 2 2 2c3 0 6-2 8-5 2 3 5 5 8 5 1 0 2-1 2-2s-1-2-2-2c-2 0-4-1-5-2 1-1 3-2 5-2 1 0 2-1 2-2s-1-2-2-2c-3 0-6 2-8 5zm0 2a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z'/%3E%3C/svg%3E" />
7
+ <link rel="icon" type="image/png" href="/branding/agenticmail-logo.png" />
8
8
  <link rel="stylesheet" href="styles.css" />
9
9
  </head>
10
10
  <body>
@@ -12,7 +12,7 @@
12
12
  <!-- ─── Auth gate (shown until master key is entered) ────────────── -->
13
13
  <div id="auth" class="auth-gate">
14
14
  <div class="auth-card">
15
- <h1><span data-icon="bow" data-icon-size="28"></span> AgenticMail</h1>
15
+ <h1><img src="/branding/agenticmail-logo.png" alt="AgenticMail" class="brand-logo" /> AgenticMail</h1>
16
16
  <p>Enter your master key to sign in. The key is stored locally in your browser; we never send it anywhere except to <span class="mono" id="auth-api-url"></span>.</p>
17
17
  <div id="auth-err" class="auth-err" style="display:none"></div>
18
18
  <input id="auth-key" type="password" placeholder="mk_…" autocomplete="off" autofocus />
@@ -27,7 +27,7 @@
27
27
  <header class="topbar">
28
28
  <button class="menu-btn" id="menu-btn" title="Menu" data-icon="menu"></button>
29
29
  <div class="brand">
30
- <span class="brand-bow" data-icon="bow" data-icon-size="28"></span>
30
+ <img src="/branding/agenticmail-logo.png" alt="AgenticMail" class="brand-logo" />
31
31
  <span class="brand-name">AgenticMail</span>
32
32
  </div>
33
33
  <div class="search-container">
@@ -52,8 +52,9 @@
52
52
  </header>
53
53
 
54
54
  <!-- Sidebar + content -->
55
- <div class="main">
56
- <aside class="sidebar">
55
+ <div class="main" id="main">
56
+ <div class="sidebar-backdrop" id="sidebar-backdrop"></div>
57
+ <aside class="sidebar" id="sidebar">
57
58
  <button class="compose-btn" id="compose-btn">
58
59
  <span class="compose-icon" data-icon="compose" data-icon-size="22"></span>
59
60
  <span class="compose-text">Compose</span>
@@ -104,6 +104,9 @@ function onFolderSelect(folder) {
104
104
  renderSidebar(onFolderSelect);
105
105
  location.hash = '#/inbox'; // any folder uses the list route
106
106
  if (state.selectedAgent) loadList(state.selectedAgent, folder);
107
+ // On mobile (the only viewport where the sidebar is over-canvas),
108
+ // close it after a folder pick so the user sees the list.
109
+ document.getElementById('main')?.classList.remove('sidebar-open');
107
110
  }
108
111
 
109
112
  // ─── Hash router ─────────────────────────────────────────────────────
@@ -119,6 +122,18 @@ function route() {
119
122
  window.addEventListener('hashchange', route);
120
123
 
121
124
  // ─── Top bar wiring ──────────────────────────────────────────────────
125
+ // Hamburger toggles the sidebar on mobile. On desktop the sidebar
126
+ // is always visible; the class only changes anything below 800 px,
127
+ // where the CSS slides it off-canvas by default.
128
+ function toggleSidebar() {
129
+ const main = document.getElementById('main');
130
+ main?.classList.toggle('sidebar-open');
131
+ }
132
+ document.getElementById('menu-btn').addEventListener('click', toggleSidebar);
133
+ document.getElementById('sidebar-backdrop').addEventListener('click', () => {
134
+ document.getElementById('main')?.classList.remove('sidebar-open');
135
+ });
136
+
122
137
  document.getElementById('refresh-btn').addEventListener('click', async () => {
123
138
  if (state.selectedAgent) {
124
139
  await loadList(state.selectedAgent, state.selectedFolder);
@@ -172,12 +187,18 @@ document.getElementById('search-clear').addEventListener('click', clearSearch);
172
187
  // r refresh current inbox
173
188
  // c compose new
174
189
  // / focus the search box
190
+ //
191
+ // IMPORTANT: every shortcut bails when ANY modifier key is held
192
+ // (Cmd / Ctrl / Alt / Meta) — otherwise Cmd+C "copy" was opening
193
+ // the compose modal, Cmd+R was overriding browser refresh, etc.
194
+ // Plain unmodified single-key shortcuts only.
175
195
  document.addEventListener('keydown', e => {
176
196
  if (document.getElementById('compose-bg').style.display !== 'none') return;
177
197
  if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
198
+ if (e.metaKey || e.ctrlKey || e.altKey) return; // never hijack OS shortcuts
178
199
  if (e.key === 'r') document.getElementById('refresh-btn').click();
179
- if (e.key === 'c') openCompose();
180
- if (e.key === '/') {
200
+ else if (e.key === 'c') openCompose();
201
+ else if (e.key === '/') {
181
202
  e.preventDefault();
182
203
  searchInput.focus();
183
204
  }
@@ -1,17 +1,18 @@
1
1
  // Agent identity + avatar helpers.
2
2
  //
3
3
  // The bridge agent (default name "claudecode") is the host's identity
4
- // inside AgenticMail. We render it with a stylised Claude-asterisk
5
- // mark and a green verified-tick so the host inbox is recognisable at
6
- // a glance vs. teammate sub-agents.
7
- //
8
- // We deliberately do NOT embed Anthropic's actual trademarked Claude
9
- // logo here — reproducing it pixel-for-pixel in third-party software
10
- // has licensing implications. The stylised approximation conveys
11
- // the same identity cue without the trademark concern.
4
+ // inside AgenticMail. We render it with the OFFICIAL Claude starburst
5
+ // mark (sourced from the public Wikipedia SVG, served as a static
6
+ // asset under /branding/claude-mark.svg) and a green verified-tick so
7
+ // the host inbox is recognisable at a glance vs. teammate sub-agents.
12
8
  import { escapeHtml } from './utils.js';
13
9
  import { icon } from './icons.js';
14
10
 
11
+ // Official Claude mark, served as a static asset under /branding/.
12
+ // Using <img src=...> rather than inlining the path keeps the SVG
13
+ // out of every avatar render and lets the browser cache the asset.
14
+ const CLAUDE_MARK_URL = '/branding/claude-mark.svg';
15
+
15
16
  export function isBridgeAgent(agent) {
16
17
  if (!agent) return false;
17
18
  const name = (agent.name ?? '').toLowerCase();
@@ -31,14 +32,10 @@ function avatarColorFor(name) {
31
32
  return AVATAR_PALETTE[hash % AVATAR_PALETTE.length];
32
33
  }
33
34
 
34
- const CLAUDE_MARK_SVG = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
35
- <path d="M12 1.5 L13.2 8.6 L19.5 6.6 L15 12 L19.5 17.4 L13.2 15.4 L12 22.5 L10.8 15.4 L4.5 17.4 L9 12 L4.5 6.6 L10.8 8.6 Z"/>
36
- </svg>`;
37
-
38
35
  export function avatarHtml(agent, size = '') {
39
36
  const cls = `avatar ${size}`.trim();
40
37
  if (isBridgeAgent(agent)) {
41
- return `<span class="${cls} avatar-host">${CLAUDE_MARK_SVG}<span class="avatar-check">${icon('check', { size: 10 })}</span></span>`;
38
+ return `<span class="${cls} avatar-host"><img src="${CLAUDE_MARK_URL}" alt="Claude" class="avatar-img" /><span class="avatar-check">${icon('check', { size: 10 })}</span></span>`;
42
39
  }
43
40
  const initial = (agent.name ?? '?').slice(0, 1).toUpperCase();
44
41
  const color = avatarColorFor(agent.name ?? '');
@@ -8,6 +8,44 @@ import { apiGet } from './api.js';
8
8
  import { FOLDERS } from './sidebar.js';
9
9
  import { icon } from './icons.js';
10
10
 
11
+ /**
12
+ * Defensive flag check. The API's IMAP layer returns `flags` as an
13
+ * array of strings most of the time (`['\\Seen', '\\Flagged']`) but
14
+ * some envelopes come back with a Set-like serialisation or even an
15
+ * object map. Without this guard, calling `.includes()` on a non-
16
+ * array crashed the list with "(m.flags ?? []).includes is not a
17
+ * function". Coerce everything we don't recognise to an empty list.
18
+ */
19
+ function flagsHas(flags, name) {
20
+ if (Array.isArray(flags)) return flags.includes(name);
21
+ if (flags && typeof flags === 'object') {
22
+ // `{Seen: true, Flagged: false}` shape — try both with and
23
+ // without the leading backslash since callers can mean either.
24
+ const key = name.replace(/^\\/, '');
25
+ return flags[name] === true || flags[key] === true;
26
+ }
27
+ return false;
28
+ }
29
+
30
+ // Map sidebar folder ids to the actual IMAP folder names the API
31
+ // expects on `/mail/folders/:folder`. `inbox` is special — the API
32
+ // has a dedicated `/mail/inbox` endpoint with extra enrichment, so
33
+ // we use that. Other folders go through the generic listing.
34
+ //
35
+ // Stalwart uses the standard IMAP names: INBOX, Sent, Drafts, Junk
36
+ // Mail (a.k.a. "Spam"), Trash. We use the canonical IMAP capitalisation.
37
+ const FOLDER_TO_IMAP = {
38
+ inbox: { endpoint: '/mail/inbox' },
39
+ sent: { endpoint: '/mail/folders/Sent' },
40
+ drafts: { endpoint: '/mail/folders/Drafts' },
41
+ spam: { endpoint: '/mail/folders/Junk%20Mail' },
42
+ trash: { endpoint: '/mail/folders/Trash' },
43
+ all: { endpoint: '/mail/folders/All%20Mail' },
44
+ // Starred is not a folder — it's the IMAP \Flagged flag, surfaced
45
+ // by client-side filtering over the inbox listing (Gmail-style).
46
+ starred: { endpoint: '/mail/inbox', clientFilter: 'flagged' },
47
+ };
48
+
11
49
  export async function loadList(agent, folder) {
12
50
  const root = document.getElementById('content');
13
51
  root.innerHTML = `
@@ -17,17 +55,19 @@ export async function loadList(agent, folder) {
17
55
  </div>
18
56
  <div class="list-rows" id="list-rows"><div class="empty">Loading…</div></div>
19
57
  `;
58
+ const route = FOLDER_TO_IMAP[folder] ?? FOLDER_TO_IMAP.inbox;
20
59
  try {
21
- // Public API today only exposes the inbox listing. Other folders
22
- // fall through to the inbox endpoint and apply a client-side
23
- // shape (e.g. starred = flag filter). When the API grows
24
- // per-mailbox listing we'll route based on `folder` here.
25
- const data = await apiGet('/mail/inbox?limit=50&offset=0', { agentKey: agent.apiKey });
60
+ const sep = route.endpoint.includes('?') ? '&' : '?';
61
+ const data = await apiGet(`${route.endpoint}${sep}limit=50&offset=0`, { agentKey: agent.apiKey });
26
62
  state.messages = data.messages ?? [];
27
63
  renderList();
28
64
  } catch (err) {
29
- document.getElementById('list-rows').innerHTML =
30
- `<div class="empty">Failed to load: ${escapeHtml(err.message)}</div>`;
65
+ // Empty folder is a normal state; "no such folder" lands here
66
+ // too. Show a friendly empty message rather than a raw HTTP error.
67
+ const msg = String(err.message ?? err);
68
+ document.getElementById('list-rows').innerHTML = msg.includes('404')
69
+ ? `<div class="empty">${escapeHtml(folderTitle(folder))} is empty.</div>`
70
+ : `<div class="empty">Failed to load: ${escapeHtml(msg)}</div>`;
31
71
  }
32
72
  }
33
73
 
@@ -45,8 +85,10 @@ export function renderList() {
45
85
 
46
86
  // Client-side folder filtering for the folders the API doesn't
47
87
  // distinguish for us yet. Starred uses the IMAP \Flagged flag.
88
+ // Flags may come back as an array OR an object map ({Seen: true})
89
+ // depending on the IMAP path — always coerce before .includes().
48
90
  if (state.selectedFolder === 'starred') {
49
- filtered = filtered.filter(m => (m.flags ?? []).includes('\\Flagged'));
91
+ filtered = filtered.filter(m => flagsHas(m.flags, '\\Flagged'));
50
92
  }
51
93
 
52
94
  const hlTerm = filters?.subject || filters?.from || filters?.text || '';
@@ -70,8 +112,8 @@ export function renderList() {
70
112
  }
71
113
 
72
114
  root.innerHTML = filtered.map(m => {
73
- const unread = !(m.flags ?? []).includes('\\Seen');
74
- const starred = (m.flags ?? []).includes('\\Flagged');
115
+ const unread = !flagsHas(m.flags, '\\Seen');
116
+ const starred = flagsHas(m.flags, '\\Flagged');
75
117
  const fromAddr = m.from?.[0]?.address ?? '?';
76
118
  const fromName = m.from?.[0]?.name || fromAddr;
77
119
  const subject = m.subject ?? '(no subject)';
@@ -78,14 +78,26 @@ a { color: var(--accent-strong); }
78
78
  }
79
79
  .menu-btn:hover { background: var(--bg-hover); }
80
80
  .brand {
81
- display: flex; align-items: center; gap: 8px;
81
+ display: flex; align-items: center; gap: 10px;
82
82
  padding: 0 8px; min-width: 200px;
83
83
  }
84
84
  .brand-bow { font-size: 28px; line-height: 1; }
85
+ .brand-logo {
86
+ width: 32px; height: 32px;
87
+ border-radius: 8px;
88
+ flex-shrink: 0;
89
+ display: block;
90
+ }
85
91
  .brand-name {
86
92
  font: 500 22px/1 'Google Sans', sans-serif;
87
93
  color: var(--pink);
88
94
  }
95
+ /* Slightly bigger logo in the auth card. */
96
+ .auth-card .brand-logo {
97
+ width: 28px; height: 28px;
98
+ border-radius: 6px;
99
+ vertical-align: middle;
100
+ }
89
101
 
90
102
  .search-container {
91
103
  flex: 1; max-width: 720px;
@@ -196,9 +208,14 @@ a { color: var(--accent-strong); }
196
208
  .avatar-sm { width: 24px; height: 24px; font-size: 11px; }
197
209
  .avatar-md { width: 40px; height: 40px; font-size: 16px; }
198
210
  .avatar-lg { width: 48px; height: 48px; font-size: 20px; }
199
- .avatar-host { background: #fce8e0; color: #cc785c; }
211
+ .avatar-host { background: #fce8e0; color: #d97757; }
200
212
  @media (prefers-color-scheme: dark) { .avatar-host { background: #2a1810; } }
201
213
  .avatar svg { width: 60%; height: 60%; }
214
+ .avatar-img {
215
+ width: 70%; height: 70%;
216
+ object-fit: contain;
217
+ display: block;
218
+ }
202
219
  .avatar-check {
203
220
  position: absolute; bottom: -2px; right: -2px;
204
221
  width: 14px; height: 14px; border-radius: 50%;
@@ -226,12 +243,66 @@ a { color: var(--accent-strong); }
226
243
  grid-template-columns: 256px 1fr;
227
244
  overflow: hidden;
228
245
  background: var(--bg-soft);
246
+ position: relative;
229
247
  }
248
+ .sidebar-backdrop {
249
+ display: none;
250
+ position: fixed; inset: 64px 0 0 0;
251
+ background: rgba(0,0,0,0.4);
252
+ z-index: 14;
253
+ }
254
+ /* ─── Mobile / narrow viewport ──────────────────────────────────── */
230
255
  @media (max-width: 800px) {
231
- .main { grid-template-columns: 72px 1fr; }
232
- .sidebar-label { display: none; }
233
- .compose-text { display: none; }
234
- .compose-btn { justify-content: center; padding: 0; width: 56px; }
256
+ .main { grid-template-columns: 1fr; }
257
+ .sidebar {
258
+ position: fixed; top: 64px; bottom: 0; left: 0;
259
+ width: 280px; max-width: 85vw;
260
+ background: var(--bg-soft);
261
+ z-index: 15;
262
+ transform: translateX(-100%);
263
+ transition: transform .22s ease;
264
+ box-shadow: 2px 0 16px rgba(0,0,0,0.1);
265
+ }
266
+ .main.sidebar-open .sidebar { transform: translateX(0); }
267
+ .main.sidebar-open .sidebar-backdrop { display: block; }
268
+ .content { border-radius: 0; margin: 0; }
269
+ .topbar { padding: 8px 8px; gap: 4px; }
270
+ .brand { min-width: auto; }
271
+ .brand-name { font-size: 18px; }
272
+ .search-container { max-width: none; }
273
+ .search-input { height: 40px; font-size: 14px; }
274
+ /* List rows lose the from column on narrow screens; the subject
275
+ gets full width with the sender folded into the preview. */
276
+ .list-row {
277
+ grid-template-columns: 24px 24px 1fr 70px;
278
+ height: 56px;
279
+ padding: 0 12px;
280
+ }
281
+ .list-row .from { display: none; }
282
+ .list-row .subject-cell {
283
+ flex-direction: column;
284
+ gap: 2px;
285
+ align-items: flex-start;
286
+ }
287
+ .list-row .subject { max-width: none; font-size: 14px; }
288
+ .list-row .preview { font-size: 13px; }
289
+ .list-row .preview::before { content: ''; }
290
+ .message-header { padding: 16px 16px 8px; }
291
+ .message-subject { font-size: 18px; }
292
+ .message-body { padding: 8px 16px 24px; max-width: none; }
293
+ .message-attachments { padding: 12px 16px; }
294
+ /* Compose modal goes full-screen on mobile rather than a tiny
295
+ bottom-right popup that nobody can type into. */
296
+ .compose-bg { padding: 0; align-items: stretch; justify-content: stretch; }
297
+ .compose-modal { width: 100%; max-height: 100vh; border-radius: 0; }
298
+ .compose-body textarea { min-height: 40vh; }
299
+ /* Hide non-essential top-bar buttons on narrow screens. */
300
+ .topbar-spacer { flex: 0; }
301
+ #refresh-btn { display: none; }
302
+ }
303
+ @media (min-width: 801px) {
304
+ /* Hamburger menu only matters on mobile; hide on desktop. */
305
+ .menu-btn { display: none; }
235
306
  }
236
307
 
237
308
  /* ─── Sidebar ──────────────────────────────────────────────────────── */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/cli",
3
- "version": "0.8.24",
3
+ "version": "0.8.25",
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",
@@ -29,7 +29,7 @@
29
29
  "prepublishOnly": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@agenticmail/api": "^0.7.9",
32
+ "@agenticmail/api": "^0.7.11",
33
33
  "@agenticmail/core": "^0.7.0",
34
34
  "json5": "^2.2.3"
35
35
  },