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:
@@ -1,147 +1,87 @@
|
||||
---
|
||||
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:
|
||||
- Bash
|
||||
---
|
||||
|
||||
# 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
|
||||
GMAIL_PY=~/.claude/mcp/gmail/venv/bin/python
|
||||
HELPER=~/.claude/mcp/delegation/gmail_delegate.py
|
||||
|
||||
# Haiku tier - list unread (no LLM call, just fetches)
|
||||
$GMAIL_PY $HELPER check-unread --days 7
|
||||
|
||||
# Sonnet tier - summarize emails (spawns claude --model sonnet)
|
||||
# Summarize emails (spawns claude --model sonnet)
|
||||
$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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
Gmail operations use tiered delegation per `model-policy.json`:
|
||||
|
||||
| Model | Use For | Examples |
|
||||
|-------|---------|----------|
|
||||
| **Haiku** | Fetch, count, list, simple search | "How many unread?", "List from X" |
|
||||
| **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.
|
||||
| Model | Use For |
|
||||
|-------|---------|
|
||||
| **Haiku** | Fetch, count, list, simple search |
|
||||
| **Sonnet** | Summarize, categorize, extract |
|
||||
| **Opus** | Prioritize, analyze, cross-reference |
|
||||
|
||||
## Policy
|
||||
|
||||
- Read-only operations only
|
||||
- Summarize results (don't dump raw content)
|
||||
- Report metadata, not full body unless asked
|
||||
- Start with lowest capable model tier
|
||||
- Escalate only when task complexity requires
|
||||
- **Read-only** operations only
|
||||
- **Summarize** results (don't dump raw content)
|
||||
- Report **metadata**, not full body unless asked
|
||||
- Start with **lowest capable** model tier
|
||||
|
||||
Reference in New Issue
Block a user