18 Commits

Author SHA1 Message Date
Justin Poehnelt 86c08cfc0d fix: remove cargo-dist, use native fetch for npm installer (#646)
* chore: remove cargo-dist, use native fetch for npm installer

* fix: harden npm scripts — spawnSync, signal handling, binary-exists check

* fix: address PR review comments

- Add null body guard for fetch response
- Fix PowerShell Expand-Archive path quoting vulnerability
- Sanitize error output to prevent ANSI escape injection
- Add proxy support limitation note
- Fix upgrade bug: use .version marker so npm update downloads new binary
- Downgrade changeset from minor to patch (chore, not feature)
- Update AGENTS.md: remove stale cargo-dist reference from labels

* fix: use flat archives for consistent tar/zip extraction

Both tar.gz and zip archives now contain files at root (no nested
directory). Removes --strip-components 1 from install.js since it is
no longer needed. This makes extraction consistent across platforms.

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-31 11:17:57 -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
Justin Poehnelt 477f5d90db docs: add helper command guidelines and anti-patterns (#598)
* docs: add helper command guidelines and anti-patterns

* Apply suggestions from code review

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-23 15:27:03 -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
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
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
Shane Huntley 4d41e52198 fix(auth): prioritize local project configuration over global ADC for quota attribution (#295)
* fix(auth): prioritize local project configuration over global ADC for quota attribution

* chore: add changeset for project ID priority fix
2026-03-08 18:20:56 -06:00
Steve Bazyl dd3fc9074d fix!: Remove MCP server mode (#275)
* BREAKING CHANGE: Remove MCP server mode

* Add changeset file
2026-03-06 11:33:23 -07:00
Justin Poehnelt d6372105eb feat!: remove multi-account, DWD, and impersonation support (#253)
* feat!: remove multi-account, DWD, and impersonation support

BREAKING CHANGE: Remove domain-wide delegation, multi-account support,
and impersonation from the CLI authentication flow.

Removed:
- `gws auth list` and `gws auth default` commands
- `--account` flag from `gws auth login` and `gws auth logout`
- `GOOGLE_WORKSPACE_CLI_ACCOUNT` env var
- `GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER` env var
- Per-account credential storage (accounts.json registry)
- Service account impersonation (subject/DWD)

Preserved:
- `GOOGLE_WORKSPACE_CLI_TOKEN` (raw access token)
- `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` (SA key path)
- `GOOGLE_WORKSPACE_CLI_CLIENT_ID` / `CLIENT_SECRET` (OAuth config)
- `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` (config dir override)

* chore: update changeset description

* docs: remove multi-account and DWD references from docs

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-05 22:43:43 -05:00
Steve Bazyl e1505afe12 chore: Remove dwd support (#250)
* chore: Remove dwd support

* Add changeset file

* chore: regenerate skills [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-05 19:26:27 -08:00
Justin Poehnelt 322529d8a9 fix: document all environment variables and enable CONFIG_DIR override (#222)
* docs: document all environment variables and enable CONFIG_DIR override (#171)

* docs: clarify env vars are trusted inputs in AGENTS.md

* chore: add Gemini Code Assist style guide

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-05 16:09:01 -08:00
Justin Poehnelt c532ea6ad8 chore: use labels (#203) 2026-03-05 12:49:44 -08:00
Justin Poehnelt d1825f9385 feat: multi-account support (#85)
* 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>
2026-03-04 23:19:14 -07:00
Justin Poehnelt 92e66a308f feat: add gws version bare subcommand (#71)
* feat: add `gws version` bare subcommand

* refactor: extract help and version flag checks into dedicated functions and add test coverage guidance to AGENTS.md

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
2026-03-04 15:29:02 -08:00
Justin Poehnelt 90adcb4379 fix: harden URL encoding and input validation for AI/LLM callers (#21)
* 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>
2026-03-03 18:36:41 -07:00
Justin Poehnelt a73ff26522 docs: improve readme 2026-03-03 11:59:56 -07:00
Justin Poehnelt c714f4b9c9 fix: use correct npm-scope/npm-package config keys for cargo-dist (#9)
* fix: use correct npm-scope/npm-package config keys for cargo-dist

* chore: add changeset

* docs: add changeset instructions to AGENTS.md
2026-03-03 11:13:33 -07:00
Justin Poehnelt f75bf6dcf7 feat: implement cli (#1) 2026-03-02 17:26:21 -07:00