Addresses CodeRabbit Review 4 nitpick: the test validates
parse_permissions_arg() so it belongs with that test class.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses CodeRabbit Review 3 nitpick: verify TASKS_READONLY_SCOPE is
present at full level, confirming cumulative scope expansion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add docstrings to test methods for coverage threshold
- Add autouse fixture to reset permission state between tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses CodeRabbit review — clear_completed is destructive and should
be blocked alongside delete at the manage permission level.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The consolidated manage_task tool bundles create/update/delete/move into a
single tool, making it impossible to deny just the delete action via tool
tiers or scope-based filtering.
This adds:
- A `manage` permission level for tasks (between readonly and full)
- A SERVICE_DENIED_ACTIONS registry mapping (service, level) to denied actions
- An is_action_denied() helper that tools call before executing actions
- Guards in manage_task and manage_task_list that reject denied actions
Usage: --permissions tasks:manage
Allows create, update, move. Denies delete.
tasks:full remains unchanged (all actions allowed).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect checklist items in Google Docs API responses and render them as
- [ ] (unchecked) and - [x] (checked) in markdown, so LLMs understand
checkbox state instead of writing literal [x] or DONE text.
Closes#516
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes#494 - Windows attachment corruption for PNG/PDF files.
On Windows, os.open() defaults to text mode, which translates LF (0x0a)
bytes to CRLF (0x0d 0x0a) during os.write(). This corrupts any binary
attachment containing 0x0a bytes (PNG headers, PDFs, etc.).
The fix adds os.O_BINARY to the os.open() flags using the standard
getattr(os, 'O_BINARY', 0) idiom, which returns the flag on Windows
and 0 (no-op) on platforms where it doesn't exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new `get_doc_as_markdown` tool that converts Google Docs to clean
Markdown preserving formatting (headings, bold/italic/strikethrough, links,
code spans, ordered/unordered lists with nesting, and tables).
Optionally overlays comments with their anchor text (quotedFileContent) —
the specific text each comment is attached to — in two modes:
- inline: footnote-style references placed at the anchor text location
- appendix: all comments grouped at the bottom with blockquoted anchors
This gives AI agents full document context in a single tool call, unlike
get_doc_content which strips all formatting to plain text.
New files:
- gdocs/docs_markdown.py: Converter + comment formatting logic
- tests/gdocs/test_docs_markdown.py: 18 tests
Tool tier: extended (alongside search_docs, export_doc_to_pdf, etc.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When reading comments via read_document_comments / read_spreadsheet_comments /
read_presentation_comments, the Drive API's quotedFileContent.value field is
now requested and displayed as "Quoted text:" in the output. This shows which
specific text in the document each comment is anchored to.
Comments without anchor text (document-level comments) omit the line.
Relates to #210
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, get_messages and search_messages completely ignored the
attachment field on Chat API messages. This adds:
- Attachment metadata (filename, type) displayed inline in get_messages
and search_messages output
- New download_chat_attachment tool that downloads attachments via the
Chat API media endpoint and saves to local disk
The download uses httpx with a Bearer token against the
chat.googleapis.com/v1/media endpoint (with alt=media), which works
correctly in both OAuth 2.0 and OAuth 2.1 modes. The attachment's
downloadUri field is intentionally ignored as it points to
chat.google.com which requires browser session cookies.
Key details:
- Uses attachmentDataRef.resourceName for the media endpoint URL
- No new OAuth scopes required (existing chat_read is sufficient)
- Tool registered in the extended tier
- 10 unit tests covering metadata display, download, and edge cases
projects().get() only returns project metadata (title, scriptId, dates),
not file contents. This caused get_script_project to always show an empty
files list and get_script_content to return "File not found" for
container-bound scripts.
The correct method is projects().getContent() which maps to
GET /v1/projects/{scriptId}/content and returns all files with
their source code.
Fixes#441
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Several docs tools (search_docs, get_doc_content, list_docs_in_folder,
export_doc_to_pdf) and sheets tools (list_spreadsheets) internally use
the Google Drive API but only receive docs/sheets-specific OAuth scopes
when configured with `--tools docs sheets` (without `drive`).
This adds the minimal required Drive scopes as cross-service dependencies:
- docs: drive.readonly (metadata queries) + drive.file (PDF export)
- sheets: drive.readonly (spreadsheet listing)
This follows the existing pattern where appscript already includes
DRIVE_FILE_SCOPE for its Drive API dependency.
The alternative workaround of adding `--tools drive` exposes 14
full-access Drive tools which is undesirable from a security perspective.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>