68 Commits

Author SHA1 Message Date
googleworkspace-bot 705fb0ecac chore: release versions (#655)
Release / plan (push) Has been cancelled
Release / publish-cargo (push) Has been cancelled
Release / build (tar.gz, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Release / build (tar.gz, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release / build (tar.gz, true, ubuntu-latest, aarch64-unknown-linux-gnu) (push) Has been cancelled
Release / build (tar.gz, true, ubuntu-latest, aarch64-unknown-linux-musl) (push) Has been cancelled
Release / build (tar.gz, true, ubuntu-latest, x86_64-unknown-linux-musl) (push) Has been cancelled
Release / build (tar.gz, ubuntu-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Release / build (zip, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
2026-03-31 12:32:18 -06:00
Justin Poehnelt b307856f6a refactor: drop serde_yaml by migrating to toml (#657)
* refactor: migrate skill registry from yaml to toml to drop unmaintained serde_yaml dependency

* chore: add changeset for yaml to toml migration

* fix(tests): resolve TOML test suite formatting
2026-03-31 12:22:48 -06:00
googleworkspace-bot f4ee658360 chore: release versions (#647)
Release / plan (push) Has been cancelled
Release / publish-cargo (push) Has been cancelled
Release / build (tar.gz, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Release / build (tar.gz, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release / build (tar.gz, true, ubuntu-latest, aarch64-unknown-linux-gnu) (push) Has been cancelled
Release / build (tar.gz, true, ubuntu-latest, aarch64-unknown-linux-musl) (push) Has been cancelled
Release / build (tar.gz, true, ubuntu-latest, x86_64-unknown-linux-musl) (push) Has been cancelled
Release / build (tar.gz, ubuntu-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Release / build (zip, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
2026-03-31 11:20:03 -06:00
googleworkspace-bot c7c6646d7a chore: release versions (#629)
Release / plan (push) Has been cancelled
Release / announce (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / publish-cargo (push) Has been cancelled
2026-03-26 12:45:06 -06:00
googleworkspace-bot a63b070a51 chore: release versions (#628)
Release / plan (push) Has been cancelled
Release / announce (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / publish-cargo (push) Has been cancelled
2026-03-26 09:56:34 -06:00
googleworkspace-bot 45ca45bfd4 chore: release versions (#624)
Release / plan (push) Has been cancelled
Release / announce (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / publish-cargo (push) Has been cancelled
2026-03-25 11:31:07 -06:00
googleworkspace-bot 9198386a3d chore: release versions (#619)
Release / plan (push) Has been cancelled
Release / announce (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / publish-cargo (push) Has been cancelled
2026-03-24 16:17:11 -06:00
googleworkspace-bot 2aec0ba5a3 chore: release versions (#618)
* chore: release versions

* chore: regenerate skills [skip ci]

---------

Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
2026-03-24 15:51:18 -06:00
googleworkspace-bot 186e88caa0 chore: release versions (#616) 2026-03-24 15:01:28 -06:00
Justin Poehnelt ea0849a657 fix: version sync workspace (#615) 2026-03-24 14:53:39 -06:00
Justin Poehnelt 029e5def2b feat: extract google-workspace library crate (cargo workspace) (#613)
* feat: extract google-workspace library crate and restructure as cargo workspace

- Create crates/google-workspace/ with public modules: discovery, error, services, validate, client
- Move CLI binary to crates/cli/ (package: google-workspace-cli, binary: gws)
- Root Cargo.toml is now workspace-only
- Binary modules use thin re-exports from library (zero behavioral changes)
- Discovery fetch_discovery_document accepts cache_dir parameter for library consumers
- Break output.rs / error.rs circular dep by moving char detection to library validate module
- Update CI workflows for workspace (--workspace flags, path filters)
- Update dist-workspace.toml and policy.yml for new crate locations

Closes #386

* refactor: rename crates/cli to crates/google-workspace-cli

* chore: sync CLI version to 0.20.1 and regenerate skills

* chore: update labeler paths for workspace crate layout

* fix: update flake.nix to read version from CLI crate Cargo.toml

* fix: use tokio::fs for non-blocking I/O in library discovery cache

* docs: update AGENTS.md source layout for workspace structure

* docs: add crate-level READMEs for crates.io

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-24 14:40:03 -06:00
googleworkspace-bot 3ac16ee595 chore: release versions (#606)
* chore: release versions

* chore: regenerate skills [skip ci]

---------

Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
2026-03-24 13:28:51 -06:00
googleworkspace-bot 3e1e0e11f3 chore: release versions (#603)
* chore: release versions

* chore: regenerate skills [skip ci]

---------

Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
2026-03-24 11:50:23 -06:00
googleworkspace-bot 834fa7d25a chore: release versions (#590)
* chore: release versions

* chore: regenerate skills [skip ci]

---------

Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
2026-03-23 14:49:28 -06:00
googleworkspace-bot e9970db26f chore: release versions (#553)
Release / plan (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-18 14:56:16 -06:00
googleworkspace-bot 23ffc33cf2 chore: release versions (#547)
Release / plan (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-18 09:48:41 -06:00
Justin Poehnelt 6e4daaf4f9 feat(gmail): Gmail helpers rollup — mail-builder, --attachment, +read (#526)
* refactor(gmail): replace hand-rolled email construction with mail-builder

Replace custom MessageBuilder, RFC 2047 encoding, header sanitization,
and address encoding (including #482) with the mail-builder crate
(Stalwart Labs, 0 runtime deps). Each command builds a
mail_builder::MessageBuilder directly.

Introduce structured types throughout:
- Mailbox type (parsed display name + email) replaces raw string passing
- sanitize_control_chars strips ASCII control characters (CRLF, null,
  tab, etc.) at the parse boundary — defense-in-depth for mail-builder's
  structured header types, superseding sanitize_header_value,
  sanitize_component, and encode_address_header from #482
- OriginalMessage fields use Option<T> instead of empty-string sentinels
- parse_original_message returns Result with validation (threadId, From,
  Message-ID)
- Pre-parsed Config types (SendConfig, ForwardConfig, ReplyConfig) with
  Vec<Mailbox> — parse at the boundary, not downstream
- parse_forward_args and parse_send_args return Result with --to
  validation, consistent with parse_reply_args
- parse_optional_mailboxes helper normalizes Some(vec![]) to None for
  optional address fields (--cc, --bcc, --from)
- Envelope types borrow from Config + OriginalMessage with lifetimes
- Message IDs stored bare (no angle brackets), parsed once at boundary
- References stored as Vec<String> instead of space-separated string
- ThreadingHeaders bundles In-Reply-To + References with debug_assert
  for bare-ID convention
- Shared CLI arg builders (common_mail_args, common_reply_args)
  eliminate duplicated --cc/--bcc/--html/--dry-run definitions

Additional improvements:
- finalize_message returns Result instead of panicking via .expect()
- Mailbox::parse_list filters empty-email entries (trailing comma edge
  case)
- format_email_link percent-encodes mailto hrefs to prevent parameter
  injection
- Forward date handling: omits Date line when absent instead of showing
  empty "Date: "
- Dry-run auth: log skipped auth as diagnostic instead of silently
  discarding errors
- Restore --html tips in after_help strings (gmail_quote CSS, cid:
  image warnings, HTML fragment advice) lost in release PR #434
- Update execute_method call for upload_content_type parameter (#429)

Delete: MessageBuilder, encode_header_value, sanitize_header_value,
encode_address_header, sanitize_component, extract_email,
extract_display_name, split_mailbox_list, build_references.

* feat(gmail): add --from flag to +send for send-as alias support

Consistent with +reply, +reply-all, and +forward which already support
--from. Uses the same parse_optional_mailboxes path and
apply_optional_headers plumbing.

* fix: quote display names with RFC 2822 special characters in +reply

When replying to emails from corporate senders with display names like
"Anderson, Rich (CORP)" <email@adp.com>, the +reply command fails with
"Invalid To header" (400) from the Gmail API.

The root cause: encode_address_header() strips quotes from the display
name via extract_display_name(), then reconstructs the address without
re-quoting. When the display name contains RFC 2822 special characters
(commas, parentheses), the unquoted form is ambiguous — commas split
it into multiple malformed mailboxes and parentheses are interpreted
as RFC 2822 comments.

Fix: re-quote the display name when it contains any RFC 2822 special
characters, using a single-pass character iterator that preserves
already-escaped sequences and escapes bare quotes/backslashes.

Fixes #512

* feat(gmail): add --attachment flag, +read helper, and mail-builder migration

Consolidates PRs #491, #513, #517, and #502 into a single rollup:

- Migrate message construction to mail-builder crate (RFC-compliant MIME)
- Add --from flag to +send for send-as alias support
- Add --attachment flag to +send with MIME auto-detection and path validation
- Add +read helper for extracting message body/headers (text, HTML, JSON)
- Serialize support for OriginalMessage and Mailbox types
- Display name quoting handled natively by mail-builder

* chore: regenerate skills [skip ci]

* fix: use validate_safe_file_path for attachment path validation

Addresses Gemini review: validate_safe_dir_path hardcodes '--dir' in
error messages. validate_safe_file_path accepts the flag name, so errors
now correctly reference '--attachment'.

* refactor: make OriginalMessage.thread_id optional

The Gmail API does not guarantee threadId on all message resources
(e.g. drafts). Making it Option<String> prevents parse failures on
valid messages and avoids requiring thread_id in helpers like +read
that don't use it.

* fix: use canonicalized path for attachment file operations (TOCTOU)

validate_safe_file_path returns a canonicalized PathBuf. Use it for
exists/is_file checks and downstream file reads instead of the original
un-resolved path to prevent time-of-check/time-of-use races.

* feat(gmail): add --attach flag for file attachments

Add -a/--attach to +send, +reply, +reply-all, and +forward. Can be
specified multiple times for multiple attachments. MIME type is auto-
detected via mime_guess2. Closes #247.

Send via the Gmail API upload endpoint (multipart/related with
message/rfc822 media type) instead of base64-encoding into a JSON raw
field. This raises the size limit from ~5MB (metadata-only endpoint) to
35MB (upload endpoint, per discovery document).

Introduce UploadSource enum in the executor to consolidate upload_path,
upload_content_type, and upload_bytes into a single type-safe parameter.
File and Bytes variants make the two upload strategies (from disk vs.
from memory) mutually exclusive by construction.

Validates attachment paths (control characters, regular file, non-empty)
and total size (25MB raw limit, accounting for base64 expansion of
attachments within the MIME message against the 35MB API limit). Size
check uses actual bytes read to avoid TOCTOU race.

* chore: update changeset and fix integration with malob's attachment impl

Update changeset to reflect combined work. Fix thread_id type mismatches
in new tests from cherry-pick. Fix upload_path scope in main.rs. Make
reject_control_chars pub(crate) for attachment validation.

Co-authored-by: Malo Bourgon <mbourgon@gmail.com>

* chore: regenerate skills [skip ci]

* fix: restore MIME sanitization and terminal escape protection in executor

Restore two security features accidentally lost during the UploadSource
refactor:

1. resolve_upload_mime: restructure from early-returns to collect-then-
   sanitize pattern — strips control chars from user-supplied MIME types
   to prevent CRLF header injection.

2. Model Armor error path: restore sanitize_for_terminal on error messages
   to prevent terminal escape sequence injection from API responses.

Co-authored-by: Malo Bourgon <mbourgon@gmail.com>

* chore: remove duplicate changeset from cherry-pick

gmail-attach-flag.md duplicated content already in gmail-helpers-rollup.md.
Both were marked minor, which would cause a double version bump.

* fix: add path traversal protection to attachment validation

Replace reject_control_chars with validate_safe_file_path in
parse_attachments. All file operations (metadata, read, filename
extraction, MIME detection) now use the canonicalized path, preventing
path traversal attacks (e.g. ../../.ssh/id_rsa) and closing TOCTOU gaps.

Update tests to use CWD-relative temp directories (tempdir_in("."))
since validate_safe_file_path rejects paths outside the working directory.

Co-authored-by: Malo Bourgon <mbourgon@gmail.com>

* refactor: deduplicate terminal sanitizer in read.rs

Replace the local sanitize_terminal_output function with the existing
crate::error::sanitize_for_terminal via import alias. This eliminates
code duplication and provides consistent sanitization across the codebase.

The crate-wide sanitizer also correctly strips CR (carriage return) which
can be abused for terminal overwrite attacks.

---------

Co-authored-by: Malo Bourgon <mbourgon@gmail.com>
Co-authored-by: Rich Anderson <richanderson00@gmail.com>
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
2026-03-17 16:55:50 -06:00
googleworkspace-bot 9c26e3c4a9 chore: release versions (#522)
Release / plan (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-17 14:27:07 -06:00
Sidharth Rajmohan 1b0a21fa13 feat: support google meet video conferencing in calendar +insert (#506)
* feat: support google meet video conferencing in calendar +insert (#461, #419)

* feat: add unit tests for google meet and align dependencies

* chore: add changeset for google meet support

* style: cargo fmt

* chore: address PR feedback - restore ratatui 0.30.0 and clarify help text

* test: use robust assertions for google meet insert

* feat: make Google Meet requestId deterministic for idempotency

* fix: restore dependencies and make Google Meet requestId seed more robust

* fix: use JSON serialization for robust Google Meet requestId seed

* fix: improve error handling for Google Meet requestId seed serialization

* fix: ensure idempotency key seed structure matches request body

* fix: align seed_payload attendees structure with actual request body
2026-03-17 12:05:00 -06:00
googleworkspace-bot 1308786bdb chore: release versions (#481)
Release / plan (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-13 17:28:18 -06:00
Justin Poehnelt 47afe5fdb3 feat(timezone): use Google account timezone for day-boundary calculations (#480)
Replace machine-local chrono::Local and UTC epoch math with the
authenticated user's Google account timezone (Calendar Settings API).

- Add chrono-tz dependency for IANA timezone parsing
- New src/timezone.rs: resolve timezone with priority:
  --timezone flag > 24h cache > Calendar API > local fallback
- calendar.rs: add --timezone/--tz flag to +agenda
- workflows.rs: fix +standup-report, +weekly-digest, +meeting-prep
- auth_commands.rs: invalidate timezone cache on logout
- Update README.md and AGENTS.md with timezone docs

Supersedes #369 and #462.

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-13 16:41:16 -06:00
googleworkspace-bot e4a59c1023 chore: release versions (#479)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-13 15:29:02 -06:00
Justin Poehnelt 6f3e0906d3 feat: add opt-in structured HTTP request logging via tracing (#478)
Add PII-free structured logging controlled by two environment variables:
- GOOGLE_WORKSPACE_CLI_LOG: stderr log filter (e.g., gws=debug)
- GOOGLE_WORKSPACE_CLI_LOG_FILE: directory for JSON log files (daily rotation)

Logging is silent by default (zero overhead). Instrumented sites:
- executor.rs: API request/response (method, status, latency)
- client.rs: 429 retry events
- discovery.rs: cache hit vs network fetch

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-13 15:27:38 -06:00
googleworkspace-bot f8bcb35aa7 chore: release versions (#475)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-13 15:02:00 -06:00
Justin Poehnelt 945ac91604 fix: stream multipart uploads to avoid OOM on large files (#477)
* fix: stream multipart uploads to avoid OOM on large files

Replace buffered file read + build_multipart_body in build_http_request
with streaming build_multipart_stream using tokio_util::io::ReaderStream.

Memory usage drops from O(file_size) to O(64 KB) regardless of upload size.
Content-Length is pre-computed from file metadata so Google APIs still
receive the correct header without buffering.

Fixes #244

* refactor: improve error messages per review feedback

- Metadata error now says 'Failed to get metadata' instead of misleading
  'Failed to read upload file'
- File::open error in stream now includes the file path for easier debugging

* test: add Drive upload smoketest to CI

Uploads a small text file, verifies the response has a file ID,
then cleans up by deleting it. Validates the streaming multipart
upload path end-to-end against real Google APIs.

* fix(ci): use drive +upload helper for upload smoketest

The upload is via the +upload helper command, not files create --upload.
Also pipe stderr through tee so errors are visible in CI logs.

* revert: remove Drive upload smoketest (insufficient CI scopes)

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-13 14:56:33 -06:00
googleworkspace-bot 6e639604a8 chore: release versions (#453)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-13 14:01:42 -06:00
googleworkspace-bot e3e1e86e07 chore: release versions (#440)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-12 14:33:37 -06:00
googleworkspace-bot fb27a510dd chore: release versions (#436)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-12 12:03:17 -06:00
googleworkspace-bot f155fdf7b4 chore: release versions (#434)
* chore: release versions

* chore: regenerate skills [skip ci]

---------

Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
2026-03-12 10:16:28 -06:00
googleworkspace-bot aacda4e634 chore: release versions (#433)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-12 10:04:46 -06:00
Zsombor Szabo 087066f946 fix(auth): enable native keyring backends on top of #359 (#373)
* fix(auth): enable native keyring backends

* test(auth): serialize config dir env test

* fix(auth): scope native keyring backends to desktop targets

* fix(ci): tolerate read-only gemini review tokens

* test(credential_store): cover race winner sync path
2026-03-12 09:58:52 -06:00
googleworkspace-bot 7e22a3d0e3 chore: release versions (#390)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-10 15:53:11 -06:00
googleworkspace-bot e6bedc0d21 chore: release versions (#387)
* chore: release versions

* chore: regenerate skills [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-10 14:18:03 -06:00
googleworkspace-bot 58c412c768 chore: release versions (#351)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-10 12:30:44 -06:00
Justin Poehnelt 8d89325a8b feat(credential_store): add GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND env var (#359)
* feat(credential_store): add GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND env var

Add gogcli-style backend selection for encryption key storage:
- keyring (default): OS keyring with file fallback
- file: .encryption_key file only (Docker/CI/headless)

Never delete .encryption_key — it always serves as a durable fallback
for environments where the keyring is ephemeral. When generating new
keys with backend=keyring, save to both keyring and file.

Extracts KeyringProvider trait + resolve_key() for testability.
25 tests covering both backends and all edge cases.

Fixes #344

* chore: regenerate skills [skip ci]

* fix(credential_store): use O_EXCL for race-safe key generation

Use create_new(true) (O_EXCL on Unix, CREATE_NEW on Windows) when
generating a new encryption key file. If another process wins the
race, read their key instead. Platform-independent.

* fix(credential_store): sync winner's key into keyring after file race

When two processes race to create the encryption key file, the loser
now syncs the winner's key back into the keyring. Without this, the
keyring and file could permanently diverge.

* test(credential_store): add 9 tests covering file exclusion, env parsing, and race paths

- save_key_file_exclusive: creates new file, rejects existing
- save_key_file: overwrites existing
- ensure_key_dir: creates nested dirs
- KeyringBackend: file/FILE/invalid parsing
- Race loser: syncs winner key to keyring
- Race loser: corrupt file gets overwritten

* feat(credential_store): security and robustness hardening

1. Warn on unrecognized KEYRING_BACKEND values instead of silent default
2. fsync after key file writes for crash durability
3. Zeroize decoded key material from heap after copy
4. Warn if key file has overly permissive Unix permissions (mode & 077)
5. Log which keyring backend was selected to stderr
6. Expose keyring_backend in 'gws auth status' JSON output

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-10 11:17:35 -06:00
googleworkspace-bot cfa0984346 chore: release versions (#348)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-09 16:05:03 -06:00
googleworkspace-bot 2b4bcbb270 chore: release versions (#330)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-09 13:16:24 -06:00
Manoj Badam 789e7f1233 fix: use native OS certificate store for TLS (#302)
Switch reqwest from `rustls-tls` (bundled Mozilla roots via webpki-roots)
to `rustls-tls-native-roots` so the CLI trusts custom/corporate CA
certificates installed in the system trust store.

This fixes TLS handshake failures in enterprise environments that use
internal certificate authorities.
2026-03-09 11:06:48 -06:00
googleworkspace-bot 643fb7a951 chore: release versions (#313)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
2026-03-08 18:26:33 -06:00
googleworkspace-bot 95bb24e9c2 chore: release versions (#277)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-06 18:31:47 -07:00
googleworkspace-bot 59ad87327b chore: release versions (#243)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 19:54:41 -08:00
googleworkspace-bot f07dd2b2c9 chore: release versions (#228)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 16:49:35 -08:00
googleworkspace-bot bc84ba71a4 chore: release versions (#218) 2026-03-05 15:02:27 -08:00
googleworkspace-bot aaa5c44c6a chore: release versions (#207)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 14:30:21 -08:00
googleworkspace-bot 0ba49d1584 chore: release versions (#199)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 13:03:38 -08:00
googleworkspace-bot c6095ddb27 chore: release versions (#180)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 09:56:15 -08:00
googleworkspace-bot f6d74b0074 chore: release versions (#147)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 01:14:25 -08:00
googleworkspace-bot b9c3698595 chore: release versions (#142)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 00:52:26 -08:00
googleworkspace-bot 29a029aef4 chore: release versions (#140)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
2026-03-05 00:44:04 -08:00
googleworkspace-bot 6ed836c81c chore: release versions (#132)
Release / plan (push) Has been cancelled
Release / build-global-artifacts (push) Has been cancelled
Release / build-local-artifacts (${{ join(matrix.targets, ', ') }}) (push) Has been cancelled
Release / host (push) Has been cancelled
Release / publish-npm (push) Has been cancelled
Release / announce (push) Has been cancelled
2026-03-05 00:10:19 -07:00