{"owner":"googleworkspace","repo":"cli","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","AGENTS.md"],"files":{"CLAUDE.md":"When contributing to this repository, you must strictly follow all guidelines outlined in the AGENTS.md file.\n","AGENTS.md":"# AGENTS.md\n\n## Project Overview\n\n`gws` is a Rust CLI tool for interacting with Google Workspace APIs. It dynamically generates its command surface at runtime by parsing Google Discovery Service JSON documents.\n\n> [!IMPORTANT]\n> **Dynamic Discovery**: This project does NOT use generated Rust crates (e.g., `google-drive3`) for API interaction. Instead, it fetches the Discovery JSON at runtime and builds `clap` commands dynamically. When adding a new service, you only need to register it in `crates/google-workspace/src/services.rs` and verify the Discovery URL pattern in `crates/google-workspace/src/discovery.rs`. Do NOT add new crates to `Cargo.toml` for standard Google APIs.\n\n> [!NOTE]\n> **Package Manager**: Use `pnpm` instead of `npm` for Node.js package management in this repository.\n\n## Build & Test\n\n> [!IMPORTANT]\n> **Test Coverage**: The `codecov/patch` check requires that new or modified lines are covered by tests. When adding code, extract testable helper functions rather than embedding logic in `main`/`run` where it's hard to unit-test. Run `cargo test` locally and verify new branches are exercised.\n\n```bash\ncargo build          # Build in dev mode\ncargo clippy -- -D warnings  # Lint check\ncargo test           # Run tests\n```\n\n## Changesets\n\nEvery PR must include a changeset file. Create one at `.changeset/<descriptive-name>.md`:\n\n```markdown\n---\n\"@googleworkspace/cli\": patch\n---\n\nBrief description of the change\n```\n\nUse `patch` for fixes/chores, `minor` for new features, `major` for breaking changes. The CI policy check will fail without a changeset.\n\n## Architecture\n\nThe CLI uses a **two-phase argument parsing** strategy:\n\n1. Parse argv to extract the service name (e.g., `drive`)\n2. Fetch the service's Discovery Document, build a dynamic `clap::Command` tree, then re-parse\n\n### Workspace Layout\n\nThe repository is a Cargo workspace with two crates:\n\n| Crate                          | Package                 | Purpose                                           |\n| ------------------------------ | ----------------------- | ------------------------------------------------- |\n| `crates/google-workspace/`     | `google-workspace`      | Publishable library — core types and helpers       |\n| `crates/google-workspace-cli/` | `google-workspace-cli`  | Binary crate — the `gws` CLI                       |\n\n#### Library (`crates/google-workspace/src/`)\n\n| File             | Purpose                                                    |\n| ---------------- | ---------------------------------------------------------- |\n| `discovery.rs`   | Serde models for Discovery Document + async fetch/cache    |\n| `services.rs`    | Service alias → Discovery API name/version mapping         |\n| `error.rs`       | `GwsError` enum, exit codes, JSON serialization            |\n| `validate.rs`    | Path/URL/resource validators, `encode_path_segment()`      |\n| `client.rs`      | HTTP client with retry logic                               |\n\n#### CLI (`crates/google-workspace-cli/src/`)\n\n| File                | Purpose                                                                  |\n| ------------------- | ------------------------------------------------------------------------ |\n| `main.rs`           | Entrypoint, two-phase CLI parsing, method resolution                     |\n| `auth.rs`           | OAuth2 token acquisition via env vars, encrypted credentials, or ADC     |\n| `credential_store.rs` | AES-256-GCM encryption/decryption of credential files                  |\n| `auth_commands.rs`  | `gws auth` subcommands: `login`, `logout`, `setup`, `status`, `export`   |\n| `commands.rs`       | Recursive `clap::Command` builder from Discovery resources               |\n| `executor.rs`       | HTTP request construction, response handling, schema validation          |\n| `schema.rs`         | `gws schema` command — introspect API method schemas                     |\n| `logging.rs`        | Opt-in structured logging (stderr + file) via `tracing`                  |\n| `timezone.rs`       | Account timezone resolution: `--timezone` flag, Calendar Settings API    |\n\n## Demo Videos\n\nDemo recordings are generated with [VHS](https://github.com/charmbracelet/vhs) (`.tape` files).\n\n```bash\nvhs docs/demo.tape\n```\n\n### VHS quoting rules\n\n- Use **double quotes** for simple strings: `Type \"gws --help\" Enter`\n- Use **backtick quotes** when the typed text contains JSON with double quotes:\n  ```\n  Type `gws drive files list --params '{\"pageSize\":5}'` Enter\n  ```\n  `\\\"` escapes inside double-quoted `Type` strings are **not supported** by VHS and will cause parse errors.\n\n### Scene art\n\nASCII art title cards live in `art/`. The `scripts/show-art.sh` helper clears the screen and cats the file. Portrait scenes use `scene*.txt`; landscape chapters use `long-*.txt`.\n\n## Input Validation & URL Safety\n\n> [!IMPORTANT]\n> This CLI is frequently invoked by AI/LLM agents. Always assume inputs can be adversarial — validate paths against traversal (`../../.ssh`), restrict format strings to allowlists, reject control characters, and encode user values before embedding them in URLs.\n\n> [!NOTE]\n> **Environment variables are trusted inputs.** The validation rules above apply to **CLI arguments** that may be passed by untrusted AI agents. Environment variables (e.g. `GOOGLE_WORKSPACE_CLI_CONFIG_DIR`) are set by the user themselves — in their shell profile, `.env` file, or deployment config — and are not subject to path traversal validation. This is consistent with standard conventions like `XDG_CONFIG_HOME`, `CARGO_HOME`, etc.\n\n### Path Safety (`crates/google-workspace/src/validate.rs`)\n\nWhen adding new helpers or CLI flags that accept file paths, **always validate** using the shared helpers:\n\n| Scenario                               | Validator                                | Rejects                                                              |\n| -------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------- |\n| File path for writing (`--output-dir`) | `validate::validate_safe_output_dir()`   | Absolute paths, `../` traversal, symlinks outside CWD, control chars |\n| File path for reading (`--dir`)        | `validate::validate_safe_dir_path()`     | Absolute paths, `../` traversal, symlinks outside CWD, control chars |\n| Enum/allowlist values (`--msg-format`) | clap `value_parser` (see `gmail/mod.rs`) | Any value not in the allowlist                                       |\n\n```rust\n// In your argument parser:\nif let Some(output_dir) = matches.get_one::<String>(\"output-dir\") {\n    crate::validate::validate_safe_output_dir(output_dir)?;\n    builder.output_dir(Some(output_dir.clone()));\n}\n```\n\n### URL Encoding (`crates/google-workspace-cli/src/helpers/mod.rs`)\n\nUser-supplied values embedded in URL **path segments** must be percent-encoded. Use the shared helper:\n\n```rust\n// CORRECT — encodes slashes, spaces, and special characters\nlet url = format!(\n    \"https://www.googleapis.com/drive/v3/files/{}\",\n    crate::helpers::encode_path_segment(file_id),\n);\n\n// WRONG — raw user input in URL path\nlet url = format!(\"https://www.googleapis.com/drive/v3/files/{}\", file_id);\n```\n\nFor **query parameters**, use reqwest's `.query()` builder which handles encoding automatically:\n\n```rust\n// CORRECT — reqwest encodes query values\nclient.get(url).query(&[(\"q\", user_query)]).send().await?;\n\n// WRONG — manual string interpolation in query strings\nlet url = format!(\"{}?q={}\", base_url, user_query);\n```\n\n### Resource Name Validation (`crates/google-workspace-cli/src/helpers/mod.rs`)\n\nWhen a user-supplied string is used as a GCP resource identifier (project ID, topic name, space name, etc.) that gets embedded in a URL path, validate it first:\n\n```rust\n// Validates the string does not contain path traversal segments (`..`), control characters, or URL-breaking characters like `?` and `#`.\nlet project = crate::validate::validate_resource_name(&project_id)?;\nlet url = format!(\"https://pubsub.googleapis.com/v1/projects/{}/topics/my-topic\", project);\n```\n\nThis prevents injection of query parameters, path traversal, or other malicious payloads through resource name arguments like `--project` or `--space`.\n\n### Checklist for New Features\n\nWhen adding a new helper or CLI command:\n\n1. **File paths** → Use `validate_safe_output_dir` / `validate_safe_dir_path`\n2. **Enum flags** → Constrain via clap `value_parser` or `validate_msg_format`\n3. **URL path segments** → Use `encode_path_segment()`\n4. **Query parameters** → Use reqwest `.query()` builder\n5. **Resource names** (project IDs, space names, topic names) → Use `validate_resource_name()`\n6. **Write tests** for both the happy path AND the rejection path (e.g., pass `../../.ssh` and assert `Err`)\n\n## PR Labels\n\nUse these labels to categorize pull requests and issues:\n\n- `area: discovery` — Discovery document fetching, caching, parsing\n- `area: http` — Request execution, URL building, response handling\n- `area: docs` — README, contributing guides, documentation\n- `area: tui` — Setup wizard, picker, input fields\n- `area: distribution` — Nix flake, npm packaging, GitHub Actions release workflow, install methods\n- `area: auth` — OAuth, credentials, multi-account, ADC\n- `area: skills` — AI skill generation and management\n\n## Helper Commands (`+verb`)\n\nHelpers are handwritten commands prefixed with `+` that provide value the schema-driven Discovery commands cannot: multi-step orchestration, format translation (e.g., Markdown → Docs JSON), or multi-API composition.\n\n> [!IMPORTANT]\n> **Do NOT add a helper that** wraps a single API call already available via Discovery, adds flags to expose data already in the API response, or re-implements Discovery parameters as custom flags. Helper flags must control orchestration logic — use `--params` and `--format`/`jq` for API parameters and output filtering.\n\nSee [`src/helpers/README.md`](crates/google-workspace-cli/src/helpers/README.md) for full guidelines, anti-patterns, and a checklist for new helpers.\n\n## Environment Variables\n\n### Authentication\n\n| Variable | Description |\n|---|---|\n| `GOOGLE_WORKSPACE_CLI_TOKEN` | Pre-obtained OAuth2 access token (highest priority; bypasses all credential file loading) |\n| `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` | Path to OAuth credentials JSON (no default; if unset, falls back to encrypted credentials in `~/.config/gws/`) |\n| `GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND` | Keyring backend: `keyring` (default, uses OS keyring with file fallback) or `file` (file only, for Docker/CI/headless) |\n\n| `GOOGLE_APPLICATION_CREDENTIALS` | Standard Google ADC path; used as fallback when no gws-specific credentials are configured |\n\n### Configuration\n\n| Variable | Description |\n|---|---|\n| `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` | Override the config directory (default: `~/.config/gws`) |\n\n### OAuth Client\n\n| Variable | Description |\n|---|---|\n| `GOOGLE_WORKSPACE_CLI_CLIENT_ID` | OAuth client ID (for `gws auth login` when no `client_secret.json` is saved) |\n| `GOOGLE_WORKSPACE_CLI_CLIENT_SECRET` | OAuth client secret (paired with `CLIENT_ID` above) |\n\n### Sanitization (Model Armor)\n\n| Variable | Description |\n|---|---|\n| `GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE` | Default Model Armor template (overridden by `--sanitize` flag) |\n| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |\n\n### Helpers\n\n| Variable | Description |\n|---|---|\n| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID override for quota/billing and fallback for helper commands (overridden by `--project` flag) |\n\n### Logging\n\n| Variable | Description |\n|---|---|\n| `GOOGLE_WORKSPACE_CLI_LOG` | Log level filter for stderr output (e.g., `gws=debug`). Off by default. |\n| `GOOGLE_WORKSPACE_CLI_LOG_FILE` | Directory for JSON-line log files with daily rotation. Off by default. |\n\nAll variables can also live in a `.env` file (loaded via `dotenvy`).\n"}}