Previously +append always used hardcoded "A1" range, limiting appends
to the first sheet. Add optional --range flag (default: "A1") to allow
appending to any sheet tab using A1 notation.
* 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
* fix: auto-install binary on run if missing
pnpm skips postinstall when the package is already cached/installed.
This commit ensures run.js will auto-trigger install.js if the
binary is missing, fixing the 'gws binary not found' error.
* chore: add changeset for pnpm binary fix
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
* 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>
* fix(auth): implement platform-aware keyring storage
* chore: regenerate skills [skip ci]
* fix(auth): address PR comments on error handling and TOCTOU vulnerabilities
* fix(test): isolate config directory to prevent race conditions during cargo test
* refactor(auth): deduplicate key generation logic in credential store
* fix(test): execute credential tests serially to prevent environment pollution
* fix(auth): sanitize os keyring error strings and handle missing file fallbacks
This commit removes the inadvertently included skills, uses output::sanitize_for_terminal to protect the user from escape sequence injection via keyring errors, and ensures removal of the fallback file properly alerts the user when an io error occurs.
* chore: regenerate skills [skip ci]
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Co-authored-by: googleworkspace-bot <googleworkspace-bot@users.noreply.github.com>
The Lint Skills job uses uvx to run agentskills validate, but uv
is not pre-installed on GitHub Actions runners. Add astral-sh/setup-uv
action and a skills path filter to the changes detection.
* feat: add HTTP proxy support via environment variables
When http_proxy/https_proxy/all_proxy environment variables are set,
use reqwest (which natively supports proxy) for token refresh instead
of yup-oauth2's hyper-based client (which doesn't support proxy).
This enables gws to work in environments that require HTTP proxy to
access Google APIs (e.g., users in China).
Changes:
- Cargo.toml: Enable reqwest's default features including proxy support
- src/auth.rs: Add proxy-aware token refresh using reqwest as fallback
Fixes#422
* feat: add proxy support for auth login flow
When proxy env vars are set, use a custom OAuth flow with reqwest
for token exchange instead of yup-oauth2's hyper-based client.
Changes to auth_commands.rs:
- Add login_with_proxy_support() for proxy-aware OAuth login
- Add exchange_code_with_reqwest() for token exchange via reqwest
- Detect proxy env vars and choose appropriate flow
* fix: address proxy auth review feedback
* fix: reuse shared reqwest client for auth flows
When --draft is set, calls users.drafts.create instead of
users.messages.send. Message construction is identical; only the
API method and metadata wrapper change. Threaded drafts (replies
and forwards) preserve threadId in the draft metadata.
The original bug from #513 (display names with commas/parens causing
'Invalid To header') was already fixed by the mail-builder migration
in #482. This adds explicit regression tests to prevent regressions.
Closes#513
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Include original message attachments on +forward by default, matching
Gmail web behavior. Add --no-original-attachments flag to opt out
(skips file attachments but preserves inline images in HTML mode).
Preserve cid: inline images in HTML mode for both +forward and
+reply/+reply-all by building the correct multipart/related MIME
structure via mail-builder's MimePart API. Gmail's API rewrites
Content-Disposition: inline to attachment in multipart/mixed, so
explicit multipart/related is required.
In plain-text mode, inline images are not included for both forward
and reply, matching Gmail web behavior.
Key implementation details:
- Single-pass MIME payload walker replaces separate text/html extractors
- OriginalPart metadata type with lazy attachment data fetching
- Part classification uses Content-Disposition to distinguish regular
attachments from inline images (some clients set Content-ID on both)
- Content-ID and content_type sanitized against CRLF header injection
- Size preflight before downloading original attachments
- Remote filename sanitization (not rejection) for sender-controlled names
- Walker does not recurse into hydratable parts (e.g., message/rfc822)
Add dry-run mode to gws events +renew and gws events +subscribe commands.
When --dry-run is specified, the commands print what actions would be
taken without making any API calls. This allows agents to simulate
requests and learn without reaching the server.
Replace flow sequences (bins: ["gws"], skills: [...]) with block-style
sequences in all generated SKILL.md frontmatter templates.
Flow sequences are valid YAML but rejected by strictyaml, which the
Agent Skills reference implementation (agentskills validate) uses to
parse frontmatter. This caused all 93 generated skills to fail
validation.
Also adds unit tests verifying that service, shared, persona, and
recipe skill templates produce block-style sequences only.
Fixes#521
* refactor: replace manual arg parsing in auth commands with clap
- Add ScopeMode enum (Default, Readonly, Full, Custom) for type-safe
scope selection
- Build auth_command() with clap subcommands: login, setup, status,
export, logout
- Replace manual --help check and match dispatch in handle_auth_command
with clap subcommand routing
- Replace two-pass manual parsing in handle_login (services extraction)
and resolve_scopes (scopes/readonly/full flags) with single clap parse
- Add --unmasked flag to export subcommand via clap
- Preserve run_login(&[]) public API for setup.rs compatibility
- Update all 63 tests to use ScopeMode-based API
* refactor: extract parse_login_args helper to deduplicate scope/services parsing
* fix: make --readonly, --full, --scopes mutually exclusive via clap conflicts
* fix: filter empty strings from custom scopes parsing
* refactor: extract build_login_subcommand to eliminate fragile .expect() lookup
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
* fix(setup): handle --help/-h before launching setup wizard
Check for --help/-h at the top of run_setup() before any work that
requires gcloud. Previously, running 'gws auth setup --help' would
launch the full setup flow.
Fixes#280
* refactor: extract SETUP_USAGE into const per review
* refactor: replace manual setup arg parsing with clap
Use clap::Command for gws auth setup argument parsing instead of manual
while-loop parser. This gives us:
- Automatic --help/-h handling (no manual check needed)
- Proper error messages for unknown flags
- Consistent with other clap usage in the codebase
- Help text stays in sync with actual arguments
* fix: distinguish --help (clean exit) from invalid flags (error)
Change parse_setup_args return type from Result<SetupOptions, ()> to
Result<Option<SetupOptions>, GwsError> so that:
- Ok(Some(opts)) = successful parse
- Ok(None) = --help/--version displayed (clean exit, code 0)
- Err(GwsError) = invalid flags (error exit, non-zero code)
* fix: propagate e.print() IO errors instead of silently ignoring
---------
Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
* fix(auth): propagate errors when token directory creation/permissions fail
Previously, failures to create the token directory or set its permissions
were silently ignored using 'let _ = ...'. This could lead to confusing
errors later or security issues if permissions were left insecure.
Now properly propagates errors from:
- tokio::fs::create_dir_all()
- std::fs::set_permissions() (on Unix)
Also sanitizes the path in error messages to prevent terminal escape
sequence injection, aligned with codebase security practices.
* fix(auth): use spawn_blocking for set_permissions to avoid blocking async runtime
Following the reviewer suggestion, std::fs::set_permissions is now executed
via tokio::task::spawn_blocking to avoid potentially blocking the async
runtime thread, which can cause performance issues or deadlocks under load.
* fix(auth): use tokio::fs::set_permissions instead of spawn_blocking
Simplifies the code by using Tokio's native async set_permissions,
removing the need for manual thread spawning.