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.
- package/README.md +149 -12
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +194 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-env.d.ts +2 -1
- package/dist/config-env.d.ts.map +1 -1
- package/dist/config-env.js +24 -0
- package/dist/config-env.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -8
- package/dist/index.js.map +1 -1
- package/dist/services/account-manager.d.ts +7 -0
- package/dist/services/account-manager.d.ts.map +1 -1
- package/dist/services/account-manager.js +59 -0
- package/dist/services/account-manager.js.map +1 -1
- package/dist/services/audit-service.d.ts +20 -0
- package/dist/services/audit-service.d.ts.map +1 -1
- package/dist/services/audit-service.js +266 -9
- package/dist/services/audit-service.js.map +1 -1
- package/dist/services/autoresponder-service.d.ts +35 -6
- package/dist/services/autoresponder-service.d.ts.map +1 -1
- package/dist/services/autoresponder-service.js +223 -108
- package/dist/services/autoresponder-service.js.map +1 -1
- package/dist/services/brief-service.d.ts.map +1 -1
- package/dist/services/brief-service.js +3 -2
- package/dist/services/brief-service.js.map +1 -1
- package/dist/services/category-service.d.ts +91 -0
- package/dist/services/category-service.d.ts.map +1 -0
- package/dist/services/category-service.js +320 -0
- package/dist/services/category-service.js.map +1 -0
- package/dist/services/commitment-service.d.ts.map +1 -1
- package/dist/services/commitment-service.js +3 -2
- package/dist/services/commitment-service.js.map +1 -1
- package/dist/services/contact-service.d.ts +60 -0
- package/dist/services/contact-service.d.ts.map +1 -0
- package/dist/services/contact-service.js +248 -0
- package/dist/services/contact-service.js.map +1 -0
- package/dist/services/delivery-queue-service.d.ts +4 -0
- package/dist/services/delivery-queue-service.d.ts.map +1 -1
- package/dist/services/delivery-queue-service.js +12 -2
- package/dist/services/delivery-queue-service.js.map +1 -1
- package/dist/services/draft-store-service.d.ts.map +1 -1
- package/dist/services/draft-store-service.js +3 -2
- package/dist/services/draft-store-service.js.map +1 -1
- package/dist/services/embedding-service.d.ts +68 -0
- package/dist/services/embedding-service.d.ts.map +1 -0
- package/dist/services/embedding-service.js +465 -0
- package/dist/services/embedding-service.js.map +1 -0
- package/dist/services/event-log-service.d.ts +67 -0
- package/dist/services/event-log-service.d.ts.map +1 -0
- package/dist/services/event-log-service.js +280 -0
- package/dist/services/event-log-service.js.map +1 -0
- package/dist/services/grant-service.d.ts +84 -0
- package/dist/services/grant-service.d.ts.map +1 -0
- package/dist/services/grant-service.js +214 -0
- package/dist/services/grant-service.js.map +1 -0
- package/dist/services/local-index-service.d.ts +18 -1
- package/dist/services/local-index-service.d.ts.map +1 -1
- package/dist/services/local-index-service.js +54 -4
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/reply-reminder-service.d.ts +4 -0
- package/dist/services/reply-reminder-service.d.ts.map +1 -1
- package/dist/services/reply-reminder-service.js +22 -2
- package/dist/services/reply-reminder-service.js.map +1 -1
- package/dist/services/rules-service.d.ts +9 -1
- package/dist/services/rules-service.d.ts.map +1 -1
- package/dist/services/rules-service.js +8 -3
- package/dist/services/rules-service.js.map +1 -1
- package/dist/services/sieve-export.d.ts +19 -0
- package/dist/services/sieve-export.d.ts.map +1 -0
- package/dist/services/sieve-export.js +192 -0
- package/dist/services/sieve-export.js.map +1 -0
- package/dist/services/simple-imap-service.d.ts +13 -0
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +140 -17
- package/dist/services/simple-imap-service.js.map +1 -1
- package/dist/services/snooze-service.d.ts +4 -0
- package/dist/services/snooze-service.d.ts.map +1 -1
- package/dist/services/snooze-service.js +12 -3
- package/dist/services/snooze-service.js.map +1 -1
- package/dist/services/template-service.d.ts.map +1 -1
- package/dist/services/template-service.js +3 -2
- package/dist/services/template-service.js.map +1 -1
- package/dist/services/triage-plan-service.d.ts +2 -1
- package/dist/services/triage-plan-service.d.ts.map +1 -1
- package/dist/services/triage-plan-service.js +3 -2
- package/dist/services/triage-plan-service.js.map +1 -1
- package/dist/tools/attachments.js +1 -1
- package/dist/tools/attachments.js.map +1 -1
- package/dist/tools/categories.d.ts +4 -0
- package/dist/tools/categories.d.ts.map +1 -0
- package/dist/tools/categories.js +269 -0
- package/dist/tools/categories.js.map +1 -0
- package/dist/tools/contacts.d.ts +6 -0
- package/dist/tools/contacts.d.ts.map +1 -0
- package/dist/tools/contacts.js +295 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/context.d.ts +4 -0
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +3 -0
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/definitions.d.ts +333 -5
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +169 -5
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/delegation.d.ts +44 -0
- package/dist/tools/delegation.d.ts.map +1 -0
- package/dist/tools/delegation.js +180 -0
- package/dist/tools/delegation.js.map +1 -0
- package/dist/tools/diagnostics.d.ts.map +1 -1
- package/dist/tools/diagnostics.js +6 -0
- package/dist/tools/diagnostics.js.map +1 -1
- package/dist/tools/drafts-support.d.ts +2 -1
- package/dist/tools/drafts-support.d.ts.map +1 -1
- package/dist/tools/drafts-support.js +10 -2
- package/dist/tools/drafts-support.js.map +1 -1
- package/dist/tools/drafts.d.ts.map +1 -1
- package/dist/tools/drafts.js +8 -0
- package/dist/tools/drafts.js.map +1 -1
- package/dist/tools/events.d.ts +47 -0
- package/dist/tools/events.d.ts.map +1 -0
- package/dist/tools/events.js +249 -0
- package/dist/tools/events.js.map +1 -0
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/reading.js +123 -3
- package/dist/tools/reading.js.map +1 -1
- package/dist/tools/resources.d.ts +4 -0
- package/dist/tools/resources.d.ts.map +1 -1
- package/dist/tools/resources.js +9 -0
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/rules-runner.d.ts.map +1 -1
- package/dist/tools/rules-runner.js +1 -0
- package/dist/tools/rules-runner.js.map +1 -1
- package/dist/tools/rules.d.ts +9 -0
- package/dist/tools/rules.d.ts.map +1 -1
- package/dist/tools/rules.js +50 -19
- package/dist/tools/rules.js.map +1 -1
- package/dist/tools/sending.d.ts.map +1 -1
- package/dist/tools/sending.js +4 -0
- package/dist/tools/sending.js.map +1 -1
- package/dist/tools/style.d.ts +2 -0
- package/dist/tools/style.d.ts.map +1 -1
- package/dist/tools/style.js +1 -1
- package/dist/tools/style.js.map +1 -1
- package/dist/tools/templates-and-reminders.d.ts +3 -0
- package/dist/tools/templates-and-reminders.d.ts.map +1 -1
- package/dist/tools/templates-and-reminders.js +31 -17
- package/dist/tools/templates-and-reminders.js.map +1 -1
- package/dist/tools/triage.d.ts.map +1 -1
- package/dist/tools/triage.js +6 -2
- package/dist/tools/triage.js.map +1 -1
- package/dist/types/index.d.ts +23 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/account-identity.d.ts.map +1 -1
- package/dist/utils/account-identity.js +3 -2
- package/dist/utils/account-identity.js.map +1 -1
- package/dist/utils/approval.d.ts +133 -0
- package/dist/utils/approval.d.ts.map +1 -0
- package/dist/utils/approval.js +507 -0
- package/dist/utils/approval.js.map +1 -0
- package/dist/utils/atomic-write.d.ts +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/atomic-write.js +10 -3
- package/dist/utils/atomic-write.js.map +1 -1
- package/dist/utils/contact-text.d.ts +8 -0
- package/dist/utils/contact-text.d.ts.map +1 -0
- package/dist/utils/contact-text.js +24 -0
- package/dist/utils/contact-text.js.map +1 -0
- package/dist/utils/delegation.d.ts +17 -0
- package/dist/utils/delegation.d.ts.map +1 -0
- package/dist/utils/delegation.js +72 -0
- package/dist/utils/delegation.js.map +1 -0
- package/dist/utils/file-lock.d.ts.map +1 -1
- package/dist/utils/file-lock.js +5 -4
- package/dist/utils/file-lock.js.map +1 -1
- package/dist/utils/mail-security.d.ts +4 -2
- package/dist/utils/mail-security.d.ts.map +1 -1
- package/dist/utils/mail-security.js +357 -51
- package/dist/utils/mail-security.js.map +1 -1
- package/dist/utils/ocr.d.ts +24 -0
- package/dist/utils/ocr.d.ts.map +1 -0
- package/dist/utils/ocr.js +109 -0
- package/dist/utils/ocr.js.map +1 -0
- package/dist/utils/office-text-worker.d.ts +2 -0
- package/dist/utils/office-text-worker.d.ts.map +1 -0
- package/dist/utils/office-text-worker.js +17 -0
- package/dist/utils/office-text-worker.js.map +1 -0
- package/dist/utils/office-text.d.ts +32 -0
- package/dist/utils/office-text.d.ts.map +1 -0
- package/dist/utils/office-text.js +540 -0
- package/dist/utils/office-text.js.map +1 -0
- package/dist/utils/pdf-text-worker.js +2 -2
- package/dist/utils/pdf-text-worker.js.map +1 -1
- package/dist/utils/pdf-text.d.ts +10 -0
- package/dist/utils/pdf-text.d.ts.map +1 -1
- package/dist/utils/pdf-text.js +49 -9
- package/dist/utils/pdf-text.js.map +1 -1
- package/dist/utils/png.d.ts +2 -0
- package/dist/utils/png.d.ts.map +1 -0
- package/dist/utils/png.js +26 -0
- package/dist/utils/png.js.map +1 -0
- package/dist/utils/regular-file.d.ts +9 -0
- package/dist/utils/regular-file.d.ts.map +1 -0
- package/dist/utils/regular-file.js +34 -0
- package/dist/utils/regular-file.js.map +1 -0
- package/dist/utils/runtime-policy.d.ts.map +1 -1
- package/dist/utils/runtime-policy.js +2 -0
- package/dist/utils/runtime-policy.js.map +1 -1
- package/dist/utils/safe-write.d.ts +7 -1
- package/dist/utils/safe-write.d.ts.map +1 -1
- package/dist/utils/safe-write.js +27 -7
- package/dist/utils/safe-write.js.map +1 -1
- package/dist/utils/server-state-paths.d.ts +12 -0
- package/dist/utils/server-state-paths.d.ts.map +1 -0
- package/dist/utils/server-state-paths.js +69 -0
- package/dist/utils/server-state-paths.js.map +1 -0
- package/dist/utils/style-profile.d.ts +16 -0
- package/dist/utils/style-profile.d.ts.map +1 -1
- package/dist/utils/style-profile.js +37 -1
- package/dist/utils/style-profile.js.map +1 -1
- package/dist/utils/vcard.d.ts +27 -0
- package/dist/utils/vcard.d.ts.map +1 -0
- package/dist/utils/vcard.js +141 -0
- package/dist/utils/vcard.js.map +1 -0
- package/dist/utils/zip-reader.d.ts +16 -0
- package/dist/utils/zip-reader.d.ts.map +1 -0
- package/dist/utils/zip-reader.js +108 -0
- package/dist/utils/zip-reader.js.map +1 -0
- 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
|
-
|
|
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
|
-
|
|
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;
|