feat(google-workspace): Drive write ops + Docs/Sheets create/append#21895
Conversation
Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-argument-type |
34 |
unresolved-attribute |
5 |
invalid-assignment |
1 |
unsupported-operator |
1 |
unresolved-import |
1 |
First entries
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str & ~AlwaysFalsy` in union `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:2419: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 5 union elements`
run_agent.py:8358: [invalid-argument-type] invalid-argument-type: Argument to function `get_transport` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:8769: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_profile` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:10633: [invalid-argument-type] invalid-argument-type: Argument to function `_fixed_temperature_for_model` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
skills/productivity/google-workspace/scripts/google_api.py:922: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["sheets"]` and value of type `list[dict[str, dict[str, Unknown]]]` on object of type `dict[str, dict[str, Unknown]]`
run_agent.py:12093: [invalid-argument-type] invalid-argument-type: Argument to function `save_context_length` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:2422: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 5 union elements`
run_agent.py:2330: [invalid-argument-type] invalid-argument-type: Argument to function `ensure_lmstudio_model_loaded` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:4279: [invalid-argument-type] invalid-argument-type: Argument to function `save_trajectory` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
cli.py:8013: [invalid-argument-type] invalid-argument-type: Argument to function `estimate_usage_cost` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:6655: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 5 union elements`
run_agent.py:7101: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_request_timeout` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:2932: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_stale_timeout` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:7101: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_request_timeout` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:3708: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:12120: [invalid-argument-type] invalid-argument-type: Argument to function `estimate_usage_cost` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:2932: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_stale_timeout` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:11379: [invalid-argument-type] invalid-argument-type: Argument to function `apply_anthropic_cache_control` is incorrect: Expected `bool`, found `int | Divergent | Unknown | ... omitted 3 union elements`
run_agent.py:12122: [invalid-argument-type] invalid-argument-type: Argument to function `estimate_usage_cost` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:5272: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["/"]` and `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
run_agent.py:12545: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 5 union elements`
run_agent.py:9194: [unresolved-attribute] unresolved-attribute: Attribute `lower` is not defined on `dict[Unknown, Unknown] & ~AlwaysFalsy`, `int & ~AlwaysFalsy`, `dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy` in union `(str & ~AlwaysFalsy) | (Unknown & ~AlwaysFalsy) | (dict[Unknown, Unknown] & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:8174: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_request_timeout` is incorrect: Expected `str`, found `Divergent | Unknown | str | ... omitted 3 union elements`
run_agent.py:12779: [invalid-argument-type] invalid-argument-type: Argument to function `_pool_may_recover_from_rate_limit` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown, Unknown] | int | dict[Unknown | str, Unknown | str | dict[str, str]]`
... and 17 more
✅ Fixed issues (42):
| Rule | Count |
|---|---|
invalid-argument-type |
31 |
unresolved-attribute |
6 |
unsupported-operator |
4 |
not-subscriptable |
1 |
First entries
run_agent.py:4279: [invalid-argument-type] invalid-argument-type: Argument to function `save_trajectory` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:2187: [invalid-argument-type] invalid-argument-type: Argument to function `query_ollama_num_ctx` is incorrect: Expected `str`, found `(str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 4 union elements`
run_agent.py:12120: [invalid-argument-type] invalid-argument-type: Argument to function `estimate_usage_cost` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
tests/run_agent/test_provider_attribution_headers.py:156: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["X-OpenRouter-Cache-TTL"]` and `Unknown | str | dict[str, str] | ... omitted 3 union elements`
tests/agent/test_codex_cloudflare_headers.py:181: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["originator"]` and `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 3 union elements`
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str & ~AlwaysFalsy`, `int & ~AlwaysFalsy` in union `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:5272: [unresolved-attribute] unresolved-attribute: Attribute `split` is not defined on `dict[Unknown | str, Unknown | str | dict[str, str]]`, `int`, `dict[Unknown, Unknown]` in union `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:4827: [invalid-argument-type] invalid-argument-type: Argument to function `parse_rate_limit_headers` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:12075: [invalid-argument-type] invalid-argument-type: Argument to function `normalize_usage` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:8915: [invalid-argument-type] invalid-argument-type: Argument to function `lmstudio_model_reasoning_options` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:8358: [invalid-argument-type] invalid-argument-type: Argument to function `get_transport` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:2330: [invalid-argument-type] invalid-argument-type: Argument to function `ensure_lmstudio_model_loaded` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:11379: [invalid-argument-type] invalid-argument-type: Argument to function `apply_anthropic_cache_control` is incorrect: Expected `bool`, found `int | str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | dict[Unknown, Unknown]`
run_agent.py:5272: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["/"]` and `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:2419: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 4 union elements`
run_agent.py:8173: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
tests/run_agent/test_provider_attribution_headers.py:154: [not-subscriptable] not-subscriptable: Cannot subscript object of type `int` with no `__getitem__` method
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `startswith` is not defined on `dict[str, str]` in union `Unknown | str | dict[str, str]`
run_agent.py:8752: [invalid-argument-type] invalid-argument-type: Argument to function `_get_anthropic_max_output` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:12122: [invalid-argument-type] invalid-argument-type: Argument to function `estimate_usage_cost` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:12999: [invalid-argument-type] invalid-argument-type: Argument to bound method `ContextCompressor.update_model` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:8942: [invalid-argument-type] invalid-argument-type: Argument to function `github_model_reasoning_efforts` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:12548: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`
tests/run_agent/test_provider_attribution_headers.py:155: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["X-OpenRouter-Cache"]` and `Unknown | str | dict[str, str] | ... omitted 3 union elements`
run_agent.py:3708: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
... and 17 more
Unchanged: 3992 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
…ousResearch#21895) Expand the google-workspace skill beyond read-only access to Drive and Docs. Sheets already had full scope — just adds the missing create verb. New subcommands: - drive get : metadata for a single file - drive upload : upload a local file (auto MIME detection) - drive download : download or export (Docs/Sheets/Slides export to pdf/csv/pdf by default) - drive create-folder - drive share : user/group/domain/anyone + reader/writer/etc. - drive delete : default trashes (reversible); --permanent skips the trash - sheets create : new spreadsheet with optional first-tab name - docs create : new doc, optional initial body - docs append : append text at end of an existing doc Scope changes: - drive.readonly -> drive - documents.readonly -> documents Existing users with old tokens will hit the existing partial-scope warning path (AUTHENTICATED (partial) ...) — the troubleshooting table now points them at $GSETUP --revoke + redo steps 3-5 to pick up the write scopes.
Summary
Expands the google-workspace skill from mostly-read to read+write for Drive and Docs. Sheets already had full scope, just adds the missing
createverb.Changes
get,upload,download,create-folder,share,deletesubcommands. Download exports Google-native files sensibly (Docs→pdf, Sheets→csv, Slides→pdf, Drawings→png) and downloads binary files as-is.deletedefaults to trash (reversible);--permanentskips the trash.create(new spreadsheet, optional first-tab name).create(with optional initial body) andappend(inserts at end viabatchUpdateinsertText).drive.readonly→drive,documents.readonly→documentsin both setup.py and google_api.py.$GSETUP --revoketo pick up the new write scopes.Migration
Existing users with old tokens will hit the existing
AUTHENTICATED (partial)path showing exactly which scopes are missing (drive,documents). The troubleshooting table now tells them to$GSETUP --revokeand redo Steps 3-5. No code breakage — read paths (drive search,docs get) keep working on the old readonly tokens because the new scopes are supersets.Validation
Offline E2E (mock-backed): every new function exercises its success path and negative paths (drive share missing --email for user/group, missing --domain for domain) correctly reject with exit 1. Scope-diff test confirms an old token reports exactly
drive+documentsas missing when checked against the new SCOPES list.No full-API E2E because re-auth requires user consent. Safe to ship since every existing command only added scope breadth, not changed behavior.