Skip to content

Add export-attachment and export-attachments CLI commands, consolidate shared export logic#56

Merged
wesm merged 6 commits intomainfrom
cli-export-attachment
Feb 4, 2026
Merged

Add export-attachment and export-attachments CLI commands, consolidate shared export logic#56
wesm merged 6 commits intomainfrom
cli-export-attachment

Conversation

@wesm
Copy link
Copy Markdown
Owner

@wesm wesm commented Feb 4, 2026

Summary

  • Adds export-attachment command to export single attachment binaries by content hash (supersedes feat: add export-attachment command #4)
  • Adds export-attachments command to export all attachments from a message as individual files
  • Consolidates attachment export logic into internal/export so TUI, CLI, and MCP share one code path
# Single attachment by content hash
msgvault export-attachment <hash> -o file.pdf
msgvault export-attachment <hash> --base64
msgvault export-attachment <hash> --json

# All attachments from a message
msgvault export-attachments 45                  # all attachments → cwd
msgvault export-attachments 45 -o ~/Downloads   # all attachments → specific dir
msgvault export-attachments 18f0abc123def       # by Gmail ID

Shared export package (internal/export)

  • AttachmentsToDir() — export attachments as individual files to a directory (streaming I/O, deduped filenames, O_EXCL file creation)
  • CreateExclusiveFile() — atomic file creation with _1, _2 suffix on conflict
  • StoragePath() — content-addressed path construction with hash validation
  • ValidateContentHash(), SanitizeFilename() — already existed, now used by all code paths

MCP consolidation

  • Removed duplicated sanitizeFilename, createExclusive, pathConflict from MCP handler (~50 lines)
  • MCP now uses shared export.SanitizeFilename, export.CreateExclusiveFile, export.StoragePath

Closes #3

Test plan

  • make test && make lint pass
  • internal/export: 8 TestAttachmentsToDir subtests, TestCreateExclusiveFile (4 subtests), TestAttachmentsToDir_FilePermissions, TestAttachmentsToDir_DiskConflict
  • cmd/.../export_attachment_test.go: binary, JSON, base64 output modes; missing file; flag exclusivity; hash validation
  • cmd/.../export_attachments_test.go: full flow with real DB, Gmail ID fallback, message not found, output dir validation, not-a-directory
  • MCP TestSanitizeFilename updated to use shared function
  • Manual: msgvault export-attachments <id> with real message

🤖 Generated with Claude Code

chgeuer and others added 2 commits February 4, 2026 08:30
Add CLI command to export attachment binaries by content hash.
Users can get the hash from 'show-message --json' and extract
attachments without parsing raw MIME/EML files.

Supports multiple output modes:
- Binary file output (-o file.pdf)
- Binary stdout (-o - or default)
- Base64 stdout (--base64)
- JSON with embedded base64 (--json)

Resolves the need for programmatic attachment access without
external MIME parsing dependencies.
- Use export.ValidateContentHash() instead of inline hex validation
- Stream binary output with io.Copy instead of loading entire file into
  memory via os.ReadFile, supporting large attachments
- Stream base64 output with base64.NewEncoder wrapping stdout
- Add flag mutual exclusivity validation (--json/--base64 vs --output)
- Extract functions for testability: exportAttachmentAsJSON,
  exportAttachmentAsBase64, exportAttachmentBinary, openAttachmentFile,
  readAttachmentFile
- Write output files with 0600 permissions (was 0644)
- Add unit tests covering binary/JSON/base64 output, missing files,
  flag validation, and hash validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
wesm and others added 3 commits February 4, 2026 08:56
Add export-attachments CLI command that exports all attachments from a
message as individual files. Core logic (AttachmentsToDir,
CreateExclusiveFile, StoragePath) lives in internal/export so it can be
reused by TUI and MCP code paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove duplicated sanitizeFilename, createExclusive, and pathConflict
from the MCP handler. Use export.SanitizeFilename, export.CreateExclusiveFile,
export.ValidateContentHash, and export.StoragePath instead. Also use
StoragePath in addAttachmentToZip for consistency.

This eliminates three code paths (TUI, CLI, MCP) each implementing their
own filename sanitization and exclusive file creation logic.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ests

- StoragePath now returns (string, error) with internal hash validation,
  preventing panics from short/empty hashes (#4466-2, #4467-1)
- Fix misleading comment: only JSON reads full file into memory; base64
  and binary stream (#4465-1)
- Fix base64 test to use io.ReadAll instead of fixed 4096-byte buffer (#4465-2)
- Add 5 integration tests for export-attachments CLI: full flow, Gmail ID
  fallback, message not found, output dir validation, not-a-directory (#4466-3)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wesm wesm changed the title Add export-attachment CLI command Add export-attachment and export-attachments CLI commands, consolidate shared export logic Feb 4, 2026
…consistency

- Reject --json + --base64 when both set (was silently preferring JSON)
- Clean up partial output file on write/close error in exportAttachmentBinary
- Standardize MCP export file permissions from 0644 to 0600 for consistency
- Add test case for --json + --base64 mutual exclusivity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wesm wesm merged commit 84f254a into main Feb 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add command to export attachments by content hash

2 participants