@rndmjoker/proton-mcp 0.4.13 → 0.5.1

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.
Files changed (95) hide show
  1. package/README.md +138 -21
  2. package/dist/browser/channel.d.ts +76 -0
  3. package/dist/browser/channel.js +162 -0
  4. package/dist/browser/channel.js.map +1 -0
  5. package/dist/browser/client.d.ts +50 -0
  6. package/dist/browser/client.js +284 -0
  7. package/dist/browser/client.js.map +1 -0
  8. package/dist/browser/connector.d.ts +15 -0
  9. package/dist/browser/connector.js +26 -0
  10. package/dist/browser/connector.js.map +1 -0
  11. package/dist/browser/host.d.ts +50 -0
  12. package/dist/browser/host.js +258 -0
  13. package/dist/browser/host.js.map +1 -0
  14. package/dist/browser/messages.d.ts +163 -0
  15. package/dist/browser/messages.js +338 -0
  16. package/dist/browser/messages.js.map +1 -0
  17. package/dist/browser/paths.d.ts +51 -0
  18. package/dist/browser/paths.js +92 -0
  19. package/dist/browser/paths.js.map +1 -0
  20. package/dist/browser/setup.d.ts +42 -0
  21. package/dist/browser/setup.js +170 -0
  22. package/dist/browser/setup.js.map +1 -0
  23. package/dist/filters/order.d.ts +66 -0
  24. package/dist/filters/order.js +108 -0
  25. package/dist/filters/order.js.map +1 -0
  26. package/dist/filters/sieve.d.ts +184 -0
  27. package/dist/filters/sieve.js +1312 -0
  28. package/dist/filters/sieve.js.map +1 -0
  29. package/dist/instances.d.ts +58 -0
  30. package/dist/instances.js +145 -0
  31. package/dist/instances.js.map +1 -0
  32. package/dist/mail/compose.d.ts +10 -0
  33. package/dist/mail/compose.js.map +1 -1
  34. package/dist/mail/record.d.ts +36 -0
  35. package/dist/mail/record.js +74 -0
  36. package/dist/mail/record.js.map +1 -0
  37. package/dist/permissions.d.ts +139 -0
  38. package/dist/permissions.js +355 -0
  39. package/dist/permissions.js.map +1 -0
  40. package/dist/server.js +330 -16
  41. package/dist/server.js.map +1 -1
  42. package/dist/session.d.ts +12 -0
  43. package/dist/session.js +23 -0
  44. package/dist/session.js.map +1 -1
  45. package/dist/settings.d.ts +81 -11
  46. package/dist/settings.js +160 -25
  47. package/dist/settings.js.map +1 -1
  48. package/dist/tools/confirm.d.ts +4 -2
  49. package/dist/tools/confirm.js +12 -3
  50. package/dist/tools/confirm.js.map +1 -1
  51. package/dist/tools/failures.d.ts +6 -8
  52. package/dist/tools/failures.js +36 -17
  53. package/dist/tools/failures.js.map +1 -1
  54. package/dist/tools/filter-tools.d.ts +82 -0
  55. package/dist/tools/filter-tools.js +729 -0
  56. package/dist/tools/filter-tools.js.map +1 -0
  57. package/dist/tools/filters.d.ts +8 -1
  58. package/dist/tools/filters.js +24 -12
  59. package/dist/tools/filters.js.map +1 -1
  60. package/dist/tools/format.d.ts +15 -0
  61. package/dist/tools/format.js +4 -3
  62. package/dist/tools/format.js.map +1 -1
  63. package/dist/tools/guidance.js +12 -7
  64. package/dist/tools/guidance.js.map +1 -1
  65. package/dist/tools/interface.d.ts +20 -6
  66. package/dist/tools/interface.js +40 -16
  67. package/dist/tools/interface.js.map +1 -1
  68. package/dist/tools/send.d.ts +9 -0
  69. package/dist/tools/send.js +51 -9
  70. package/dist/tools/send.js.map +1 -1
  71. package/dist/tools/sign-in-required.d.ts +15 -2
  72. package/dist/tools/sign-in-required.js +30 -2
  73. package/dist/tools/sign-in-required.js.map +1 -1
  74. package/dist/web/layout.d.ts +12 -5
  75. package/dist/web/layout.js +32 -9
  76. package/dist/web/layout.js.map +1 -1
  77. package/dist/web/pages.d.ts +4 -2
  78. package/dist/web/pages.js +10 -9
  79. package/dist/web/pages.js.map +1 -1
  80. package/dist/web/pending.d.ts +9 -6
  81. package/dist/web/pending.js +9 -7
  82. package/dist/web/pending.js.map +1 -1
  83. package/dist/web/preview-server.d.ts +33 -0
  84. package/dist/web/preview-server.js +102 -0
  85. package/dist/web/preview-server.js.map +1 -0
  86. package/dist/web/sections.d.ts +86 -1
  87. package/dist/web/sections.js +290 -7
  88. package/dist/web/sections.js.map +1 -1
  89. package/dist/web/security.d.ts +48 -13
  90. package/dist/web/security.js +68 -16
  91. package/dist/web/security.js.map +1 -1
  92. package/dist/web/server.d.ts +81 -24
  93. package/dist/web/server.js +478 -117
  94. package/dist/web/server.js.map +1 -1
  95. package/package.json +1 -1
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Sets up the connector Chrome starts for the browser extension (#59):
3
+ *
4
+ * npx -y @rndmjoker/proton-mcp@latest setup-browser
5
+ * npx -y @rndmjoker/proton-mcp@latest setup-browser --remove
6
+ *
7
+ * What it writes, all of it for the current user only:
8
+ *
9
+ * - the secret both ends of the socket prove they know, readable by the user
10
+ * alone, kept when the setup runs again so a running connector keeps working;
11
+ * - a copy of the connector's five files under ~/.config/proton-mcp/browser/host,
12
+ * because an npx cache can be cleared or replaced at any time and Chrome
13
+ * needs a path that stays;
14
+ * - a launcher that starts that copy with the Node this setup runs under,
15
+ * since Chrome's environment may not find Node on its own;
16
+ * - the manifest that tells Chrome which program the extension may start,
17
+ * naming the extension's fixed id as the only one allowed;
18
+ * - on Windows, the registry entry that points Chrome at that manifest.
19
+ *
20
+ * Nothing here needs administrator rights, and nothing outside the user's own
21
+ * directories and, on Windows, the user's own part of the registry is touched.
22
+ */
23
+ import { mkdirSync, writeFileSync, copyFileSync, chmodSync, existsSync, rmSync, readFileSync } from 'node:fs';
24
+ import { execFileSync } from 'node:child_process';
25
+ import { randomBytes } from 'node:crypto';
26
+ import { dirname, join, win32 } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { HOST_NAME, EXTENSION_ID } from './messages.js';
29
+ import { browserPaths, UnsupportedPlatform } from './paths.js';
30
+ import { readSecret } from './channel.js';
31
+ /** The files the connector needs, copied together because they import each other and nothing else. */
32
+ export const HOST_FILES = ['connector.js', 'host.js', 'messages.js', 'paths.js', 'channel.js'];
33
+ /** Quotes a path for a POSIX shell script: single quotes, with any single quote closed and escaped. */
34
+ function shellQuote(value) {
35
+ return `'${value.replace(/'/g, `'\\''`)}'`;
36
+ }
37
+ function launcherText(paths, node) {
38
+ if (paths.platform === 'win32') {
39
+ // Chrome runs a .cmd through cmd.exe and passes the origin and the parent
40
+ // window as arguments, which %* hands on. cmd.exe reads a batch file in
41
+ // the console's code page, so it switches to UTF-8 first for paths with
42
+ // letters like the umlauts in a user name, and a percent sign in a path
43
+ // is doubled so cmd.exe does not read it as a variable. Not measured on
44
+ // Windows yet.
45
+ const quoted = (value) => `"${value.replace(/%/g, '%%')}"`;
46
+ return ('@echo off\r\n' +
47
+ 'rem Written by proton-mcp setup-browser. Chrome starts this for the extension.\r\n' +
48
+ 'chcp 65001 >nul\r\n' +
49
+ `${quoted(node)} ${quoted(paths.hostScript)} %*\r\n`);
50
+ }
51
+ return ('#!/bin/sh\n' +
52
+ '# Written by proton-mcp setup-browser. Chrome starts this for the extension.\n' +
53
+ `exec ${shellQuote(node)} ${shellQuote(paths.hostScript)} "$@"\n`);
54
+ }
55
+ function manifestText(paths) {
56
+ return `${JSON.stringify({
57
+ name: HOST_NAME,
58
+ description: 'Connector for the Proton MCP Helper extension',
59
+ path: paths.launcher,
60
+ type: 'stdio',
61
+ allowed_origins: [`chrome-extension://${EXTENSION_ID}/`],
62
+ }, null, 2)}\n`;
63
+ }
64
+ function defaultRegistry(args) {
65
+ // By its full path: a bare name is looked up in the current directory first.
66
+ const reg = win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'reg.exe');
67
+ execFileSync(reg, args, { stdio: 'ignore', windowsHide: true });
68
+ }
69
+ /** Writes everything, or with remove takes it all away again. Returns the exit code. */
70
+ export function setupBrowser(options = {}) {
71
+ const write = options.write ?? ((text) => process.stdout.write(text));
72
+ let paths;
73
+ try {
74
+ paths = options.paths ?? browserPaths(options.platform !== undefined ? { platform: options.platform } : {});
75
+ }
76
+ catch (error) {
77
+ if (error instanceof UnsupportedPlatform) {
78
+ write(`${error.message}\n`);
79
+ return 1;
80
+ }
81
+ throw error;
82
+ }
83
+ const registry = options.registry ?? defaultRegistry;
84
+ if (options.remove) {
85
+ rmSync(paths.manifestFile, { force: true });
86
+ rmSync(paths.hostDir, { recursive: true, force: true });
87
+ rmSync(paths.secretFile, { force: true });
88
+ if (paths.registryKey) {
89
+ try {
90
+ registry(['delete', paths.registryKey, '/f']);
91
+ }
92
+ catch {
93
+ // Not there to begin with.
94
+ }
95
+ }
96
+ write('The connector for the browser extension is removed: its manifest, its copy and its secret.\n' +
97
+ 'Remove the extension itself in chrome://extensions.\n');
98
+ return 0;
99
+ }
100
+ const node = options.node ?? process.execPath;
101
+ const sourceDir = options.sourceDir ?? dirname(fileURLToPath(import.meta.url));
102
+ const missing = HOST_FILES.filter((file) => !existsSync(join(sourceDir, file)));
103
+ if (missing.length > 0) {
104
+ write(`The connector's files are missing from ${sourceDir}: ${missing.join(', ')}. Build the server first.\n`);
105
+ return 1;
106
+ }
107
+ mkdirSync(paths.browserDir, { recursive: true, mode: 0o700 });
108
+ mkdirSync(paths.hostDir, { recursive: true, mode: 0o700 });
109
+ if (paths.platform !== 'win32') {
110
+ chmodSync(paths.browserDir, 0o700);
111
+ chmodSync(paths.hostDir, 0o700);
112
+ }
113
+ // Kept when it exists: a connector Chrome already started read it once and
114
+ // would refuse every connection after a change, until the extension reloads.
115
+ const keptSecret = readSecret(paths.secretFile) !== undefined;
116
+ if (!keptSecret) {
117
+ writeFileSync(paths.secretFile, `${randomBytes(32).toString('hex')}\n`, { mode: 0o600 });
118
+ }
119
+ if (paths.platform !== 'win32')
120
+ chmodSync(paths.secretFile, 0o600);
121
+ for (const file of HOST_FILES)
122
+ copyFileSync(join(sourceDir, file), join(paths.hostDir, file));
123
+ // The copies are ES modules and need to be read as such without the package around them.
124
+ writeFileSync(join(paths.hostDir, 'package.json'), '{ "type": "module", "private": true }\n');
125
+ writeFileSync(paths.launcher, launcherText(paths, node), { mode: 0o700 });
126
+ if (paths.platform !== 'win32')
127
+ chmodSync(paths.launcher, 0o700);
128
+ mkdirSync(dirname(paths.manifestFile), { recursive: true });
129
+ // Readable by the user alone: whoever can change "path" in it decides what
130
+ // Chrome starts as this user.
131
+ writeFileSync(paths.manifestFile, manifestText(paths), { mode: 0o600 });
132
+ if (paths.platform !== 'win32')
133
+ chmodSync(paths.manifestFile, 0o600);
134
+ if (paths.registryKey) {
135
+ registry(['add', paths.registryKey, '/ve', '/t', 'REG_SZ', '/d', paths.manifestFile, '/f']);
136
+ }
137
+ write([
138
+ 'The connector for the browser extension is set up.',
139
+ '',
140
+ ` Connector: ${paths.launcher}`,
141
+ ` Node: ${node}`,
142
+ ` Manifest: ${paths.manifestFile}`,
143
+ ...(paths.registryKey ? [` Registry: ${paths.registryKey}`] : []),
144
+ ` Secret: ${paths.secretFile}${keptSecret ? ' (kept)' : ' (new)'}`,
145
+ ` Socket: ${paths.socket}`,
146
+ '',
147
+ 'Next:',
148
+ ' 1. Load the extension in Chrome: open chrome://extensions, switch on Developer mode,',
149
+ ' choose "Load unpacked" and pick the unpacked folder of the extension from the release.',
150
+ ` Its id has to be ${EXTENSION_ID}.`,
151
+ ' If it was loaded before, click its reload arrow instead.',
152
+ ' 2. Reload any open Proton tab once, or open https://mail.proton.me and sign in.',
153
+ ' 3. Ask your assistant to list your filters.',
154
+ '',
155
+ 'If Node moves, for example after an upgrade under another path, run this again.',
156
+ 'To take all of it away: the same command with --remove.',
157
+ '',
158
+ ].join('\n'));
159
+ return 0;
160
+ }
161
+ /** Reads the manifest back, for the diagnosis and the tests. */
162
+ export function readManifest(paths) {
163
+ try {
164
+ return JSON.parse(readFileSync(paths.manifestFile, 'utf8'));
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ }
170
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/browser/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Moving filters in the order Proton runs them (#59), shared by the server and
3
+ * the extension. Nothing from Node in here.
4
+ *
5
+ * Proton runs every filter on every incoming message, top to bottom. A filter
6
+ * that matches does not stop the ones after it; only stop and discard end the
7
+ * run. A message lives in one folder, so when several filters file it into
8
+ * folders, the last one wins, while labels add up. So the order decides where
9
+ * mail ends up, and moving a filter can change that for every message it and
10
+ * the filters it passes have in common.
11
+ *
12
+ * Proton takes a new order as the complete list of ids. Assistants describe
13
+ * moves instead, one or several, each a filter and the position it should
14
+ * end up at, applied one after the other. Asked for by Tobi on 06.11.2026.
15
+ */
16
+ export interface FilterMove {
17
+ id: string;
18
+ /** Where the filter ends up, counted from 1 as list_filters shows the order. */
19
+ position: number;
20
+ }
21
+ /** A move that cannot be made. The message says why. */
22
+ export declare class OrderError extends Error {
23
+ }
24
+ /** The most moves taken in one go. */
25
+ export declare const MAX_MOVES = 50;
26
+ /**
27
+ * The order after the moves, each applied to the result of the one before.
28
+ * Throws OrderError for a filter that does not exist, a position outside the
29
+ * list, or a filter moved twice, which would leave its place to chance.
30
+ */
31
+ export declare function applyMoves(order: readonly string[], moves: readonly FilterMove[]): string[];
32
+ /**
33
+ * The filters whose place relative to this one changed: before the move they
34
+ * ran on one side of it, after the move on the other. For each of them, the
35
+ * outcome for a message both match can now be different.
36
+ */
37
+ export declare function passed(before: readonly string[], after: readonly string[], id: string): string[];
38
+ /** True when both lists hold the same ids in the same order. */
39
+ export declare function sameOrder(a: readonly string[], b: readonly string[]): boolean;
40
+ /** A filter whose place changed relative to others, and on which side of them it now runs. */
41
+ export interface MovedFilter {
42
+ id: string;
43
+ /** Positions counted from 1. */
44
+ from: number;
45
+ to: number;
46
+ /** The filters it used to run after and now runs before. */
47
+ before: string[];
48
+ /** The filters it used to run before and now runs after: against these it now has the last word. */
49
+ after: string[];
50
+ }
51
+ /**
52
+ * The filters that were asked to move and now stand in a new place relative
53
+ * to at least one other, even when their position number did not change:
54
+ * moved to the front and pushed back by a later move, a filter can end on its
55
+ * old number with a different neighbour order. The ones that only shifted
56
+ * because another moved past them are not listed; they appear in the lists of
57
+ * the one that did.
58
+ */
59
+ export declare function movedFilters(before: readonly string[], after: readonly string[], ids: readonly string[]): MovedFilter[];
60
+ /**
61
+ * Where a move touches a filter that does more than file and flag, such as
62
+ * filing into Trash, whether the user allowed it or not: the order decides
63
+ * which folder wins and what still runs after a stop, so the person agreeing
64
+ * should look twice there. One line per such filter and move.
65
+ */
66
+ export declare function orderWarnings(moved: readonly MovedFilter[], refusedOf: (id: string) => string[], nameOf: (id: string) => string): string[];
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Moving filters in the order Proton runs them (#59), shared by the server and
3
+ * the extension. Nothing from Node in here.
4
+ *
5
+ * Proton runs every filter on every incoming message, top to bottom. A filter
6
+ * that matches does not stop the ones after it; only stop and discard end the
7
+ * run. A message lives in one folder, so when several filters file it into
8
+ * folders, the last one wins, while labels add up. So the order decides where
9
+ * mail ends up, and moving a filter can change that for every message it and
10
+ * the filters it passes have in common.
11
+ *
12
+ * Proton takes a new order as the complete list of ids. Assistants describe
13
+ * moves instead, one or several, each a filter and the position it should
14
+ * end up at, applied one after the other. Asked for by Tobi on 06.11.2026.
15
+ */
16
+ /** A move that cannot be made. The message says why. */
17
+ export class OrderError extends Error {
18
+ }
19
+ /** The most moves taken in one go. */
20
+ export const MAX_MOVES = 50;
21
+ /**
22
+ * The order after the moves, each applied to the result of the one before.
23
+ * Throws OrderError for a filter that does not exist, a position outside the
24
+ * list, or a filter moved twice, which would leave its place to chance.
25
+ */
26
+ export function applyMoves(order, moves) {
27
+ if (moves.length === 0)
28
+ throw new OrderError('No move was given.');
29
+ if (moves.length > MAX_MOVES)
30
+ throw new OrderError(`At most ${MAX_MOVES} moves are taken at once.`);
31
+ const result = [...order];
32
+ const seen = new Set();
33
+ for (const move of moves) {
34
+ if (seen.has(move.id))
35
+ throw new OrderError(`The filter ${move.id} is moved twice. Move each filter once.`);
36
+ seen.add(move.id);
37
+ const from = result.indexOf(move.id);
38
+ if (from < 0)
39
+ throw new OrderError(`There is no filter with the id ${move.id}. Call list_filters for the current ones.`);
40
+ if (!Number.isInteger(move.position) || move.position < 1 || move.position > result.length) {
41
+ throw new OrderError(`Position ${move.position} is outside the order, which runs from 1 to ${result.length}.`);
42
+ }
43
+ result.splice(from, 1);
44
+ result.splice(move.position - 1, 0, move.id);
45
+ }
46
+ return result;
47
+ }
48
+ /**
49
+ * The filters whose place relative to this one changed: before the move they
50
+ * ran on one side of it, after the move on the other. For each of them, the
51
+ * outcome for a message both match can now be different.
52
+ */
53
+ export function passed(before, after, id) {
54
+ const was = before.indexOf(id);
55
+ const is = after.indexOf(id);
56
+ return after.filter((other) => other !== id && before.indexOf(other) < was !== after.indexOf(other) < is);
57
+ }
58
+ /** True when both lists hold the same ids in the same order. */
59
+ export function sameOrder(a, b) {
60
+ return a.length === b.length && a.every((id, index) => id === b[index]);
61
+ }
62
+ /**
63
+ * The filters that were asked to move and now stand in a new place relative
64
+ * to at least one other, even when their position number did not change:
65
+ * moved to the front and pushed back by a later move, a filter can end on its
66
+ * old number with a different neighbour order. The ones that only shifted
67
+ * because another moved past them are not listed; they appear in the lists of
68
+ * the one that did.
69
+ */
70
+ export function movedFilters(before, after, ids) {
71
+ const moved = [];
72
+ for (const id of new Set(ids)) {
73
+ const others = passed(before, after, id);
74
+ if (others.length === 0)
75
+ continue;
76
+ const at = after.indexOf(id);
77
+ moved.push({
78
+ id,
79
+ from: before.indexOf(id) + 1,
80
+ to: at + 1,
81
+ before: others.filter((other) => after.indexOf(other) > at),
82
+ after: others.filter((other) => after.indexOf(other) < at),
83
+ });
84
+ }
85
+ return moved;
86
+ }
87
+ /**
88
+ * Where a move touches a filter that does more than file and flag, such as
89
+ * filing into Trash, whether the user allowed it or not: the order decides
90
+ * which folder wins and what still runs after a stop, so the person agreeing
91
+ * should look twice there. One line per such filter and move.
92
+ */
93
+ export function orderWarnings(moved, refusedOf, nameOf) {
94
+ const warnings = [];
95
+ for (const move of moved) {
96
+ for (const other of [move.id, ...move.before, ...move.after]) {
97
+ const refused = refusedOf(other);
98
+ if (refused.length === 0)
99
+ continue;
100
+ const warning = `"${nameOf(other)}" does more than file and flag (${refused.join(', ')}), ` +
101
+ `and its place relative to "${nameOf(move.id)}" changes.`;
102
+ if (!warnings.includes(warning))
103
+ warnings.push(warning);
104
+ }
105
+ }
106
+ return warnings;
107
+ }
108
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.js","sourceRoot":"","sources":["../../src/filters/order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,wDAAwD;AACxD,MAAM,OAAO,UAAW,SAAQ,KAAK;CAAG;AAExC,sCAAsC;AACtC,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAA;AAE3B;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAwB,EAAE,KAA4B;IAC/E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,oBAAoB,CAAC,CAAA;IAClE,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS;QAAE,MAAM,IAAI,UAAU,CAAC,WAAW,SAAS,2BAA2B,CAAC,CAAA;IACnG,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,cAAc,IAAI,CAAC,EAAE,yCAAyC,CAAC,CAAA;QAC3G,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACjB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpC,IAAI,IAAI,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,kCAAkC,IAAI,CAAC,EAAE,2CAA2C,CAAC,CAAA;QACxH,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC3F,MAAM,IAAI,UAAU,CAAC,YAAY,IAAI,CAAC,QAAQ,+CAA+C,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;QAChH,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACtB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,MAAyB,EAAE,KAAwB,EAAE,EAAU;IACpF,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC9B,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;AAC3G,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,SAAS,CAAC,CAAoB,EAAE,CAAoB;IAClE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;AACzE,CAAC;AAcD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAyB,EAAE,KAAwB,EAAE,GAAsB;IACtG,MAAM,KAAK,GAAkB,EAAE,CAAA;IAC/B,KAAK,MAAM,EAAE,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;QACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QACjC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC5B,KAAK,CAAC,IAAI,CAAC;YACT,EAAE;YACF,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC;YAC5B,EAAE,EAAE,EAAE,GAAG,CAAC;YACV,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YAC3D,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;SAC3D,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA6B,EAC7B,SAAmC,EACnC,MAA8B;IAE9B,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7D,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;YAChC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAQ;YAClC,MAAM,OAAO,GACX,IAAI,MAAM,CAAC,KAAK,CAAC,mCAAmC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;gBAC3E,8BAA8B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAA;YAC3D,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACzD,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Reading and checking a Sieve script before it reaches Proton (#59).
3
+ *
4
+ * A filter keeps acting on every message that arrives, long after the
5
+ * conversation that wrote it. So a script an assistant proposes is held to a
6
+ * list of what it may do, not to a list of what it may not: anything this file
7
+ * does not know is refused, and the refusal says why.
8
+ *
9
+ * The same code runs in two places: in this server before a proposal leaves,
10
+ * and in the browser extension before anything is written. Neither trusts the
11
+ * check of the other, which is why this file uses nothing from Node.
12
+ *
13
+ * What it refuses, as decided on 05.11.2026:
14
+ *
15
+ * - discard, expire, reject and vacation: they delete mail, turn it away or
16
+ * answer it in the user's name.
17
+ * - stop: it ends every later filter for a matching message, which switches
18
+ * those filters off without anyone seeing it. Proton's own assistant allows
19
+ * it; this one does not.
20
+ * - filing into Trash or Spam: it hides mail.
21
+ * - a script without Proton's spam guard at the top: without it the filter
22
+ * runs on spam as well and can file spam among normal mail.
23
+ * - a script that does nothing beyond the guard: an empty filter is how a rule
24
+ * is switched off, and switching off is the user's decision.
25
+ *
26
+ * Tightened on 06.11.2026, after two reviewers found scripts that passed and
27
+ * read differently in the confirmation from what they do, or that Proton's
28
+ * own parser may read differently from this one:
29
+ *
30
+ * - no comment but Proton's own two, the line above the spam guard and the
31
+ * one its filter editor writes above each rule, and no text: blocks: both
32
+ * are where Proton's public parser (libsieve-php) and RFC 5228 part ways,
33
+ * and where a discard can sit that looks commented out;
34
+ * - no line break, no invisible character and no lone carriage return
35
+ * anywhere, and addresses in plain ASCII, as for recipients since #42;
36
+ * - the spam guard written as Proton writes it, without escapes;
37
+ * - no :regex and no :eval: a pattern or a sum that fails while Proton runs
38
+ * the filter might end it like stop, and that is not known;
39
+ * - folder paths that name Trash, Spam or Junk in any spelling, and folders
40
+ * given by Proton's internal numbers;
41
+ * - arguments, tags and comparators that are not on the list, and conditions
42
+ * where only allof, anyof and not take them;
43
+ * - rules that can never match or are never reached count as doing nothing.
44
+ *
45
+ * It is not Proton's check and does not replace it. Proton reads the grammar
46
+ * again through mail/v4/filters/check before anything is saved.
47
+ */
48
+ /**
49
+ * The longest script accepted. A filter is confirmed by reading it, so it has
50
+ * to stay readable. Raised from 20,000 on 06.11.2026, after a real account
51
+ * held a filter of 20,130 characters, a long list of senders, that an
52
+ * assistant could then neither change nor apply. A change to such a script is
53
+ * read through the difference the window shows, not line by line.
54
+ */
55
+ export declare const MAX_SCRIPT_LENGTH = 50000;
56
+ /** The longest filter name accepted. */
57
+ export declare const MAX_NAME_LENGTH = 100;
58
+ /** One of the two comments a script may carry: Proton's own line above the spam guard. */
59
+ export declare const GUARD_COMMENT = "# Generated: Do not run this script on spam messages";
60
+ /**
61
+ * The other: the comment Proton's filter editor writes above each rule, which
62
+ * its settings read back to show the rule in the simple editor. It holds
63
+ * nothing but Proton's fixed words, "@type and" or "@type or" and one
64
+ * "@comparator" per condition, so nothing can hide in it. Allowed since Tobi
65
+ * decided so on 06.11.2026: 94 of his 98 filters carry it, and refused, it
66
+ * kept an assistant from replacing or applying any of them. Proton writes it
67
+ * with Windows line ends; a plain line feed is read the same.
68
+ */
69
+ export declare const EDITOR_COMMENT: RegExp;
70
+ /**
71
+ * Proton's spam guard, as Proton's own filter editor and assistant write it.
72
+ *
73
+ * The comparison below is on the parsed structure and the strings as written,
74
+ * so a reformatted guard still counts, an escaped one does not. The text is
75
+ * here for the tool description and the examples, which must show the guard
76
+ * the same way Proton does.
77
+ */
78
+ export declare const SPAM_GUARD_PROLOGUE: string;
79
+ /**
80
+ * What a filter may do beyond filing and flags, each only where the user
81
+ * allows it: on the Permissions page of proton-mcp and, as a second lock, in
82
+ * the popup of the extension. All off by default. Tobi's principle of
83
+ * 06.11.2026: what an assistant may set up is the user's decision, and the
84
+ * settings are where they make it. The checks against a script that reads
85
+ * differently from what it does stay, whatever is allowed, and so does the
86
+ * spam guard.
87
+ */
88
+ export declare const UNLOCKABLE: readonly ['trash', 'discard', 'expire', 'reject', 'vacation', 'stop'];
89
+ export type Unlockable = (typeof UNLOCKABLE)[number];
90
+ /** How each is named for people, and what it costs, in the settings, in refusals and in the window. */
91
+ export declare const UNLOCKABLE_TEXT: Readonly<Record<Unlockable, {
92
+ title: string;
93
+ risk: string;
94
+ }>>;
95
+ /** Nothing allowed beyond filing and flags: how every check runs unless the user allowed more. */
96
+ export declare const NO_UNLOCKS: ReadonlySet<Unlockable>;
97
+ /** A list of allowed actions as read from settings or a message, or nothing when it is not one. */
98
+ export declare function parseUnlocks(value: unknown): Unlockable[] | undefined;
99
+ export type SieveArgument = {
100
+ kind: 'strings';
101
+ values: string[];
102
+ raws: string[];
103
+ list: boolean;
104
+ multiLine: boolean;
105
+ line: number;
106
+ } | {
107
+ kind: 'number';
108
+ value: number;
109
+ line: number;
110
+ } | {
111
+ kind: 'tag';
112
+ name: string;
113
+ line: number;
114
+ };
115
+ export interface SieveTest {
116
+ name: string;
117
+ args: SieveArgument[];
118
+ tests: SieveTest[];
119
+ line: number;
120
+ }
121
+ export interface SieveCommand {
122
+ name: string;
123
+ args: SieveArgument[];
124
+ tests: SieveTest[];
125
+ block?: SieveCommand[];
126
+ line: number;
127
+ }
128
+ /** A comment as written, kept so the check can see it. */
129
+ export interface SieveComment {
130
+ kind: 'hash' | 'bracket';
131
+ text: string;
132
+ line: number;
133
+ }
134
+ /** A script this file cannot read. The line is where the reading stopped. */
135
+ export declare class SieveSyntaxError extends Error {
136
+ readonly line: number;
137
+ constructor(message: string, line: number);
138
+ }
139
+ /** Reads a script into commands. Throws SieveSyntaxError when it cannot. */
140
+ export declare function parseSieve(source: string): SieveCommand[];
141
+ export interface SieveReview {
142
+ /** Why the script cannot be proposed. Empty when it can. */
143
+ problems: string[];
144
+ /** What the script does to a matching message, in plain words, without repeats. */
145
+ effects: string[];
146
+ /** What a person should look at twice before agreeing: mail that would go unnoticed. */
147
+ warnings: string[];
148
+ /**
149
+ * The refused things the script does, in a few words each, such as
150
+ * "discard" or "filing into Trash". Empty when it does none of them. For a
151
+ * script this file cannot read, it says so. Used where a filter the user
152
+ * wrote is moved: the order decides what those actions end up doing.
153
+ */
154
+ refused: string[];
155
+ /**
156
+ * What the script does beyond filing and flags that the user allowed, in
157
+ * the same words. Shown where filters move, like refused: the order
158
+ * decides what those actions end up doing.
159
+ */
160
+ unlocked: string[];
161
+ }
162
+ /** Why a filter name cannot be used, or nothing when it can. */
163
+ export declare function filterNameProblem(name: string): string | undefined;
164
+ /**
165
+ * The path segments of a folder name, folded the way a lenient reader might:
166
+ * compatibility forms joined (fullwidth letters, the long s), case dropped,
167
+ * the backslashes Proton uses to escape a slash removed, empty and "." parts
168
+ * dropped. Whatever Proton does exactly, a name that folds to Trash, Spam or
169
+ * Junk here is refused.
170
+ */
171
+ export declare function folderSegments(target: string): string[];
172
+ /**
173
+ * Checks a proposed script against everything above.
174
+ *
175
+ * Every problem is listed, not just the first, so an assistant can correct
176
+ * the script in one go rather than one refusal at a time.
177
+ */
178
+ export declare function reviewSieve(source: string, allowed?: ReadonlySet<Unlockable>): SieveReview;
179
+ /**
180
+ * Whether two scripts are the same filter as far as a person could tell:
181
+ * the same characters, with line ends and trailing spaces evened out. Proton
182
+ * may store a script with other line ends than it was sent with.
183
+ */
184
+ export declare function sameScript(a: string, b: string): boolean;