When glibc isn't compatible (Alpine, NixOS, Docker scratch images),
the npm install script falls back to a static musl binary. However,
no musl target was configured in dist-workspace.toml, causing the
fallback to fail with "Platform not supported".
Fixes#86
* fix: respect account selection in MCP server and CLI --account flag (#221, #181)
MCP server now reads GOOGLE_WORKSPACE_CLI_ACCOUNT env var and passes it
to get_token instead of always using None (default account).
CLI filter_args_for_subcommand now dynamically locates the service name
instead of hardcoding skip(2), fixing --account before service name
causing unrecognized subcommand errors.
* fix: skip --api-version in first_arg detection
The first_arg loop only skipped --account but not --api-version,
so `gws --api-version v3 drive ...` would misidentify --api-version
as the service name. Now both global flags are consistently skipped.
* fix(mcp): conditionally include body/upload in full-mode tool schemas and drop empty body on execution
Full-mode tool schemas now only include `body` when the Discovery Document
method defines a request body, and `upload` when `supportsMediaUpload` is
true. This prevents LLMs from hallucinating these fields on GET-only methods.
Additionally, empty body objects (`{}`) are filtered out before execution
in both compact and full modes, and empty upload strings are ignored. LLMs
commonly send "body": {} on read-only methods, which causes Google APIs to
return HTTP 400.
* style: cargo fmt and add changeset for MCP tool schema fix
* fix(mcp): conditionally include page_all only for paginated methods
Only include the page_all property in full-mode tool schemas when the
method has a pageToken parameter, preventing LLMs from attempting
pagination on non-paginable methods.
* docs: update changeset to include page_all conditional change
Two call sites in auth_commands.rs and setup_tui.rs used
.strip_suffix(".readonly").unwrap(), which panics if a scope URL
flagged as is_readonly doesn't end with ".readonly".
Replace with .unwrap_or() to gracefully fall back to the original
URL rather than crashing on inconsistent discovery data.
* fix: select broadest scope instead of all method scopes
Discovery Documents list method scopes as alternatives (any one grants
access), but passing all of them to yup_oauth2 caused Google to include
restrictive scopes like gmail.metadata in the token. The API then
enforced that scope's restrictions, blocking query parameters like `q`.
Select only the first (broadest) scope from the method's scope list.
* fix: filter gmail.metadata from login scopes and remove token cache superset fallback
gmail.metadata restricts API behavior (blocks `q` parameter) even when
broader scopes are present in the token. Filter it out during login when
broader Gmail scopes like gmail.modify or mail.google.com are selected.
Also remove the superset fallback in token storage to prevent stale
all-scopes tokens from being reused when a narrower scope is requested.
* fix: apply select_scope to MCP server code path
The MCP server had the same bug as the CLI — passing all method scopes
to get_token. Use select_scope to pick only the broadest scope.
* style: fix cargo fmt formatting issues
---------
Co-authored-by: Justin Poehnelt <jpoehnelt@google.com>
* fix(client): send x-goog-user-project header from ADC quota project
When using Application Default Credentials with a quota_project_id set,
API requests failed with 403 because the quota project header was never
sent. Read quota_project_id from ADC and set it as a default header.
* Update src/auth.rs
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Replace removed `darwin.apple_sdk.frameworks.Security` with `apple-sdk`,
following the nixpkgs Darwin SDK migration (NixOS/nixpkgs#354146).
Co-authored-by: joshgodsiff <josh.godsiff@gmail.com>
Replaced silent `let _ =` on set_permissions calls in save_encrypted
with eprintln! warnings so users are aware if their credential files
end up with insecure permissions. Also log keyring access failures
instead of silently falling through to file storage.
load_from_disk used four nested if-let-Ok blocks that silently
returned an empty HashMap on any failure. When the encryption key
rotated or the cache file was corrupted, tokens silently stopped
loading and users were forced to re-authenticate with no explanation.
Replace with explicit match arms that log specific warnings to
stderr for each failure mode:
- Decryption failure (key changed, corrupted data)
- Invalid UTF-8 in decrypted data
- JSON deserialization failure
File-not-found is still silent since that's normal on first run.
- Update stale docstring on resolve_account to reflect fallthrough behavior
- Add breadcrumb comment on string-based error matching in main.rs
- Move identity scope injection before authenticator build for readability
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
- Add dorny/paths-filter to detect .rs, Cargo.toml/Lock, flake.nix/lock changes
- Gate test, lint, nix, skills, and build jobs behind change detection
- Remove duplicate coverage job (already in coverage.yml with Codecov upload)
- Main pushes always run all jobs unconditionally
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Three related bugs caused all API calls to fail with "Access denied.
No credentials provided" even after successful `gws auth login`:
1. resolve_account() rejected valid credentials.enc as "legacy" when
accounts.json was absent, instead of falling through to use them.
2. main.rs silently swallowed all auth errors (Err(_) => None),
masking real failures behind a generic "no credentials" message.
3. auth login didn't include openid/email scopes, so
fetch_userinfo_email() couldn't identify the user, causing
credentials to be saved without an accounts.json entry.
{attribution.commit: ""}
* fix: isolate flaky auth tests from host ADC credentials
Fixes#206
Both test_load_credentials_no_options and
test_get_token_env_var_empty_falls_through now override HOME to a
temp dir and clear GOOGLE_APPLICATION_CREDENTIALS, preventing the
well-known ADC path from matching on CI runners that have gcloud
credentials.
* refactor: use RAII EnvVarGuard for panic-safe env var cleanup in tests
- Introduce EnvVarGuard struct that saves/restores env vars on Drop
- Replace all manual save/restore patterns in auth tests
- Fix bug where test_get_token_env_var_empty_falls_through did not
restore GOOGLE_WORKSPACE_CLI_TOKEN
- Ensures cleanup runs even if a test panics
* fix: use var_os/OsString in EnvVarGuard for non-UTF-8 safety
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
* fix(executor): add Content-Length: 0 header for body-less POST/PUT/PATCH requests
Google API servers return HTTP 411 (Length Required) when a POST request
is sent without a Content-Length header, even if there is no body.
This affects all Discovery API methods where httpMethod is POST but no
requestBody is defined (e.g. gmail users.messages.trash).
Fixes#182
* chore: add changeset for content-length fix
* feat: add Application Default Credentials (ADC) support (#103)
Extends the credential chain in get_token() to include ADC as a 4th source:
1. GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE env var
2. Encrypted credentials (~/.config/gws/credentials.enc)
3. Plaintext credentials (~/.config/gws/credentials.json)
4. ADC — GOOGLE_APPLICATION_CREDENTIALS env var, then
~/.config/gcloud/application_default_credentials.json
Both authorized_user and service_account ADC formats are detected via the
'type' field and parsed accordingly. This means users can authenticate with:
gcloud auth application-default login --client-id-file=client_secret.json
and gws will automatically pick up those credentials.
Closes#103
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(auth): address review feedback on ADC support
- Extract duplicated JSON credential parsing into parse_credential_file()
helper to reduce duplication between GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE
and ADC code paths; uses serde_json::from_value to avoid second string parse
- Fix well-known ADC path on macOS: dirs::config_dir() returns
~/Library/Application Support on macOS, not ~/.config; use
dirs::home_dir().join('.config/gcloud/...') instead
- Hard-error when GOOGLE_APPLICATION_CREDENTIALS points to a missing file
(was: silently fall through to 'No credentials found')
- Add test_load_credentials_adc_env_var_service_account covering service
account credentials loaded via GOOGLE_APPLICATION_CREDENTIALS
- Remove unnecessary unsafe blocks from env var tests (set_var/remove_var
are not unsafe functions; thread safety is already handled by serial_test)
- Update changeset to include GOOGLE_WORKSPACE_CLI_TOKEN at top of lookup
order and clarify ADC fallback behaviour
Addresses review feedback from jpoehnelt on #125.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(setup): drain stale keypresses between TUI screen transitions
The picker's vim-style j/k navigation can leave buffered keypresses in
crossterm's event queue that leak into the next input field, corrupting
pre-populated values like client_id.
* refactor: extract drain logic into helper, add changeset
Replace bg(Color::DarkGray) with Modifier::REVERSED in highlight_style
so the selected-row highlight adapts to both light and dark terminal themes.
Co-authored-by: jpoehnelt-bot <jpoehnelt-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>
Admin-only scopes (apps.*, cloud-identity.*, ediscovery, directory.readonly,
groups) require Workspace domain-admin access and cannot be granted to personal
@gmail.com accounts — Google returns 400 invalid_scope when they're included.
Changes:
- Add is_workspace_admin_scope() helper (mirrors is_app_only_scope())
to identify scopes that fail for personal Google accounts
- Exclude these scopes from the template_selects of the 'Recommended' preset
in run_discovery_scope_picker()
- Exclude them from the resolved scope list when the Recommended template
is confirmed
- Add 8 unit tests covering the new helper
Workspace admins can still access these scopes via 'Full Access' template
or by selecting them individually in the picker.
Note: this is complementary to PR #108 which filters alertcenter scopes
at the API-discovery level. This PR handles the broader set at the
recommendation layer.
Addresses #119 (Bug 1: admin scopes in Recommended preset)
Co-authored-by: Claude <noreply@anthropic.com>
Previously used dirs::config_dir() which resolves to different paths per
OS (~/Library/Application Support/gws on macOS, %APPDATA%\gws on Windows),
contradicting the documented ~/.config/gws/ path and causing users to place
config files in the wrong location (ref #119).
Now uses ~/.config/gws/ everywhere with a fallback to the legacy OS-specific
path for existing installs. Also consolidates duplicated dirs::config_dir()
calls in auth.rs and discovery.rs to use the central config_dir() helper.
When `gcloud projects list` times out (10s limit) for users with many
projects, the picker now includes a '⌨ Enter project ID manually'
option so they can type a known project ID instead of waiting or failing.
- Add '⌨ Enter project ID manually' item to project picker
- Handle the new item by prompting for input and calling set_gcloud_project
- Add EnterProjectId variant to SetupAction (tests)
- Add test_project_select_enter_manually unit test
Fixes#116
Co-authored-by: Claude <noreply@anthropic.com>
The reject_control_chars helper rejected bytes 0x00-0x1F but allowed
the DEL character (0x7F), which is also an ASCII control character.
This could allow malformed input from LLM agents to bypass validation.
* fix: use gcloud.cmd on Windows and show platform-correct config paths
On Windows, Google Cloud SDK installs `gcloud.cmd` (not `gcloud.exe`).
Rust's `Command::new("gcloud")` does not search PATHEXT, so all gcloud
invocations failed silently. Add `gcloud_bin()` helper that returns
`gcloud.cmd` on Windows and `gcloud` elsewhere.
Also replace hardcoded `~/.config/gws/` paths in error messages with
the actual platform-resolved path (`%APPDATA%\gws\` on Windows).
* chore: add changeset for Windows gcloud compat fix
* feat: add Linux ARM64 build targets (gnu + musl)
Add aarch64-unknown-linux-gnu and aarch64-unknown-linux-musl to
cargo-dist targets, enabling prebuilt binaries for ARM64 Linux
users via npm, the shell installer, and GitHub Releases.
* chore: trigger CLA recheck