* feat(gmail): add +reply, +reply-all, and +forward helper commands
Add first-class reply and forward support to the Gmail helpers,
addressing the gap described in #88. These commands handle the
complex RFC 2822 threading mechanics (In-Reply-To, References,
threadId) that agents and CLI users struggle with today.
New commands:
- +reply: reply to a message with automatic threading
- +reply-all: reply to all recipients with --remove/--cc support
- +forward: forward a message with quoted original content
* fix(gmail): encode message_id in URL path and fix auth signature
- Use crate::validate::encode_path_segment() on message_id in
fetch_message_metadata URL construction per AGENTS.md rules
- Update auth::get_token calls to pass None for the new account
parameter added on main
* refactor(gmail): extract send_raw_email and deduplicate handlers
- Add send_raw_email() to mod.rs: shared encode→json→auth→execute
pattern for sending raw RFC 2822 messages via users.messages.send
- Simplify handle_reply: delegate send logic to send_raw_email
- Simplify handle_forward: delegate send logic to send_raw_email
Addresses code duplication feedback from PR review.
* fix(gmail): register --dry-run flag on reply/forward commands
The handlers read matches.get_flag("dry-run") but the flag was missing
from the clap command definitions, so it always returned false. Now
dry-run works for +reply, +reply-all, and +forward.
* chore: add changeset for gmail reply/forward feature
* style: apply cargo fmt formatting
* fix(gmail): register --dry-run flag on +send command
Same class of bug fixed for +reply/+reply-all/+forward — the handler
reads matches.get_flag("dry-run") but the arg was not registered.
* fix(gmail): honor Reply-To header and use exact address matching
- Prefer Reply-To over From when selecting reply recipients, fixing
incorrect routing for mailing lists and support systems
- Use exact email address comparison instead of substring matching
for --remove filtering and sender deduplication, preventing
unintended recipient removal (e.g. ann@ no longer drops joann@)
* test(gmail): add comprehensive coverage for reply address handling
- extract_email: malformed input (no closing bracket), empty string,
whitespace-only
- build_reply_all_recipients: display-name sender exclusion,
--remove with display name, extra --cc, CC becomes None when all
filtered, case-insensitive sender exclusion
* Improves reply-all recipient deduplication
Corrects how `build_reply_all_recipients` handles multi-address `Reply-To` headers.
Previously, only the first address from `Reply-To` was used for deduplication, leading to potential redundancy by including those addresses in the `Cc` field.
The updated logic now parses all addresses in `Reply-To`, ensuring they are fully moved to the `To` field and properly excluded from `Cc`.
* style(gmail): add missing Apache 2.0 copyright headers
reply.rs and forward.rs were missing the copyright header that all
other source files in the repo include.
* fix(gmail): use try_get_one for optional --remove arg in +reply
parse_reply_args used get_one("remove") which panics when called
from +reply (which does not register --remove). Switch to
try_get_one to safely return None for unregistered args.
* feat(gmail): support --dry-run without auth for reply/forward commands
Skip auth and message fetch when --dry-run is set by using placeholder
OriginalMessage data. This lets users preview the request structure
without needing credentials.
* fix(gmail): use RFC-aware mailbox list parsing for recipient splitting
Replace naive comma-split with split_mailbox_list that respects
quoted strings, so display names containing commas like
"Doe, John" <john@example.com> are handled correctly in reply-all
recipient parsing, deduplication, and --remove filtering.
* fix(gmail): handle escaped quotes in mailbox list splitting
split_mailbox_list toggled quote state on every `"` without accounting
for backslash-escaped quotes (`\"`), causing display names like
`"Doe \"JD, Sr\""` to split incorrectly at interior commas.
Track `prev_backslash` so `\"` inside quoted strings is treated as a
literal quote character rather than a delimiter toggle. Double
backslashes (`\\`) are handled correctly as well.
* fix(gmail): address PR review feedback for reply/forward helpers
- Use reqwest .query() for metadata params per AGENTS.md convention
- Add MIME-Version and Content-Type headers to raw messages
- Add --from flag to +reply, +reply-all, +forward for send-as/alias
- Narrow ReplyConfig/ForwardConfig visibility to pub(super)
- Refactor create_reply_raw_message args into ReplyEnvelope struct
* fix(gmail): address review feedback for reply/forward helpers
- Exclude authenticated user's own email from reply-all CC by
fetching user profile via Gmail API
- Use format=full to extract full plain-text body instead of
truncated snippet for quoting and forwarding
- Deduplicate CC addresses using a HashSet
- Reuse auth token from message fetch in send_raw_email to
eliminate double auth round-trip
- Propagate auth errors in send_raw_email instead of silently
falling back to unauthenticated requests
- Use consistent CRLF line endings in quoted and forwarded
message bodies per RFC 2822
* fix(gmail): Gmail reply and forward helpers
* fix(gmail): refactor shared reply-forward helpers
* Preserve repeated Gmail address headers
* chore: regenerate skills [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* refactor: replace manual urlencoded() with reqwest .query() builder
Remove duplicate hand-rolled urlencoded() functions from workflows.rs
and calendar.rs. All query parameters are now passed via reqwest's
.query() API, which handles percent-encoding correctly and completely.
* fix: percent-encode path parameters to prevent path traversal
Use percent_encoding::utf8_percent_encode for calendar_id, cal.id,
message_id, and file_id before interpolating into URL path segments.
Addresses code review feedback on security regression.
* fix: add shared URL safety helpers for path params
Add encode_path_segment() for single-segment IDs and
validate_resource_name() for multi-segment resource names.
encode_path_segment: percent-encodes all non-alphanumeric chars,
used for calendar IDs, file IDs, and message IDs.
validate_resource_name: rejects path traversal (..) and control
chars while preserving intentional / structure, used for Chat
space names, task list IDs, and subscription names. Returns clear
error messages for LLM callers.
* test: add AI edge case tests for URL safety helpers
Cover query/fragment injection, double-encoding, unicode, spaces,
path traversal via encoding, control chars (CR/tab), and clear
error message assertions for LLM callers.
* fix: warn on stderr when API calls fail silently
- Daily briefing calendar events fetch
- Daily briefing tasks fetch
- Daily summary calendar events fetch
- Daily summary unread email count fetch
Addresses PR review feedback about confusing silent failures,
especially for LLM callers that cannot see visual cues.
* fix: harden input validation for AI/LLM callers
- Add src/validate.rs with validate_safe_output_dir, validate_msg_format,
and validate_safe_dir_path helpers
- Validate --output-dir against path traversal in gmail +watch and
events +subscribe
- Validate --msg-format against allowlist in gmail +watch
- Validate --dir against path traversal in script +push
- Add clap value_parser constraint for --msg-format
- Document input validation patterns in AGENTS.md
Closes#23
* chore: add changesets for PR #21 commits
* test: add comprehensive test coverage for input validation handlers
* docs: document input validation and URL safety patterns in AGENTS.md and CONTRIBUTING.md
* fix: address PR review comments — reject ?/# in resource names, validate subscription arg, remove redundant validate_msg_format
* fix: store validated PathBuf, remove dead code, delete duplicate SubscribeConfig
Addresses review comments:
- Store validated PathBuf from validate_safe_output_dir instead of
discarding it (output_dir is now Option<PathBuf>)
- Remove duplicate SubscribeConfig from events/mod.rs
- Delete unused validate_msg_format (clap value_parser handles this)
- Remove all #[allow(dead_code)] annotations
* fix: per-segment traversal check in validate_resource_name, fix docs
* fix: harden security validation and deduplicate logic
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>