@agentcomms/resend 0.8.0 → 0.10.0
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/dist/cli.mjs +40 -5
- package/dist/index.d.mts +146 -5
- package/dist/index.mjs +2 -2
- package/dist/{send-BpwXJL8C.mjs → send-Ba1caJ6x.mjs} +5052 -3064
- package/dist/{server-C3PKeQSN.mjs → server-BHHLf1lV.mjs} +15 -3
- package/dist/{stdio-entry-CVMfmkCS.mjs → stdio-entry-CKC1hDbG.mjs} +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { $ as openCore, B as approvalKind, C as policyChange, D as sendPolicyOf, E as removeAccountChange, F as CHANNEL_SERVERS, H as approveChangeAtTerminal, I as CommsError, J as escapeForDisplay, K as colorEnabled, L as EXIT_CODES, N as checkNewName, O as showAccount, Q as installExitStatus, R as SEND_LOOKUP, S as policyApprovalRefusal, T as policyWanted, U as canPrompt, V as approvalsOf, Y as exemptFromUpdateGate, Z as gatedChangeAtTerminal, _ as runDoctor, _t as VERSION, a as revokeSendApproval, at as serverInstallChange, b as listAccounts, c as downloadReceived, d as listReceived, et as paint, f as listScheduled, ft as writeResult, g as showSentEmail, h as showReceived, i as prepareSend, it as runCommand, k as ResendContext, l as getMetrics, lt as truncateDisplay, m as listSuppressions, n as executeSend, nt as renderInstall, o as sendStatus, ot as serverPruneChange, p as listSentEmails, q as commandPathOf, r as finishSendApproval, rt as renderPrune, s as cancelScheduledChange, st as terminalUpdateHooks, t as beginSendApproval, tt as refuseUnclaimedApproval, u as listDomains, ut as updateGateAtTerminal, v as addAccountChange, w as policyReport, x as modeOf, y as inspectKey, z as agentMarker } from "./send-Ba1caJ6x.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import childProcess from "node:child_process";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import { stripVTControlCharacters } from "node:util";
|
|
6
|
-
import childProcess from "node:child_process";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import process$1 from "node:process";
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
@@ -3160,7 +3160,8 @@ Getting started:
|
|
|
3160
3160
|
agent-resend received list --account acme/resend
|
|
3161
3161
|
agent-resend mcp install --client claude-code register the server with an agent, once a person approves it
|
|
3162
3162
|
|
|
3163
|
-
Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs approval ·
|
|
3163
|
+
Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs approval · 11 an update
|
|
3164
|
+
is out: update first, or put it off (agentcomms update, agentcomms update --later) · 64 usage ·
|
|
3164
3165
|
65 bad data · 66 not found · 69 provider or secret store unavailable · 75 temporary (retry later) ·
|
|
3165
3166
|
77 key or permission needed · 78 configuration problem.`).exitOverride();
|
|
3166
3167
|
const globals = () => {
|
|
@@ -3174,9 +3175,41 @@ Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs a
|
|
|
3174
3175
|
json: globals().json,
|
|
3175
3176
|
color: globals().color
|
|
3176
3177
|
});
|
|
3178
|
+
let gated = null;
|
|
3179
|
+
program.hook("preAction", async (_program, command) => {
|
|
3180
|
+
const path = commandPathOf(command);
|
|
3181
|
+
if (exemptFromUpdateGate(path)) return;
|
|
3182
|
+
const core = openCore({ env });
|
|
3183
|
+
let ended = null;
|
|
3184
|
+
const code = await runCommand(output(), async () => {
|
|
3185
|
+
ended = await updateGateAtTerminal({
|
|
3186
|
+
core,
|
|
3187
|
+
env,
|
|
3188
|
+
binary: "agent-resend",
|
|
3189
|
+
channel: "resend",
|
|
3190
|
+
running: VERSION,
|
|
3191
|
+
output: output(),
|
|
3192
|
+
noInput: false,
|
|
3193
|
+
streams,
|
|
3194
|
+
approveCommand: "agent-resend approve",
|
|
3195
|
+
approvals: approvalsOf(command),
|
|
3196
|
+
approvalClaim: path.join(" ") === "send status" ? SEND_LOOKUP : void 0,
|
|
3197
|
+
...terminalUpdateHooks(core, env, {
|
|
3198
|
+
output: output(),
|
|
3199
|
+
streams,
|
|
3200
|
+
approveCommand: "agent-resend approve"
|
|
3201
|
+
})
|
|
3202
|
+
});
|
|
3203
|
+
}, streams);
|
|
3204
|
+
gated = code !== 0 ? code : ended;
|
|
3205
|
+
});
|
|
3177
3206
|
const act = (body) => async (...args) => {
|
|
3178
3207
|
ran = true;
|
|
3179
3208
|
softExit = null;
|
|
3209
|
+
if (gated !== null) {
|
|
3210
|
+
exitCode = gated;
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3180
3213
|
const context = new ResendContext({
|
|
3181
3214
|
...deps,
|
|
3182
3215
|
env,
|
|
@@ -3237,7 +3270,9 @@ Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs a
|
|
|
3237
3270
|
mode: flags.mode,
|
|
3238
3271
|
change: flags.change
|
|
3239
3272
|
});
|
|
3240
|
-
const
|
|
3273
|
+
const reporting = wanted.send === void 0 && wanted.mode === void 0 && wanted.change === void 0;
|
|
3274
|
+
if (reporting) refuseUnclaimedApproval(flags.approval, policyApprovalRefusal("cli"));
|
|
3275
|
+
const result = reporting ? await policyReport(context, name) : await changeAt(context, policyChange(context, name, wanted), flags, `agent-resend account policy ${name}${wanted.send ? ` --send ${wanted.send}` : ""}${wanted.mode ? ` --mode ${wanted.mode}` : ""}${wanted.change ? ` --change ${wanted.change}` : ""}`);
|
|
3241
3276
|
writeResult(result, output(), renderPolicy, streams);
|
|
3242
3277
|
}));
|
|
3243
3278
|
program.command("doctor").description("check what has to work, and say who enforces what — read-only is this package’s promise, not the key’s").option("--account <name>", "check only this account").option("--offline", "do not ask Resend anything", false).action(act(async (context, options, flags) => {
|
|
@@ -3370,7 +3405,7 @@ Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs a
|
|
|
3370
3405
|
}));
|
|
3371
3406
|
const mcp = program.command("mcp").description("run the MCP server on stdio, for a coding agent to connect to").option("--account <name>", "pin the server to one account; every tool then acts on it and no other").action(async (flags) => {
|
|
3372
3407
|
ran = true;
|
|
3373
|
-
const { startResendStdioServer } = await import("./stdio-entry-
|
|
3408
|
+
const { startResendStdioServer } = await import("./stdio-entry-CKC1hDbG.mjs");
|
|
3374
3409
|
await startResendStdioServer({
|
|
3375
3410
|
...deps,
|
|
3376
3411
|
env,
|
package/dist/index.d.mts
CHANGED
|
@@ -11519,6 +11519,8 @@ type SendPolicy = "chat" | "confirm" | "never";
|
|
|
11519
11519
|
* way to change it, and that passes no gate at all.
|
|
11520
11520
|
*/
|
|
11521
11521
|
type ChangePolicy = "chat" | "confirm";
|
|
11522
|
+
/** Whether this machine checks for a newer release once a day, and stops until it is updated or put off. */
|
|
11523
|
+
type UpdateCheckSetting = "on" | "off";
|
|
11522
11524
|
type StoreKind = "keychain" | "file";
|
|
11523
11525
|
interface ClientConfig {
|
|
11524
11526
|
provider: string;
|
|
@@ -11573,6 +11575,15 @@ interface Defaults {
|
|
|
11573
11575
|
*/
|
|
11574
11576
|
elicitationClients: string[];
|
|
11575
11577
|
};
|
|
11578
|
+
/**
|
|
11579
|
+
* The daily update check (design 2026-09-28): once a day this machine asks npm whether a newer release is out, and
|
|
11580
|
+
* every server and command stops until it is updated or put off until tomorrow. Absent reads as `on`.
|
|
11581
|
+
*
|
|
11582
|
+
* Absent rather than filled in, as `changePolicy` is: a default the schema supplied would be written into the file
|
|
11583
|
+
* by the next unrelated change, where it would look like a choice somebody made. Turning it off is a loosening —
|
|
11584
|
+
* it is what keeps a machine from quietly falling behind a release — so it is approved like one; on applies at once.
|
|
11585
|
+
*/
|
|
11586
|
+
updateCheck?: UpdateCheckSetting | undefined;
|
|
11576
11587
|
}
|
|
11577
11588
|
/**
|
|
11578
11589
|
* A connected account on a platform that is not mail: a Slack workspace, and every channel after it (design
|
|
@@ -11786,6 +11797,11 @@ interface LooseningConsent {
|
|
|
11786
11797
|
*/
|
|
11787
11798
|
changes?: readonly Loosening[] | undefined;
|
|
11788
11799
|
}
|
|
11800
|
+
/**
|
|
11801
|
+
* Why a saved name has {@link DOWNLOAD_SUFFIX} after it: a name tools read or run on their own (`auto-read`), an
|
|
11802
|
+
* extension that is not one of the inert ones (`type`), or no extension at all (`no-extension`).
|
|
11803
|
+
*/
|
|
11804
|
+
type RenameReason = "auto-read" | "type" | "no-extension";
|
|
11789
11805
|
//#endregion
|
|
11790
11806
|
//#region src/approvals.d.ts
|
|
11791
11807
|
/**
|
|
@@ -11801,17 +11817,26 @@ interface LooseningConsent {
|
|
|
11801
11817
|
*
|
|
11802
11818
|
* A change approval (`kind: 'change'`) lives in the same store and goes through the same states, except that its
|
|
11803
11819
|
* claim goes straight to `used`: what it permits is a write to the configuration, which the claimant makes itself.
|
|
11820
|
+
*
|
|
11821
|
+
* So does a download's question (`kind: 'download'`): where to save the files a Gmail or Slack download names. It is
|
|
11822
|
+
* held to the change policy of the mailbox or workspace it is about, as every other change there is. Under `chat` the
|
|
11823
|
+
* person's answer, relayed from the chat, claims it straight from `pending`. Under `confirm` it has to be answered
|
|
11824
|
+
* where an agent cannot answer for them — at their own terminal, or in a form a trusted client shows them — which
|
|
11825
|
+
* records the answer and moves it to `approved`; only then is it claimed, straight to `used`, and the recorded answer
|
|
11826
|
+
* is the one that saves. It gets the store's expiry, its single use and its binding for the reason a change does: an
|
|
11827
|
+
* answer given for three invoices must not save the next conversation's files, nor be spent twice.
|
|
11804
11828
|
*/
|
|
11805
11829
|
type ApprovalState = "pending" | "approved" | "sending" | "used" | "failed" | "unknown" | "expired" | "revoked";
|
|
11806
11830
|
type ApprovalChannel = "elicitation" | "terminal";
|
|
11807
11831
|
/**
|
|
11808
|
-
* What an approval permits: a send (a mail, a post, a reaction)
|
|
11832
|
+
* What an approval permits: a send (a mail, a post, a reaction), a change to the configuration, or a download saved
|
|
11833
|
+
* where the person said.
|
|
11809
11834
|
*
|
|
11810
|
-
* One store for
|
|
11811
|
-
* a kind on every record, so that
|
|
11835
|
+
* One store for all three, so a change gets the machinery a send already has — expiry, single use, the typed code —
|
|
11836
|
+
* and a kind on every record, so that none can be spent as another. Without it, a person who approved a post at a
|
|
11812
11837
|
* terminal would also have approved whatever change an agent claimed under the same id.
|
|
11813
11838
|
*/
|
|
11814
|
-
type ApprovalKind = "send" | "change";
|
|
11839
|
+
type ApprovalKind = "send" | "change" | "download";
|
|
11815
11840
|
/** The inbox or account a change is about. `id` is absent when the change connects it, and it does not exist yet. */
|
|
11816
11841
|
interface ChangeTarget {
|
|
11817
11842
|
kind: "inbox" | "account";
|
|
@@ -11839,6 +11864,70 @@ interface ChangeBinding {
|
|
|
11839
11864
|
/** What it does outside the configuration, in words: a sign-in, a registration, files removed. */
|
|
11840
11865
|
effects: string[];
|
|
11841
11866
|
}
|
|
11867
|
+
/**
|
|
11868
|
+
* Exactly which download a question was asked for, stored on the record so the answer can be held to it.
|
|
11869
|
+
*
|
|
11870
|
+
* The files are the ones the question listed, by the platform's own ids — `<message id>/<part id>` for Gmail, the file
|
|
11871
|
+
* id for Slack — in the order they were listed: a claim for any other set is a claim for a download the person was
|
|
11872
|
+
* not asked about. So are the names they would be saved under, in the same order: a Slack file renamed between the
|
|
11873
|
+
* question and the answer would otherwise be saved under a name the person was never shown. The folders are the two
|
|
11874
|
+
* the question showed, as absolute paths. They are not part of the digest, because they are what the answer *means*
|
|
11875
|
+
* rather than what it is for: `downloads` in the answer is the path the person read, even when an agent runs the
|
|
11876
|
+
* download again from another folder.
|
|
11877
|
+
*/
|
|
11878
|
+
interface DownloadBinding {
|
|
11879
|
+
/** One line about the download, for a listing: "where to save 2 files from acme/gmail". Not part of the digest. */
|
|
11880
|
+
summary: string;
|
|
11881
|
+
/** The mailbox or workspace the files come from, by the id it has now. */
|
|
11882
|
+
target: {
|
|
11883
|
+
kind: "inbox" | "account";
|
|
11884
|
+
name: string;
|
|
11885
|
+
id: string;
|
|
11886
|
+
};
|
|
11887
|
+
/** Which download asked: `attachments.download`, `files.download`. */
|
|
11888
|
+
operation: string;
|
|
11889
|
+
/** The request as the caller made it, in the words of its own arguments: the selection, and how many at most. */
|
|
11890
|
+
request: Record<string, unknown>;
|
|
11891
|
+
/** The files the question listed, by the platform's ids, in order. */
|
|
11892
|
+
files: string[];
|
|
11893
|
+
/** The names those files would be saved under, as the question listed them, in the same order. */
|
|
11894
|
+
names: string[];
|
|
11895
|
+
/** The two folders the question offered, as absolute paths. */
|
|
11896
|
+
folders: {
|
|
11897
|
+
downloads: string;
|
|
11898
|
+
current: string;
|
|
11899
|
+
};
|
|
11900
|
+
/**
|
|
11901
|
+
* The files as the question listed them to the person — the names they would be saved under, their sizes, why a
|
|
11902
|
+
* name has `.download` after it, and their risk flags — so a terminal or a form can show the question, and its
|
|
11903
|
+
* warnings, again. Not part of the digest: the ids and names above are.
|
|
11904
|
+
*/
|
|
11905
|
+
listing?: ListedFile[] | undefined;
|
|
11906
|
+
/** The person's answer, when they gave it where an agent cannot: at a terminal, or in a trusted client's form. */
|
|
11907
|
+
answer?: RecordedSaveAnswer | undefined;
|
|
11908
|
+
}
|
|
11909
|
+
/** One file as a question lists it. */
|
|
11910
|
+
interface ListedFile {
|
|
11911
|
+
/** The name it would be saved under: the sender's made safe, with `.download` after it unless it is inert. */
|
|
11912
|
+
name: string;
|
|
11913
|
+
size: number | null;
|
|
11914
|
+
/** Why `.download` is after its name; absent when the name is the sender's, made safe. */
|
|
11915
|
+
renamed?: RenameReason | undefined;
|
|
11916
|
+
/** Its risk flags, as the file's own listing gives them. */
|
|
11917
|
+
flags?: string[] | undefined;
|
|
11918
|
+
}
|
|
11919
|
+
/**
|
|
11920
|
+
* An answer recorded on a question: one of the two folders it offered, or the folder the person named, as an absolute
|
|
11921
|
+
* path — resolved where they typed it, so it means the folder they read.
|
|
11922
|
+
*/
|
|
11923
|
+
type RecordedSaveAnswer = {
|
|
11924
|
+
readonly choice: "downloads" | "current";
|
|
11925
|
+
} | {
|
|
11926
|
+
readonly choice: "other";
|
|
11927
|
+
readonly folder: string;
|
|
11928
|
+
};
|
|
11929
|
+
/** What a claim of a download's question is held to: all of the binding but its summary and the folders it offered. */
|
|
11930
|
+
type DownloadRequest = Pick<DownloadBinding, "target" | "operation" | "request" | "files" | "names">;
|
|
11842
11931
|
interface Expectation {
|
|
11843
11932
|
to: string[];
|
|
11844
11933
|
cc: string[];
|
|
@@ -11877,6 +11966,8 @@ interface ApprovalRecord {
|
|
|
11877
11966
|
reason?: string | undefined;
|
|
11878
11967
|
/** For a change: exactly what it permits. `digest` is `changeDigest` of this. */
|
|
11879
11968
|
change?: ChangeBinding | undefined;
|
|
11969
|
+
/** For a download's question: what it asked about, and the folders it offered. `digest` is `downloadDigest`. */
|
|
11970
|
+
download?: DownloadBinding | undefined;
|
|
11880
11971
|
}
|
|
11881
11972
|
interface CreateApprovalInput {
|
|
11882
11973
|
inboxId: string;
|
|
@@ -11922,6 +12013,7 @@ declare class ApprovalStore {
|
|
|
11922
12013
|
constructor(stateDir: string, options?: {
|
|
11923
12014
|
now?: () => Date;
|
|
11924
12015
|
ttlMs?: number;
|
|
12016
|
+
downloadTtlMs?: number;
|
|
11925
12017
|
});
|
|
11926
12018
|
create(input: CreateApprovalInput): Promise<ApprovalRecord>;
|
|
11927
12019
|
get(approvalId: string): Promise<ApprovalRecord | null>;
|
|
@@ -11968,6 +12060,52 @@ declare class ApprovalStore {
|
|
|
11968
12060
|
* person to have typed the code at a terminal first; until then the refusal leaves the record as it is.
|
|
11969
12061
|
*/
|
|
11970
12062
|
claimForChange(approvalId: string, live: LiveChange, options?: ClaimOptions): Promise<ApprovalRecord>;
|
|
12063
|
+
/**
|
|
12064
|
+
* A download's question, pending, bound to `downloadDigest(input.download)` — computed here, never taken from the
|
|
12065
|
+
* caller, as a change's digest is. It stands in for the draft revision too.
|
|
12066
|
+
*
|
|
12067
|
+
* `policy` is the change policy of the mailbox or workspace the files come from, as it stands when the question is
|
|
12068
|
+
* asked: where a stranger's files land on this machine is a change to it, and is answered the way that account's
|
|
12069
|
+
* other changes are approved. A claim holds the question to the stricter of this and the policy then.
|
|
12070
|
+
*/
|
|
12071
|
+
createDownload(input: {
|
|
12072
|
+
download: DownloadBinding;
|
|
12073
|
+
policy: ChangePolicy;
|
|
12074
|
+
}): Promise<ApprovalRecord & {
|
|
12075
|
+
download: DownloadBinding;
|
|
12076
|
+
}>;
|
|
12077
|
+
/**
|
|
12078
|
+
* Records the person's answer to a download's question, given where an agent cannot give it: at their own terminal,
|
|
12079
|
+
* or in a form a trusted client showed them. The question moves to `approved`, carrying the answer, and waits for
|
|
12080
|
+
* the download to claim it — which it may then do under `confirm`, and which saves where this answer says.
|
|
12081
|
+
*
|
|
12082
|
+
* Only a pending question can be answered, and only once: a second answer is refused, not taken over the first.
|
|
12083
|
+
*/
|
|
12084
|
+
answerDownload(approvalId: string, via: ApprovalChannel, answer: RecordedSaveAnswer): Promise<ApprovalRecord & {
|
|
12085
|
+
download: DownloadBinding;
|
|
12086
|
+
}>;
|
|
12087
|
+
/**
|
|
12088
|
+
* Claims a download's question, once, for the download the caller is about to make — and returns it, with the
|
|
12089
|
+
* folders it offered, so that `downloads` and `current` in the answer mean the paths the person read, and with the
|
|
12090
|
+
* answer the person recorded, when they recorded one.
|
|
12091
|
+
*
|
|
12092
|
+
* `live` is the download as the caller computes it now: the same account, the same request, the same files under
|
|
12093
|
+
* the same names — the person answered for those files and no others. Any other is refused, and the question left
|
|
12094
|
+
* open, as it was: a second call that got an argument wrong is the agent's slip, not the person's, and voiding the
|
|
12095
|
+
* question for it would make the person answer again for nothing. It still expires, and is claimed once.
|
|
12096
|
+
*
|
|
12097
|
+
* `options.policy` is the change policy of that account now, and the stricter of it and the one the question was
|
|
12098
|
+
* asked under decides. Under `chat`, a pending question is claimed with the answer the caller carries: the person
|
|
12099
|
+
* gave it in the conversation. Under `confirm`, only a question the person answered at a terminal or in a trusted
|
|
12100
|
+
* form can be claimed; a pending one is refused, and left as it is, so they can still answer it. One used, voided or
|
|
12101
|
+
* expired is refused as an approval in that state is.
|
|
12102
|
+
*/
|
|
12103
|
+
claimForDownload(approvalId: string, live: DownloadRequest, options?: {
|
|
12104
|
+
policy?: ChangePolicy | undefined;
|
|
12105
|
+
pendingHint?: string | undefined;
|
|
12106
|
+
}): Promise<ApprovalRecord & {
|
|
12107
|
+
download: DownloadBinding;
|
|
12108
|
+
}>;
|
|
11971
12109
|
/** Records the outcome of the one send attempt. */
|
|
11972
12110
|
complete(approvalId: string, outcome: {
|
|
11973
12111
|
sentMessageId: string;
|
|
@@ -12067,7 +12205,10 @@ interface ResolvedPaths {
|
|
|
12067
12205
|
secretsDir: string;
|
|
12068
12206
|
/** Managed runtime installs for MCP clients. */
|
|
12069
12207
|
dataDir: string;
|
|
12070
|
-
/**
|
|
12208
|
+
/**
|
|
12209
|
+
* Default root for exports and for the files Resend's received mail carries. A Gmail attachment or a Slack file is
|
|
12210
|
+
* not saved here: those downloads ask the person where to save (`save-destination.ts`).
|
|
12211
|
+
*/
|
|
12071
12212
|
downloadsDir: string;
|
|
12072
12213
|
}
|
|
12073
12214
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as createResendMcpServer } from "./server-
|
|
1
|
+
import { _t as VERSION, gt as ROUTES, ht as RESEND_DOWNLOAD_ORIGIN, mt as RESEND_API_ORIGIN, pt as REFUSED } from "./send-Ba1caJ6x.mjs";
|
|
2
|
+
import { t as createResendMcpServer } from "./server-BHHLf1lV.mjs";
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
/**
|
|
5
5
|
* The library entry of `@agentcomms/resend`.
|