Add blob.download command and attachment metadata to email.read#12
Add blob.download command and attachment metadata to email.read#12
Conversation
Add blob.download command that downloads a blob to a local file using the JMAP session downloadUrl template (RFC 8620 Section 6.2). Returns a JSON envelope with blobId, name, contentType, output path, bytesWritten, and sha256 hash. File-only output keeps the JSON envelope convention intact. Extend email.read to include attachment metadata (partId, blobId, name, type, size) so agents can discover attachments and download them in a clean two-step workflow: email.read → blob.download. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skips 0.2.2 which had a broken tag (pyproject.toml wasn't bumped before tagging, so CI failed to publish to PyPI with "File already exists"). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0f04a9e32
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/fastmail_cli/jmapc.py
Outdated
| blob_url = download_url_template.format( | ||
| accountId=account_id, | ||
| blobId=args.blob_id, | ||
| name=args.name, | ||
| type=args.type, | ||
| ) |
There was a problem hiding this comment.
Expand downloadUrl variables with URI encoding
downloadUrl is a URI template, but this code uses plain str.format(...) with raw blob_id/name/type values. When an attachment name contains reserved characters (for example #, /, ?, or non-ASCII text), the generated URL is malformed or semantically different (# becomes a fragment, / changes path segments), so blob.download can fail or fetch the wrong resource for valid inputs. Use proper URI-template expansion (or percent-encode each variable according to RFC6570 rules) before issuing the request.
Useful? React with 👍 / 👎.
Replace raw str.format() on the JMAP downloadUrl URI template with expand_download_url() that percent-encodes each variable (accountId, blobId, name, type) using urllib.parse.quote(safe=""). Without this, attachment names containing reserved characters (#, ?, /, spaces) or non-ASCII text would produce malformed URLs. Centralizes the logic so any future use of downloadUrl goes through the same safe expansion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
blob.download: New command that downloads a blob to a local file path using the JMAP sessiondownloadUrlURI template (RFC 8620 §6.2). Returns a JSON envelope with metadata — never writes binary to stdout, preserving the CLI's "every command returns a JSON envelope" convention.email.readattachments: Now includes anattachmentsarray withpartId,blobId,name,type, andsizefor each attachment, enabling a clean two-step agent workflow.Agent workflow example
blob.downloadenvelope fieldsblobIdnamecontentTypeoutputbytesWrittensha256Test plan
Tested against live Fastmail API:
email.query --filter '{"hasAttachment": true}'to find email with attachmentemail.read --id <id>returnsattachmentsarray withblobId,name,type,size,partIdemail.readon email without attachments returnsattachments: []blob.downloadwith blobId/name/type/output downloads file successfullyfilecommand), correct size (29174 bytes matches attachment metadata)sha256hash andbytesWrittendescribe blob.downloadshows all four required optionshelplistsblob.downloadwith correct summary🤖 Generated with Claude Code