From 8483f83a77e4ea0f4291f17519a4a05ace66f998 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Sat, 8 Aug 2026 23:02:08 +0000 Subject: [PATCH] feat(mail): add batch subcommand, docs, and CHANGELOG (0.2.6) Carries forward the unique portion of #13. The env-var and mail-list work in that PR landed independently via #14 and #16, so only the batch command remains; this drops the duplicated implementations rather than conflicting with what shipped. mail batch runs multiple label/unlabel/archive/move/flag/delete operations over a single IMAP session, avoiding a connect and auth round-trip per operation. All operations are validated up front, so malformed input fails before any connection is opened or any mailbox is mutated. Names containing IMAP special characters or CRLF are rejected, and input is capped at 10MB. Also fixes two defects from #13: - The --help-json examples used \" inside raw string literals, which emitted literal backslashes into the agent-facing schema. - docs/commands.md listed `seq` as a --fields name; the implementation that shipped in #16 uses `seq_num`. Adds a CHANGELOG covering everything in 0.2.6 and bumps the version. Closes #10. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 37 +++++++ docs/batch-format.md | 70 +++++++++++++ docs/commands.md | 53 ++++++++++ internal/cli/batch.go | 210 +++++++++++++++++++++++++++++++++++++ internal/cli/batch_test.go | 129 +++++++++++++++++++++++ internal/cli/cli.go | 8 +- internal/cli/help.go | 13 +++ 7 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 docs/batch-format.md create mode 100644 internal/cli/batch.go create mode 100644 internal/cli/batch_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..543ad02 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +Targeted for the 0.2.6 release. + +### Added +- `mail batch` subcommand: execute multiple label/unlabel/archive/move/flag/delete + operations from a JSON array over a single IMAP session, with per-op validation and + an optional `--stop-on-error` flag. Reported by @Juan-de-Costa-Rica (#10). +- Server-side filtering for `mail list` (`--unread` via IMAP `SEARCH UNSEEN`, new + `--flagged` via `SEARCH FLAGGED`), additional envelope fields (`from_address`, `to`, + `message_id`, `in_reply_to`), and `--fields`/`--compact` JSON selection. + Reported by @Juan-de-Costa-Rica (#9), implemented by @kochj23 (#16). +- `PM_CLI_BRIDGE_PASSWORD` environment variable as a fallback for the Bridge password, + for headless environments with no secret service. Takes precedence over the keyring + when set and non-empty. Reported by @Juan-de-Costa-Rica (#8), implemented by + @kochj23 (#14). + +### Fixed +- `mail delete`, `mail move`, and label operations no longer report success when the + target UIDs are not present in the selected mailbox. A STORE that matches nothing is + a valid no-op per RFC 3501, so the affected-message count is now checked. + Reported by @Juan-de-Costa-Rica (#11), implemented by @kochj23 (#15). +- The COPY no-match check is gated on the server advertising UIDPLUS, since COPYUID is + only guaranteed there; without the guard every successful copy on a non-UIDPLUS + server would have been reported as a failure (#17). + +### Changed +- `mail list --unread` now returns up to `--limit` unread messages. Previously the + limit was applied before filtering, so the command could return far fewer results + than requested (#9). diff --git a/docs/batch-format.md b/docs/batch-format.md new file mode 100644 index 0000000..795fc31 --- /dev/null +++ b/docs/batch-format.md @@ -0,0 +1,70 @@ +# Batch Operation Format + +`pm-cli mail batch` reads a JSON array of operations from stdin (or from a file via `--file`) and executes them in order over a single IMAP connection. + +## Input + +A JSON array of operation objects: + +```json +[ + {"op": "label", "uids": ["uid:123"], "label": "Important"}, + {"op": "flag", "uids": ["uid:456"], "read": true}, + {"op": "archive", "uids": ["uid:789"]} +] +``` + +Input is limited to 10MB. An empty array, malformed JSON, or any invalid operation causes the whole batch to be rejected **before** a connection is opened — nothing is executed. + +## Operation fields + +| Field | Type | Applies to | Description | +|-------|------|-----------|-------------| +| `op` | string | all | One of `label`, `unlabel`, `archive`, `move`, `flag`, `delete`. **Required.** | +| `uids` | string[] | all | Message selectors — `uid:` or a bare sequence number. **Required, non-empty.** Do not mix UID and sequence selectors within one operation. | +| `mailbox` | string | all | Source mailbox. Defaults to `INBOX`. | +| `label` | string | `label`, `unlabel` | Label name (mapped to the `Labels/` folder). **Required** for these ops. | +| `to` | string | `move` | Destination mailbox. **Required** for `move`. | +| `read` | bool | `flag` | Mark messages read (`\Seen`). | +| `unread` | bool | `flag` | Mark messages unread (remove `\Seen`). | +| `star` | bool | `flag` | Star messages (`\Flagged`). | +| `unstar` | bool | `flag` | Unstar messages (remove `\Flagged`). | + +A `flag` operation requires at least one of `read`, `unread`, `star`, `unstar`. + +Mailbox and label names containing IMAP special characters (`{`, `*`, `%`, CR, LF) are rejected. + +## Operations + +| op | Effect | +|----|--------| +| `label` | Copies the messages into `Labels/