* docs: fix Environment Variables table formatting in README
Remove blank line that was breaking the markdown table into two separate tables.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: trigger CLA recheck
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
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>
* docs: document helper commands and the + prefix convention
Add a "Helper Commands" section to the Advanced Usage chapter of the
README explaining:
- What the `+` prefix means and why it exists (visually distinct from
Discovery-generated method names, no collision risk)
- How to discover helpers via `gws <service> --help`
- A full reference table of all 23 helper commands across 11 services
- Usage examples for the most common helpers (gmail, sheets, calendar,
drive, workflow)
Fixes discoverability gap: users had no way to learn about helper
commands without reading the source code.
* fix(docs): correct +append and +upload examples per Gemini review
- gws sheets +append: flag is --spreadsheet (not --spreadsheet-id) and
+append has no --range argument
- gws drive +upload: file path is a positional argument, not --file flag
* docs: clarify script +push is destructive (replaces, not adds)
The +push helper replaces all files in an Apps Script project.
Update description to reflect this so users understand the action
is destructive before running it.
Addresses code-review feedback.
* chore(changeset): correct helper command count to 24 across 10 services
Addresses code-review feedback noting the count was off.
* feat(error): add structured exit codes for scriptable error handling
Replace the hardcoded `std::process::exit(1)` with a type-specific exit
code derived from the GwsError variant:
0 — success
1 — API error (GwsError::Api)
2 — auth error (GwsError::Auth)
3 — validation (GwsError::Validation)
4 — discovery (GwsError::Discovery)
5 — internal (GwsError::Other)
This allows shell scripts to branch on failure type without parsing the
JSON error output:
gws drive files list ...
case $? in
1) echo "API error — check your params" ;;
2) echo "Auth error — run: gws auth login" ;;
3) echo "Bad arguments" ;;
esac
Changes:
- Add GwsError::exit_code() mapping variants to codes
- Update main() to call std::process::exit(err.exit_code())
- Document exit codes in gws --help (print_usage)
- Document exit codes in README under new Exit Codes section
- Add 6 unit tests including a regression guard asserting all codes are distinct
* refactor(error): replace magic exit-code numbers with named constants
Add EXIT_CODE_API/AUTH/VALIDATION/DISCOVERY/OTHER associated constants
on GwsError so callers and tests reference symbolic names rather than
bare integers. Update exit_code() match arms and all tests accordingly.
The distinctness test now validates the constants array directly.
Addresses code-review feedback requesting named constants.
* refactor(error): centralize exit code help text via EXIT_CODE_DOCUMENTATION
Add a module-level EXIT_CODE_DOCUMENTATION constant — a static slice of
(code, description) pairs built from the EXIT_CODE_* constants. Replace
the hardcoded println! block in print_usage() with a loop over this slice
so the help output is always in sync with the defined constants and cannot
drift out of date.
Addresses code-review feedback requesting a single source of truth.
* 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>
* docs: improve readme especially auth which is complex no matter what
* docs: add changeset for README overhaul
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
* feat: multi-account support with --account flag, per-account credential storage
- Add --account global flag and GOOGLE_WORKSPACE_CLI_ACCOUNT env var
- Per-account encrypted credential files (credentials.<b64-email>.enc)
- Per-account token cache (token_cache.<b64-email>.json)
- accounts.json registry with default account tracking
- New auth subcommands: list, default, per-account logout
- login_hint in OAuth URL for account pre-selection
- Email validation via Google userinfo after OAuth flow
- 12 new unit tests (380 total)
BREAKING CHANGE: Existing users must run 'gws auth login' again.
Credential storage changed from single credentials.enc to per-account files.
* refactor: Improve error handling for file system operations, rename `GWS_ACCOUNT` to `GOOGLE_WORKSPACE_CLI_ACCOUNT`, and refine service account token cache path generation.
* fix: clean up per-account token caches on logout
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
* feat: add gws mcp server
Adds a new `gws mcp` subcommand that starts a Model Context Protocol
(MCP) server over stdio, exposing Google Workspace APIs as structured
tools to any MCP-compatible client.
- New `src/mcp_server.rs`: JSON-RPC stdio transport, handles
`initialize`, `tools/list`, and `tools/call`
- Tool discovery dynamically builds schemas from Google Discovery Docs
- Filtering via `-s <services>` flag (e.g. `-s drive,gmail` or `-s all`)
- `-w/--workflows` and `-e/--helpers` flags for optional extras
- stderr startup warning when no services are configured
- Refactored `executor::execute_method` to support output capture
(returns `Option<Value>` instead of printing to stdout) so the MCP
transport is not corrupted
- Updated README.md with MCP Server section and usage examples
* fix: address PR review comments
- Add stderr warning when discovery doc fails to load (mcp_server.rs)
- Remove redundant 'all' string check in service validation (mcp_server.rs)
- Validate upload path to prevent arbitrary file reads - security fix (mcp_server.rs)
- Remove redundant inner capture_output check in handle_binary_response (executor.rs)
- Add changeset for minor version bump
* fix: resolve CI lint, fmt, and test failures
- cargo fmt: format all changed files
- clippy: add #[allow(clippy::too_many_arguments)] on private handle_json_response
- clippy: collapse else { if } to else if in executor.rs
- clippy: replace svc_name.clone() with std::slice::from_ref in mcp_server.rs
- clippy: replace index-based loop with iterator in walk path resolution
- test: add ::<()> turbofish annotation to handle_error_response test calls
to fix E0282 type inference errors
* feat(error): detect accessNotConfigured and guide users to enable APIs
When the Google API returns a 403 with reason accessNotConfigured,
gws now:
- Extracts the GCP Console enable URL from the error message.
- Adds an optional enable_url field to the JSON error output.
- Prints an actionable hint with the enable URL to stderr.
Also adds extract_enable_url() helper with tests, and a Troubleshooting
section to README.
Fixes#31
* fix(error): trim trailing punctuation from accessNotConfigured enable URL
* fix(docs): improve README typography and spacing
- Remove center alignment for tagline and badges to match left-aligned body
- Add `<br>` after badges for visual separation before install block
- Increase whitespace above install block to emphasize it
- Add an empty line above `> [!IMPORTANT]` block to decouple it from previous paragraph
Co-authored-by: jpoehnelt <3392975+jpoehnelt@users.noreply.github.com>
* Update README.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* docs: improve README typography and layout
- Remove center alignment from tagline and badges
- Add space below badges
- Adjust spacing around install code block (remove borders, add empty line above)
- Add empty line above IMPORTANT callout
Co-authored-by: jpoehnelt <3392975+jpoehnelt@users.noreply.github.com>
* docs: refine copy after install code block
- Change "When Google adds an API endpoint" to "When Google Workspace adds an API endpoint or method" to be more accurate.
Co-authored-by: jpoehnelt <3392975+jpoehnelt@users.noreply.github.com>
* docs: remove horizontal borders from README
Per user request, removed all remaining Markdown horizontal rules (`---`) throughout `README.md` to create a cleaner, borderless design. All previous typography and spacing improvements remain intact.
Co-authored-by: jpoehnelt <3392975+jpoehnelt@users.noreply.github.com>
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: jpoehnelt <3392975+jpoehnelt@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>