The core design of gws is schema-driven: commands are dynamically generated from Google Discovery Documents at runtime. This avoids maintaining a hardcoded, unbounded argument surface. Helpers must complement this design, not duplicate it.
A +helper command should exist only when it provides value that Discovery-based commands cannot:
| Justification | Example | Why Discovery Can't Do It |
|---|---|---|
| Multi-step orchestration | +subscribe |
Creates Pub/Sub topic → subscription → Workspace Events subscription (3 APIs) |
| Format translation | +write |
Transforms Markdown → Docs batchUpdate JSON |
| Multi-API composition | +search |
Lists messages, resolves labels, fetches N metadata payloads concurrently |
| Complex body construction | +send, +reply |
Builds RFC 2822 MIME from simple flags |
| Multipart upload | +upload |
Handles resumable upload protocol with progress |
| Workflow recipes | +standup-report |
Chains calls across multiple services |
Litmus test: Can the user achieve the same result with gws <service> <resource> <method> --params '{...}'? If yes, don't add a helper.
If a helper wraps one API call that Discovery already exposes, reject it.
Real example: +revisions (PR #563) wrapped gws drive files-revisions list — same single API call, zero added value.
Adding flags to expose data that is already in the API response creates unbounded surface area.
Real example: --thread-id, --delivered-to, --sent-last on +search (PR #597) — all three values are already present in the Gmail API response. Agents and users should extract them with --format or jq, not new flags.
Why this is harmful: Every API response contains dozens of fields. If we add a flag for each one, helpers become unbounded maintenance burdens — the exact problem Discovery-driven design solves.
Don't re-expose Discovery-defined parameters (e.g., pageSize, fields, orderBy) as custom helper flags. Use --params passthrough instead.
Helper flags must control orchestration logic, not API parameters or output fields.
| Flag | Helper | Why It's Good |
|---|---|---|
--spreadsheet, --range |
+read |
Identifies which resource to operate on |
--to, --subject, --body |
+send |
Inputs to MIME construction (format translation) |
--dry-run |
+subscribe |
Controls whether API calls are actually made |
--subscription |
+subscribe |
Switches between "create new" vs. "use existing" orchestration path |
--target, --project |
+subscribe |
Required for multi-service resource creation |
| Flag | Why It's Bad | Alternative |
|---|---|---|
--thread-id |
Already in API response | jq '.threadId' |
--delivered-to |
Already in response headers | `jq '.payload.headers[] |
--include-labels |
Output field filtering | --format or jq |
- Does this flag control what API call to make or how to orchestrate multiple calls? → ✅ Add it
- Does this flag control what data appears in output? → ❌ Use
--format/jq - Does this flag duplicate a Discovery parameter? → ❌ Use
--params - Could the user achieve this with existing flags + post-processing? → ❌ Don't add it
Helpers are implemented using the Helper trait defined in mod.rs.
inject_commands: Adds subcommands to the main service command. All helper commands are always shown regardless of authentication state.handle: Implementation of the command logic. ReturnsOk(true)if the command was handled, orOk(false)to let the default raw resource handler attempt to handle it.
- Passes the litmus test — cannot be done with a single Discovery command
- Flags are bounded — only flags controlling orchestration, not API params/output
- Uses shared infrastructure:
crate::client::build_client()for HTTPcrate::validate::validate_resource_name()for user-supplied resource IDscrate::validate::encode_path_segment()for URL path segmentscrate::output::sanitize_for_terminal()for error messages
- Has tests — at minimum: command registration, required args, happy path
- Supports
--dry-runwhere the helper creates or mutates resources
- Create
src/helpers/<service>.rs - Implement the
Helpertrait - Register it in
src/helpers/mod.rs - Prefix the command with
+(e.g.,+create)