proton-mail-bridge-client 2.16.3 → 2.17.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 (231) hide show
  1. package/README.md +149 -12
  2. package/dist/cli.d.ts +16 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +194 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config-env.d.ts +2 -1
  7. package/dist/config-env.d.ts.map +1 -1
  8. package/dist/config-env.js +24 -0
  9. package/dist/config-env.js.map +1 -1
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +172 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/services/account-manager.d.ts +7 -0
  15. package/dist/services/account-manager.d.ts.map +1 -1
  16. package/dist/services/account-manager.js +59 -0
  17. package/dist/services/account-manager.js.map +1 -1
  18. package/dist/services/audit-service.d.ts +20 -0
  19. package/dist/services/audit-service.d.ts.map +1 -1
  20. package/dist/services/audit-service.js +266 -9
  21. package/dist/services/audit-service.js.map +1 -1
  22. package/dist/services/autoresponder-service.d.ts +35 -6
  23. package/dist/services/autoresponder-service.d.ts.map +1 -1
  24. package/dist/services/autoresponder-service.js +223 -108
  25. package/dist/services/autoresponder-service.js.map +1 -1
  26. package/dist/services/brief-service.d.ts.map +1 -1
  27. package/dist/services/brief-service.js +3 -2
  28. package/dist/services/brief-service.js.map +1 -1
  29. package/dist/services/category-service.d.ts +91 -0
  30. package/dist/services/category-service.d.ts.map +1 -0
  31. package/dist/services/category-service.js +320 -0
  32. package/dist/services/category-service.js.map +1 -0
  33. package/dist/services/commitment-service.d.ts.map +1 -1
  34. package/dist/services/commitment-service.js +3 -2
  35. package/dist/services/commitment-service.js.map +1 -1
  36. package/dist/services/contact-service.d.ts +60 -0
  37. package/dist/services/contact-service.d.ts.map +1 -0
  38. package/dist/services/contact-service.js +248 -0
  39. package/dist/services/contact-service.js.map +1 -0
  40. package/dist/services/delivery-queue-service.d.ts +4 -0
  41. package/dist/services/delivery-queue-service.d.ts.map +1 -1
  42. package/dist/services/delivery-queue-service.js +12 -2
  43. package/dist/services/delivery-queue-service.js.map +1 -1
  44. package/dist/services/draft-store-service.d.ts.map +1 -1
  45. package/dist/services/draft-store-service.js +3 -2
  46. package/dist/services/draft-store-service.js.map +1 -1
  47. package/dist/services/embedding-service.d.ts +68 -0
  48. package/dist/services/embedding-service.d.ts.map +1 -0
  49. package/dist/services/embedding-service.js +465 -0
  50. package/dist/services/embedding-service.js.map +1 -0
  51. package/dist/services/event-log-service.d.ts +67 -0
  52. package/dist/services/event-log-service.d.ts.map +1 -0
  53. package/dist/services/event-log-service.js +280 -0
  54. package/dist/services/event-log-service.js.map +1 -0
  55. package/dist/services/grant-service.d.ts +84 -0
  56. package/dist/services/grant-service.d.ts.map +1 -0
  57. package/dist/services/grant-service.js +214 -0
  58. package/dist/services/grant-service.js.map +1 -0
  59. package/dist/services/local-index-service.d.ts +18 -1
  60. package/dist/services/local-index-service.d.ts.map +1 -1
  61. package/dist/services/local-index-service.js +54 -4
  62. package/dist/services/local-index-service.js.map +1 -1
  63. package/dist/services/reply-reminder-service.d.ts +4 -0
  64. package/dist/services/reply-reminder-service.d.ts.map +1 -1
  65. package/dist/services/reply-reminder-service.js +22 -2
  66. package/dist/services/reply-reminder-service.js.map +1 -1
  67. package/dist/services/rules-service.d.ts +9 -1
  68. package/dist/services/rules-service.d.ts.map +1 -1
  69. package/dist/services/rules-service.js +8 -3
  70. package/dist/services/rules-service.js.map +1 -1
  71. package/dist/services/sieve-export.d.ts +19 -0
  72. package/dist/services/sieve-export.d.ts.map +1 -0
  73. package/dist/services/sieve-export.js +192 -0
  74. package/dist/services/sieve-export.js.map +1 -0
  75. package/dist/services/simple-imap-service.d.ts +13 -0
  76. package/dist/services/simple-imap-service.d.ts.map +1 -1
  77. package/dist/services/simple-imap-service.js +140 -17
  78. package/dist/services/simple-imap-service.js.map +1 -1
  79. package/dist/services/snooze-service.d.ts +4 -0
  80. package/dist/services/snooze-service.d.ts.map +1 -1
  81. package/dist/services/snooze-service.js +12 -3
  82. package/dist/services/snooze-service.js.map +1 -1
  83. package/dist/services/template-service.d.ts.map +1 -1
  84. package/dist/services/template-service.js +3 -2
  85. package/dist/services/template-service.js.map +1 -1
  86. package/dist/services/triage-plan-service.d.ts +2 -1
  87. package/dist/services/triage-plan-service.d.ts.map +1 -1
  88. package/dist/services/triage-plan-service.js +3 -2
  89. package/dist/services/triage-plan-service.js.map +1 -1
  90. package/dist/tools/attachments.js +1 -1
  91. package/dist/tools/attachments.js.map +1 -1
  92. package/dist/tools/categories.d.ts +4 -0
  93. package/dist/tools/categories.d.ts.map +1 -0
  94. package/dist/tools/categories.js +269 -0
  95. package/dist/tools/categories.js.map +1 -0
  96. package/dist/tools/contacts.d.ts +6 -0
  97. package/dist/tools/contacts.d.ts.map +1 -0
  98. package/dist/tools/contacts.js +295 -0
  99. package/dist/tools/contacts.js.map +1 -0
  100. package/dist/tools/context.d.ts +4 -0
  101. package/dist/tools/context.d.ts.map +1 -1
  102. package/dist/tools/context.js +3 -0
  103. package/dist/tools/context.js.map +1 -1
  104. package/dist/tools/definitions.d.ts +333 -5
  105. package/dist/tools/definitions.d.ts.map +1 -1
  106. package/dist/tools/definitions.js +169 -5
  107. package/dist/tools/definitions.js.map +1 -1
  108. package/dist/tools/delegation.d.ts +44 -0
  109. package/dist/tools/delegation.d.ts.map +1 -0
  110. package/dist/tools/delegation.js +180 -0
  111. package/dist/tools/delegation.js.map +1 -0
  112. package/dist/tools/diagnostics.d.ts.map +1 -1
  113. package/dist/tools/diagnostics.js +6 -0
  114. package/dist/tools/diagnostics.js.map +1 -1
  115. package/dist/tools/drafts-support.d.ts +2 -1
  116. package/dist/tools/drafts-support.d.ts.map +1 -1
  117. package/dist/tools/drafts-support.js +10 -2
  118. package/dist/tools/drafts-support.js.map +1 -1
  119. package/dist/tools/drafts.d.ts.map +1 -1
  120. package/dist/tools/drafts.js +8 -0
  121. package/dist/tools/drafts.js.map +1 -1
  122. package/dist/tools/events.d.ts +47 -0
  123. package/dist/tools/events.d.ts.map +1 -0
  124. package/dist/tools/events.js +249 -0
  125. package/dist/tools/events.js.map +1 -0
  126. package/dist/tools/reading.d.ts.map +1 -1
  127. package/dist/tools/reading.js +123 -3
  128. package/dist/tools/reading.js.map +1 -1
  129. package/dist/tools/resources.d.ts +4 -0
  130. package/dist/tools/resources.d.ts.map +1 -1
  131. package/dist/tools/resources.js +9 -0
  132. package/dist/tools/resources.js.map +1 -1
  133. package/dist/tools/rules-runner.d.ts.map +1 -1
  134. package/dist/tools/rules-runner.js +1 -0
  135. package/dist/tools/rules-runner.js.map +1 -1
  136. package/dist/tools/rules.d.ts +9 -0
  137. package/dist/tools/rules.d.ts.map +1 -1
  138. package/dist/tools/rules.js +50 -19
  139. package/dist/tools/rules.js.map +1 -1
  140. package/dist/tools/sending.d.ts.map +1 -1
  141. package/dist/tools/sending.js +4 -0
  142. package/dist/tools/sending.js.map +1 -1
  143. package/dist/tools/style.d.ts +2 -0
  144. package/dist/tools/style.d.ts.map +1 -1
  145. package/dist/tools/style.js +1 -1
  146. package/dist/tools/style.js.map +1 -1
  147. package/dist/tools/templates-and-reminders.d.ts +3 -0
  148. package/dist/tools/templates-and-reminders.d.ts.map +1 -1
  149. package/dist/tools/templates-and-reminders.js +31 -17
  150. package/dist/tools/templates-and-reminders.js.map +1 -1
  151. package/dist/tools/triage.d.ts.map +1 -1
  152. package/dist/tools/triage.js +6 -2
  153. package/dist/tools/triage.js.map +1 -1
  154. package/dist/types/index.d.ts +23 -0
  155. package/dist/types/index.d.ts.map +1 -1
  156. package/dist/utils/account-identity.d.ts.map +1 -1
  157. package/dist/utils/account-identity.js +3 -2
  158. package/dist/utils/account-identity.js.map +1 -1
  159. package/dist/utils/approval.d.ts +133 -0
  160. package/dist/utils/approval.d.ts.map +1 -0
  161. package/dist/utils/approval.js +507 -0
  162. package/dist/utils/approval.js.map +1 -0
  163. package/dist/utils/atomic-write.d.ts +1 -1
  164. package/dist/utils/atomic-write.d.ts.map +1 -1
  165. package/dist/utils/atomic-write.js +10 -3
  166. package/dist/utils/atomic-write.js.map +1 -1
  167. package/dist/utils/contact-text.d.ts +8 -0
  168. package/dist/utils/contact-text.d.ts.map +1 -0
  169. package/dist/utils/contact-text.js +24 -0
  170. package/dist/utils/contact-text.js.map +1 -0
  171. package/dist/utils/delegation.d.ts +17 -0
  172. package/dist/utils/delegation.d.ts.map +1 -0
  173. package/dist/utils/delegation.js +72 -0
  174. package/dist/utils/delegation.js.map +1 -0
  175. package/dist/utils/file-lock.d.ts.map +1 -1
  176. package/dist/utils/file-lock.js +5 -4
  177. package/dist/utils/file-lock.js.map +1 -1
  178. package/dist/utils/mail-security.d.ts +4 -2
  179. package/dist/utils/mail-security.d.ts.map +1 -1
  180. package/dist/utils/mail-security.js +357 -51
  181. package/dist/utils/mail-security.js.map +1 -1
  182. package/dist/utils/ocr.d.ts +24 -0
  183. package/dist/utils/ocr.d.ts.map +1 -0
  184. package/dist/utils/ocr.js +109 -0
  185. package/dist/utils/ocr.js.map +1 -0
  186. package/dist/utils/office-text-worker.d.ts +2 -0
  187. package/dist/utils/office-text-worker.d.ts.map +1 -0
  188. package/dist/utils/office-text-worker.js +17 -0
  189. package/dist/utils/office-text-worker.js.map +1 -0
  190. package/dist/utils/office-text.d.ts +32 -0
  191. package/dist/utils/office-text.d.ts.map +1 -0
  192. package/dist/utils/office-text.js +540 -0
  193. package/dist/utils/office-text.js.map +1 -0
  194. package/dist/utils/pdf-text-worker.js +2 -2
  195. package/dist/utils/pdf-text-worker.js.map +1 -1
  196. package/dist/utils/pdf-text.d.ts +10 -0
  197. package/dist/utils/pdf-text.d.ts.map +1 -1
  198. package/dist/utils/pdf-text.js +49 -9
  199. package/dist/utils/pdf-text.js.map +1 -1
  200. package/dist/utils/png.d.ts +2 -0
  201. package/dist/utils/png.d.ts.map +1 -0
  202. package/dist/utils/png.js +26 -0
  203. package/dist/utils/png.js.map +1 -0
  204. package/dist/utils/regular-file.d.ts +9 -0
  205. package/dist/utils/regular-file.d.ts.map +1 -0
  206. package/dist/utils/regular-file.js +34 -0
  207. package/dist/utils/regular-file.js.map +1 -0
  208. package/dist/utils/runtime-policy.d.ts.map +1 -1
  209. package/dist/utils/runtime-policy.js +2 -0
  210. package/dist/utils/runtime-policy.js.map +1 -1
  211. package/dist/utils/safe-write.d.ts +7 -1
  212. package/dist/utils/safe-write.d.ts.map +1 -1
  213. package/dist/utils/safe-write.js +27 -7
  214. package/dist/utils/safe-write.js.map +1 -1
  215. package/dist/utils/server-state-paths.d.ts +12 -0
  216. package/dist/utils/server-state-paths.d.ts.map +1 -0
  217. package/dist/utils/server-state-paths.js +69 -0
  218. package/dist/utils/server-state-paths.js.map +1 -0
  219. package/dist/utils/style-profile.d.ts +16 -0
  220. package/dist/utils/style-profile.d.ts.map +1 -1
  221. package/dist/utils/style-profile.js +37 -1
  222. package/dist/utils/style-profile.js.map +1 -1
  223. package/dist/utils/vcard.d.ts +27 -0
  224. package/dist/utils/vcard.d.ts.map +1 -0
  225. package/dist/utils/vcard.js +141 -0
  226. package/dist/utils/vcard.js.map +1 -0
  227. package/dist/utils/zip-reader.d.ts +16 -0
  228. package/dist/utils/zip-reader.d.ts.map +1 -0
  229. package/dist/utils/zip-reader.js +108 -0
  230. package/dist/utils/zip-reader.js.map +1 -0
  231. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { execFile } from "node:child_process";
3
3
  import { accessSync, constants as fsConstants, realpathSync } from "node:fs";
4
4
  import { readFile } from "node:fs/promises";
5
+ import { createInterface } from "node:readline";
5
6
  import { homedir, release as osRelease } from "node:os";
6
7
  import { promisify } from "node:util";
7
8
  import { fileURLToPath } from "node:url";
@@ -10,8 +11,10 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
10
11
  import { buildConfigFromEnv, createServer, withAudit } from "./index.js";
11
12
  import { isMainModule } from "./is-main.js";
12
13
  import { isLikelyAuthenticationError, isLikelyCertificateError, isLikelyConnectionError, isLikelyTlsMismatchError, SimpleIMAPService } from "./services/simple-imap-service.js";
14
+ import { GrantService, grantStatus } from "./services/grant-service.js";
15
+ import { GRANTABLE_TOOLS, MAX_GRANT_DAYS, MAX_GRANT_USES } from "./utils/delegation.js";
13
16
  import { ensureDestructiveConfirmed, ensureEmailActionAllowed, ensureMailboxWriteAllowed, sanitizeRuntimeConfig } from "./utils/runtime-policy.js";
14
- import { isValidEmail, parseEmails } from "./utils/helpers.js";
17
+ import { InvalidArgumentError, isValidEmail, parseEmails } from "./utils/helpers.js";
15
18
  import { checkForUpdate } from "./utils/update-check.js";
16
19
  import { healthProblems } from "./utils/health.js";
17
20
  import { bridgeVersionNote } from "./utils/bridge-version.js";
@@ -176,6 +179,9 @@ function printHelp() {
176
179
  " draft-send <id> Send a saved draft",
177
180
  " draft-delete <id> Delete a saved draft",
178
181
  " draft-thread-reply <id> Create a reply draft for a thread",
182
+ " grant-create Give the assistant a narrow, expiring permission to send (--tools --recipients --expires --uses --note); asks you to confirm at a terminal",
183
+ " grant-list List the delegation grants in force (--all, --account)",
184
+ " grant-revoke <id> Revoke a delegation grant",
179
185
  " tools List every MCP tool exposed by the server",
180
186
  " tool <name> Call any MCP tool with JSON arguments",
181
187
  "",
@@ -300,6 +306,9 @@ export const COMMAND_SPECS = {
300
306
  "draft-send": { usage: "draft-send <id> [--args '{...}']", description: "Send a saved draft (dryRun etc. via --args)", flags: ["id", "args", "args-file"] },
301
307
  "draft-delete": { usage: "draft-delete <id>", description: "Delete a saved draft", flags: ["id"] },
302
308
  "draft-thread-reply": { usage: "draft-thread-reply <threadId> (--body <text> | stdin) [--reply-all]", description: "Create a reply draft for a thread", flags: ["id", ...BODY_FLAGS, "reply-all", "all", ...DRAFT_COPY_FLAGS] },
309
+ "grant-create": { usage: "grant-create --tools <a,b> --recipients <addr|@domain,...> --expires <30m|12h|7d|ISO> [--uses <n>] [--note <text>] [--account <slug|address>]", description: `Give the assistant a narrow, expiring permission to send (tools: ${GRANTABLE_TOOLS.join(", ")}; every To/Cc/Bcc must match; at most ${MAX_GRANT_USES} uses and ${MAX_GRANT_DAYS} days). Honoured only by a server started with PROTONMAIL_DELEGATION=on`, flags: ["tools", "recipients", "expires", "uses", "note", "account"] },
310
+ "grant-list": { usage: "grant-list [--all] [--account <slug|address>]", description: "List the delegation grants in force (--all also shows used up, expired and revoked ones)", flags: ["all", "account"] },
311
+ "grant-revoke": { usage: "grant-revoke <id> [--account <slug|address>]", description: "Revoke a delegation grant at once", flags: ["account"] },
303
312
  tools: { usage: "tools", description: "List every MCP tool exposed by the server", flags: [] },
304
313
  tool: { usage: "tool <name> [--args '{...}' | --args-file <path>]", description: "Call any MCP tool with JSON arguments", flags: ["args", "args-file"] },
305
314
  };
@@ -1725,7 +1734,20 @@ async function runNotify(parsed) {
1725
1734
  let running = true;
1726
1735
  let previousCount;
1727
1736
  let problemCause;
1728
- const shutdown = () => { running = false; };
1737
+ // Exit from the handler itself: the loop may be parked in a connect, an IDLE or a retry wait that would otherwise
1738
+ // outlast launchd's/systemd's stop timeout.
1739
+ // `stopping` makes this the only place that prints "Stopped." and exits, and ignores a second signal.
1740
+ let stopping = false;
1741
+ const shutdown = () => {
1742
+ if (stopping)
1743
+ return;
1744
+ stopping = true;
1745
+ running = false;
1746
+ void Promise.race([imapService.disconnect().catch(() => undefined), new Promise((resolve) => setTimeout(resolve, 500))]).then(() => {
1747
+ process.stderr.write("[notify] Stopped.\n");
1748
+ process.exit(0);
1749
+ });
1750
+ };
1729
1751
  process.on("SIGINT", shutdown);
1730
1752
  process.on("SIGTERM", shutdown);
1731
1753
  process.stderr.write(`[notify] Watching ${folder} for new mail (IMAP IDLE). Ctrl+C to stop.\n`);
@@ -1787,7 +1809,8 @@ async function runNotify(parsed) {
1787
1809
  catch {
1788
1810
  // ignore
1789
1811
  }
1790
- process.stderr.write("[notify] Stopped.\n");
1812
+ if (!stopping)
1813
+ process.stderr.write("[notify] Stopped.\n");
1791
1814
  }
1792
1815
  async function runTestEmail(parsed) {
1793
1816
  const to = parsed.positionals[0] || getStringFlag(parsed.flags, "to");
@@ -1967,6 +1990,154 @@ async function runDraftDelete(parsed) {
1967
1990
  printToolCallResult(result, wantJson);
1968
1991
  });
1969
1992
  }
1993
+ // Delegation grants are made and revoked here and nowhere else: they are read and written straight from the account's data
1994
+ // directory, never through an MCP tool, so a model that has only MCP tools cannot grant itself anything.
1995
+ function grantStores(parsed, options) {
1996
+ const { accounts } = buildConfigFromEnv();
1997
+ const wanted = getStringFlag(parsed.flags, "account");
1998
+ const chosen = wanted ? accounts.filter((account) => account.address.toLowerCase() === wanted.toLowerCase() || account.slug === wanted) : accounts;
1999
+ if (chosen.length === 0)
2000
+ throw new CliUsageError(`Unknown account "${wanted}". Configured: ${accounts.map((account) => account.slug).join(", ")}`);
2001
+ if (options.single && !wanted && chosen.length > 1)
2002
+ throw new CliUsageError(`Several accounts are configured; name one with --account (${accounts.map((account) => account.slug).join(", ")}).`);
2003
+ return chosen.map((account) => ({ account, grants: new GrantService({ dataDir: account.dataDir, smtp: account.smtp }, account.address) }));
2004
+ }
2005
+ const GRANT_SPAN_MS = { m: 60_000, h: 3_600_000, d: 86_400_000 };
2006
+ // "30m", "12h", "7d" from now, or a timestamp (a bare date is midnight UTC).
2007
+ export function parseGrantExpiry(value, now = new Date()) {
2008
+ const span = /^(\d{1,4})([mhd])$/.exec(value.trim());
2009
+ const date = span
2010
+ ? new Date(now.getTime() + Number(span[1]) * GRANT_SPAN_MS[span[2]])
2011
+ : /^\d{4}-\d{2}-\d{2}/.test(value.trim())
2012
+ ? new Date(value.trim())
2013
+ : new Date(Number.NaN);
2014
+ if (Number.isNaN(date.getTime()))
2015
+ throw new CliUsageError(`--expires must be like 30m, 12h, 7d or a timestamp such as 2026-11-05T18:00:00Z, got "${value}".`);
2016
+ return date;
2017
+ }
2018
+ const grantLine = (grant, now) => [
2019
+ grant.id,
2020
+ grant.account,
2021
+ grant.tools.join(","),
2022
+ grant.recipients.join(","),
2023
+ `${grant.maxUses - grant.usedCount}/${grant.maxUses} uses left`,
2024
+ `expires ${grant.expiresAt}`,
2025
+ ...(grantStatus(grant, now) === "active" ? [] : [grantStatus(grant, now)]),
2026
+ JSON.stringify(grant.note),
2027
+ ].join(" | ");
2028
+ // A grant is made or revoked only by a person at a terminal: both stdin and stdout must be one, the whole grant is shown, and
2029
+ // a phrase that names its recipient count (or id) is typed back. There is no flag, variable or argument that answers for the
2030
+ // person, so a model that can run shell commands is stopped here (one that can write grants.json itself is not: the file is not signed).
2031
+ export const isInteractiveTerminal = (input = process.stdin, output = process.stdout) => Boolean(input?.isTTY && output?.isTTY);
2032
+ export const grantCreatePhrase = (recipientCount) => `grant ${recipientCount} recipient${recipientCount === 1 ? "" : "s"}`;
2033
+ export const grantRevokePhrase = (id) => `revoke ${id}`;
2034
+ const grantTerminal = {
2035
+ isInteractive: () => isInteractiveTerminal(),
2036
+ ask: (text) => {
2037
+ const lines = createInterface({ input: process.stdin, output: process.stdout });
2038
+ return new Promise((resolve) => {
2039
+ lines.once("close", () => resolve(""));
2040
+ lines.question(text, resolve);
2041
+ }).finally(() => lines.close());
2042
+ },
2043
+ write: (text) => void process.stdout.write(text),
2044
+ };
2045
+ function requireTerminal(terminal, what) {
2046
+ if (!terminal.isInteractive()) {
2047
+ throw new Error(`${what} needs an interactive terminal (stdin and stdout) and a person to confirm it; it cannot be run from a script or by the assistant. Nothing was changed.`);
2048
+ }
2049
+ }
2050
+ async function confirmAtTerminal(terminal, summary, phrase) {
2051
+ const answer = await terminal.ask(`${summary}\nTo confirm, type exactly: ${phrase}\n> `);
2052
+ if (answer.trim() !== phrase)
2053
+ throw new Error("Not confirmed. Nothing was changed.");
2054
+ }
2055
+ export async function runGrantCreate(parsed, terminal = grantTerminal) {
2056
+ const [{ account, grants }] = grantStores(parsed, { single: true });
2057
+ const split = (key) => (getStringFlag(parsed.flags, key) ?? "").split(/[,\s]+/).filter(Boolean);
2058
+ const tools = split("tools");
2059
+ const recipients = split("recipients");
2060
+ const expires = getStringFlag(parsed.flags, "expires");
2061
+ if (tools.length === 0)
2062
+ throw new CliUsageError(`--tools is required: ${GRANTABLE_TOOLS.join(", ")} (comma-separated).`);
2063
+ if (recipients.length === 0)
2064
+ throw new CliUsageError("--recipients is required: exact addresses and/or @domain entries (comma-separated).");
2065
+ if (!expires)
2066
+ throw new CliUsageError(`--expires is required (30m, 12h, 7d or a timestamp; at most ${MAX_GRANT_DAYS} days).`);
2067
+ const maxUses = parseIntegerFlag(parsed.flags, "uses", 1, `a whole number from 1 to ${MAX_GRANT_USES}`) ?? 1;
2068
+ const input = { note: getTextFlag(parsed.flags, "note"), tools, recipients, maxUses, expiresAt: parseGrantExpiry(expires) };
2069
+ let plan;
2070
+ try {
2071
+ plan = grants.planGrant(input);
2072
+ }
2073
+ catch (error) {
2074
+ if (error instanceof InvalidArgumentError)
2075
+ throw new CliUsageError(error.message);
2076
+ throw error;
2077
+ }
2078
+ requireTerminal(terminal, "grant-create");
2079
+ await confirmAtTerminal(terminal, [
2080
+ `New grant for ${account.address}:`,
2081
+ ` tools: ${plan.tools.join(", ")}`,
2082
+ ` recipients: ${plan.recipients.join(", ")} (every To, Cc and Bcc must match)`,
2083
+ ` uses: ${plan.maxUses}`,
2084
+ ` expires: ${plan.expiresAt.toISOString()}`,
2085
+ ` note: ${JSON.stringify(plan.note)}`,
2086
+ "With PROTONMAIL_DELEGATION=on the assistant can then send these without asking you again.",
2087
+ ].join("\n"), grantCreatePhrase(plan.recipients.length));
2088
+ let grant;
2089
+ try {
2090
+ grant = await grants.createGrant({ ...input, expiresAt: plan.expiresAt });
2091
+ }
2092
+ catch (error) {
2093
+ if (error instanceof InvalidArgumentError)
2094
+ throw new CliUsageError(error.message);
2095
+ throw error;
2096
+ }
2097
+ if (isTruthyFlag(parsed.flags.json)) {
2098
+ terminal.write(json(grant));
2099
+ return;
2100
+ }
2101
+ terminal.write([
2102
+ `Grant ${grant.id} created for ${account.address}.`,
2103
+ ` tools: ${grant.tools.join(", ")}`,
2104
+ ` recipients: ${grant.recipients.join(", ")} (every To, Cc and Bcc must match)`,
2105
+ ` uses: ${grant.maxUses}`,
2106
+ ` expires: ${grant.expiresAt}`,
2107
+ ` note: ${JSON.stringify(grant.note)}`,
2108
+ "Only a server started with PROTONMAIL_DELEGATION=on honours it. Revoke it with: grant-revoke " + grant.id,
2109
+ "",
2110
+ ].join("\n"));
2111
+ }
2112
+ async function runGrantList(parsed) {
2113
+ const now = new Date();
2114
+ const all = isTruthyFlag(parsed.flags.all);
2115
+ const found = (await Promise.all(grantStores(parsed, { single: false }).map(({ grants }) => grants.list()))).flat();
2116
+ const shown = found
2117
+ .filter((grant) => all || grantStatus(grant, now) === "active")
2118
+ .sort((left, right) => Date.parse(left.expiresAt) - Date.parse(right.expiresAt));
2119
+ if (isTruthyFlag(parsed.flags.json)) {
2120
+ process.stdout.write(json(shown.map((grant) => ({ ...grant, status: grantStatus(grant, now) }))));
2121
+ return;
2122
+ }
2123
+ process.stdout.write(shown.length === 0 ? `No ${all ? "" : "active "}grants.\n` : `${shown.map((grant) => grantLine(grant, now)).join("\n")}\n`);
2124
+ }
2125
+ export async function runGrantRevoke(parsed, terminal = grantTerminal) {
2126
+ const id = parsed.positionals[0];
2127
+ if (!id)
2128
+ throw new CliUsageError("grant-revoke requires a grant id (see grant-list).");
2129
+ requireTerminal(terminal, "grant-revoke");
2130
+ for (const { account, grants } of grantStores(parsed, { single: false })) {
2131
+ const grant = (await grants.list()).find((candidate) => candidate.id === id);
2132
+ if (!grant)
2133
+ continue;
2134
+ await confirmAtTerminal(terminal, `Revoke this grant for ${account.address}:\n ${grantLine(grant, new Date())}`, grantRevokePhrase(grant.id));
2135
+ const result = await grants.revokeGrant(id);
2136
+ terminal.write(isTruthyFlag(parsed.flags.json) ? json({ id, account: account.address, revoked: true, alreadyRevoked: result.alreadyRevoked }) : `${result.alreadyRevoked ? "Already revoked" : "Revoked"}: ${id} (${account.address})\n`);
2137
+ return;
2138
+ }
2139
+ throw new Error(`No grant ${JSON.stringify(id.slice(0, 40))} found.`);
2140
+ }
1970
2141
  async function runTools(parsed) {
1971
2142
  const wantJson = isTruthyFlag(parsed.flags.json);
1972
2143
  await withMcpClient(async (client) => {
@@ -2031,17 +2202,27 @@ export const TOOL_ONLY_COMMANDS = [
2031
2202
  { command: "rules", tool: "get_rules", positionals: [], help: "Show the mailbox rules, the proposals waiting and what the rules did (--account)" },
2032
2203
  { command: "set-rule", tool: "set_rule", positionals: [], help: "Create, change or accept a proposal as a rule (--proposalId, --id, --name, --enabled, --when, --actions, --confirmed)" },
2033
2204
  { command: "delete-rule", tool: "delete_rule", positionals: ["id"], help: "Delete a rule, or dismiss a proposal" },
2205
+ { command: "export-sieve", tool: "export_sieve", positionals: [], help: "Turn the rules into a Sieve script for Proton's own filters, listing what cannot be expressed (--ruleId, --account)" },
2034
2206
  { command: "apply-rules", tool: "apply_rules", positionals: [], help: "Run the rules on mail already in the index; a preview unless --dryRun false (--ruleId, --limit, --confirmed)" },
2035
2207
  { command: "plan-inbox", tool: "plan_inbox", positionals: [], help: "Prepare a batch of mailbox changes to approve at once (--args '{\"scan\":true,\"limit\":30}')" },
2036
2208
  { command: "approve-plan", tool: "approve_plan", positionals: ["planId"], help: "Apply a plan from plan-inbox (--args '{\"exclude\":[3]}')" },
2037
2209
  { command: "undo-plan", tool: "undo_plan", positionals: ["planId"], help: "Put back what an approved plan changed" },
2210
+ { command: "define-category", tool: "define_category", positionals: ["name"], help: "Define or change an inbox category of your own (--args '{\"keywords\":[\"invoice\"],\"label\":\"Receipts\"}'; '{\"starter\":true}' for the starter set)" },
2211
+ { command: "categories", tool: "list_categories", positionals: [], help: "Your inbox categories and what corrections taught them (--account)" },
2212
+ { command: "categorize-inbox", tool: "categorize_inbox", positionals: [], help: "Sort recent INBOX mail into your categories with the reasons; a preview unless --args '{\"dryRun\":false}' (then a plan for approve-plan)" },
2213
+ { command: "correct-category", tool: "correct_category", positionals: ["emailId", "category"], help: "Teach the categories where a message belongs; shows what else would change" },
2038
2214
  { command: "find-commitments", tool: "find_commitments", positionals: [], help: "Find sentences that look like promises in recent mail (--args '{\"days\":7,\"direction\":\"both\"}')" },
2039
2215
  { command: "set-commitment", tool: "set_commitment", positionals: [], help: "Record a commitment, or close one (--args '{\"text\":\"...\",\"direction\":\"mine\",\"due\":\"2026-11-05\"}')" },
2040
2216
  { command: "commitments", tool: "list_commitments", positionals: [], help: "The commitment ledger: overdue, due today, open, probably done (--args '{\"status\":\"all\"}')" },
2217
+ { command: "find-contact", tool: "find_contact", positionals: ["query"], help: "A person by name or address: addresses, organisation, tags, notes, counts both ways, last subjects" },
2218
+ { command: "set-contact", tool: "set_contact", positionals: ["address"], help: "Tag, name, annotate, merge or unmerge a contact (--args '{\"addTags\":[\"vip\"]}' or '{\"mergeWith\":\"other@example.com\"}' or '{\"unmerge\":true}')" },
2219
+ { command: "import-contacts", tool: "import_contacts", positionals: ["path"], help: "Import people from a local .vcf file into the contact store" },
2041
2220
  { command: "style-profile", tool: "get_style_profile", positionals: [], help: "How you write, from your sent mail: language, greeting, closing, tone (--args '{\"recipient\":\"pat@client.example\",\"refresh\":true}')" },
2042
2221
  { command: "get-brief", tool: "get_brief", positionals: [], help: "(tool form; see also `brief`)" },
2043
2222
  { command: "autoresponder", tool: "get_autoresponder", positionals: [], help: "Show the automatic reply of an address (--account)" },
2044
2223
  { command: "set-autoresponder", tool: "set_autoresponder", positionals: [], help: "Set up or switch off the automatic reply of an address (--enabled, --body, --subject, --startsAt, --endsAt, --outgoingNote, --mode, --timezone, --rules, --confirmed)" },
2224
+ { command: "list-grants", tool: "list_grants", positionals: [], help: "(tool form; see also `grant-list`)" },
2225
+ { command: "events", tool: "get_events", positionals: [], help: "What happened while nobody asked: new mail, snoozes, reminders, sends, rules, plans (--args '{\"sinceCursor\":\"<nextCursor>\",\"types\":[\"mail\"],\"limit\":50}')" },
2045
2226
  { command: "list-templates", tool: "list_templates", positionals: [], help: "List saved email templates" },
2046
2227
  { command: "get-template", tool: "get_template", positionals: ["id"], help: "Get a saved template by id" },
2047
2228
  { command: "delete-template", tool: "delete_template", positionals: ["id"], help: "Delete a saved template" },
@@ -2080,6 +2261,7 @@ export const TOOL_ONLY_COMMANDS = [
2080
2261
  { command: "import-email", tool: "import_email", positionals: [], fileFieldBase64: "rawBase64", help: "Import a .eml message via IMAP APPEND (--file <path.eml>)" },
2081
2262
  { command: "clear-index", tool: "clear_index", positionals: [], help: "Delete the local SQLite mailbox index" },
2082
2263
  { command: "get-audit-logs", tool: "get_audit_logs", positionals: [], help: "Recent write-operation audit log entries" },
2264
+ { command: "verify-audit-log", tool: "verify_audit_log", positionals: [], help: "Check the audit log hash chain and print its head hash" },
2083
2265
  ];
2084
2266
  async function runToolOnlyCommand(entry, parsed) {
2085
2267
  const wantJson = isTruthyFlag(parsed.flags.json);
@@ -2376,6 +2558,15 @@ export async function main() {
2376
2558
  case "draft-thread-reply":
2377
2559
  await runDraftThreadReply(parsed);
2378
2560
  return;
2561
+ case "grant-create":
2562
+ await runGrantCreate(parsed);
2563
+ return;
2564
+ case "grant-list":
2565
+ await runGrantList(parsed);
2566
+ return;
2567
+ case "grant-revoke":
2568
+ await runGrantRevoke(parsed);
2569
+ return;
2379
2570
  case "tools":
2380
2571
  await runTools(parsed);
2381
2572
  return;