window.close() is blocked by modern browsers for tabs not opened via
window.open(). The success page's close button and auto-close timer
silently fail as a result.
- Add tryClose() that attempts window.close() and falls back to
showing "You can close this tab manually" after 500ms
- Remove ineffective auto-close scripts from error pages
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>
When `create_oauth_flow()` is called without an explicit `code_verifier`
(i.e. during the initial auth flow in `start_auth_flow()`), the function
never sets `autogenerate_code_verifier=True` on the Flow constructor.
oauthlib 3.2+ automatically adds `code_challenge` to the authorization
URL at the session level, so Google expects a matching `code_verifier`
during the token exchange. However, since `Flow.code_verifier` remains
`None`, that `None` gets stored in the session store and later passed
back during the callback — causing Google to reject the token exchange
with `(invalid_grant) Missing code verifier`.
The fix adds `autogenerate_code_verifier=True` in the else branch so
the Flow object generates and exposes a proper PKCE code verifier that
gets stored and reused during the callback token exchange.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The list_spaces tool was using chat.messages.readonly which is overly
broad for simply enumerating available spaces. This adds the
chat.spaces.readonly scope and uses it for list_spaces, following the
principle of least privilege.
Closes#479
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LocalDirectoryCredentialStore.list_users() enumerates all .json files
in the credentials directory, but oauth_states.json (written by
PersistentOAuthStateStore) is not a user credential file. In
single-user mode, this file can be picked up first alphabetically,
causing a TypeError when accessing credentials.scopes (None) since
the state file has no scopes field.
Filter out known non-credential files and filenames without '@' to
ensure only actual user credential files are returned.
The store_session call in the OAuth 2.1 credential refresh path (get_credentials)
omits token_uri, client_id, client_secret, and issuer. These are stored as None,
causing subsequent refresh attempts to fail and forcing full re-authentication.
The correct pattern already exists in three other store_session calls in the same
file (lines 151, 522, 750) — this aligns the refresh path to match.
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>