From a98e9b44cf37c377e185baf60d372931eea83cb4 Mon Sep 17 00:00:00 2001 From: Brian Scott <191290+bscott@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:12:14 -0700 Subject: [PATCH 1/4] feat(mail): env password, list enhancements, and batch command (0.3.0) Implements three feature requests for automated/headless email workflows. #8 - Bridge password from environment: - GetPassword() checks PM_CLI_BRIDGE_PASSWORD before the system keyring, enabling headless/CI use where no secret service is available. Interactive use is unchanged (env var only consulted when set and non-empty). #9 - mail list enhancements: - --unread/--flagged now use server-side IMAP SEARCH, so results honor --limit instead of being thinned by client-side filtering. - Adds from_address, to, message_id, and in_reply_to to list/search output (all already in the ENVELOPE, no extra fetch). - Adds --fields (project onto named JSON fields) and --compact (bare array). - ListMessages now takes a ListOptions struct; all callers updated. #10 - mail batch: - New 'mail batch' subcommand executes a JSON array of operations (label, unlabel, archive, move, flag, delete) over a single IMAP session. - Operations are fully validated before connecting; mailbox/label names with IMAP special characters (including the source mailbox) are rejected; input is capped at 10MB; errors are sanitized for terminal output. - --stop-on-error halts after the first failure; per-op results reported. Docs (commands.md, batch-format.md) and JSON help schema updated; version bumped to 0.3.0. Note: batch's per-op success reporting depends on issue #11 (silent no-op detection in STORE/COPY) for full accuracy. --- docs/batch-format.md | 70 +++++++++++ docs/commands.md | 53 ++++++++ internal/cli/batch.go | 210 +++++++++++++++++++++++++++++++ internal/cli/batch_test.go | 129 +++++++++++++++++++ internal/cli/cli.go | 11 +- internal/cli/help.go | 22 +++- internal/cli/list_fields.go | 63 ++++++++++ internal/cli/list_fields_test.go | 82 ++++++++++++ internal/cli/mail.go | 44 +++++-- internal/config/config.go | 8 ++ internal/config/config_test.go | 29 +++++ internal/imap/client.go | 151 ++++++++++++++++------ internal/imap/types.go | 30 +++-- 13 files changed, 841 insertions(+), 61 deletions(-) create mode 100644 docs/batch-format.md create mode 100644 internal/cli/batch.go create mode 100644 internal/cli/batch_test.go create mode 100644 internal/cli/list_fields.go create mode 100644 internal/cli/list_fields_test.go 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/