himalaya/cairn/spec/commands.md
2026-08-25 04:44:16 +02:00

7.9 KiB

cairn capability status
spec commands current

Commands

The command tree splits into three groups. The shared API (mailbox, envelope, flag, message, attachment) is the cross-protocol least-common-denominator surface, behaving the same whatever backend serves the active account. The protocol-specific APIs (imap, jmap, gmail, msgraph, maildir, m2dir, smtp, sieve) each expose the full surface of one backend, including operations the shared API cannot model. The meta commands (account, completion, manual, json-schema) cover account configuration, shell completions, man pages and JSON Schemas.

Requirement: Shared commands over a selected backend

The shared commands SHALL run over an EmailClient that owns one backend-client variant per compiled-in backend. It selects the first configured storage backend the global --backend flag allows, preferring local backends over network ones, plus an optional SMTP transport for storage backends that cannot send (IMAP, Maildir, m2dir). Each shared method matches the active backend and calls its per-protocol adapter.

Requirement: Protocol commands ignore backend selection

Each protocol command SHALL build its own <Proto>Client via a build_<proto>_client helper and run against that backend directly, ignoring --backend. The imap command mirrors IMAP's flat command list; gmail and msgraph track their REST resource domains; the filesystem backends expose only operations that map to their on-disk layout, leaving MIME rendering to the shared commands.

Requirement: ManageSieve commands

When built with the sieve feature, the protocol-specific API SHALL expose sieve capability, list, get, put, check, activate, deactivate, rename, delete, and raw. Data commands SHALL use dedicated serializable output types; mutation commands SHALL use confirmation messages. put and check SHALL accept script bytes from a file, inline arguments, or stdin, and SHALL report the warning text a server attaches to an accepted script.

The protocol SHALL live in io-managesieve, so this repository holds a SieveClient wrapping ManagesieveClientStd, one file per subcommand, and its own serializable output types, exactly as the other protocol modules do.

Requirement: Raw passthrough is byte-verbatim

The imap raw and smtp raw commands SHALL forward the command bytes to the server verbatim, resolving the argument through the shared RawCommandArg (positional or stdin). It decodes literal \r / \n escapes into real CRLF so a shell-typed command survives intact. imap raw sends a batch of caller-tagged commands: it appends a trailing CRLF when missing and delegates tagging, framing and out-of-order completion tracking to io-imap. smtp raw stays a single command line: it strips the trailing CRLF (io-smtp appends its own) and rejects a multi-line batch, since the SMTP exchange reads exactly one reply. sieve raw sends one command line and reads the complete ManageSieve response; literal-bearing Sieve commands use their typed commands so the response reader remains synchronized.

Requirement: Account threaded as a sibling argument

The active account context SHALL be threaded as a sibling argument through every execute chain, never reached through the client. Subcommands receive account and client side by side.

Requirement: Output discipline

Data and errors SHALL go to stdout through the printer; --json switches every command to JSON. stderr carries logs only. Each command's doc comment is its --help text, so himalaya <command> --help is the canonical per-command usage reference.

Requirement: Generation commands print to the standard output

The completion, manual and json-schema commands SHALL share one shape: a positional list selecting what to generate, defaulting to everything, and an optional --dir deciding where it lands. Without a directory they SHALL print the single selected item to the standard output, so a packaging helper can capture it and a shell can redirect it to a file, and SHALL fail when several items are selected rather than concatenating pieces valid for nothing. With a directory they SHALL write one file per selected item in it, creating the directory when missing, and report where each landed.

Requirement: Data commands serialize their data

A command returning data SHALL hand the printer a dedicated output type implementing both Display and Serialize, and register its JSON Schema under the command's invocation key. Message is reserved for confirmations, since it serializes as a single message string and leaves --json unparseable. Where a sibling list already serializes a backend resource, the get output SHALL emit that resource verbatim through a transparent newtype, so one item read with get has the shape of one row of list. Where the wire type is unsuitable (a recursive MIME tree, a type carrying no schema), the output type SHALL name its fields instead.

Requirement: Serialized collections are always present

An output field holding a collection SHALL be serialized even when empty, because the schema marks it required regardless and a skipped field would contradict the published schema.

Requirement: Gmail header selection applies to every format

gmail messages get --header and gmail threads get --header SHALL narrow the rendered headers whatever the requested format. Gmail honours its metadataHeaders parameter under the metadata format alone and returns every header otherwise, so the narrowing SHALL also be applied to the response. Matching is case-insensitive, order and repeats are preserved, and passing no --header renders every header. Headers are read from the top-level payload part, where Gmail puts the RFC 5322 headers.

Requirement: Raw message formats write bytes

A Gmail get command asked for the raw format SHALL decode the fetched message and write its RFC 5322 bytes through the shared byte writer rather than rendering a summary. This covers messages get and drafts get.

Requirement: One module per subcommand

A protocol command resource SHALL live in a sibling module file carrying its pub mod declarations and its Command enum, next to a folder holding one file per subcommand. Types live with the subcommand that owns them; a type serving several subcommands lives in the file of the one that owns it, or in its own module when it belongs to none.

Requirement: Command types carry a domain and a target

A protocol command type SHALL be named <Domain><Target><Verb>Command, so a bare verb is never a type name. The domain prefix stays wherever the bare name would collide across the backends a CLI spans, and is otherwise omitted for the tables and value enums under the cli subtree.

Requirement: Composers default the From header

messages compose, messages reply and messages forward SHALL fill the From header from the resolved account when --from is not passed: email as the address, display-name as the name it carries. An explicit --from SHALL win whole, the configured name never being grafted onto an address the user spelled out. With neither, the header SHALL be omitted rather than guessed.

The name SHALL be handed to the MIME builder apart from the address, so that a name carrying a comma, a quote or a non-ASCII character is encoded by the builder rather than by a quoting rule of Himalaya's own.

The same composers SHALL end the body with the account's signature, introduced by its signature-delim, when neither --signature nor --signature-file is passed. --signature SHALL win, and --signature-file SHALL win too, the configured signature standing down rather than shadowing the file the flag names.

Requirement: Raw message input is shared

A command taking a raw RFC 5322 message SHALL resolve it through the shared MessageArg: a file path, an inline value after --, or piped stdin. The resolved message is normalised to CRLF and rejected when empty, so no backend receives a zero-length message.