diff --git a/README.md b/README.md index 5ac5105b..95e15195 100644 --- a/README.md +++ b/README.md @@ -192,17 +192,19 @@ Accounts can be (re)configured later with `himalaya account configure `. T Backend-agnostic commands operate on the account's first configured backend, or the one selected with `-b/--backend`: ``` -himalaya mailboxes list -himalaya envelopes list -m INBOX --page 2 -himalaya envelopes search from alice and after 2026-01-01 order by date desc -himalaya flags add -m INBOX --flag seen 1:3,5 -himalaya messages copy --from INBOX --to Archives 42 -himalaya attachments download -m INBOX 42 +himalaya mailbox list +himalaya envelope list -m INBOX --page 2 +himalaya envelope search from alice and after 2026-01-01 order by date desc +himalaya flag add -m INBOX --flag seen 1:3,5 +himalaya message copy --from INBOX --to Archives 42 +himalaya attachment download -m INBOX 42 ``` -When the `inbox` alias is configured under `[mailbox.alias]`, `-m/--mailbox` becomes optional: shared commands fall back to that id. With `[mailbox.alias] inbox = "INBOX"`, the calls above shorten to `envelopes list --page 2`, `flags add --flag seen 1:3,5`, etc. +When the `inbox` alias is configured under `[mailbox.alias]`, `-m/--mailbox` becomes optional: shared commands fall back to that id. With `[mailbox.alias] inbox = "INBOX"`, the calls above shorten to `envelope list --page 2`, `flag add --flag seen 1:3,5`, etc. -`envelopes list` is plain pagination, ordered by date descending. To filter or sort, use `envelopes search` with a trailing query covering `date`, `after`, `from`, `to`, `subject`, `body`, `flag` conditions (combined with `and`, `or`, `not`, grouped with parens) and an `order by date|from|to|subject [asc|desc]` sort chain. Date clauses target the `Date:` header (sent-at) on every backend. +`envelope list` is plain pagination, ordered by date descending. To filter or sort, use `envelope search` with a trailing query covering `date`, `after`, `from`, `to`, `subject`, `body`, `flag` conditions (combined with `and`, `or`, `not`, grouped with parens) and an `order by date|from|to|subject [asc|desc]` sort chain. Date clauses target the `Date:` header (sent-at) on every backend. The full grammar lives in `himalaya envelope search --help`, which is the source of truth for the query DSL. + +The query DSL is himalaya's own and compiles to each backend's native search: provider-specific operators (Gmail's `in:`/`label:` syntax, `X-GM-RAW`, …) are not supported. On IMAP the search currently runs server-side as `UID SORT`, so it requires the `SORT` capability — servers without it (notably Gmail) reject the command for now (see [#698](https://github.com/pimalaya/himalaya/issues/698)). The shared surface is a strict least-common-denominator subset across IMAP, JMAP and Maildir. Operations that do not generalize (mailbox roles, attribute flags, JMAP-specific queries…) live under the protocol-specific subcommands. @@ -211,9 +213,9 @@ The shared surface is a strict least-common-denominator subset across IMAP, JMAP Each backend exposes its full native API under its own subgroup: ``` -himalaya imap mailboxes select INBOX -himalaya imap mailboxes status INBOX -himalaya imap mailboxes subscribe INBOX +himalaya imap mailbox select INBOX +himalaya imap mailbox status INBOX +himalaya imap mailbox subscribe INBOX himalaya jmap mailboxes query --role drafts himalaya jmap identity get @@ -229,25 +231,42 @@ The `-b/--backend` flag is only consumed by the shared commands; protocol subcom ### Composing messages -The built-in `messages compose` / `reply` / `forward` commands cover simple cases via CLI flags: +The built-in `message compose` / `reply` / `forward` commands cover simple cases via CLI flags: ``` -himalaya messages compose --from me@example.org --to you@example.org \ +himalaya message compose --from me@example.org --to you@example.org \ --subject "Hello" --body "Hi!" --send ``` -For richer composition (multipart MIME, MML directives, signing/encryption, editor-driven workflows), chain a standalone composer such as [mml](https://github.com/pimalaya/mml) into `messages send` / `messages add` through a tempfile or bash/zsh process substitution: +For richer composition (multipart MIME, MML directives, signing/encryption, editor-driven workflows), chain a standalone composer such as [mml](https://github.com/pimalaya/mml) into `message send` / `message add` through a tempfile or bash/zsh process substitution: ```sh # Explicit tempfile, works in plain POSIX sh -mml compose /tmp/draft.eml && himalaya messages send /tmp/draft.eml +mml compose /tmp/draft.eml && himalaya message send /tmp/draft.eml # Bash / zsh process substitution, single command, no tempfile -mml compose >(himalaya messages send) -himalaya messages read 42 | mml reply >(himalaya messages send) +mml compose >(himalaya message send) +himalaya message read 42 | mml reply >(himalaya message send) ``` -The path-arg or process-substitution forms keep the composer's stdout connected to the terminal, so any `$EDITOR` it spawns sees a real tty. The bare-pipe form (`mml compose | himalaya messages send`) hangs because the editor inherits a pipe on its stdout. +The path-arg or process-substitution forms keep the composer's stdout connected to the terminal, so any `$EDITOR` it spawns sees a real tty. The bare-pipe form (`mml compose | himalaya message send`) hangs because the editor inherits a pipe on its stdout. + +A prepared RFC 5322 file can also be staged as a draft instead of sent right away — handy for "compose, review in another client, then send" workflows: + +```sh +himalaya message add -m drafts --flag draft < message.eml # save as draft +himalaya message send --save sent < message.eml # send + keep a copy +``` + +Both `-m`/`--save` values are resolved through the account's `[mailbox.alias]` map. + +### Reading messages + +`himalaya message read ` renders headers and text bodies; `--raw` dumps the original RFC 5322 bytes; `--json` emits the parsed message. A few behaviours worth knowing, especially when scripting: + +- Reading is side-effect-free: messages are fetched with `BODY.PEEK`, so `message read` never sets `\Seen`. Mark explicitly with `flag add --flag seen `. +- Ids are per-mailbox (IMAP UID, JMAP email id or Maildir filename id): the same message gets a new id when copied or moved. The `message-id` field exposed in `--json` envelope output is the stable cross-mailbox key. +- Every command accepts `--json`; envelope listings serialize as `{"envelopes": [{"id", "message-id", "flags": [{"raw", "iana"}], "subject", "from": [{"name", "email"}], "to", "date", "size", "has-attachment"}]}`. ### Re-using sessions @@ -314,7 +333,7 @@ Himalaya CLI is one of several front-ends to the Pimalaya libraries: Use `--log-level ` (alias `--log`) where `` is one of `off`, `error`, `warn`, `info`, `debug`, `trace`: ``` - himalaya --log trace mailboxes list + himalaya --log trace mailbox list ``` The `RUST_LOG` environment variable is consulted when `--log` is not passed, and supports per-target filters (see the [`env_logger` documentation](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)). `RUST_BACKTRACE=1` enables full error backtraces. @@ -322,13 +341,13 @@ Himalaya CLI is one of several front-ends to the Pimalaya libraries: Logs are written to `stderr`, so they can be redirected easily to a file: ``` - himalaya --log trace mailboxes list 2>/tmp/himalaya.log + himalaya --log trace mailbox list 2>/tmp/himalaya.log ``` You can also send logs straight to a file via `--log-file `: ``` - himalaya --log trace --log-file /tmp/himalaya.log mailboxes list + himalaya --log trace --log-file /tmp/himalaya.log mailbox list ``` diff --git a/src/account/context.rs b/src/account/context.rs index 85ebe65a..ec8ee2e0 100644 --- a/src/account/context.rs +++ b/src/account/context.rs @@ -360,7 +360,7 @@ impl From for Account { mailboxes_list_table: config.mailbox.list.table, attachments_list_table: config.attachment.list.table, - mailbox_alias: lowercase_alias_keys(config.mailbox.alias), + mailbox_alias: lowercase_alias_keys(config.mailbox.aliases), } } } @@ -380,7 +380,7 @@ impl From for Account { mailboxes_list_table: config.mailbox.list.table, attachments_list_table: config.attachment.list.table, - mailbox_alias: lowercase_alias_keys(config.mailbox.alias), + mailbox_alias: lowercase_alias_keys(config.mailbox.aliases), } } } @@ -391,13 +391,13 @@ mod tests { use crate::config::MailboxConfig; fn account_with_aliases(pairs: &[(&str, &str)]) -> Account { - let alias = pairs + let aliases = pairs .iter() .map(|(k, v)| ((*k).to_string(), (*v).to_string())) .collect(); let config = Config { mailbox: MailboxConfig { - alias, + aliases, ..MailboxConfig::default() }, ..Config::default() diff --git a/src/cli.rs b/src/cli.rs index b659fe4f..82e590f5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -71,15 +71,15 @@ pub struct Cli { pub enum Command { // --- Shared API // - #[command(subcommand, visible_alias = "mbox")] + #[command(subcommand, visible_alias = "mbox", alias = "mailboxes")] Mailbox(MailboxCommand), - #[command(subcommand)] + #[command(subcommand, alias = "envelopes")] Envelope(EnvelopeCommand), - #[command(subcommand)] + #[command(subcommand, alias = "flags")] Flag(FlagCommand), - #[command(subcommand, visible_alias = "msg")] + #[command(subcommand, visible_alias = "msg", alias = "messages")] Message(MessageCommand), - #[command(subcommand)] + #[command(subcommand, alias = "attachments")] Attachment(AttachmentCommand), // --- Protocol-specific APIs diff --git a/src/config.rs b/src/config.rs index dc0deaee..f6daba29 100644 --- a/src/config.rs +++ b/src/config.rs @@ -133,8 +133,8 @@ pub struct EnvelopeConfig { #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct MailboxConfig { - #[serde(default, alias = "aliases")] - pub alias: HashMap, + #[serde(default, rename = "alias", alias = "aliases")] + pub aliases: HashMap, #[serde(default)] pub list: MailboxListConfig,