diff --git a/dotfiles/agents/AGENTS.md b/dotfiles/agents/AGENTS.md index fb7d9dba..e83788c6 100644 --- a/dotfiles/agents/AGENTS.md +++ b/dotfiles/agents/AGENTS.md @@ -62,7 +62,7 @@ This is an org-mode repository containing personal task management, calendars, habits, and project tracking files. It serves as the central hub for Ivan's personal organization. -## Available MCP Tools +## Available Tools ### Chrome DevTools MCP A browser automation MCP is available for interacting with web pages. Use it to: @@ -72,13 +72,12 @@ A browser automation MCP is available for interacting with web pages. Use it to: - Read page content and extract information - Automate multi-step web workflows (booking, purchasing, form submission, etc.) -### Gmail MCP -A Gmail MCP is available for email operations. Use it to: -- Search, read, and send emails -- Draft emails for review before sending -- Manage labels and filters -- Batch modify or clean up emails -- Download attachments +### Google Workspace CLI (`gws`) +The local `gws` CLI is available for Google Workspace operations. Use it to: +- Search, read, and send Gmail messages +- Manage Gmail labels and filters +- Download attachments and inspect message payloads +- Access Drive, Calendar, Docs, Sheets, and other Google Workspace APIs ## Credentials via `pass` @@ -101,7 +100,7 @@ Examples of what's stored: - When filling out forms or making purchases, pull personal info from this file and credentials from `pass` rather than asking the user to provide them. - For web tasks, prefer using the Chrome DevTools MCP to automate interactions directly. -- For email tasks, prefer using the Gmail MCP over navigating to Gmail in the browser. +- For email tasks, prefer using `gws gmail` over navigating to Gmail in the browser. - If a task requires a credential not found in `pass`, ask the user rather than guessing. - This repo's org files (gtd.org, calendar.org, habits.org, projects.org) contain task and scheduling data. The org-agenda-api skill/service can also be used to query agenda data programmatically. @@ -127,4 +126,3 @@ Examples of what's stored: - `./project-guides/taffybar.md` - `./project-guides/railbird.md` - `./project-guides/org-emacs-packages.md` - diff --git a/dotfiles/agents/skills/email-unsubscribe-check/SKILL.md b/dotfiles/agents/skills/email-unsubscribe-check/SKILL.md index aeba8b13..760fbcc7 100644 --- a/dotfiles/agents/skills/email-unsubscribe-check/SKILL.md +++ b/dotfiles/agents/skills/email-unsubscribe-check/SKILL.md @@ -51,7 +51,7 @@ For each confirmed sender, do ALL of these: Two approaches depending on the sender: **For emails with unsubscribe links:** -- Read the email via Gmail MCP to find the unsubscribe URL (usually at bottom of email body) +- Read the email via `gws gmail` to find the unsubscribe URL (usually at bottom of email body) - Navigate to the URL with Chrome DevTools MCP - Take a snapshot, find the confirmation button/checkbox - Click through to complete the unsubscribe @@ -67,7 +67,9 @@ Two approaches depending on the sender: Even after unsubscribing, create a filter to catch stragglers: ``` -create_filter criteria:{from:"domain.com"} action:{removeLabelIds:["INBOX"]} +gws gmail users settings filters create \ + --params '{"userId":"me"}' \ + --json '{"criteria":{"from":"domain.com"},"action":{"removeLabelIds":["INBOX"]}}' ``` ### 3. Mark old emails as read and archive them (minimum hygiene) @@ -79,8 +81,10 @@ After unsubscribing, clean up existing email from the sender. Example: ``` -search_emails query:"from:domain.com" maxResults:50 -batch_modify_emails messageIds:[...] removeLabelIds:["UNREAD","INBOX"] +gws gmail users messages list --params '{"userId":"me","q":"from:domain.com","maxResults":50}' +gws gmail users messages batchModify \ + --params '{"userId":"me"}' \ + --json '{"ids":["..."],"removeLabelIds":["UNREAD","INBOX"]}' ``` ## Signals That an Email is Unsubscribeable diff --git a/dotfiles/agents/skills/password-reset/SKILL.md b/dotfiles/agents/skills/password-reset/SKILL.md index 33c8bf23..990fa148 100644 --- a/dotfiles/agents/skills/password-reset/SKILL.md +++ b/dotfiles/agents/skills/password-reset/SKILL.md @@ -1,18 +1,18 @@ --- name: password-reset -description: Use when the user wants to reset or rotate a website or service password end-to-end, including finding the right `pass` entry, generating a new password with `xkcdpassgen`, retrieving reset emails through Gmail MCP or a local Gmail CLI, completing the reset in the browser with Chrome DevTools MCP, and updating the password store safely without losing entry metadata. +description: Use when the user wants to reset or rotate a website or service password end-to-end, including finding the right `pass` entry, generating a new password with `xkcdpassgen`, retrieving reset emails through `gws gmail` or a local mail CLI, completing the reset in the browser with Chrome DevTools MCP, and updating the password store safely without losing entry metadata. --- # Password Reset ## Overview -Handle password resets end-to-end. Prefer Gmail MCP for reset-email retrieval, Chrome DevTools MCP for website interaction, and the local `xkcdpassgen` helper for password generation. +Handle password resets end-to-end. Prefer `gws gmail` for reset-email retrieval, Chrome DevTools MCP for website interaction, and the local `xkcdpassgen` helper for password generation. ## Tool Priorities -- Prefer Gmail MCP over opening Gmail in the browser. -- If Gmail MCP is unavailable, use an installed Gmail CLI or IMAP-based mail tool if one exists locally. Inspect the environment first instead of guessing command names. +- Prefer `gws gmail` over opening Gmail in the browser. +- If `gws` is unavailable, use an installed Gmail CLI or IMAP-based mail tool if one exists locally. Inspect the environment first instead of guessing command names. - Prefer Chrome DevTools MCP for all browser interaction. - Use `pass find` and `pass show` before asking the user for credentials or account details. @@ -77,7 +77,7 @@ If the site rejects the password because of policy constraints, keep the canonic - navigate to the login or account page - use the site's "forgot password" flow, or - sign in and navigate to security settings if the user asked for a rotation rather than a reset -6. Use Gmail MCP to retrieve the reset email when needed: +6. Use `gws gmail` to retrieve the reset email when needed: - search recent mail by sender domain, subject, or reset-related keywords - open the message and extract the reset link - navigate to that link in Chrome DevTools MCP @@ -87,15 +87,15 @@ If the site rejects the password because of policy constraints, keep the canonic - successful login with the new password 9. Promote the temp password into the canonical `pass` entry while preserving metadata, then remove the temp entry. -## Gmail Guidance +## Email Guidance -Prefer Gmail MCP for reset-email handling. Typical pattern: +Prefer `gws gmail` for reset-email handling. Typical pattern: -- search for recent messages from the service domain +- list recent messages with `gws gmail users messages list --params '{"userId":"me","q":"from:service.example newer_than:7d"}'` - bias toward reset keywords such as `reset`, `password`, `security`, `verify`, or `signin` -- read the shortlisted messages rather than browsing Gmail manually +- read shortlisted messages with `gws gmail users messages get --params '{"userId":"me","id":"MESSAGE_ID","format":"full"}'` rather than browsing Gmail manually -If Gmail MCP is unavailable, use an installed Gmail CLI or local mail helper only as a fallback. Keep that discovery lightweight and local to the current environment. +If `gws` is unavailable, use an installed Gmail CLI or local mail helper only as a fallback. Keep that discovery lightweight and local to the current environment. ## Browser Guidance diff --git a/dotfiles/codex/config.toml b/dotfiles/codex/config.toml index ffbb57b4..8a165a5c 100644 --- a/dotfiles/codex/config.toml +++ b/dotfiles/codex/config.toml @@ -111,10 +111,6 @@ args = ["-y", "chrome-devtools-mcp@latest", "--auto-connect"] command = "npx" args = ["-y", "@google-cloud/observability-mcp"] -[mcp_servers.gmail] -command = "nix" -args = ["run", "/home/imalison/Projects/gmail-mcp#gmail-mcp-server"] - [mcp_servers.openaiDeveloperDocs] url = "https://developers.openai.com/mcp" @@ -123,8 +119,5 @@ unified_exec = true apps = true steer = true -[plugins."gmail@openai-curated"] -enabled = true - [plugins."google-drive@openai-curated"] enabled = true diff --git a/nixos/notifications-tray-icon.nix b/nixos/notifications-tray-icon.nix index 90a12757..a1c89230 100644 --- a/nixos/notifications-tray-icon.nix +++ b/nixos/notifications-tray-icon.nix @@ -4,12 +4,18 @@ makeEnable config "myModules.notifications-tray-icon" true { inputs.notifications-tray-icon.overlays.default ]; - home-manager.users.imalison = let + home-manager.users.imalison = { config, ... }: let notificationsTrayIcon = pkgs.haskellPackages.notifications-tray-icon; gmailExecStart = pkgs.writeShellScript "notifications-tray-icon-gmail" '' - creds=$(${pkgs.pass}/bin/pass show gmail-mcp/oauth-credentials) - client_id=$(echo "$creds" | ${pkgs.gnugrep}/bin/grep '^client_id:' | cut -d' ' -f2) - client_secret=$(echo "$creds" | ${pkgs.gnugrep}/bin/grep '^client_secret:' | cut -d' ' -f2) + creds_file="${config.xdg.configHome}/gws/client_secret.json" + client_id=$(${pkgs.jq}/bin/jq -r '.installed.client_id // .web.client_id // empty' "$creds_file") + client_secret=$(${pkgs.jq}/bin/jq -r '.installed.client_secret // .web.client_secret // empty' "$creds_file") + + if [ -z "$client_id" ] || [ -z "$client_secret" ]; then + echo "Failed to read Gmail OAuth client credentials from $creds_file" >&2 + exit 1 + fi + exec ${notificationsTrayIcon}/bin/notifications-tray-icon gmail \ --client-id "$client_id" \ --client-secret "$client_secret"