@bobfrankston/mailx 1.0.306 → 1.0.313
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 +15 -2
- package/bin/mailx.js +56 -1
- package/client/app.js +436 -21
- package/client/components/folder-picker.js +119 -0
- package/client/components/message-list.js +23 -1
- package/client/components/message-viewer.js +140 -22
- package/client/compose/compose.css +50 -0
- package/client/compose/compose.js +68 -12
- package/client/compose/editor.js +51 -7
- package/client/index.html +19 -0
- package/client/lib/api-client.js +12 -0
- package/client/lib/mailxapi.js +16 -7
- package/client/styles/components.css +115 -0
- package/client/styles/layout.css +64 -14
- package/client/styles/variables.css +1 -0
- package/package.json +8 -5
- package/packages/mailx-core/index.d.ts +3 -0
- package/packages/mailx-core/index.js +45 -7
- package/packages/mailx-host/index.d.ts +21 -0
- package/packages/mailx-host/index.js +31 -0
- package/packages/mailx-host/package.json +23 -0
- package/packages/mailx-imap/index.js +33 -0
- package/packages/mailx-service/index.d.ts +18 -1
- package/packages/mailx-service/index.js +198 -6
- package/packages/mailx-service/jsonrpc.js +8 -0
- package/packages/mailx-store/db.js +47 -5
- package/packages/mailx-store-web/android-bootstrap.js +91 -2
- package/packages/mailx-store-web/db.js +4 -1
- package/packages/mailx-store-web/main-thread-host.js +2 -2
- package/packages/mailx-types/index.d.ts +21 -0
- package/tempfix.cmd +77 -0
package/README.md
CHANGED
|
@@ -123,11 +123,16 @@ Gmail OAuth requires a one-time Google Cloud setup:
|
|
|
123
123
|
|
|
124
124
|
## Usage
|
|
125
125
|
|
|
126
|
+
### Layout
|
|
127
|
+
|
|
128
|
+
A vertical **icon rail** sits on the far left (Thunderbird / Dovecot style) with one-click access to Compose, Inbox, All Inboxes, and Settings; Calendar / Tasks / Contacts slots are placeholders until those features land. The rail is always visible on wide and medium screens; it folds into the hamburger menu on narrow ones.
|
|
129
|
+
|
|
126
130
|
### Reading Mail
|
|
127
131
|
|
|
128
132
|
- Click a **folder** to see its messages
|
|
129
133
|
- Click a **message** to read it in the preview pane
|
|
130
134
|
- **All Inboxes** combines inboxes from all accounts (appears with 2+ accounts)
|
|
135
|
+
- A small filled teal dot before the date means the message body is downloaded locally (offline-ready); a hollow circle means not yet prefetched. Prefetch runs as a background task every ~60 s independent of folder sync.
|
|
131
136
|
- Unread counts show on folders; sub-folder counts bubble up to collapsed parents
|
|
132
137
|
- Use the **folder search** box to find folders by name
|
|
133
138
|
|
|
@@ -150,15 +155,18 @@ Gmail OAuth requires a one-time Google Cloud setup:
|
|
|
150
155
|
|
|
151
156
|
### Managing Messages
|
|
152
157
|
|
|
153
|
-
- **Delete** or **Ctrl+D** -- Delete selected messages (moves to Trash)
|
|
158
|
+
- **Delete** or **Ctrl+D** -- Delete selected messages (moves to Trash; second delete in Trash is a hard delete + EXPUNGE)
|
|
154
159
|
- **Ctrl+Z** -- Undo the last **delete or move** (whichever came last, 60s window)
|
|
155
160
|
- **Ctrl+A** -- Select all messages in the list
|
|
156
161
|
- **Drag and drop** -- Move messages to a folder by dragging them
|
|
162
|
+
- **Right-click a message → Move to folder…** -- Searchable folder picker; useful on narrow layouts where the folder tree is hidden
|
|
157
163
|
- Click the **star** column to flag/unflag a message
|
|
158
|
-
- **Unsubscribe** button appears when the message has a List-Unsubscribe header
|
|
164
|
+
- **Unsubscribe** button appears when the message has a List-Unsubscribe header. One-click (RFC 8058) when the sender advertises `List-Unsubscribe-Post: List-Unsubscribe=One-Click`; otherwise opens the URL or a pre-filled compose for `mailto:` lists.
|
|
159
165
|
- **Right-click on a From/To/Cc address** -- Copy name, Copy address, Copy both, Add to contacts, or Reply/Reply All/Forward
|
|
166
|
+
- **Right-click in the message body → Translate** (opt-in) -- Uses the configured AI provider; select text first to translate just the selection. Off by default; enable under **Settings → AI translate**.
|
|
160
167
|
- **Preview pane zoom** -- Ctrl+wheel, Ctrl+= / Ctrl+- / Ctrl+0, or right-click menu (Zoom in/out/reset, Copy, Select all). Persisted across messages.
|
|
161
168
|
- **Cross-folder search results** show the folder name for each hit
|
|
169
|
+
- **Empty Trash / Empty Junk** -- Right-click the folder in the tree → Empty (confirmation prompt)
|
|
162
170
|
|
|
163
171
|
### Searching
|
|
164
172
|
|
|
@@ -191,6 +199,10 @@ Under **View** in the toolbar:
|
|
|
191
199
|
Under **Settings** in the toolbar:
|
|
192
200
|
- **Editor** -- Choose between Quill (default) and tiptap for compose
|
|
193
201
|
- **AI autocomplete** -- Enable LLM-powered writing suggestions (Ollama, Claude, or OpenAI)
|
|
202
|
+
- **AI translate** -- Enable the right-click Translate item in the message viewer (off by default; uses the same provider as autocomplete)
|
|
203
|
+
- **AI proofread** -- Enable the proofread path (off by default; provider method available, UI wiring in progress)
|
|
204
|
+
|
|
205
|
+
All AI features are opt-in. Provider + API key live in the autocomplete settings; toggling a feature only controls whether that specific capability calls out to the provider.
|
|
194
206
|
|
|
195
207
|
### Keyboard Shortcuts
|
|
196
208
|
|
|
@@ -402,6 +414,7 @@ Use `mailx --verbose` to see logs in the terminal instead.
|
|
|
402
414
|
## Architecture
|
|
403
415
|
|
|
404
416
|
- **IPC-first** -- Default mode uses msger (Rust/WebView2) with bidirectional IPC. No TCP server, no CLOSE_WAIT zombies.
|
|
417
|
+
- **Host abstraction** -- mailx talks to the WebView host through `@bobfrankston/mailx-host`, which picks msger (default) or msgview (Electron, planned for Mac and niche Linux) at runtime. Override with `MAILX_HOST=msger|msgview`.
|
|
405
418
|
- **HTTP fallback** -- `--server` flag starts Express for browser/remote access.
|
|
406
419
|
- **Local-first** -- Changes update local DB immediately; background worker syncs to IMAP.
|
|
407
420
|
- **Offline reading** -- Full message bodies cached as .eml files.
|
package/bin/mailx.js
CHANGED
|
@@ -21,8 +21,9 @@ import path from "node:path";
|
|
|
21
21
|
import os from "node:os";
|
|
22
22
|
import net from "node:net";
|
|
23
23
|
import { ports } from "@bobfrankston/miscinfo";
|
|
24
|
-
import { showMessageBox, showService, setAppName } from "@bobfrankston/
|
|
24
|
+
import { showMessageBox, showService, setAppName, setAppIcon } from "@bobfrankston/mailx-host";
|
|
25
25
|
setAppName("mailx");
|
|
26
|
+
setAppIcon(path.resolve(import.meta.dirname, "..", "client", "icon.png"));
|
|
26
27
|
const PORT = ports.mailx;
|
|
27
28
|
const args = process.argv.slice(2);
|
|
28
29
|
// Normalize: accept both -flag and --flag
|
|
@@ -146,6 +147,46 @@ for (const arg of args) {
|
|
|
146
147
|
}
|
|
147
148
|
function log(...msg) { if (verbose)
|
|
148
149
|
console.log("[mailx]", ...msg); }
|
|
150
|
+
/** Detect whether we're running with administrator / root privileges.
|
|
151
|
+
* Windows: `net session` requires admin — succeeds silently when elevated,
|
|
152
|
+
* errors "Access is denied" otherwise. Linux/Mac: check process uid.
|
|
153
|
+
* Returns true only when positively detected as elevated; on ambiguity
|
|
154
|
+
* (e.g. child_process spawn failed for non-privilege reasons), returns
|
|
155
|
+
* false so we don't block users on false positives. */
|
|
156
|
+
function isElevated() {
|
|
157
|
+
try {
|
|
158
|
+
if (process.platform === "win32") {
|
|
159
|
+
const { execSync } = require("node:child_process");
|
|
160
|
+
execSync("net session >nul 2>&1", { stdio: "ignore", windowsHide: true });
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (typeof process.getuid === "function") {
|
|
164
|
+
return process.getuid() === 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch { /* non-admin → net session fails */ }
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
/** Put up a blocking warning dialog via showMessageBox. Returns the label
|
|
171
|
+
* the user clicked. The default (Quit) is first so Enter dismisses to
|
|
172
|
+
* safety. Caller decides what to do with "Continue anyway". */
|
|
173
|
+
async function warnElevated() {
|
|
174
|
+
const res = await showMessageBox({
|
|
175
|
+
title: "mailx — elevated run not recommended",
|
|
176
|
+
message: "mailx is running with Administrator privileges.\n\n" +
|
|
177
|
+
"This can corrupt the per-user WebView2 profile at\n" +
|
|
178
|
+
"%LOCALAPPDATA%\\msger\\webview2\\ and create admin-owned files\n" +
|
|
179
|
+
"under ~/.mailx/ that later non-admin runs can't write to\n" +
|
|
180
|
+
"(SQLite db, tokens, config).\n\n" +
|
|
181
|
+
"Quit, relaunch from a normal shell, and only use admin if\n" +
|
|
182
|
+
"you specifically know you need it. To bypass this warning\n" +
|
|
183
|
+
"(for scripted admin use), pass --allow-elevated.",
|
|
184
|
+
buttons: ["Quit", "Continue anyway"],
|
|
185
|
+
size: { width: 540, height: 340 },
|
|
186
|
+
escapeCloses: true,
|
|
187
|
+
});
|
|
188
|
+
return res.button;
|
|
189
|
+
}
|
|
149
190
|
// Kill any running mailx server
|
|
150
191
|
if (hasFlag("kill")) {
|
|
151
192
|
log("Killing mailx processes...");
|
|
@@ -731,6 +772,20 @@ async function main() {
|
|
|
731
772
|
log(`Platform: ${process.platform} ${process.arch}`);
|
|
732
773
|
log(`Node: ${process.version}`);
|
|
733
774
|
log(`Mode: ${setupMode ? "setup" : "auto"}`);
|
|
775
|
+
// Refuse to run elevated unless explicitly opted in. An elevated mailx
|
|
776
|
+
// can poison %LOCALAPPDATA%\msger\webview2\ (see msger/notes.md WebView2
|
|
777
|
+
// profile playbook) and create admin-owned files under ~/.mailx/ that
|
|
778
|
+
// later non-admin runs can't write to. `net session` requires admin on
|
|
779
|
+
// Windows; succeeds → admin, fails → non-admin. Linux/Mac use process
|
|
780
|
+
// uid (0 = root). --allow-elevated bypasses for scripted admin use.
|
|
781
|
+
if (!hasFlag("allow-elevated") && !isDaemon && isElevated()) {
|
|
782
|
+
const button = await warnElevated();
|
|
783
|
+
if (button !== "Continue anyway") {
|
|
784
|
+
log("User chose Quit on elevated-run warning. Exiting.");
|
|
785
|
+
process.exit(0);
|
|
786
|
+
}
|
|
787
|
+
log("User chose Continue anyway on elevated-run warning. Proceeding (will likely poison local state).");
|
|
788
|
+
}
|
|
734
789
|
// Test connectivity
|
|
735
790
|
if (testMode) {
|
|
736
791
|
await runTest();
|