Add hooks and refactor skills to use resources pattern
Phase 1 of plugin-structure refactor: - Add hooks/hooks.json for SessionStart automation - Refactor gmail skill: - Extract inline scripts to scripts/check_unread.py, check_urgent.py, search.py - Add references/query-patterns.md for query documentation - Simplify SKILL.md to reference scripts instead of inline code - Add gcal/scripts/agenda.py for direct calendar access - Make all scripts executable This follows the "Skill with Bundled Resources" pattern from developing-claude-code-plugins best practices. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
14
hooks/hooks.json
Normal file
14
hooks/hooks.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "echo 'SessionStart:Callback hook success: Success'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
104
skills/gcal/scripts/agenda.py
Executable file
104
skills/gcal/scripts/agenda.py
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Get calendar agenda for a time range."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# Set credentials path
|
||||||
|
os.environ.setdefault('GMAIL_CREDENTIALS_PATH', os.path.expanduser('~/.gmail-mcp/credentials.json'))
|
||||||
|
|
||||||
|
from gmail_mcp.utils.GCP.gmail_auth import get_calendar_service
|
||||||
|
|
||||||
|
def format_event(event):
|
||||||
|
"""Format a single event for display."""
|
||||||
|
start = event['start'].get('dateTime', event['start'].get('date'))
|
||||||
|
end = event['end'].get('dateTime', event['end'].get('date'))
|
||||||
|
|
||||||
|
# Parse datetime
|
||||||
|
if 'T' in start:
|
||||||
|
start_dt = datetime.fromisoformat(start.replace('Z', '+00:00'))
|
||||||
|
end_dt = datetime.fromisoformat(end.replace('Z', '+00:00'))
|
||||||
|
time_str = start_dt.strftime('%I:%M %p').lstrip('0')
|
||||||
|
duration = end_dt - start_dt
|
||||||
|
if duration.seconds >= 3600:
|
||||||
|
dur_str = f"{duration.seconds // 3600}h"
|
||||||
|
else:
|
||||||
|
dur_str = f"{duration.seconds // 60}m"
|
||||||
|
else:
|
||||||
|
time_str = "All day"
|
||||||
|
dur_str = ""
|
||||||
|
|
||||||
|
summary = event.get('summary', '(No title)')
|
||||||
|
location = event.get('location', '')
|
||||||
|
|
||||||
|
line = f" {time_str:>10} {summary}"
|
||||||
|
if dur_str:
|
||||||
|
line += f" ({dur_str})"
|
||||||
|
if location:
|
||||||
|
line += f"\n 📍 {location}"
|
||||||
|
|
||||||
|
return line
|
||||||
|
|
||||||
|
def main():
|
||||||
|
mode = sys.argv[1] if len(sys.argv) > 1 else 'today'
|
||||||
|
|
||||||
|
service = get_calendar_service()
|
||||||
|
now = datetime.utcnow()
|
||||||
|
|
||||||
|
if mode == 'today':
|
||||||
|
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
end = start + timedelta(days=1)
|
||||||
|
title = f"Today — {start.strftime('%A, %b %d')}"
|
||||||
|
elif mode == 'tomorrow':
|
||||||
|
start = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
end = start + timedelta(days=1)
|
||||||
|
title = f"Tomorrow — {start.strftime('%A, %b %d')}"
|
||||||
|
elif mode == 'week':
|
||||||
|
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
end = start + timedelta(days=7)
|
||||||
|
title = f"This Week — {start.strftime('%b %d')}-{end.strftime('%d')}"
|
||||||
|
else:
|
||||||
|
start = now
|
||||||
|
end = now + timedelta(days=7)
|
||||||
|
title = "Upcoming"
|
||||||
|
|
||||||
|
events_result = service.events().list(
|
||||||
|
calendarId='primary',
|
||||||
|
timeMin=start.isoformat() + 'Z',
|
||||||
|
timeMax=end.isoformat() + 'Z',
|
||||||
|
singleEvents=True,
|
||||||
|
orderBy='startTime',
|
||||||
|
maxResults=50
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
events = events_result.get('items', [])
|
||||||
|
|
||||||
|
print(f"📅 {title}\n")
|
||||||
|
|
||||||
|
if not events:
|
||||||
|
print("No events scheduled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == 'week':
|
||||||
|
# Group by day
|
||||||
|
by_day = {}
|
||||||
|
for event in events:
|
||||||
|
start_str = event['start'].get('dateTime', event['start'].get('date'))
|
||||||
|
day = start_str[:10]
|
||||||
|
if day not in by_day:
|
||||||
|
by_day[day] = []
|
||||||
|
by_day[day].append(event)
|
||||||
|
|
||||||
|
for day, day_events in sorted(by_day.items()):
|
||||||
|
day_dt = datetime.fromisoformat(day)
|
||||||
|
print(f"━━━ {day_dt.strftime('%A, %b %d')} ━━━")
|
||||||
|
for event in day_events:
|
||||||
|
print(format_event(event))
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
for event in events:
|
||||||
|
print(format_event(event))
|
||||||
|
print("\nNo more events" + (" today." if mode == 'today' else "."))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,147 +1,87 @@
|
|||||||
---
|
---
|
||||||
name: gmail
|
name: gmail
|
||||||
description: Gmail read access via direct Python API - search, check unread, detect urgent emails
|
description: Gmail read access via Python API - search, check unread, detect urgent emails. Use when user asks about email, inbox, or messages.
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
---
|
---
|
||||||
|
|
||||||
# Gmail Skill
|
# Gmail Skill
|
||||||
|
|
||||||
Access Gmail via direct Python API calls. Uses OAuth credentials at `~/.gmail-mcp/`.
|
Access Gmail via Python API calls. Uses OAuth credentials at `~/.gmail-mcp/`.
|
||||||
|
|
||||||
## Delegated Operations (Recommended)
|
## Quick Commands
|
||||||
|
|
||||||
Use the tiered delegation helper for cost-efficient operations. Uses Claude CLI with your subscription (no API key needed):
|
Run scripts using the gmail venv:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GMAIL_PY=~/.claude/mcp/gmail/venv/bin/python
|
||||||
|
SCRIPTS=~/.claude/skills/gmail/scripts
|
||||||
|
|
||||||
|
# Check unread (last 7 days, grouped by sender)
|
||||||
|
$GMAIL_PY $SCRIPTS/check_unread.py 7
|
||||||
|
|
||||||
|
# Check urgent emails
|
||||||
|
$GMAIL_PY $SCRIPTS/check_urgent.py
|
||||||
|
|
||||||
|
# Search with custom query
|
||||||
|
$GMAIL_PY $SCRIPTS/search.py "from:github.com" 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Script Reference
|
||||||
|
|
||||||
|
| Script | Purpose | Args |
|
||||||
|
|--------|---------|------|
|
||||||
|
| `check_unread.py` | List unread, grouped by sender | `[days] [max]` |
|
||||||
|
| `check_urgent.py` | Find urgent/important emails | none |
|
||||||
|
| `search.py` | Custom query search | `<query> [max]` |
|
||||||
|
|
||||||
|
## Request Routing
|
||||||
|
|
||||||
|
| User Request | Script | Tier |
|
||||||
|
|--------------|--------|------|
|
||||||
|
| "Check my email" | `check_unread.py` | Haiku |
|
||||||
|
| "How many unread?" | `check_unread.py` | Haiku |
|
||||||
|
| "Any urgent emails?" | `check_urgent.py` | Haiku |
|
||||||
|
| "Search for X" | `search.py "X"` | Haiku |
|
||||||
|
| "Summarize my inbox" | Run script + analyze | Sonnet |
|
||||||
|
| "What should I prioritize?" | Run script + reason | Opus |
|
||||||
|
|
||||||
|
## Query Patterns
|
||||||
|
|
||||||
|
For custom searches, see [references/query-patterns.md](references/query-patterns.md).
|
||||||
|
|
||||||
|
Common queries:
|
||||||
|
- `is:unread newer_than:7d` - Unread last week
|
||||||
|
- `from:github.com` - GitHub notifications
|
||||||
|
- `has:attachment larger:5M` - Large attachments
|
||||||
|
- `subject:urgent is:unread` - Urgent unread
|
||||||
|
|
||||||
|
## Delegation Helper (Advanced)
|
||||||
|
|
||||||
|
For LLM-assisted operations (summarization, triage):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GMAIL_PY=~/.claude/mcp/gmail/venv/bin/python
|
GMAIL_PY=~/.claude/mcp/gmail/venv/bin/python
|
||||||
HELPER=~/.claude/mcp/delegation/gmail_delegate.py
|
HELPER=~/.claude/mcp/delegation/gmail_delegate.py
|
||||||
|
|
||||||
# Haiku tier - list unread (no LLM call, just fetches)
|
# Summarize emails (spawns claude --model sonnet)
|
||||||
$GMAIL_PY $HELPER check-unread --days 7
|
|
||||||
|
|
||||||
# Sonnet tier - summarize emails (spawns claude --model sonnet)
|
|
||||||
$GMAIL_PY $HELPER summarize --query "from:github.com"
|
$GMAIL_PY $HELPER summarize --query "from:github.com"
|
||||||
|
|
||||||
# Sonnet tier - triage urgent (spawns claude --model sonnet)
|
# Triage urgent (spawns claude --model sonnet)
|
||||||
$GMAIL_PY $HELPER urgent
|
$GMAIL_PY $HELPER urgent
|
||||||
```
|
```
|
||||||
|
|
||||||
### When to Use Each Tier
|
|
||||||
|
|
||||||
| Request Type | Command | Model |
|
|
||||||
|--------------|---------|-------|
|
|
||||||
| "Check my email" | `check-unread` | Haiku |
|
|
||||||
| "How many unread?" | `check-unread` | Haiku |
|
|
||||||
| "Summarize X" | `summarize --query "X"` | Sonnet |
|
|
||||||
| "What's urgent?" | `urgent` | Sonnet |
|
|
||||||
| "What should I prioritize?" | (PA direct) | Opus |
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
For any Gmail request, use Bash to run the Python helper:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GMAIL_CREDENTIALS_PATH=~/.gmail-mcp/credentials.json ~/.claude/mcp/gmail/venv/bin/python << 'EOF'
|
|
||||||
from gmail_mcp.utils.GCP.gmail_auth import get_gmail_service
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
service = get_gmail_service()
|
|
||||||
|
|
||||||
# Your query here
|
|
||||||
results = service.users().messages().list(userId='me', q='is:unread newer_than:3d', maxResults=25).execute()
|
|
||||||
messages = results.get('messages', [])
|
|
||||||
|
|
||||||
for msg in messages:
|
|
||||||
detail = service.users().messages().get(userId='me', id=msg['id'], format='metadata', metadataHeaders=['From', 'Subject', 'Date']).execute()
|
|
||||||
headers = {h['name']: h['value'] for h in detail['payload']['headers']}
|
|
||||||
print(f"From: {headers.get('From', 'Unknown')}")
|
|
||||||
print(f"Subject: {headers.get('Subject', '(no subject)')}")
|
|
||||||
print(f"Date: {headers.get('Date', 'Unknown')}")
|
|
||||||
print("---")
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## Query Patterns
|
|
||||||
|
|
||||||
| Request | Gmail Query |
|
|
||||||
|---------|-------------|
|
|
||||||
| Unread | `is:unread` |
|
|
||||||
| Last N days | `newer_than:Nd` |
|
|
||||||
| From sender | `from:email@example.com` |
|
|
||||||
| With attachments | `has:attachment` |
|
|
||||||
| Important | `is:important` |
|
|
||||||
| Urgent keywords | `subject:(urgent OR asap OR "action required")` |
|
|
||||||
|
|
||||||
## Common Tasks
|
|
||||||
|
|
||||||
### Check unread (grouped by sender)
|
|
||||||
```bash
|
|
||||||
GMAIL_CREDENTIALS_PATH=~/.gmail-mcp/credentials.json ~/.claude/mcp/gmail/venv/bin/python << 'EOF'
|
|
||||||
from gmail_mcp.utils.GCP.gmail_auth import get_gmail_service
|
|
||||||
from collections import defaultdict
|
|
||||||
service = get_gmail_service()
|
|
||||||
results = service.users().messages().list(userId='me', q='is:unread newer_than:7d', maxResults=25).execute()
|
|
||||||
by_sender = defaultdict(list)
|
|
||||||
for msg in results.get('messages', []):
|
|
||||||
detail = service.users().messages().get(userId='me', id=msg['id'], format='metadata', metadataHeaders=['From', 'Subject']).execute()
|
|
||||||
headers = {h['name']: h['value'] for h in detail['payload']['headers']}
|
|
||||||
sender = headers.get('From', 'Unknown').split('<')[0].strip().strip('"')
|
|
||||||
by_sender[sender].append(headers.get('Subject', '(no subject)')[:50])
|
|
||||||
for sender, subjects in sorted(by_sender.items(), key=lambda x: -len(x[1])):
|
|
||||||
print(f"* {sender} ({len(subjects)})")
|
|
||||||
for s in subjects[:2]: print(f" - {s}")
|
|
||||||
if len(subjects) > 2: print(f" - ...+{len(subjects)-2} more")
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check urgent
|
|
||||||
```bash
|
|
||||||
GMAIL_CREDENTIALS_PATH=~/.gmail-mcp/credentials.json ~/.claude/mcp/gmail/venv/bin/python << 'EOF'
|
|
||||||
from gmail_mcp.utils.GCP.gmail_auth import get_gmail_service
|
|
||||||
service = get_gmail_service()
|
|
||||||
results = service.users().messages().list(userId='me', q='is:unread newer_than:3d (subject:urgent OR subject:asap OR subject:"action required" OR is:important)', maxResults=15).execute()
|
|
||||||
for msg in results.get('messages', []):
|
|
||||||
detail = service.users().messages().get(userId='me', id=msg['id'], format='metadata', metadataHeaders=['From', 'Subject', 'Date']).execute()
|
|
||||||
headers = {h['name']: h['value'] for h in detail['payload']['headers']}
|
|
||||||
print(f"From: {headers.get('From', 'Unknown')}")
|
|
||||||
print(f"Subject: {headers.get('Subject', '(no subject)')}")
|
|
||||||
print(f"Date: {headers.get('Date', 'Unknown')}")
|
|
||||||
print("---")
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## Model Selection
|
## Model Selection
|
||||||
|
|
||||||
Gmail operations use tiered delegation per `model-policy.json`:
|
| Model | Use For |
|
||||||
|
|-------|---------|
|
||||||
| Model | Use For | Examples |
|
| **Haiku** | Fetch, count, list, simple search |
|
||||||
|-------|---------|----------|
|
| **Sonnet** | Summarize, categorize, extract |
|
||||||
| **Haiku** | Fetch, count, list, simple search | "How many unread?", "List from X" |
|
| **Opus** | Prioritize, analyze, cross-reference |
|
||||||
| **Sonnet** | Summarize, categorize, extract | "Summarize this email", "Group by topic" |
|
|
||||||
| **Opus** | Prioritize, analyze, cross-reference | "What should I handle first?" |
|
|
||||||
|
|
||||||
### Delegation Pattern
|
|
||||||
|
|
||||||
When invoking gmail operations, the PA should:
|
|
||||||
|
|
||||||
1. **Classify the request** — Is it fetch-only, summarization, or analysis?
|
|
||||||
2. **Delegate appropriately**:
|
|
||||||
- Haiku: API calls + simple formatting
|
|
||||||
- Sonnet: API calls + content understanding
|
|
||||||
- Opus: Only for strategic reasoning
|
|
||||||
3. **Escalate if needed** — If lower tier can't handle it, escalate
|
|
||||||
|
|
||||||
### Implementation Note
|
|
||||||
|
|
||||||
Until Task tool delegation is available, the PA executes gmail operations directly
|
|
||||||
but should mentally "account" for which tier the work belongs to. This policy
|
|
||||||
enables future cost optimization when subagent spawning is implemented.
|
|
||||||
|
|
||||||
## Policy
|
## Policy
|
||||||
|
|
||||||
- Read-only operations only
|
- **Read-only** operations only
|
||||||
- Summarize results (don't dump raw content)
|
- **Summarize** results (don't dump raw content)
|
||||||
- Report metadata, not full body unless asked
|
- Report **metadata**, not full body unless asked
|
||||||
- Start with lowest capable model tier
|
- Start with **lowest capable** model tier
|
||||||
- Escalate only when task complexity requires
|
|
||||||
|
|||||||
57
skills/gmail/references/query-patterns.md
Normal file
57
skills/gmail/references/query-patterns.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Gmail Query Patterns
|
||||||
|
|
||||||
|
## Basic Queries
|
||||||
|
|
||||||
|
| Request | Gmail Query |
|
||||||
|
|---------|-------------|
|
||||||
|
| Unread | `is:unread` |
|
||||||
|
| Last N days | `newer_than:Nd` |
|
||||||
|
| From sender | `from:email@example.com` |
|
||||||
|
| To recipient | `to:email@example.com` |
|
||||||
|
| With attachments | `has:attachment` |
|
||||||
|
| Important | `is:important` |
|
||||||
|
| Starred | `is:starred` |
|
||||||
|
| In inbox | `in:inbox` |
|
||||||
|
|
||||||
|
## Urgent/Priority
|
||||||
|
|
||||||
|
| Pattern | Query |
|
||||||
|
|---------|-------|
|
||||||
|
| Urgent keywords | `subject:(urgent OR asap OR "action required")` |
|
||||||
|
| Important + unread | `is:important is:unread` |
|
||||||
|
| Time-sensitive | `subject:(deadline OR "due date" OR "end of day")` |
|
||||||
|
|
||||||
|
## Sender Patterns
|
||||||
|
|
||||||
|
| Pattern | Query |
|
||||||
|
|---------|-------|
|
||||||
|
| GitHub notifications | `from:github.com` |
|
||||||
|
| Google alerts | `from:googlealerts-noreply@google.com` |
|
||||||
|
| Calendar invites | `from:calendar-notification@google.com` |
|
||||||
|
| Newsletters | `category:promotions` |
|
||||||
|
|
||||||
|
## Combining Queries
|
||||||
|
|
||||||
|
Queries can be combined:
|
||||||
|
|
||||||
|
```
|
||||||
|
is:unread newer_than:7d from:github.com
|
||||||
|
is:unread (subject:urgent OR is:important) newer_than:3d
|
||||||
|
has:attachment from:client@example.com newer_than:30d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Date Queries
|
||||||
|
|
||||||
|
| Pattern | Query |
|
||||||
|
|---------|-------|
|
||||||
|
| Last 24 hours | `newer_than:1d` |
|
||||||
|
| Last week | `newer_than:7d` |
|
||||||
|
| Last month | `newer_than:30d` |
|
||||||
|
| Specific date | `after:2024/01/01 before:2024/01/31` |
|
||||||
|
|
||||||
|
## Size Queries
|
||||||
|
|
||||||
|
| Pattern | Query |
|
||||||
|
|---------|-------|
|
||||||
|
| Large attachments | `larger:10M` |
|
||||||
|
| Small emails | `smaller:100K` |
|
||||||
48
skills/gmail/scripts/check_unread.py
Executable file
48
skills/gmail/scripts/check_unread.py
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check unread emails, grouped by sender."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Set credentials path
|
||||||
|
os.environ.setdefault('GMAIL_CREDENTIALS_PATH', os.path.expanduser('~/.gmail-mcp/credentials.json'))
|
||||||
|
|
||||||
|
from gmail_mcp.utils.GCP.gmail_auth import get_gmail_service
|
||||||
|
|
||||||
|
def main():
|
||||||
|
days = int(sys.argv[1]) if len(sys.argv) > 1 else 7
|
||||||
|
max_results = int(sys.argv[2]) if len(sys.argv) > 2 else 25
|
||||||
|
|
||||||
|
service = get_gmail_service()
|
||||||
|
results = service.users().messages().list(
|
||||||
|
userId='me',
|
||||||
|
q=f'is:unread newer_than:{days}d',
|
||||||
|
maxResults=max_results
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
by_sender = defaultdict(list)
|
||||||
|
for msg in results.get('messages', []):
|
||||||
|
detail = service.users().messages().get(
|
||||||
|
userId='me',
|
||||||
|
id=msg['id'],
|
||||||
|
format='metadata',
|
||||||
|
metadataHeaders=['From', 'Subject']
|
||||||
|
).execute()
|
||||||
|
headers = {h['name']: h['value'] for h in detail['payload']['headers']}
|
||||||
|
sender = headers.get('From', 'Unknown').split('<')[0].strip().strip('"')
|
||||||
|
by_sender[sender].append(headers.get('Subject', '(no subject)')[:50])
|
||||||
|
|
||||||
|
if not by_sender:
|
||||||
|
print("No unread emails in the last", days, "days")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Unread emails (last {days} days):\n")
|
||||||
|
for sender, subjects in sorted(by_sender.items(), key=lambda x: -len(x[1])):
|
||||||
|
print(f"* {sender} ({len(subjects)})")
|
||||||
|
for s in subjects[:2]:
|
||||||
|
print(f" - {s}")
|
||||||
|
if len(subjects) > 2:
|
||||||
|
print(f" - ...+{len(subjects)-2} more")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
38
skills/gmail/scripts/check_urgent.py
Executable file
38
skills/gmail/scripts/check_urgent.py
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check for urgent unread emails."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Set credentials path
|
||||||
|
os.environ.setdefault('GMAIL_CREDENTIALS_PATH', os.path.expanduser('~/.gmail-mcp/credentials.json'))
|
||||||
|
|
||||||
|
from gmail_mcp.utils.GCP.gmail_auth import get_gmail_service
|
||||||
|
|
||||||
|
def main():
|
||||||
|
service = get_gmail_service()
|
||||||
|
results = service.users().messages().list(
|
||||||
|
userId='me',
|
||||||
|
q='is:unread newer_than:3d (subject:urgent OR subject:asap OR subject:"action required" OR is:important)',
|
||||||
|
maxResults=15
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
messages = results.get('messages', [])
|
||||||
|
if not messages:
|
||||||
|
print("No urgent emails found")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Found {len(messages)} urgent email(s):\n")
|
||||||
|
for msg in messages:
|
||||||
|
detail = service.users().messages().get(
|
||||||
|
userId='me',
|
||||||
|
id=msg['id'],
|
||||||
|
format='metadata',
|
||||||
|
metadataHeaders=['From', 'Subject', 'Date']
|
||||||
|
).execute()
|
||||||
|
headers = {h['name']: h['value'] for h in detail['payload']['headers']}
|
||||||
|
print(f"From: {headers.get('From', 'Unknown')}")
|
||||||
|
print(f"Subject: {headers.get('Subject', '(no subject)')}")
|
||||||
|
print(f"Date: {headers.get('Date', 'Unknown')}")
|
||||||
|
print("---")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
47
skills/gmail/scripts/search.py
Executable file
47
skills/gmail/scripts/search.py
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Search emails with a custom query."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Set credentials path
|
||||||
|
os.environ.setdefault('GMAIL_CREDENTIALS_PATH', os.path.expanduser('~/.gmail-mcp/credentials.json'))
|
||||||
|
|
||||||
|
from gmail_mcp.utils.GCP.gmail_auth import get_gmail_service
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: search.py <query> [max_results]")
|
||||||
|
print("Example: search.py 'from:github.com' 10")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
query = sys.argv[1]
|
||||||
|
max_results = int(sys.argv[2]) if len(sys.argv) > 2 else 20
|
||||||
|
|
||||||
|
service = get_gmail_service()
|
||||||
|
results = service.users().messages().list(
|
||||||
|
userId='me',
|
||||||
|
q=query,
|
||||||
|
maxResults=max_results
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
messages = results.get('messages', [])
|
||||||
|
if not messages:
|
||||||
|
print(f"No emails found for query: {query}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Found {len(messages)} email(s) for query: {query}\n")
|
||||||
|
for msg in messages:
|
||||||
|
detail = service.users().messages().get(
|
||||||
|
userId='me',
|
||||||
|
id=msg['id'],
|
||||||
|
format='metadata',
|
||||||
|
metadataHeaders=['From', 'Subject', 'Date']
|
||||||
|
).execute()
|
||||||
|
headers = {h['name']: h['value'] for h in detail['payload']['headers']}
|
||||||
|
print(f"From: {headers.get('From', 'Unknown')}")
|
||||||
|
print(f"Subject: {headers.get('Subject', '(no subject)')}")
|
||||||
|
print(f"Date: {headers.get('Date', 'Unknown')}")
|
||||||
|
print("---")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user