Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
434 changes: 434 additions & 0 deletions src/cli/commands/send-controlled.test.ts

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions src/cli/commands/send-controlled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Command } from "commander";
import { executeControlledSend } from "../../lib/controlled-send-envelope.js";
import { handleError, isCliJsonOutput } from "../utils.js";

type OutputFn = (data: unknown, formatted: string) => void;

interface ControlledSendOptions {
descriptor: string;
requestId: string;
receipt: string;
}

export function registerControlledSendCommands(program: Command, output: OutputFn): void {
const command = program
.command("send-controlled")
.description("Send from a private descriptor and write an owner-only terminal receipt");

for (const operation of ["apply", "readback"] as const) {
command
.command(operation)
.description(operation === "apply"
? "Apply one idempotent send request from a private descriptor"
: "Read back one send intent without sending")
.requiredOption("--descriptor <path>", "Owner-only mode 0600 request descriptor")
.requiredOption("--request-id <id>", "Opaque request identifier")
.requiredOption("--receipt <path>", "New owner-only receipt path")
.action(async (opts: ControlledSendOptions) => {
try {
const result = await executeControlledSend(operation, {
descriptorPath: opts.descriptor,
requestId: opts.requestId,
receiptPath: opts.receipt,
});
output(
result.receipt,
`${result.receipt.request_id} ${result.receipt.terminal_state}`,
);
if (result.diagnostic && !isCliJsonOutput()) console.error(result.diagnostic);
if (result.exitCode !== 0) process.exitCode = result.exitCode;
} catch (error) {
handleError(error);
}
});
}
}
4 changes: 3 additions & 1 deletion src/cli/commands/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
evaluateAttachmentCaps,
evaluateSenderPreflight,
} from "../../lib/send-preflight.js";
import { registerControlledSendCommands } from "./send-controlled.js";

const MAX_ATTACHMENT_SIZE = LOCAL_SEND_ATTACHMENT_LIMITS.maxBytesPerFile;
const MAX_ATTACHMENT_COUNT = LOCAL_SEND_ATTACHMENT_LIMITS.maxFiles;
Expand Down Expand Up @@ -103,7 +104,8 @@ function readSendAttachments(paths: string[] | undefined): MailSendAttachment[]
return attachments;
}

export function registerSendCommands(program: Command, _output: (data: unknown, formatted: string) => void): void {
export function registerSendCommands(program: Command, output: (data: unknown, formatted: string) => void): void {
registerControlledSendCommands(program, output);
program
.command("send")
.description("Send an email")
Expand Down
4 changes: 3 additions & 1 deletion src/cli/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const knownCommandNames = new Set([
"address",
"addresses",
"send",
"send-controlled",
"email",
"log",
"search",
Expand Down Expand Up @@ -116,7 +117,8 @@ export function commandModulesFor(args: string[]): readonly CommandModule[] {
case "domains": return ["domain"];
case "address":
case "addresses": return ["address"];
case "send": return ["send"];
case "send":
case "send-controlled": return ["send"];
case "email":
case "log":
case "search":
Expand Down
2 changes: 2 additions & 0 deletions src/lib/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
addresses: "List sender addresses",
forwarding: "Manage app-level forwarding rules",
send: "Send an email",
"send-controlled": "Private descriptor send with terminal receipt",
email: "Sent email log, search, and history",
inbox: "Sync and browse inbound emails",
pull: "Sync events from providers",
Expand Down Expand Up @@ -91,6 +92,7 @@ const SUBCOMMANDS: Record<string, string> = {
schedule: "list cancel run",
alias: "add catch-all global list remove resolve",
sendkey: "create list revoke check",
"send-controlled": "apply readback",
owner: "register list addresses",
inbox: "list unread-count search read open mailboxes sources status sync-status mark-read archive star label attachments delete clear sync-s3 code links wait",
email: "list search show replies thread send",
Expand Down
Loading
Loading