@rndmjoker/proton-mcp 0.4.7 → 0.4.12
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 +69 -21
- package/dist/bridge/certificate.d.ts +33 -1
- package/dist/bridge/certificate.js +82 -9
- package/dist/bridge/certificate.js.map +1 -1
- package/dist/bridge/connection.js +31 -6
- package/dist/bridge/connection.js.map +1 -1
- package/dist/bridge/errors.d.ts +8 -0
- package/dist/bridge/errors.js +28 -3
- package/dist/bridge/errors.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials/availability.d.ts +5 -0
- package/dist/credentials/availability.js +7 -0
- package/dist/credentials/availability.js.map +1 -1
- package/dist/credentials/encrypted-file.js +7 -2
- package/dist/credentials/encrypted-file.js.map +1 -1
- package/dist/credentials/keyring.d.ts +12 -2
- package/dist/credentials/keyring.js +44 -6
- package/dist/credentials/keyring.js.map +1 -1
- package/dist/credentials/plain-file.js +8 -2
- package/dist/credentials/plain-file.js.map +1 -1
- package/dist/credentials/session-bus.d.ts +38 -0
- package/dist/credentials/session-bus.js +68 -0
- package/dist/credentials/session-bus.js.map +1 -0
- package/dist/credentials/store.d.ts +5 -1
- package/dist/credentials/store.js.map +1 -1
- package/dist/in-flight.d.ts +6 -3
- package/dist/in-flight.js +6 -3
- package/dist/in-flight.js.map +1 -1
- package/dist/mail/actions.d.ts +0 -2
- package/dist/mail/actions.js +8 -7
- package/dist/mail/actions.js.map +1 -1
- package/dist/mail/attachments.d.ts +12 -2
- package/dist/mail/attachments.js +19 -11
- package/dist/mail/attachments.js.map +1 -1
- package/dist/mail/compose.d.ts +70 -15
- package/dist/mail/compose.js +147 -11
- package/dist/mail/compose.js.map +1 -1
- package/dist/mail/drafts.d.ts +28 -2
- package/dist/mail/drafts.js +91 -21
- package/dist/mail/drafts.js.map +1 -1
- package/dist/mail/ids.d.ts +10 -0
- package/dist/mail/ids.js +16 -4
- package/dist/mail/ids.js.map +1 -1
- package/dist/mail/labels.js +9 -4
- package/dist/mail/labels.js.map +1 -1
- package/dist/mail/messages.d.ts +50 -10
- package/dist/mail/messages.js +94 -6
- package/dist/mail/messages.js.map +1 -1
- package/dist/mail/saved-attachments.d.ts +128 -0
- package/dist/mail/saved-attachments.js +448 -0
- package/dist/mail/saved-attachments.js.map +1 -0
- package/dist/mail/search.d.ts +73 -5
- package/dist/mail/search.js +150 -27
- package/dist/mail/search.js.map +1 -1
- package/dist/mail/send.js +24 -9
- package/dist/mail/send.js.map +1 -1
- package/dist/mail/uid-search.d.ts +17 -0
- package/dist/mail/uid-search.js +25 -0
- package/dist/mail/uid-search.js.map +1 -0
- package/dist/mime/filenames.d.ts +24 -0
- package/dist/mime/filenames.js +58 -0
- package/dist/mime/filenames.js.map +1 -0
- package/dist/mime/parts.d.ts +34 -0
- package/dist/mime/parts.js +92 -0
- package/dist/mime/parts.js.map +1 -0
- package/dist/server.js +39 -8
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +15 -3
- package/dist/session.js +35 -8
- package/dist/session.js.map +1 -1
- package/dist/tools/actions.js +2 -1
- package/dist/tools/actions.js.map +1 -1
- package/dist/tools/confirm.js +47 -11
- package/dist/tools/confirm.js.map +1 -1
- package/dist/tools/drafts.d.ts +3 -0
- package/dist/tools/drafts.js +31 -10
- package/dist/tools/drafts.js.map +1 -1
- package/dist/tools/format.d.ts +8 -0
- package/dist/tools/format.js +44 -6
- package/dist/tools/format.js.map +1 -1
- package/dist/tools/guidance.js +27 -3
- package/dist/tools/guidance.js.map +1 -1
- package/dist/tools/messages.js +41 -13
- package/dist/tools/messages.js.map +1 -1
- package/dist/tools/pending-view.d.ts +7 -0
- package/dist/tools/pending-view.js +4 -1
- package/dist/tools/pending-view.js.map +1 -1
- package/dist/tools/saved.d.ts +15 -0
- package/dist/tools/saved.js +107 -0
- package/dist/tools/saved.js.map +1 -0
- package/dist/tools/send.js +12 -4
- package/dist/tools/send.js.map +1 -1
- package/dist/web/layout.js +2 -0
- package/dist/web/layout.js.map +1 -1
- package/dist/web/pages.js +1 -0
- package/dist/web/pages.js.map +1 -1
- package/dist/web/pending.d.ts +4 -0
- package/dist/web/pending.js +32 -4
- package/dist/web/pending.js.map +1 -1
- package/dist/web/server.d.ts +16 -5
- package/dist/web/server.js +12 -8
- package/dist/web/server.js.map +1 -1
- package/package.json +21 -18
package/dist/mail/messages.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* full text is fetched one message at a time.
|
|
8
8
|
*/
|
|
9
9
|
import { BridgeError } from '../bridge/errors.js';
|
|
10
|
-
import { resolveMessageId, presentMessageId } from './ids.js';
|
|
10
|
+
import { resolveMessageId, presentMessageId, ALL_MAIL, TRASH } from './ids.js';
|
|
11
|
+
import { searchUids } from './uid-search.js';
|
|
11
12
|
import { parseMessage } from '../mime/parse.js';
|
|
12
13
|
/** Upper bound for one listing. Deliberately low, callers can page. */
|
|
13
14
|
export const MAX_LIST_LIMIT = 100;
|
|
@@ -50,6 +51,16 @@ export function detectAttachments(node) {
|
|
|
50
51
|
}
|
|
51
52
|
return false;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The date of an envelope, or nothing when there is none that can be used.
|
|
56
|
+
*
|
|
57
|
+
* imapflow hands out the header text itself, not a Date, when it cannot read
|
|
58
|
+
* the date, and calling getTime() on that text throws. Such a message is
|
|
59
|
+
* treated as one without a date, which puts it at the end of a listing.
|
|
60
|
+
*/
|
|
61
|
+
export function envelopeDate(value) {
|
|
62
|
+
return value instanceof Date ? value : undefined;
|
|
63
|
+
}
|
|
53
64
|
/**
|
|
54
65
|
* Reads nothing but the date of each message.
|
|
55
66
|
*
|
|
@@ -71,7 +82,29 @@ export async function fetchDates(client, uids) {
|
|
|
71
82
|
return [];
|
|
72
83
|
const entries = [];
|
|
73
84
|
for await (const msg of client.fetch(uids, { uid: true, envelope: true }, { uid: true })) {
|
|
74
|
-
entries.push({ uid: msg.uid, date: msg.envelope?.date });
|
|
85
|
+
entries.push({ uid: msg.uid, date: envelopeDate(msg.envelope?.date) });
|
|
86
|
+
}
|
|
87
|
+
return entries;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The date of each message, and the decoded subject, sender and recipients.
|
|
91
|
+
*
|
|
92
|
+
* Costs the same as fetchDates: both read the envelope, this one keeps more of
|
|
93
|
+
* it. A search needs those fields because the Bridge compares its text against
|
|
94
|
+
* the header as stored, encoded, see searchMessages.
|
|
95
|
+
*/
|
|
96
|
+
export async function fetchEnvelopes(client, uids) {
|
|
97
|
+
if (uids.length === 0)
|
|
98
|
+
return [];
|
|
99
|
+
const entries = [];
|
|
100
|
+
for await (const msg of client.fetch(uids, { uid: true, envelope: true }, { uid: true })) {
|
|
101
|
+
entries.push({
|
|
102
|
+
uid: msg.uid,
|
|
103
|
+
date: envelopeDate(msg.envelope?.date),
|
|
104
|
+
subject: msg.envelope?.subject ?? '',
|
|
105
|
+
from: toAddresses(msg.envelope?.from),
|
|
106
|
+
to: toAddresses(msg.envelope?.to),
|
|
107
|
+
});
|
|
75
108
|
}
|
|
76
109
|
return entries;
|
|
77
110
|
}
|
|
@@ -119,6 +152,42 @@ export function orderNewestFirst(entries) {
|
|
|
119
152
|
* page: fetching headers for a whole result set would defeat the purpose, since
|
|
120
153
|
* a common word matched 6803 messages in the measured mailbox.
|
|
121
154
|
*/
|
|
155
|
+
/**
|
|
156
|
+
* Which of these identifiers are in the trash.
|
|
157
|
+
*
|
|
158
|
+
* One search per page rather than one per message. The alternative was to
|
|
159
|
+
* resolve the real folder of every entry, which is what findHomeFolder costs and
|
|
160
|
+
* would mean 25 lookups for a listing that is supposed to be the cheap call.
|
|
161
|
+
*
|
|
162
|
+
* A single OR over the page's identifiers, then the envelopes of whatever it
|
|
163
|
+
* found - so an empty trash or a page with nothing discarded pays for one search
|
|
164
|
+
* and no fetch.
|
|
165
|
+
*
|
|
166
|
+
* Failure is not an error. The mark is an addition to a listing, so a trash that
|
|
167
|
+
* cannot be opened costs the mark and not the answer.
|
|
168
|
+
*/
|
|
169
|
+
async function trashedAmong(connection, ids) {
|
|
170
|
+
const wanted = ids.filter((id) => id !== '');
|
|
171
|
+
if (wanted.length === 0)
|
|
172
|
+
return new Set();
|
|
173
|
+
return connection
|
|
174
|
+
.withMailbox(TRASH, async (client, status) => {
|
|
175
|
+
// The Bridge answers a fetch on an empty mailbox with BAD.
|
|
176
|
+
if (status.messages === 0)
|
|
177
|
+
return new Set();
|
|
178
|
+
const hits = await client.search({ or: wanted.map((id) => ({ header: { 'message-id': id } })) }, { uid: true });
|
|
179
|
+
if (!Array.isArray(hits) || hits.length === 0)
|
|
180
|
+
return new Set();
|
|
181
|
+
const found = new Set();
|
|
182
|
+
for await (const msg of client.fetch(hits, { uid: true, envelope: true }, { uid: true })) {
|
|
183
|
+
const id = presentMessageId(msg.envelope?.messageId ?? '');
|
|
184
|
+
if (id)
|
|
185
|
+
found.add(id);
|
|
186
|
+
}
|
|
187
|
+
return found;
|
|
188
|
+
})
|
|
189
|
+
.catch(() => new Set());
|
|
190
|
+
}
|
|
122
191
|
export async function fetchHeaders(client, uids) {
|
|
123
192
|
if (uids.length === 0)
|
|
124
193
|
return [];
|
|
@@ -132,7 +201,7 @@ export async function fetchHeaders(client, uids) {
|
|
|
132
201
|
subject: env?.subject ?? '',
|
|
133
202
|
from: toAddresses(env?.from),
|
|
134
203
|
to: toAddresses(env?.to),
|
|
135
|
-
date: env?.date,
|
|
204
|
+
date: envelopeDate(env?.date),
|
|
136
205
|
size: msg.size ?? 0,
|
|
137
206
|
seen: msg.flags?.has('\\Seen') ?? false,
|
|
138
207
|
flagged: msg.flags?.has('\\Flagged') ?? false,
|
|
@@ -168,14 +237,14 @@ export async function listMessages(connection, path, options = {}) {
|
|
|
168
237
|
const limit = Math.min(Math.max(options.limit ?? 25, 1), MAX_LIST_LIMIT);
|
|
169
238
|
const offset = Math.max(options.offset ?? 0, 0);
|
|
170
239
|
const nothing = { messages: 0, elapsedMs: 0 };
|
|
171
|
-
|
|
240
|
+
const result = await connection.withMailbox(path, async (client, status) => {
|
|
172
241
|
// The Bridge answers FETCH on an empty mailbox with `BAD no such message`
|
|
173
242
|
// instead of an empty result, so nothing may be fetched here.
|
|
174
243
|
if (status.messages === 0) {
|
|
175
244
|
return { path, total: 0, offset, headers: [], ordering: nothing };
|
|
176
245
|
}
|
|
177
|
-
const uids = await client
|
|
178
|
-
if (
|
|
246
|
+
const uids = await searchUids(client, options.unreadOnly ? { seen: false } : { all: true }, `the messages in "${path}"`);
|
|
247
|
+
if (uids.length === 0) {
|
|
179
248
|
return { path, total: 0, offset, headers: [], ordering: nothing };
|
|
180
249
|
}
|
|
181
250
|
const started = process.hrtime.bigint();
|
|
@@ -191,6 +260,25 @@ export async function listMessages(connection, path, options = {}) {
|
|
|
191
260
|
const headers = await fetchHeaders(client, page);
|
|
192
261
|
return { path, total: ordered.length, offset, headers, ordering };
|
|
193
262
|
});
|
|
263
|
+
return withTrashMarks(connection, result);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Marks the entries of an "All Mail" page that are in the trash.
|
|
267
|
+
*
|
|
268
|
+
* Outside the withMailbox above rather than inside it, and not by choice: a
|
|
269
|
+
* mailbox is held under a lock for the duration of the operation, so a second
|
|
270
|
+
* withMailbox nested in the first would wait for a lock its own frame holds.
|
|
271
|
+
*/
|
|
272
|
+
export async function withTrashMarks(connection, result) {
|
|
273
|
+
if (result.path !== ALL_MAIL || result.headers.length === 0)
|
|
274
|
+
return result;
|
|
275
|
+
const trashed = await trashedAmong(connection, result.headers.map((h) => h.messageId));
|
|
276
|
+
if (trashed.size === 0)
|
|
277
|
+
return result;
|
|
278
|
+
return {
|
|
279
|
+
...result,
|
|
280
|
+
headers: result.headers.map((h) => (trashed.has(h.messageId) ? { ...h, inTrash: true } : h)),
|
|
281
|
+
};
|
|
194
282
|
}
|
|
195
283
|
/**
|
|
196
284
|
* Reads a single message and converts it into readable text.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/mail/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/mail/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachments saved to this machine, and taking them away again.
|
|
3
|
+
*
|
|
4
|
+
* Saving means writing what Proton keeps encrypted, decrypted, to a disk. That
|
|
5
|
+
* is worth doing so an assistant can open a PDF or a spreadsheet rather than
|
|
6
|
+
* only say it exists, and it is bounded on every side:
|
|
7
|
+
*
|
|
8
|
+
* - **Only into a directory the user named**, in PROTON_MCP_ATTACHMENT_DIR, and
|
|
9
|
+
* only through the environment: a setting in the web interface would be
|
|
10
|
+
* within reach of an assistant, which gets that page's address. Without the
|
|
11
|
+
* variable nothing is written and the tool says how to set it up.
|
|
12
|
+
* - **Nothing outside it.** A folder is checked after it is resolved, `..` is
|
|
13
|
+
* refused, and so is a symbolic link anywhere on the way.
|
|
14
|
+
* - **The name comes from the sender** and is reduced to a plain one, with the
|
|
15
|
+
* rule in mime/filenames.ts. An existing file is never replaced: the file is
|
|
16
|
+
* created with O_EXCL, which also refuses to follow a link in its place.
|
|
17
|
+
* - **Readable by the user only**, 0600, and folders created here 0700.
|
|
18
|
+
* - **Gone again within the hour.** Decided by Tobi on 2026-11-02: a file lies
|
|
19
|
+
* unattended for at most one hour, and is deleted when the server stops.
|
|
20
|
+
* Better the assistant fetches it again than that it lies around for days.
|
|
21
|
+
* A server that was killed leaves its files to the next one to start.
|
|
22
|
+
*
|
|
23
|
+
* What may be deleted is recorded per process, in this server's own
|
|
24
|
+
* configuration directory rather than in the directory the files go to. Each
|
|
25
|
+
* running server writes only its own record, so two of them never overwrite
|
|
26
|
+
* each other's. Before anything is deleted it is checked to be the very file
|
|
27
|
+
* that was saved: the same inode, size and modification time, a plain file and
|
|
28
|
+
* not a link. A file replaced or changed since is left where it is, because in
|
|
29
|
+
* the directory there may be files that are not ours.
|
|
30
|
+
*
|
|
31
|
+
* A folder created for a file goes again once it is empty, decided by Tobi on
|
|
32
|
+
* 2026-11-02: its name can say what was in it. Recorded like the files, with
|
|
33
|
+
* its inode, and removed with rmdir, which takes an empty folder and nothing
|
|
34
|
+
* else. A folder that was there before is never recorded and never removed.
|
|
35
|
+
*/
|
|
36
|
+
import { BridgeError } from '../bridge/errors.js';
|
|
37
|
+
/** How long a saved file may lie, fixed. */
|
|
38
|
+
export declare const KEEP_MS: number;
|
|
39
|
+
/** Proton accepts up to 25 MB per message, so no real attachment is larger. */
|
|
40
|
+
export declare const MAX_SAVE_BYTES: number;
|
|
41
|
+
/** The variable that names the directory. */
|
|
42
|
+
export declare const ATTACHMENT_DIR_VARIABLE = "PROTON_MCP_ATTACHMENT_DIR";
|
|
43
|
+
/** A refusal to save or delete, explained. Reported like any explained failure. */
|
|
44
|
+
export declare class SaveRefused extends BridgeError {
|
|
45
|
+
name: string;
|
|
46
|
+
}
|
|
47
|
+
/** One file this server wrote, with what identifies it as that file. */
|
|
48
|
+
export interface SavedEntry {
|
|
49
|
+
path: string;
|
|
50
|
+
dev: number;
|
|
51
|
+
ino: number;
|
|
52
|
+
size: number;
|
|
53
|
+
mtimeMs: number;
|
|
54
|
+
savedAt: string;
|
|
55
|
+
expiresAt: string;
|
|
56
|
+
}
|
|
57
|
+
/** A folder this server created, with what identifies it as that folder. */
|
|
58
|
+
export interface CreatedFolder {
|
|
59
|
+
path: string;
|
|
60
|
+
dev: number;
|
|
61
|
+
ino: number;
|
|
62
|
+
}
|
|
63
|
+
/** What save hands back. */
|
|
64
|
+
export interface SavedFile {
|
|
65
|
+
path: string;
|
|
66
|
+
size: number;
|
|
67
|
+
contentType: string;
|
|
68
|
+
sha256: string;
|
|
69
|
+
expiresAt: Date;
|
|
70
|
+
}
|
|
71
|
+
/** What delete did, file by file. */
|
|
72
|
+
export interface DeleteReport {
|
|
73
|
+
deleted: string[];
|
|
74
|
+
/** Changed or replaced since it was saved, so left in place. */
|
|
75
|
+
left: string[];
|
|
76
|
+
/** Already gone. */
|
|
77
|
+
missing: string[];
|
|
78
|
+
/** Not a file this server saved, so not touched. */
|
|
79
|
+
unknown: string[];
|
|
80
|
+
/** Folders this server had created and removed again, because they were empty. */
|
|
81
|
+
folders: string[];
|
|
82
|
+
}
|
|
83
|
+
export interface SavedAttachmentsOptions {
|
|
84
|
+
/** The directory from PROTON_MCP_ATTACHMENT_DIR, if it is set. */
|
|
85
|
+
root: string | undefined;
|
|
86
|
+
/** Where the records live. Defaults to this server's configuration directory. */
|
|
87
|
+
recordDir?: string;
|
|
88
|
+
now?: () => number;
|
|
89
|
+
pid?: number;
|
|
90
|
+
/** Whether a process still runs. Replaced in tests. */
|
|
91
|
+
isAlive?: (pid: number) => boolean;
|
|
92
|
+
}
|
|
93
|
+
/** Where the records live unless told otherwise. */
|
|
94
|
+
export declare function defaultRecordDir(): string;
|
|
95
|
+
export declare class SavedAttachments {
|
|
96
|
+
#private;
|
|
97
|
+
constructor(options: SavedAttachmentsOptions);
|
|
98
|
+
/** Whether the user set a directory at all. */
|
|
99
|
+
get configured(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Throws the explanation when nothing can be saved: no directory set, or one
|
|
102
|
+
* that is not there. Called before a message is fetched, so that nothing is
|
|
103
|
+
* decrypted only to be thrown away.
|
|
104
|
+
*/
|
|
105
|
+
assertUsable(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Saves one attachment. Synchronous on purpose: the file and its record are
|
|
108
|
+
* written in one step, with nothing in between that could stop halfway.
|
|
109
|
+
*/
|
|
110
|
+
save(name: string, content: Buffer, contentType: string, folder?: string): SavedFile;
|
|
111
|
+
/**
|
|
112
|
+
* Deletes what this server saved: everything, or the files named.
|
|
113
|
+
*
|
|
114
|
+
* A path it did not save is not touched, whatever it is.
|
|
115
|
+
*/
|
|
116
|
+
delete(paths?: string[]): DeleteReport;
|
|
117
|
+
/**
|
|
118
|
+
* Clears up after other servers: everything of one that no longer runs, and
|
|
119
|
+
* what one that still runs has left lying well past its hour.
|
|
120
|
+
*/
|
|
121
|
+
sweepOthers(): void;
|
|
122
|
+
/** Called once at start: clears up what earlier servers left. */
|
|
123
|
+
start(): void;
|
|
124
|
+
/** Called on the way out: deletes everything this server saved. */
|
|
125
|
+
stop(): void;
|
|
126
|
+
/** What this server has saved and not yet deleted. */
|
|
127
|
+
get entries(): readonly SavedEntry[];
|
|
128
|
+
}
|