@agentcomms/resend 0.13.0 → 0.14.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/README.md +9 -7
- package/dist/cli.mjs +121 -62
- package/dist/index.d.mts +12914 -12032
- package/dist/index.mjs +5 -6
- package/dist/{send-D2ZOWO_6.mjs → send-9pskzeUY.mjs} +9346 -4699
- package/dist/{server-CLjdcZDg.mjs → server-DFbgMT3K.mjs} +41 -18
- package/dist/{stdio-entry-BCnq_EyF.mjs → stdio-entry-PsvT2AI2.mjs} +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -17,12 +17,13 @@ code, but the key itself would not stop anything else that held it. A sending-on
|
|
|
17
17
|
enforces that — and it cannot read anything, so reads with it are reported as unavailable. `agent-resend doctor`
|
|
18
18
|
says this on every run.
|
|
19
19
|
|
|
20
|
-
**Nothing is sent without a person.** A send is prepared, previewed — every recipient, BCC included, the reach
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
an address that arrived in mail read here, a person at a terminal
|
|
24
|
-
the request's `Idempotency-Key` and an `agentcomms_approval` tag; a
|
|
25
|
-
only checked
|
|
20
|
+
**Nothing is sent without a person.** A send is prepared, previewed — every recipient, BCC included, the reach (unique
|
|
21
|
+
recipients), the From domain and whether it is verified — and approved: a yes in the conversation under the account's
|
|
22
|
+
`chat` policy, or the approve command the result gives and a typed code under `confirm` — which the agent learns of
|
|
23
|
+
with `resend_send_wait`. Above ten recipients, or to an address that arrived in mail read here, a person at a terminal
|
|
24
|
+
approves, whatever the policy. The approval id is the request's `Idempotency-Key` and an `agentcomms_approval` tag; a
|
|
25
|
+
send whose outcome is unknown (`SEND_OUTCOME_UNKNOWN`) is never repeated, only checked, and a scheduled one is
|
|
26
|
+
"accepted", never "sent", until Resend's own last event for it says so.
|
|
26
27
|
|
|
27
28
|
**The key is typed by a person, at a terminal.** `agent-resend account add` reads it from a hidden prompt, or from
|
|
28
29
|
`RESEND_API_KEY` in that terminal, and stores it in the system keychain. No MCP tool accepts a key: a key typed into
|
|
@@ -96,9 +97,10 @@ terminal), `approve` (under `confirm`, a person at a terminal) and `mcp` (it sta
|
|
|
96
97
|
| `agent-resend send prepare` | `resend_send_prepare` |
|
|
97
98
|
| `agent-resend send execute <approvalId>` | `resend_send_execute` |
|
|
98
99
|
| `agent-resend send status <approvalId>` | `resend_send_status` |
|
|
100
|
+
| `agent-resend send wait <approvalId>` | `resend_send_wait` |
|
|
99
101
|
| `agent-resend scheduled list` | `resend_scheduled_list` |
|
|
100
102
|
| `agent-resend scheduled cancel <id>` | `resend_scheduled_cancel` |
|
|
101
|
-
| `agent-resend approve <approvalId>` | — a person at a terminal |
|
|
103
|
+
| `agent-resend approve <approvalId>` | — a person at a terminal, with the command the result gives |
|
|
102
104
|
| `agent-resend mcp` | — it starts the server |
|
|
103
105
|
| `agent-resend mcp install --client <client>` | `comms_server_install` (core) |
|
|
104
106
|
| `agent-resend mcp prune` | `comms_server_prune` (core) |
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as exemptFromUpdateGate, B as UPDATE_CHECK_CHILD_COMMAND, C as policyChange, D as sendPolicyOf, E as removeAccountChange, Et as writeResult, F as CHANNEL_SERVERS, G as canPrompt, H as approvalsOf, I as CommsError, J as cliHandoffs, L as EXIT_CODES, N as checkNewName, Nt as VERSION, O as showAccount, Ot as RESEND_CALLER, Q as escapeForDisplay, R as PATH_OPTIONS, S as policyApprovalRefusal, T as policyWanted, Tt as wholeNumber, U as approveChangeAtTerminal, V as agentMarker, W as approveRefusedHint, X as commandPathOf, Y as colorEnabled, Z as describeFenceReport, _ as runDoctor, _t as serverPruneChange, a as revokeSendApproval, at as kindOf, b as listAccounts, bt as truncateDisplay, c as downloadReceived, ct as pathOverridesFromCliOptions, d as listReceived, dt as renderInstall, f as listScheduled, ft as renderPrune, g as showSentEmail, gt as serverInstallChange, h as showReceived, ht as runUpdateCheckChild, i as prepareSend, it as installExitStatus, k as ResendContext, l as getMetrics, lt as refuseUnclaimedApproval, m as listSuppressions, mt as runCommand, n as executeSend, nt as handoffSentence, o as sendStatus, ot as openCore, p as listSentEmails, pt as resolvePaths, r as finishSendApproval, rt as handoffSentenceToFill, s as cancelScheduledChange, st as paint, t as beginSendApproval, tt as gatedChangeAtTerminal, u as listDomains, ut as renderApprovalWait, v as addAccountChange, vt as terminalUpdateHooks, w as policyReport, wt as waitForApproval, x as modeOf, xt as updateGateAtTerminal, y as inspectKey, z as SEND_LOOKUP } from "./send-9pskzeUY.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { stripVTControlCharacters } from "node:util";
|
|
@@ -3040,12 +3040,12 @@ const KEY_SHAPE = /^re_[A-Za-z0-9_]{8,200}$/;
|
|
|
3040
3040
|
async function readApiKey(env, streams, options) {
|
|
3041
3041
|
const marker = agentMarker(env);
|
|
3042
3042
|
if (marker) throw new CommsError("AUTH_REQUIRED", "a Resend API key is added by a person at their own terminal, not by an agent", {
|
|
3043
|
-
hint: `Ask the user to run ${
|
|
3043
|
+
hint: `${handoffSentence(options.command, (command) => `Ask the user to run ${command} in their own terminal.`)} Never paste a key into a chat: the transcript keeps it.`,
|
|
3044
3044
|
details: { marker }
|
|
3045
3045
|
});
|
|
3046
3046
|
let key = env["RESEND_API_KEY"]?.trim() ?? "";
|
|
3047
3047
|
if (key === "") {
|
|
3048
|
-
if (!canPrompt(env, streams, { json: options.json })) throw new CommsError("AUTH_REQUIRED", "a Resend API key is needed, and there is no terminal to ask for it", { hint: `A person runs ${
|
|
3048
|
+
if (!canPrompt(env, streams, { json: options.json })) throw new CommsError("AUTH_REQUIRED", "a Resend API key is needed, and there is no terminal to ask for it", { hint: handoffSentence(options.command, (command) => `A person runs ${command} in a terminal, which asks for the key without showing it, or sets ${KEY_ENV} for that one command.`) });
|
|
3049
3049
|
key = (await askHidden(streams, "Resend API key (https://resend.com/api-keys; it is not shown as you type): ")).trim();
|
|
3050
3050
|
}
|
|
3051
3051
|
if (!KEY_SHAPE.test(key)) throw new CommsError("BAD_DATA", "that does not look like a Resend API key (they start with re_)", { hint: "Nothing was stored. Copy the key again from the Resend dashboard." });
|
|
@@ -3069,8 +3069,9 @@ function renderAccount(view, color) {
|
|
|
3069
3069
|
` ${view.guarantee}`
|
|
3070
3070
|
].join("\n");
|
|
3071
3071
|
}
|
|
3072
|
-
|
|
3073
|
-
|
|
3072
|
+
/** The accounts; with none, how a person adds one — this installation's own command, `handoffs`'s, or why there is none. */
|
|
3073
|
+
function renderAccounts(result, color, handoffs) {
|
|
3074
|
+
if (result.accounts.length === 0) return handoffSentenceToFill(handoffs.own(["account", "add"]), ["<org/resend>"], (command) => `No Resend account yet. A person adds one with ${command}.`, { instead: "No Resend account yet." });
|
|
3074
3075
|
return result.accounts.map((view) => renderAccount(view, color)).join("\n\n");
|
|
3075
3076
|
}
|
|
3076
3077
|
function renderAdded(view, color) {
|
|
@@ -3088,7 +3089,8 @@ function renderPolicy(report) {
|
|
|
3088
3089
|
return [
|
|
3089
3090
|
`${report.name}: ${report.mode} mode, send policy ${report.sendPolicy}${report.sendPolicyFrom === "default" ? " (the machine default)" : ""}`,
|
|
3090
3091
|
`Changes to it are approved under the machine's change policy: ${report.changePolicy}.`,
|
|
3091
|
-
`A send to more than ${report.confirmAboveRecipients} people needs a person at a terminal, whatever the policy
|
|
3092
|
+
`A send to more than ${report.confirmAboveRecipients} people needs a person at a terminal, whatever the policy.`,
|
|
3093
|
+
...report.fenced === void 0 ? [] : describeFenceReport(report.fenced)
|
|
3092
3094
|
].join("\n");
|
|
3093
3095
|
}
|
|
3094
3096
|
function renderDoctor(result, color) {
|
|
@@ -3114,15 +3116,22 @@ function renderRead(result, color) {
|
|
|
3114
3116
|
function renderPrepared(result) {
|
|
3115
3117
|
return `${result.preview}\n\nNext: ${escapeForDisplay(result.nextStep)}`;
|
|
3116
3118
|
}
|
|
3119
|
+
/**
|
|
3120
|
+
* What Resend's acceptance means (`said`), with its id when it gave one: a scheduled send is accepted for its time and
|
|
3121
|
+
* never called sent; one with no id says so, and names none.
|
|
3122
|
+
*/
|
|
3117
3123
|
function renderSent(result) {
|
|
3118
|
-
const
|
|
3124
|
+
const to = truncateDisplay(result.to.join(", "), 200);
|
|
3125
|
+
const said = `${result.said.charAt(0).toUpperCase()}${result.said.slice(1)}`;
|
|
3126
|
+
const sent = result.resendId === void 0 ? result.state === "scheduled" ? `${said} — scheduled for ${result.scheduledAt ?? "?"}, to ${to}.` : `${said} — to ${to}.` : result.state === "scheduled" ? `${said}, as ${result.resendId}, to ${to}.` : `Sent as ${result.resendId}, to ${to}.`;
|
|
3119
3127
|
return result.note ? `${sent}\nNote: ${escapeForDisplay(result.note)}.` : sent;
|
|
3120
3128
|
}
|
|
3121
3129
|
function renderStatus(result) {
|
|
3122
3130
|
return `${result.approvalId}: ${escapeForDisplay(result.verdict)}`;
|
|
3123
3131
|
}
|
|
3124
3132
|
function renderCancelled(result) {
|
|
3125
|
-
|
|
3133
|
+
const cancelled = `Cancelled ${result.id} (${result.recipients} recipient(s)). It cannot be rescheduled.`;
|
|
3134
|
+
return result.hint ? `${cancelled}\nNote: ${escapeForDisplay(result.hint)}` : cancelled;
|
|
3126
3135
|
}
|
|
3127
3136
|
//#endregion
|
|
3128
3137
|
//#region src/cli/program.ts
|
|
@@ -3131,6 +3140,17 @@ function withoutOptionValues(text) {
|
|
|
3131
3140
|
return text.replace(/'(-{1,2}[^'=\s]+)=[^']*'/g, "'$1=…'").replace(/\bre_[A-Za-z0-9_]{4,}/g, "re_…");
|
|
3132
3141
|
}
|
|
3133
3142
|
const collect = (value, previous = []) => [...previous, value];
|
|
3143
|
+
/** What a usage error tells the person: this installation's own `--help`, located — or why there is none here. */
|
|
3144
|
+
function usageHint(help) {
|
|
3145
|
+
return handoffSentence(help, (command) => `Run ${command} to see the commands.`);
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* What a command line that ran nothing says. Commander shows the help for an empty one itself, so this is the fallback
|
|
3149
|
+
* for a parse that ends with no command run and no error — never a bare name either way.
|
|
3150
|
+
*/
|
|
3151
|
+
function nothingToDo(help) {
|
|
3152
|
+
return handoffSentence(help, (command) => `Nothing to do. Try ${command}.`);
|
|
3153
|
+
}
|
|
3134
3154
|
/** `--expect-to a@x.test,b@x.test`, or `none`: a list is always written out, so a permission prompt shows it. */
|
|
3135
3155
|
function expectList(raw, flag) {
|
|
3136
3156
|
const value = String(raw ?? "").trim();
|
|
@@ -3155,17 +3175,44 @@ async function run(argv, deps = {}) {
|
|
|
3155
3175
|
writeErr: (text) => streams.stderr.write(text),
|
|
3156
3176
|
outputError: (text, write) => write(withoutOptionValues(text))
|
|
3157
3177
|
}).addHelpText("after", `
|
|
3158
|
-
Getting started:
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3178
|
+
Getting started — run these with this CLI:
|
|
3179
|
+
account add acme/resend a person types the key; it goes into the keychain
|
|
3180
|
+
doctor what works, and who enforces what
|
|
3181
|
+
domains --account acme/resend
|
|
3182
|
+
received list --account acme/resend
|
|
3183
|
+
mcp install --client claude-code register the server with an agent, once a person approves it
|
|
3164
3184
|
|
|
3165
3185
|
Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs approval · 11 an update
|
|
3166
|
-
is out: update first, or put it off (
|
|
3186
|
+
is out: update first, or put it off (the stop names both commands) · 64 usage ·
|
|
3167
3187
|
65 bad data · 66 not found · 69 provider or secret store unavailable · 75 temporary (retry later) ·
|
|
3168
3188
|
77 key or permission needed · 78 configuration problem.`).exitOverride();
|
|
3189
|
+
for (const { flag } of PATH_OPTIONS) program.addOption(new Option(`${flag} <dir>`, "pin this suite directory for this run").argParser((value) => {
|
|
3190
|
+
if (value.length === 0) throw new InvalidArgumentError(`${flag} needs a non-empty directory`);
|
|
3191
|
+
return value;
|
|
3192
|
+
}));
|
|
3193
|
+
let invocationCore;
|
|
3194
|
+
const coreForInvocation = () => {
|
|
3195
|
+
if (invocationCore) return invocationCore;
|
|
3196
|
+
const options = program.opts();
|
|
3197
|
+
const pathOverrides = pathOverridesFromCliOptions(Object.fromEntries(PATH_OPTIONS.map(({ key, option }) => [option, options[key]])));
|
|
3198
|
+
invocationCore = Object.keys(pathOverrides).length === 0 && deps.core ? deps.core : openCore({
|
|
3199
|
+
env,
|
|
3200
|
+
platform,
|
|
3201
|
+
pathOverrides,
|
|
3202
|
+
caller: RESEND_CALLER
|
|
3203
|
+
});
|
|
3204
|
+
return invocationCore;
|
|
3205
|
+
};
|
|
3206
|
+
/** This installation's own `--help`, for a usage error or an empty command line: it opens no folder, so pins none. */
|
|
3207
|
+
const help = () => cliHandoffs({
|
|
3208
|
+
caller: RESEND_CALLER,
|
|
3209
|
+
paths: resolvePaths({
|
|
3210
|
+
env,
|
|
3211
|
+
platform
|
|
3212
|
+
}),
|
|
3213
|
+
platform,
|
|
3214
|
+
env
|
|
3215
|
+
}).own(["--help"], { uses: [] });
|
|
3169
3216
|
const globals = () => {
|
|
3170
3217
|
const options = program.opts();
|
|
3171
3218
|
return {
|
|
@@ -3182,7 +3229,7 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3182
3229
|
program.hook("preAction", async (_program, command) => {
|
|
3183
3230
|
const path = commandPathOf(command);
|
|
3184
3231
|
if (exemptFromUpdateGate(path, ["update-check-child"])) return;
|
|
3185
|
-
const core =
|
|
3232
|
+
const core = coreForInvocation();
|
|
3186
3233
|
let ended = null;
|
|
3187
3234
|
const code = await runCommand(output(), async () => {
|
|
3188
3235
|
ended = await updateGateAtTerminal({
|
|
@@ -3194,13 +3241,12 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3194
3241
|
output: output(),
|
|
3195
3242
|
noInput: false,
|
|
3196
3243
|
streams,
|
|
3197
|
-
approveCommand: "agent-resend approve",
|
|
3198
3244
|
approvals: approvalsOf(command),
|
|
3199
|
-
approvalClaim: path.join(" ") === "send status" ? SEND_LOOKUP : void 0,
|
|
3245
|
+
approvalClaim: path.join(" ") === "send status" ? SEND_LOOKUP : path.join(" ") === "send wait" ? { lookup: true } : void 0,
|
|
3200
3246
|
...terminalUpdateHooks(core, env, {
|
|
3247
|
+
channel: "resend",
|
|
3201
3248
|
output: output(),
|
|
3202
|
-
streams
|
|
3203
|
-
approveCommand: "agent-resend approve"
|
|
3249
|
+
streams
|
|
3204
3250
|
})
|
|
3205
3251
|
});
|
|
3206
3252
|
}, streams);
|
|
@@ -3213,24 +3259,30 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3213
3259
|
exitCode = gated;
|
|
3214
3260
|
return;
|
|
3215
3261
|
}
|
|
3262
|
+
const core = coreForInvocation();
|
|
3216
3263
|
const context = new ResendContext({
|
|
3217
3264
|
...deps,
|
|
3265
|
+
core,
|
|
3218
3266
|
env,
|
|
3267
|
+
pathOverrides: core.pathOverrides,
|
|
3219
3268
|
surface: "cli"
|
|
3220
3269
|
});
|
|
3221
3270
|
exitCode = await runCommand(output(), () => body(context, globals(), ...args), streams);
|
|
3222
3271
|
if (exitCode === 0 && softExit !== null) exitCode = softExit;
|
|
3223
3272
|
};
|
|
3224
|
-
/**
|
|
3225
|
-
|
|
3273
|
+
/**
|
|
3274
|
+
* A change the way every changing command runs one: core's `gatedChangeAtTerminal`. `rerun` is this command's words
|
|
3275
|
+
* after the program, which core locates as Resend's own and runs again with `--approval <id>`.
|
|
3276
|
+
*/
|
|
3277
|
+
const changeAt = (context, change, flags, rerun) => gatedChangeAtTerminal(context.core, change, {
|
|
3278
|
+
channel: "resend",
|
|
3226
3279
|
approvalId: flags.approval === void 0 ? void 0 : String(flags.approval),
|
|
3227
3280
|
env,
|
|
3228
3281
|
output: output(),
|
|
3229
|
-
|
|
3230
|
-
approveCommand: "agent-resend approve",
|
|
3282
|
+
rerun,
|
|
3231
3283
|
streams
|
|
3232
3284
|
});
|
|
3233
|
-
const approvalOption = (command) => command.option("--approval <approvalId>", "apply a change a person approved: said yes to in chat, or approved with
|
|
3285
|
+
const approvalOption = (command) => command.option("--approval <approvalId>", "apply a change a person approved: said yes to in chat, or approved with the approve command it gave");
|
|
3234
3286
|
const accountOption = (command) => command.requiredOption("--account <name>", "which account, as `organisation/resend`");
|
|
3235
3287
|
const account = program.command("account").description("connect, inspect, re-police and remove Resend accounts");
|
|
3236
3288
|
approvalOption(account.command("add <name>").description("connect a Resend API key, typed by a person at this terminal (or RESEND_API_KEY); never from a chat").addOption(new Option("--mode <mode>", "read (the default for a full-access key) or send").choices(["read", "send"])).addOption(new Option("--send <policy>", "how its sends are approved; the machine default if left out").choices([
|
|
@@ -3239,19 +3291,18 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3239
3291
|
"never"
|
|
3240
3292
|
])).option("--domain <domain>", "for a sending-only key restricted to one domain: that domain")).action(act(async (context, options, name, flags) => {
|
|
3241
3293
|
const mode = modeOf(flags.mode);
|
|
3242
|
-
const
|
|
3243
|
-
"agent-resend",
|
|
3294
|
+
const rerun = [
|
|
3244
3295
|
"account",
|
|
3245
3296
|
"add",
|
|
3246
3297
|
name,
|
|
3247
3298
|
...mode ? ["--mode", mode] : [],
|
|
3248
3299
|
...flags.send ? ["--send", String(flags.send)] : [],
|
|
3249
3300
|
...flags.domain ? ["--domain", String(flags.domain)] : []
|
|
3250
|
-
]
|
|
3251
|
-
checkNewName(await context.config(), name, context.
|
|
3301
|
+
];
|
|
3302
|
+
checkNewName(await context.config(), name, context.handoffs);
|
|
3252
3303
|
const key = await readApiKey(env, streams, {
|
|
3253
3304
|
json: options.json,
|
|
3254
|
-
command
|
|
3305
|
+
command: context.handoffs.own(rerun)
|
|
3255
3306
|
});
|
|
3256
3307
|
const inspection = await inspectKey(context, key);
|
|
3257
3308
|
const added = await changeAt(context, addAccountChange(context, {
|
|
@@ -3260,24 +3311,23 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3260
3311
|
mode,
|
|
3261
3312
|
sendPolicy: sendPolicyOf(flags.send),
|
|
3262
3313
|
domain: flags.domain === void 0 ? void 0 : String(flags.domain)
|
|
3263
|
-
}, inspection), flags,
|
|
3314
|
+
}, inspection), flags, rerun);
|
|
3264
3315
|
writeResult(added, output(), (data) => renderAdded(data, options.color), streams);
|
|
3265
3316
|
}));
|
|
3266
3317
|
account.command("list").description("the Resend accounts this machine can use, and what each may do").action(act(async (context, options) => {
|
|
3267
3318
|
const result = await listAccounts(context);
|
|
3268
|
-
writeResult(result, output(), (data) => renderAccounts(data, options.color), streams);
|
|
3319
|
+
writeResult(result, output(), (data) => renderAccounts(data, options.color, context.handoffs), streams);
|
|
3269
3320
|
}));
|
|
3270
3321
|
account.command("show <name>").description("one account: its key’s permission, its mode, its send policy, and who enforces what").action(act(async (context, options, name) => {
|
|
3271
3322
|
const result = await showAccount(context, name);
|
|
3272
3323
|
writeResult(result, output(), (data) => renderAccount(data, options.color), streams);
|
|
3273
3324
|
}));
|
|
3274
3325
|
approvalOption(account.command("remove <name>").description("forget an account and delete its key, once a person approves it")).action(act(async (context, _options, name, flags) => {
|
|
3275
|
-
const removed = await changeAt(context, removeAccountChange(context, name), flags,
|
|
3276
|
-
"agent-resend",
|
|
3326
|
+
const removed = await changeAt(context, removeAccountChange(context, name), flags, [
|
|
3277
3327
|
"account",
|
|
3278
3328
|
"remove",
|
|
3279
3329
|
name
|
|
3280
|
-
]
|
|
3330
|
+
]);
|
|
3281
3331
|
writeResult(removed, output(), renderRemoved, streams);
|
|
3282
3332
|
}));
|
|
3283
3333
|
approvalOption(account.command("policy <name>").description("report how its sends and changes are approved, or set --send, --mode and --change; loosening is approved by a person").option("--send <policy>", "chat, confirm or never").option("--mode <mode>", "read or send").option("--change <policy>", "how a loosening of this account is approved: chat or confirm")).action(act(async (context, _options, name, flags) => {
|
|
@@ -3288,15 +3338,14 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3288
3338
|
});
|
|
3289
3339
|
const reporting = wanted.send === void 0 && wanted.mode === void 0 && wanted.change === void 0;
|
|
3290
3340
|
if (reporting) refuseUnclaimedApproval(flags.approval, policyApprovalRefusal("cli"));
|
|
3291
|
-
const result = reporting ? await policyReport(context, name) : await changeAt(context, policyChange(context, name, wanted), flags,
|
|
3292
|
-
"agent-resend",
|
|
3341
|
+
const result = reporting ? await policyReport(context, name) : await changeAt(context, policyChange(context, name, wanted), flags, [
|
|
3293
3342
|
"account",
|
|
3294
3343
|
"policy",
|
|
3295
3344
|
name,
|
|
3296
3345
|
...wanted.send ? ["--send", wanted.send] : [],
|
|
3297
3346
|
...wanted.mode ? ["--mode", wanted.mode] : [],
|
|
3298
3347
|
...wanted.change ? ["--change", wanted.change] : []
|
|
3299
|
-
]
|
|
3348
|
+
]);
|
|
3300
3349
|
writeResult(result, output(), renderPolicy, streams);
|
|
3301
3350
|
}));
|
|
3302
3351
|
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) => {
|
|
@@ -3391,39 +3440,42 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3391
3440
|
const status = await sendStatus(context, String(flags.account), approvalId);
|
|
3392
3441
|
writeResult(status, output(), renderStatus, streams);
|
|
3393
3442
|
}));
|
|
3443
|
+
send.command("wait <approvalId>").description("wait for an approval to be usable or finished, and say where it stands — it only looks, and never sends").option("--wait-seconds <n>", `how long to wait: 30 when left out, 300 at most, 0 for the status now`).action(act(async (context, _options, approvalId, flags) => {
|
|
3444
|
+
const result = await waitForApproval(context.core, approvalId, {
|
|
3445
|
+
waitSeconds: wholeNumber(flags.waitSeconds, {
|
|
3446
|
+
name: "--wait-seconds",
|
|
3447
|
+
min: 0,
|
|
3448
|
+
max: 300
|
|
3449
|
+
}),
|
|
3450
|
+
channel: "resend"
|
|
3451
|
+
});
|
|
3452
|
+
writeResult(result, output(), renderApprovalWait, streams);
|
|
3453
|
+
}));
|
|
3394
3454
|
const scheduled = program.command("scheduled").description("emails waiting to be sent at a later time");
|
|
3395
3455
|
accountOption(scheduled.command("list")).description("scheduled emails among the most recent 300 sent").action(act(async (context, options, flags) => {
|
|
3396
3456
|
const result = await listScheduled(context, String(flags.account));
|
|
3397
3457
|
writeResult(result, output(), (data) => renderRead(data, options.color), streams);
|
|
3398
3458
|
}));
|
|
3399
3459
|
approvalOption(accountOption(scheduled.command("cancel <id>")).description("cancel a scheduled email; one this machine did not schedule is approved by a person first")).action(act(async (context, _options, id, flags) => {
|
|
3400
|
-
const cancelled = await changeAt(context, cancelScheduledChange(context, String(flags.account), id), flags,
|
|
3401
|
-
"agent-resend",
|
|
3460
|
+
const cancelled = await changeAt(context, cancelScheduledChange(context, String(flags.account), id), flags, [
|
|
3402
3461
|
"scheduled",
|
|
3403
3462
|
"cancel",
|
|
3404
3463
|
id,
|
|
3405
3464
|
"--account",
|
|
3406
3465
|
String(flags.account)
|
|
3407
|
-
]
|
|
3466
|
+
]);
|
|
3408
3467
|
writeResult(cancelled, output(), renderCancelled, streams);
|
|
3409
3468
|
}));
|
|
3410
3469
|
program.command("approve <approvalId>").description("approve a send or a change at this terminal: read it, then type the code back").action(act(async (context, options, approvalId) => {
|
|
3411
3470
|
const marker = agentMarker(env);
|
|
3471
|
+
const approve = context.handoffs.own(["approve", approvalId]);
|
|
3412
3472
|
if (marker) throw new CommsError("APPROVAL_REQUIRED", "only a person can approve a send or a change, not an agent", {
|
|
3413
|
-
hint:
|
|
3414
|
-
"agent-resend",
|
|
3415
|
-
"approve",
|
|
3416
|
-
approvalId
|
|
3417
|
-
], context.platform))} in their own terminal.`,
|
|
3473
|
+
hint: approveRefusedHint(context.handoffs, approvalId),
|
|
3418
3474
|
details: { marker }
|
|
3419
3475
|
});
|
|
3420
|
-
if (!canPrompt(env, streams, { json: options.json })) throw new CommsError("APPROVAL_REQUIRED", "approving needs an interactive terminal", { hint: `Run ${
|
|
3421
|
-
"agent-resend",
|
|
3422
|
-
"approve",
|
|
3423
|
-
approvalId
|
|
3424
|
-
], context.platform))} directly in a terminal.` });
|
|
3476
|
+
if (!canPrompt(env, streams, { json: options.json })) throw new CommsError("APPROVAL_REQUIRED", "approving needs an interactive terminal", { hint: handoffSentence(approve, (command) => `Run ${command} directly in a terminal.`) });
|
|
3425
3477
|
const pending = await context.core.approvals.get(approvalId);
|
|
3426
|
-
if (
|
|
3478
|
+
if (kindOf(pending) === "change") {
|
|
3427
3479
|
const outcome = await approveChangeAtTerminal(context.core, approvalId, env, {
|
|
3428
3480
|
json: options.json,
|
|
3429
3481
|
color: options.color,
|
|
@@ -3445,10 +3497,14 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3445
3497
|
}));
|
|
3446
3498
|
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) => {
|
|
3447
3499
|
ran = true;
|
|
3448
|
-
const
|
|
3500
|
+
const startResendStdioServer = deps.startMcp ?? (await import("./stdio-entry-PsvT2AI2.mjs")).startResendStdioServer;
|
|
3501
|
+
const core = coreForInvocation();
|
|
3449
3502
|
await startResendStdioServer({
|
|
3450
3503
|
...deps,
|
|
3504
|
+
core,
|
|
3451
3505
|
env,
|
|
3506
|
+
pathOverrides: core.pathOverrides,
|
|
3507
|
+
platform,
|
|
3452
3508
|
...flags.account ? { account: String(flags.account) } : {}
|
|
3453
3509
|
});
|
|
3454
3510
|
});
|
|
@@ -3465,12 +3521,16 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3465
3521
|
"npx",
|
|
3466
3522
|
"local"
|
|
3467
3523
|
])).option("--no-verify", "do not start the server to check the entry works").option("--force", "replace this server's own earlier entry — this is how you upgrade", false).option("--print", "only print what would be written", false).option("--approval <approvalId>", "register the server this approval was given for").action(act(async (context, options, flags) => {
|
|
3468
|
-
if (!flags.client) throw new CommsError("USAGE", "name the client with --client", { hint:
|
|
3524
|
+
if (!flags.client) throw new CommsError("USAGE", "name the client with --client", { hint: handoffSentence(context.handoffs.own([
|
|
3525
|
+
"mcp",
|
|
3526
|
+
"install",
|
|
3527
|
+
"--client",
|
|
3528
|
+
"claude-code"
|
|
3529
|
+
]), (command) => `For example: ${command}.`) });
|
|
3469
3530
|
const pinned = flags.account ?? mcp.opts().account;
|
|
3470
3531
|
const launcher = flags.launcher;
|
|
3471
3532
|
const name = flags.name;
|
|
3472
|
-
const again =
|
|
3473
|
-
"agent-resend",
|
|
3533
|
+
const again = [
|
|
3474
3534
|
"mcp",
|
|
3475
3535
|
"install",
|
|
3476
3536
|
"--client",
|
|
@@ -3480,7 +3540,7 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3480
3540
|
...launcher !== void 0 ? ["--launcher", launcher] : [],
|
|
3481
3541
|
...flags.verify === false ? ["--no-verify"] : [],
|
|
3482
3542
|
...flags.force === true ? ["--force"] : []
|
|
3483
|
-
]
|
|
3543
|
+
];
|
|
3484
3544
|
const result = await changeAt(context, serverInstallChange(context.core, env, {
|
|
3485
3545
|
channel: "resend",
|
|
3486
3546
|
client: flags.client,
|
|
@@ -3500,12 +3560,11 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3500
3560
|
channel: "resend",
|
|
3501
3561
|
dryRun: flags.dryRun === true,
|
|
3502
3562
|
includePrinted: flags.includePrinted === true
|
|
3503
|
-
}, RESEND_MCP), flags,
|
|
3504
|
-
"agent-resend",
|
|
3563
|
+
}, RESEND_MCP), flags, [
|
|
3505
3564
|
"mcp",
|
|
3506
3565
|
"prune",
|
|
3507
3566
|
...flags.includePrinted === true ? ["--include-printed"] : []
|
|
3508
|
-
]
|
|
3567
|
+
]);
|
|
3509
3568
|
writeResult(result, output(), () => renderPrune(result, options.color), streams);
|
|
3510
3569
|
}));
|
|
3511
3570
|
program.command(`${UPDATE_CHECK_CHILD_COMMAND} <claimedAt>`, { hidden: true }).description("internal: finish the day's update check a command handed on").action(act(async (context, _options, claimedAt) => {
|
|
@@ -3522,13 +3581,13 @@ is out: update first, or put it off (agentcomms update, agentcomms update --late
|
|
|
3522
3581
|
].includes(error.code)) return 0;
|
|
3523
3582
|
const message = withoutOptionValues(error.message.replace(/^error: /, ""));
|
|
3524
3583
|
return runCommand(output(), async () => {
|
|
3525
|
-
throw new CommsError("USAGE", message, { hint:
|
|
3584
|
+
throw new CommsError("USAGE", message, { hint: usageHint(help()) });
|
|
3526
3585
|
}, streams);
|
|
3527
3586
|
}
|
|
3528
3587
|
throw error;
|
|
3529
3588
|
}
|
|
3530
3589
|
if (!ran) {
|
|
3531
|
-
streams.stderr.write(`${paint(globals().color, "dim",
|
|
3590
|
+
streams.stderr.write(`${paint(globals().color, "dim", nothingToDo(help()))}\n`);
|
|
3532
3591
|
return 64;
|
|
3533
3592
|
}
|
|
3534
3593
|
return exitCode;
|