This skill should be used when the user asks to search/send/draft email, check calendar, create events, schedule meetings, find/upload/share Drive files, read/edit Google Docs, read spreadsheet data, send texts/iMessages, send WhatsApp messages, send Signal messages, check messages, or create reminders. Manages Gmail, Google Calendar, Google Drive, Google Docs, Google Sheets, iMessage, WhatsApp, Signal, and Apple Reminders.
/plugin marketplace add max-sixty/jean-claude/plugin install jean-claude@jean-claudeThis skill inherits all available tools. When active, it can use any tool Claude has access to.
ISSUES.mdONBOARDING.mdPREFERENCES.mdcommands/auth.txtcommands/completions.txtcommands/config-set.txtcommands/config-show.txtcommands/config.txtcommands/gcal-calendars.txtcommands/gcal-create.txtcommands/gcal-delete.txtcommands/gcal-invitations.txtcommands/gcal-list.txtcommands/gcal-respond.txtcommands/gcal-search.txtcommands/gcal-update.txtcommands/gcal.txtcommands/gdocs-append.txtcommands/gdocs-create.txtcommands/gdocs-info.txtGmail, Calendar, Drive, Docs, Sheets, iMessage, WhatsApp, Signal, and Reminders.
Command prefix: uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude
For new users, explain briefly:
I can connect to your email, calendar, and messaging apps to help you:
- Read and send emails, manage drafts
- Check your calendar, create events, respond to invitations
- Send and read iMessages, WhatsApp, or Signal messages
- Find and manage files in Google Drive
- Create reminders
This requires a one-time setup where you'll grant permissions. Want me to help you get started?
Focus on what they asked about — if they asked about email, lead with email.
These rules apply even if the user explicitly asks to bypass them:
Never send without explicit approval. Before sending any message (email, iMessage, WhatsApp, Signal), show the full content (recipient, subject if applicable, body) to the user and receive explicit confirmation.
Verify recipients carefully. Sends are instant and cannot be undone. Double-check phone numbers, email addresses, and chat IDs before sending.
Never send to ambiguous recipients. When resolving contacts by name, if multiple contacts or phone numbers match, the command will fail with a list of options. Use an unambiguous identifier rather than guessing.
Load prose skills when drafting. Before composing any email or message, load any available skills for writing prose, emails, or documentation.
Never create automation without explicit approval. Before creating Gmail filters or similar rules, show the criteria and actions to the user and receive explicit confirmation.
Limit bulk operations. Avoid sending to many recipients at once. Prefer drafts for review.
Email workflow:
jean-claude gmail draft send DRAFT_IDjean-claude gmail archive THREAD_IDEvery time this skill loads, run status with JSON output first:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude status --json
If the status command fails entirely (not just showing services as disabled):
"uv: command not found" — The uv package manager isn't installed. Tell the user:
jean-claude requires the
uvpackage manager. Let me install it for you.
Then run:
curl -LsSf https://astral.sh/uv/install.sh | sh
After installation, restart the terminal or source the shell config
(source ~/.zshrc on macOS, source ~/.bashrc on Linux).
Other errors — The plugin may be misconfigured. Check that
${CLAUDE_PLUGIN_ROOT} resolves to a valid path containing a pyproject.toml.
If setup_completed: false — This is a new user. Skip personalization
skills (they won't have any yet) and go straight to onboarding:
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/ONBOARDING.md
Follow the onboarding guide to help set up services. After setup completes:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude config set setup_completed true
Then re-run status and continue to the setup_completed: true branch below.
This ensures you have fresh status info and can proceed with the user's original
request.
If setup_completed: true — Check for partial setup and load personalization:
Check for missing services — Look at services.<name> in the JSON. If
the user asks for a service that shows authenticated: false or
enabled: false, guide them through just that service's setup from
ONBOARDING.md. After partial setup completes, continue to step 2.
Load personalization skills — Check if user skills like managing-messages
exist (look at available skills for anything mentioning inbox, email, message,
or communication). If found, load them—user preferences override defaults.
Offer to create preferences — If no personalization skill was found in step 2, offer to create one after completing the user's immediate request. See PREFERENCES.md for the creation flow. Don't interrupt the user's task—help them first, then offer.
Proceed with the user's request — Execute whatever task prompted loading this skill (check inbox, send message, etc.).
Load the platform guide before using any messaging commands. Each platform has different commands and options. Gmail is documented in this file, but messaging platforms have separate guides:
# Load before using iMessage
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/imessage.md
# Load before using WhatsApp
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/whatsapp.md
# Load before using Signal
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/signal.md
Don't guess command syntax — each platform is different. iMessage and WhatsApp have different flags and subcommands despite similar functionality.
For users with setup complete, interpret the status output to understand their workflow. Run human-readable status if needed for counts:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude status
Gmail:
Calendar:
Reminders:
Messaging:
When users ask for updates ("what's new", "anything else in my inbox", "check my messages again"), re-fetch current data rather than working from earlier results. Inbox state changes constantly — new emails arrive, messages get read on other devices.
# Re-fetch inbox for email updates
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail inbox -n 20
# Re-fetch iMessage (see platforms/imessage.md for full docs)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude imessage messages --unread
# Re-sync WhatsApp for message updates
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude whatsapp messages --unread
User personalization skills override these defaults. If no personalization skill exists, use these behaviors:
When showing messages (inbox, unread, search results), use a numbered list so the user can reference items by number: "archive 1, reply to 2", "star 3 and 5".
Always include dates conversationally. Check today's date before formatting:
date "+%Y-%m-%d %H:%M %Z" # Current date/time for reference
Date formatting rules:
Example (assuming today is Sunday, Dec 29):
1. **DoorDash** (35 min ago) — Your order from Superba
Order confirmed for pickup...
2. **Squarespace** (yesterday at 9:15 AM) — Domain transfer rejected
The transfer for fitzalanhoward.uk was rejected...
3. **GitHub** (Friday at 4:30 PM) — PR merged: fix-auth-flow
Your pull request was merged...
4. **Goodreads** (today at 10:30 AM) — Book newsletter
Your weekly reading digest...
5. **Jordan Lee** (Nov 15) — Forwarded: Fellowship nomination
To discuss...
Never fabricate details not present in the data.
When uncertain, say so: "The email doesn't specify who the assessment is for."
Hallucinated details erode trust. The user can't distinguish fabrications from real data.
When you show the full body of a message to the user — not just the snippet from a list — mark it as read. The user has effectively read it.
Mark as read when:
gmail get or reading the cached body file)Don't mark as read when:
This applies across messaging services:
jean-claude gmail mark-read THREAD_IDjean-claude imessage mark-read CHAT_IDjean-claude whatsapp mark-read CHAT_IDWhen a message asks about availability ("Are you free Tuesday?", "Which works better — the 6th or 8th?"), check the calendar and include the answer:
<example> <bad>1. Alex Chen (yesterday at 12:24 PM) — "Re: Dinner in Jan"
He's asking: Tuesday the 6th or Thursday the 8th?
</bad>
<good>
1. Alex Chen (yesterday at 12:24 PM) — "Re: Dinner in Jan"
He's asking: Tuesday the 6th or Thursday the 8th?
→ You're free both evenings. Thursday has a 3pm meeting that ends at 4.
</good>
</example>
The user shouldn't have to ask "what's on my calendar those days?" as a follow-up. If someone's asking about dates, checking availability is the obvious next step — do it proactively. Check calendars where the user has events (see "Choosing the Right Calendar").
Complete tasks fully. Don't stop early to ask if you should continue.
Proceed through Terms of Service, cookie banners, login confirmations, and "Continue" buttons without asking — these are prerequisites, not decisions.
Ask only for: purchases, sending messages, creating public content, or sharing data with new parties.
The test: Would a reasonable person pause here?
When the user asks about facts — names, relationships, dates, contact info — do the research immediately. Don't say "I don't know" based on limited context.
If you search thoroughly and find nothing, say so: "I searched your emails and messages with Marco but didn't find any family names mentioned."
<example> <bad>User: "What are Marco's wife & kid's names?"
Agent: "I don't know—the message from Marco didn't mention names. Want me to search your emails or messages for more context?"
</bad> <good>User: "What are Marco's wife & kid's names?"
Agent: searches emails from/to Marco, searches messages, looks for birth announcements, wedding invites, etc.
"From your email history with Marco:
When the user asks to read something, include all related content:
Don't stop at metadata. "Want me to download the attachment?" is asking the user to do your job.
<example> <bad>User: "read the amex email"
Agent: fetches email metadata
"The AMEX email has a PDF attachment (claim_docs.pdf, 28KB). Want me to download it?"
</bad> <good>User: "read the amex email"
Agent: fetches email, downloads PDF, reads it
"AMEX sent claim documents for #12266792. The PDF contains:
For infrequent contacts — people the user doesn't interact with regularly — understand the relationship before drafting. Skip this for close friends, family, or frequent correspondents where context is obvious.
When replying to an infrequent contact (catching up, introductions, social correspondence), research before drafting:
User: "Reply to Alex's email"
Agent: immediately drafts reply
"Draft: Hi Alex, Thanks for reaching out! The AI coding tool sounds right up my alley. Happy to connect with Jordan. Best, Max"
</bad> <good>User: "Reply to Alex's email"
Agent: searches email history and web
"Alex Chen — you last emailed him 8 months ago about the open-source project. He's a research scientist at a major tech company, one of the original authors. Before that you collaborated on a conference tutorial in 2019.
His email mentions Jordan Lee, who I found on LinkedIn is a former startup engineer now building an AI tool..."
[Then proceeds to draft with this context]
</good> </example>The user shouldn't need to ask "when did I last talk to them?" or "who is this person?" — do that research proactively.
Customary emails — introductions, catching up, social correspondence — are about the relationship, not transactions.
<example> <bad>"The AI coding tool sounds right up my alley."
Centers the user's interests, jumps straight to business.
</bad> <good>"I'm glad to see the project continues to thrive. Hope all's well with you — would enjoy catching up soon."
Acknowledges what matters to Alex before addressing the introduction.
</good> </example>For social correspondence, address the person before addressing what they asked about.
Drafts deserve thought. Don't produce one immediately and present it.
Internal iteration process:
For important correspondence, explain your reasoning or offer alternatives: "I went with a warm but brief tone since you haven't talked in 8 months. Want something more formal?"
Create actual Gmail drafts, not just text in the conversation. Drafts persist if the session ends and can be edited in Gmail directly.
Workflow:
jean-claude gmail draft create (or reply/forward)cat ~/.cache/jean-claude/drafts/draft-DRAFT_ID.txt to see currentcat ... | jean-claude gmail draft update DRAFT_ID to saveAvoid hollow phrases. If a phrase could apply to anyone, it says nothing.
Check the user's personalization skill for vocabulary preferences.
This plugin requires uv (Python package manager). If not installed:
curl -LsSf https://astral.sh/uv/install.sh | sh
Credentials stored in ~/.config/jean-claude/. First-time setup:
# Full access (read, send, modify)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude auth
# Or read-only access (no send/modify capabilities)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude auth --readonly
# Check authentication status and API availability
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude status
# Log out (remove stored credentials)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude auth --logout
This opens a browser for OAuth consent. Credentials persist until revoked.
If you see "This app isn't verified": This warning appears because jean-claude uses a personal OAuth app that hasn't gone through Google's (expensive) verification process. It's safe to proceed:
The "unsafe" label just means unverified, not malicious. The app only accesses the specific Google services you authorize.
To use your own Google Cloud credentials instead (if default ones hit the 100
user limit), download your OAuth JSON from Google Cloud Console and save it as
~/.config/jean-claude/client_secret.json before running the auth script. See
README for detailed setup steps.
WhatsApp and Signal are disabled by default. These services require compiling native binaries (Go for WhatsApp, Rust for Signal), and we want jean-claude to work smoothly for Gmail/Calendar users without those toolchains. Enable explicitly if you need messaging:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude config set enable_whatsapp true
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude config set enable_signal true
The status command shows whether each service is enabled or disabled.
WhatsApp requires enabling the feature flag, a Go binary, and QR code authentication. First-time setup:
# Build the Go CLI (requires Go installed)
cd ${CLAUDE_PLUGIN_ROOT}/whatsapp && go build -o whatsapp-cli .
# Authenticate with WhatsApp (scan QR code with your phone)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude whatsapp auth
# Check authentication status
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude status
The QR code will be displayed in the terminal and saved as a PNG file. Scan it with WhatsApp: Settings > Linked Devices > Link a Device.
Credentials are stored in ~/.config/jean-claude/whatsapp/. To log out:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude whatsapp logout
Signal requires enabling the feature flag, a Rust binary, and QR code linking. First-time setup:
# Build the Rust CLI (requires Rust/Cargo and protobuf installed)
cd ${CLAUDE_PLUGIN_ROOT}/signal && cargo build --release
# Link as a secondary device (scan QR code with your phone)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude signal link
# Check authentication status
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude status
The QR code will be displayed in the terminal. Scan it with Signal on your phone: Settings > Linked Devices > Link New Device.
Credentials are stored in ~/.local/share/jean-claude/signal/.
See "Personalization" section for default behaviors and user skill overrides.
cat if you need the full bodySearch/Inbox response schema:
{
"messages": [
{
"id": "19b29039fd36d1c1",
"threadId": "19b29039fd36d1c1",
"from": "Name <email@example.com>",
"to": "recipient@example.com",
"cc": "other@example.com",
"subject": "Subject line",
"date": "Tue, 16 Dec 2025 21:12:21 +0000",
"snippet": "First ~200 chars of body...",
"labels": ["INBOX", "UNREAD"],
"file": "~/.cache/jean-claude/emails/email-19b29039fd36d1c1.json"
}
],
"nextPageToken": "abc123..."
}
Split file format: Each email creates three files in ~/.cache/jean-claude/emails/:
email-{id}.json — Metadata (queryable with jq)email-{id}.txt — Plain text body (readable with cat/less)email-{id}.html — HTML body when present (viewable in browser)The JSON includes body_file and html_file paths. HTML contains unsubscribe links.
The nextPageToken field is only present when more results are available. Use
--page-token to fetch the next page:
# First page
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail search "is:unread" -n 50
# If nextPageToken is in the response, fetch next page
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail search "is:unread" -n 50 \
--page-token "TOKEN_FROM_PREVIOUS_RESPONSE"
# Inbox emails from a sender
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail search "in:inbox from:someone@example.com"
# Limit results with -n
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail search "from:newsletter@example.com" -n 10
# Unread inbox emails
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail search "in:inbox is:unread"
# Shortcut for inbox
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail inbox
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail inbox --unread
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail inbox -n 5
# Inbox also supports pagination
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail inbox --unread -n 50 --page-token "TOKEN"
Common Gmail search operators: in:inbox, is:unread, is:starred, from:,
to:, subject:, after:2025/01/01, has:attachment, label:
# Get message by ID (writes full body to ~/.cache/jean-claude/emails/)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail get MESSAGE_ID
Use this when you have a specific message ID and want to read its full content.
All draft commands read body from stdin. Create uses flags for metadata.
IMPORTANT: Use heredocs, not echo. Claude Code's Bash tool has a known bug that escapes exclamation marks ('!' becomes '!'). Always use heredocs:
# Create a new draft
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft create --to "recipient@example.com" --subject "Subject"
Message body here!
EOF
# Create with CC/BCC
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft create --to "recipient@example.com" --subject "Subject" --cc "cc@example.com"
Multi-line message body here.
EOF
# Reply to a message (body from stdin, preserves threading, includes quoted original)
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft reply MESSAGE_ID
Thanks for your email!
EOF
# Reply with custom CC
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft reply MESSAGE_ID --cc "manager@example.com"
Thanks for the update!
EOF
# Forward a message (TO as argument, optional note from stdin)
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft forward MESSAGE_ID someone@example.com
FYI - see below!
EOF
# Forward without a note
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft forward MESSAGE_ID someone@example.com < /dev/null
# Reply-all (includes all original recipients)
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft reply-all MESSAGE_ID
Thanks everyone!
EOF
# List drafts
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft list
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft list -n 5
# Get draft (writes metadata to .json and body to .txt)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft get DRAFT_ID
# Update draft body (from stdin)
cat ~/.cache/jean-claude/drafts/draft-DRAFT_ID.txt | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft update DRAFT_ID
# Update metadata only
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft update DRAFT_ID --subject "New subject" --cc "added@example.com"
# Update both body and metadata
cat body.txt | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft update DRAFT_ID --subject "Updated"
# Send a draft (after approval)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft send DRAFT_ID
# Delete a draft
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft delete DRAFT_ID
Draft creation commands (create, reply, reply-all, forward, update)
support --attach for file attachments. Use multiple times for multiple files:
# Create draft with attachments
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft create --to "x@y.com" --subject "Report" --attach report.pdf --attach data.csv
Please see the attached files.
EOF
# Reply with attachment
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft reply MESSAGE_ID --attach response.pdf
Here is my response with the requested document.
EOF
# Forward (original attachments included automatically)
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft forward MESSAGE_ID someone@example.com
FYI - see below.
EOF
# Forward with additional attachment
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft forward MESSAGE_ID someone@example.com --attach notes.pdf
FYI - I added my notes.
EOF
# Add attachment to existing draft (replaces any existing attachments)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft update DRAFT_ID --attach newfile.pdf < /dev/null
# Remove all attachments from a draft
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft update DRAFT_ID --clear-attachments < /dev/null
Viewing attachments: Use draft get to see what files are attached:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft get DRAFT_ID
# Output includes: {"attachments": [{"filename": "report.pdf", "mimeType": "application/pdf"}]}
Iterating on long emails: For complex emails, use file editing to iterate with the user without rewriting the full email each time:
jean-claude gmail draft get DRAFT_ID (writes .json and .txt)~/.cache/jean-claude/drafts/draft-DRAFT_ID.txtcat ~/.cache/jean-claude/drafts/draft-DRAFT_ID.txt | jean-claude gmail draft update DRAFT_IDVerifying important drafts: For important emails, read the draft back after creating it to confirm formatting is correct:
# Create draft
cat << 'EOF' | uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft create --to "..." --subject "..."
Email body here!
EOF
# Verify the draft content (check for escaping issues like \!)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail draft get DRAFT_ID
cat ~/.cache/jean-claude/drafts/draft-DRAFT_ID.txt
This catches any escaping bugs before sending. Also verify drafts with attachments — confirm the right files are attached before sending.
Most commands operate on threads (matching Gmail UI behavior). Use threadId from
inbox/search output. Star/unstar operate on individual messages (use latestMessageId).
# Star/unstar (message-level - use latestMessageId)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail star MSG_ID1 MSG_ID2 MSG_ID3
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail unstar MSG_ID1 MSG_ID2
# Archive/unarchive (thread-level - use threadId)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail archive THREAD_ID1 THREAD_ID2 THREAD_ID3
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail archive --query "from:newsletter@example.com" -n 50
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail unarchive THREAD_ID1 THREAD_ID2 THREAD_ID3
# Mark read/unread (thread-level - use threadId)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail mark-read THREAD_ID1 THREAD_ID2
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail mark-unread THREAD_ID1 THREAD_ID2
# Trash (thread-level - use threadId)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail trash THREAD_ID1 THREAD_ID2 THREAD_ID3
Which ID to use:
threadIdlatestMessageId--query for pattern-based operations (archive supports this)# List attachments for a message
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail attachments MESSAGE_ID
# Download an attachment (saved to ~/.cache/jean-claude/attachments/)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail attachment-download MESSAGE_ID ATTACHMENT_ID filename.pdf
# Download to specific directory
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail attachment-download MESSAGE_ID ATTACHMENT_ID filename.pdf --output ./
Unsubscribe links are in the HTML file, not the plain text. Note: HTML files are only created when the email has HTML content (most newsletters do).
# Search HTML body for unsubscribe links (if HTML file exists)
grep -oE 'https?://[^"<>]+unsubscribe[^"<>]*' ~/.cache/jean-claude/emails/email-MESSAGE_ID.html
Decoding tracking URLs: Newsletters often wrap links in tracking redirects. URL-decode to get the actual destination:
import urllib.parse
print(urllib.parse.unquote(encoded_url))
Completing the unsubscribe:
List all Gmail labels to get label IDs for filtering:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail labels
Returns system labels (INBOX, SENT, TRASH, etc.) and custom labels (Label_123...).
Filters automatically process incoming mail based on criteria.
# List all filters
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter list
# Get a specific filter
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter get FILTER_ID
# Delete a filter
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter delete FILTER_ID
Creating filters — query + label operations:
# Archive (remove INBOX label)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter create \
"to:reports@company.com" -r INBOX
# Star and mark important
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter create \
"from:boss@company.com" -a STARRED -a IMPORTANT
# Mark as read (remove UNREAD label)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter create \
"from:alerts@service.com" -r UNREAD
# Forward
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gmail filter create \
"from:vip@example.com" -f backup@example.com
Common labels: INBOX, UNREAD, STARRED, IMPORTANT, TRASH, SPAM,
CATEGORY_PROMOTIONS, CATEGORY_SOCIAL, CATEGORY_UPDATES
Custom labels: Use gmail labels to get IDs like Label_123456.
All calendar commands return JSON.
By default, commands operate on the primary calendar. Use --calendar to work
with other calendars.
# List available calendars
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal calendars
Returns:
[
{"id": "user@gmail.com", "name": "Personal", "primary": true, "accessRole": "owner"},
{"id": "work@company.com", "name": "Work Calendar", "primary": false, "accessRole": "writer"},
{"id": "family@group.calendar.google.com", "name": "Family", "primary": false, "accessRole": "owner"}
]
Use --calendar with any command to specify a different calendar:
# List events from a specific calendar
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list --calendar work@company.com
# Create event on another calendar
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal create "Team Sync" \
--start "2025-01-15 14:00" --calendar work@company.com
# Search by calendar name (case-insensitive substring match)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list --calendar "Family"
The --calendar flag accepts:
primary (default)If a name matches multiple calendars, the command fails with a list of options.
Multiple calendars in one query: The list, search, and invitations
commands accept multiple --calendar flags to query across calendars:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list --from 2025-01-15 --to 2025-01-15 \
--calendar "Personal" --calendar "Work"
Each event includes calendar_id and calendar_name in the output.
The status command shows participation metrics:
Max @ Personal (primary) (24 upcoming: 10 yours, 14 invited) [owner]
Roos family (81 upcoming: 3 invited) [owner]
Max @ TGS (44 upcoming, 0 yours) [freeBusyReader]
Check user preferences for which calendars represent their availability vs calendars they just view. When in doubt, ask.
Checking availability: Query multiple calendars at once:
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list --from 2025-01-07 --to 2025-01-07 \
--calendar "Max @ Personal" --calendar "Roos family"
Creating events: Use the primary calendar unless the user names a different one or context suggests otherwise (work event → work calendar).
When creating events, add useful information proactively:
A calendar invite should have everything attendees need to show up prepared.
Never guess dates. When the user says "Sunday" or "next week", calculate:
date -v+0d "+%A %Y-%m-%d"
Then verify: "Sunday is 2025-12-28 — creating the event for that date."
Never hallucinate emails. If the user says "add Ursula", look up her email or ask. Never invent addresses.
Verify after creating. Run gcal list for that date to confirm. If
wrong, delete and recreate before confirming to the user.
Show what you're creating. Before gcal create, state: title, date/time
(with day of week), attendees (with emails), location (with address).
Confirm ambiguous locations. "SVB" could mean West Hollywood or Santa Monica — ask which one.
Example workflow:
User: "Add a meeting with Alice for next Tuesday at 2pm"
1. Check: date -v+0d "+%A %Y-%m-%d" → "Friday 2025-12-26"
2. Calculate: next Tuesday = 2025-12-30
3. Look up Alice's email
4. State: "Creating for Tuesday 2025-12-30 at 2pm, inviting alice@example.com"
5. Create, then verify with gcal list
# Today's events
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list
# Next 7 days
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list --days 7
# Date range
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal list --from 2025-01-15 --to 2025-01-20
# Simple event
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal create "Team Meeting" \
--start "2025-01-15 14:00" --end "2025-01-15 15:00"
# With attendees, location, and description
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal create "1:1 with Alice" \
--start "2025-01-15 10:00" --duration 30 \
--attendees alice@example.com \
--location "Conference Room A" \
--description "Weekly sync"
# All-day event (single day)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal create "Holiday" \
--start 2025-01-15 --all-day
# Multi-day all-day event
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal create "Vacation" \
--start 2025-01-15 --end 2025-01-20 --all-day
# Search (default: 30 days ahead)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal search "standup"
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal search "standup" --days 90
# Update
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal update EVENT_ID --start "2025-01-16 14:00"
# Delete
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal delete EVENT_ID --notify
List and respond to calendar invitations (events you've been invited to).
Recurring events: The invitations command collapses recurring event instances into a single entry. Each collapsed entry includes:
recurring: true - indicates this is a recurring seriesinstanceCount: N - number of pending instancesid - the parent event ID (use this to respond to all instances at once)Responding to a parent ID accepts/declines all instances in the series. Responding to an instance ID (if you have one) affects only that instance.
# List all pending invitations (no time limit by default)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal invitations
# Limit to next 7 days
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal invitations --days 7
# Show all individual instances (don't collapse recurring events)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal invitations --expand
# Accept an invitation (or all instances if recurring)
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal respond EVENT_ID --accept
# Decline an invitation
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal respond EVENT_ID --decline
# Tentatively accept
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal respond EVENT_ID --tentative
# Respond without notifying organizer
uv run --project ${CLAUDE_PLUGIN_ROOT} jean-claude gcal respond EVENT_ID --accept --no-notify
Load these platform-specific guides as needed:
# Messaging
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/imessage.md
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/whatsapp.md
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/signal.md
# Google Workspace (non-Gmail)
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/drive.md
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/docs.md
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/sheets.md
# Apple
cat ${CLAUDE_PLUGIN_ROOT}/skills/jean-claude/platforms/reminders.md
When to load: Load the relevant platform file when the user asks about that service. For example, if they ask "send a WhatsApp message", load whatsapp.md first.
Safety rules still apply: The messaging safety rules in this file (never send without approval, verify recipients) apply to all messaging platforms.
For "near me" queries, use these sources in order:
curl -s ipinfo.io/json | jq '{city, region, country, loc}'
State your assumption: "I see you're in the LA area based on your calendar."
When users correct drafts or behavior, offer to save learnable preferences:
One-off corrections ("make this shorter") are situational, not preferences.
Default behavior: Make the correction, then ask "Want me to remember this?"
If yes, update their personalization skill file (e.g., ~/.claude/skills/managing-messages/SKILL.md).
Auto-learn mode: After a few saved corrections, offer to enable auto-learning. When enabled, save preferences automatically and confirm briefly: "Noted — I'll sign emails 'Best' from now on."
Never auto-learn anything affecting recipients or major workflow changes.
When you hit unexpected exceptions, schema mismatches, or behavior that contradicts documentation, offer to file a GitHub issue.
Don't offer for: authentication failures, permission errors, network issues, or user configuration problems.
I ran into what looks like a bug in jean-claude. Want me to create a GitHub issue? I'll show you the report first and remove any personal information.
See ISSUES.md for formatting and submission.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.