mcpeasy 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.mcp.json +9 -0
- data/CHANGELOG.md +11 -2
- data/lib/mcpeasy/cli.rb +29 -0
- data/lib/mcpeasy/version.rb +1 -1
- data/lib/utilities/gmail/README.md +278 -0
- data/lib/utilities/gmail/cli.rb +264 -0
- data/lib/utilities/gmail/mcp.rb +846 -0
- data/lib/utilities/gmail/service.rb +547 -0
- data/lib/utilities/gmeet/service.rb +4 -2
- data/mcpeasy.gemspec +6 -1
- metadata +77 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96510ec1696160a8727b73d66eb7fbe9e9ac267bcdbe7c2e3db1f3a7840657f2
|
4
|
+
data.tar.gz: 64d8e8b25d58845501acba165271cc31010efbff4d3b65799ed932ab244aee09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6558ab4692baf95e5144db4f888bf594e28f8031a564d5d1127d90197c509b422225ae00438b61e640b1adc1ac355a444f1361db60485586441004d26b379b54
|
7
|
+
data.tar.gz: 8a12fc568bd98fd4ca14ea21f8e8ae6a13ae39bf34eb877a0e1b5e4e69c581f491578710147040a84550c7c4e5e4d791695208b25951ae492d877e860fc71a4f
|
data/.mcp.json
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,7 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [0.3.0] - 2025-06-07
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Gmail MCP server integration with comprehensive email support
|
12
|
+
- List and search emails with various filters
|
13
|
+
- Read email content including headers and body
|
14
|
+
- Send new emails and reply to existing ones
|
15
|
+
- Manage email labels, mark as read/unread
|
16
|
+
- Archive and trash emails
|
17
|
+
- Full OAuth2 authentication flow integration
|
9
18
|
|
10
19
|
## [0.2.0] - 2025-06-03
|
11
20
|
|
@@ -47,4 +56,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
47
56
|
- Google Calendar integration with event listing and search
|
48
57
|
- Google Drive integration with file search and content retrieval
|
49
58
|
- Basic MCP server implementations for all services
|
50
|
-
- Development tooling with standardrb for code quality
|
59
|
+
- Development tooling with standardrb for code quality
|
data/lib/mcpeasy/cli.rb
CHANGED
@@ -103,6 +103,32 @@ module Mcpeasy
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
+
# Load the existing GmailCLI and extend it with MCP functionality
|
107
|
+
require_relative "../utilities/gmail/cli"
|
108
|
+
|
109
|
+
class GmailCommands < Gmail::CLI
|
110
|
+
namespace "gmail"
|
111
|
+
|
112
|
+
desc "mcp", "Run Gmail MCP server"
|
113
|
+
def mcp
|
114
|
+
require_relative "../utilities/gmail/mcp"
|
115
|
+
Gmail::MCPServer.new.run
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "auth", "Authenticate with Gmail API"
|
119
|
+
def auth
|
120
|
+
require_relative "../utilities/gmail/service"
|
121
|
+
tool = Gmail::Service.new(skip_auth: true)
|
122
|
+
result = tool.authenticate
|
123
|
+
if result[:success]
|
124
|
+
puts "✅ Successfully authenticated with Gmail"
|
125
|
+
else
|
126
|
+
puts "❌ Authentication failed: #{result[:error]}"
|
127
|
+
exit 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
106
132
|
class CLI < Thor
|
107
133
|
desc "version", "Show mcpeasy version"
|
108
134
|
def version
|
@@ -166,6 +192,9 @@ module Mcpeasy
|
|
166
192
|
desc "notion COMMAND", "Notion commands"
|
167
193
|
subcommand "notion", NotionCommands
|
168
194
|
|
195
|
+
desc "gmail COMMAND", "Gmail commands"
|
196
|
+
subcommand "gmail", GmailCommands
|
197
|
+
|
169
198
|
class << self
|
170
199
|
private
|
171
200
|
|
data/lib/mcpeasy/version.rb
CHANGED
@@ -0,0 +1,278 @@
|
|
1
|
+
# Gmail MCP Server
|
2
|
+
|
3
|
+
A Model Context Protocol (MCP) server for Gmail integration with AI assistants.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **List emails** with filtering by date range, sender, subject, labels, and read/unread status
|
8
|
+
- **Search emails** using Gmail's powerful search syntax
|
9
|
+
- **Get email content** including full body, headers, and attachment information
|
10
|
+
- **Send emails** with support for CC, BCC, and reply-to fields
|
11
|
+
- **Reply to emails** with automatic threading and optional quoted text
|
12
|
+
- **Email management** - mark as read/unread, add/remove labels, archive, and trash
|
13
|
+
- **Test connection** to verify Gmail API connectivity
|
14
|
+
|
15
|
+
## Prerequisites
|
16
|
+
|
17
|
+
1. **Google Cloud Project** with Gmail API enabled
|
18
|
+
2. **OAuth 2.0 credentials** (client ID and client secret)
|
19
|
+
3. **Ruby environment** with required gems
|
20
|
+
|
21
|
+
## Setup
|
22
|
+
|
23
|
+
### 1. Enable Gmail API
|
24
|
+
|
25
|
+
1. Go to the [Google Cloud Console](https://console.cloud.google.com)
|
26
|
+
2. Create a new project or select an existing one
|
27
|
+
3. Enable the Gmail API:
|
28
|
+
- Navigate to "APIs & Services" > "Library"
|
29
|
+
- Search for "Gmail API" and click "Enable"
|
30
|
+
|
31
|
+
### 2. Create OAuth 2.0 Credentials
|
32
|
+
|
33
|
+
1. Go to "APIs & Services" > "Credentials"
|
34
|
+
2. Click "Create Credentials" > "OAuth 2.0 Client IDs"
|
35
|
+
3. Set application type to "Desktop application"
|
36
|
+
4. Download the JSON file containing your credentials
|
37
|
+
|
38
|
+
### 3. Install and Configure MCPEasy
|
39
|
+
|
40
|
+
```bash
|
41
|
+
# Install the gem
|
42
|
+
gem install mcpeasy
|
43
|
+
|
44
|
+
# Set up configuration directories
|
45
|
+
mcpz setup
|
46
|
+
|
47
|
+
# Save your Google credentials
|
48
|
+
mcpz set_google_credentials path/to/your/credentials.json
|
49
|
+
|
50
|
+
# Authenticate with Gmail (will open browser for OAuth)
|
51
|
+
mcpz gmail auth
|
52
|
+
```
|
53
|
+
|
54
|
+
### 4. Verify Setup
|
55
|
+
|
56
|
+
```bash
|
57
|
+
# Test the connection
|
58
|
+
mcpz gmail test
|
59
|
+
```
|
60
|
+
|
61
|
+
## CLI Usage
|
62
|
+
|
63
|
+
### Authentication
|
64
|
+
```bash
|
65
|
+
# Authenticate with Gmail API
|
66
|
+
mcpz gmail auth
|
67
|
+
```
|
68
|
+
|
69
|
+
### Listing Emails
|
70
|
+
```bash
|
71
|
+
# List recent emails
|
72
|
+
mcpz gmail list
|
73
|
+
|
74
|
+
# Filter by date range
|
75
|
+
mcpz gmail list --start_date 2024-01-01 --end_date 2024-01-31
|
76
|
+
|
77
|
+
# Filter by sender
|
78
|
+
mcpz gmail list --sender "someone@example.com"
|
79
|
+
|
80
|
+
# Filter by subject
|
81
|
+
mcpz gmail list --subject "Important"
|
82
|
+
|
83
|
+
# Filter by labels
|
84
|
+
mcpz gmail list --labels "inbox,important"
|
85
|
+
|
86
|
+
# Filter by read status
|
87
|
+
mcpz gmail list --read_status unread
|
88
|
+
|
89
|
+
# Limit results
|
90
|
+
mcpz gmail list --max_results 10
|
91
|
+
```
|
92
|
+
|
93
|
+
### Searching Emails
|
94
|
+
```bash
|
95
|
+
# Basic search
|
96
|
+
mcpz gmail search "quarterly report"
|
97
|
+
|
98
|
+
# Advanced Gmail search syntax
|
99
|
+
mcpz gmail search "from:boss@company.com subject:urgent"
|
100
|
+
mcpz gmail search "has:attachment filename:pdf"
|
101
|
+
mcpz gmail search "is:unread after:2024/01/01"
|
102
|
+
```
|
103
|
+
|
104
|
+
### Reading Emails
|
105
|
+
```bash
|
106
|
+
# Read a specific email by ID
|
107
|
+
mcpz gmail read 18c8b5d4e8f9a2b6
|
108
|
+
```
|
109
|
+
|
110
|
+
### Sending Emails
|
111
|
+
```bash
|
112
|
+
# Send a basic email
|
113
|
+
mcpz gmail send \
|
114
|
+
--to "recipient@example.com" \
|
115
|
+
--subject "Hello from MCPEasy" \
|
116
|
+
--body "This is a test email sent via Gmail API."
|
117
|
+
|
118
|
+
# Send with CC and BCC
|
119
|
+
mcpz gmail send \
|
120
|
+
--to "recipient@example.com" \
|
121
|
+
--cc "cc@example.com" \
|
122
|
+
--bcc "bcc@example.com" \
|
123
|
+
--subject "Team Update" \
|
124
|
+
--body "Weekly team update..." \
|
125
|
+
--reply_to "noreply@example.com"
|
126
|
+
```
|
127
|
+
|
128
|
+
### Replying to Emails
|
129
|
+
```bash
|
130
|
+
# Reply to an email
|
131
|
+
mcpz gmail reply 18c8b5d4e8f9a2b6 \
|
132
|
+
--body "Thank you for your message."
|
133
|
+
|
134
|
+
# Reply without including quoted original message
|
135
|
+
mcpz gmail reply 18c8b5d4e8f9a2b6 \
|
136
|
+
--body "Thank you for your message." \
|
137
|
+
--include_quoted false
|
138
|
+
```
|
139
|
+
|
140
|
+
### Email Management
|
141
|
+
```bash
|
142
|
+
# Mark as read/unread
|
143
|
+
mcpz gmail mark_read 18c8b5d4e8f9a2b6
|
144
|
+
mcpz gmail mark_unread 18c8b5d4e8f9a2b6
|
145
|
+
|
146
|
+
# Add/remove labels
|
147
|
+
mcpz gmail add_label 18c8b5d4e8f9a2b6 "important"
|
148
|
+
mcpz gmail remove_label 18c8b5d4e8f9a2b6 "important"
|
149
|
+
|
150
|
+
# Archive email (remove from inbox)
|
151
|
+
mcpz gmail archive 18c8b5d4e8f9a2b6
|
152
|
+
|
153
|
+
# Move to trash
|
154
|
+
mcpz gmail trash 18c8b5d4e8f9a2b6
|
155
|
+
```
|
156
|
+
|
157
|
+
## MCP Server Usage
|
158
|
+
|
159
|
+
### Running the Server
|
160
|
+
|
161
|
+
```bash
|
162
|
+
# Start the Gmail MCP server
|
163
|
+
mcpz gmail mcp
|
164
|
+
```
|
165
|
+
|
166
|
+
### MCP Configuration
|
167
|
+
|
168
|
+
Add to your `.mcp.json` configuration:
|
169
|
+
|
170
|
+
```json
|
171
|
+
{
|
172
|
+
"mcpServers": {
|
173
|
+
"gmail": {
|
174
|
+
"command": "mcpz",
|
175
|
+
"args": ["gmail", "mcp"]
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
```
|
180
|
+
|
181
|
+
### Available MCP Tools
|
182
|
+
|
183
|
+
- `test_connection` - Test Gmail API connectivity
|
184
|
+
- `list_emails` - List emails with filtering options
|
185
|
+
- `search_emails` - Search emails using Gmail syntax
|
186
|
+
- `get_email_content` - Get full email content including attachments
|
187
|
+
- `send_email` - Send new emails
|
188
|
+
- `reply_to_email` - Reply to existing emails
|
189
|
+
- `mark_as_read` / `mark_as_unread` - Change read status
|
190
|
+
- `add_label` / `remove_label` - Manage email labels
|
191
|
+
- `archive_email` - Archive emails
|
192
|
+
- `trash_email` - Move emails to trash
|
193
|
+
|
194
|
+
### Available MCP Prompts
|
195
|
+
|
196
|
+
- `check_email` - Check inbox for new messages
|
197
|
+
- `compose_email` - Compose and send emails
|
198
|
+
- `email_search` - Search through emails
|
199
|
+
- `email_management` - Manage emails (read/unread, archive, etc.)
|
200
|
+
|
201
|
+
## Gmail Search Syntax
|
202
|
+
|
203
|
+
The Gmail MCP server supports Gmail's advanced search operators:
|
204
|
+
|
205
|
+
- `from:sender@example.com` - From specific sender
|
206
|
+
- `to:recipient@example.com` - To specific recipient
|
207
|
+
- `subject:keyword` - Subject contains keyword
|
208
|
+
- `has:attachment` - Has attachments
|
209
|
+
- `filename:pdf` - Attachment filename contains "pdf"
|
210
|
+
- `is:unread` / `is:read` - Read status
|
211
|
+
- `is:important` / `is:starred` - Importance/starred
|
212
|
+
- `label:labelname` - Has specific label
|
213
|
+
- `after:2024/01/01` / `before:2024/12/31` - Date ranges
|
214
|
+
- `newer_than:7d` / `older_than:1m` - Relative dates
|
215
|
+
|
216
|
+
## API Scopes
|
217
|
+
|
218
|
+
This MCP server requires the following Gmail API scopes:
|
219
|
+
|
220
|
+
- `https://www.googleapis.com/auth/gmail.readonly` - Read access to Gmail
|
221
|
+
- `https://www.googleapis.com/auth/gmail.send` - Send emails
|
222
|
+
- `https://www.googleapis.com/auth/gmail.modify` - Modify email labels and status
|
223
|
+
|
224
|
+
## Security Notes
|
225
|
+
|
226
|
+
- **OAuth tokens are stored locally** in `~/.config/mcpeasy/`
|
227
|
+
- **Tokens are automatically refreshed** when they expire
|
228
|
+
- **Only your authenticated user** can access emails through this server
|
229
|
+
- **No emails are stored** by the MCP server - all data comes directly from Gmail
|
230
|
+
|
231
|
+
## Troubleshooting
|
232
|
+
|
233
|
+
### Authentication Issues
|
234
|
+
|
235
|
+
```bash
|
236
|
+
# Re-authenticate if you see authentication errors
|
237
|
+
mcpz gmail auth
|
238
|
+
|
239
|
+
# Check configuration status
|
240
|
+
mcpz config
|
241
|
+
```
|
242
|
+
|
243
|
+
### API Quota Errors
|
244
|
+
|
245
|
+
Gmail API has usage quotas. If you hit rate limits:
|
246
|
+
- Reduce the number of requests
|
247
|
+
- Add delays between operations
|
248
|
+
- Check your Google Cloud Console quota usage
|
249
|
+
|
250
|
+
### Common Error Messages
|
251
|
+
|
252
|
+
- **"Gmail authentication required"** - Run `mcpz gmail auth`
|
253
|
+
- **"Google API credentials not configured"** - Run `mcpz set_google_credentials <path>`
|
254
|
+
- **"Gmail API Error: Insufficient Permission"** - Re-run authentication to grant necessary scopes
|
255
|
+
|
256
|
+
## Development
|
257
|
+
|
258
|
+
The Gmail MCP server follows the same patterns as other MCPEasy services:
|
259
|
+
|
260
|
+
- `service.rb` - Core Gmail API functionality
|
261
|
+
- `cli.rb` - Thor-based CLI commands
|
262
|
+
- `mcp.rb` - MCP server implementation
|
263
|
+
- `README.md` - Documentation
|
264
|
+
|
265
|
+
For development setup:
|
266
|
+
|
267
|
+
```bash
|
268
|
+
# Clone the repository
|
269
|
+
git clone https://github.com/your-repo/mcpeasy.git
|
270
|
+
cd mcpeasy
|
271
|
+
|
272
|
+
# Install dependencies
|
273
|
+
bundle install
|
274
|
+
|
275
|
+
# Build and install locally
|
276
|
+
gem build mcpeasy.gemspec
|
277
|
+
gem install mcpeasy-*.gem
|
278
|
+
```
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "thor"
|
5
|
+
require_relative "service"
|
6
|
+
|
7
|
+
module Gmail
|
8
|
+
class CLI < Thor
|
9
|
+
desc "test", "Test the Gmail API connection"
|
10
|
+
def test
|
11
|
+
response = tool.test_connection
|
12
|
+
|
13
|
+
if response[:ok]
|
14
|
+
puts "✅ Successfully connected to Gmail"
|
15
|
+
puts " Email: #{response[:email]}"
|
16
|
+
puts " Messages: #{response[:messages_total]}"
|
17
|
+
puts " Threads: #{response[:threads_total]}"
|
18
|
+
else
|
19
|
+
warn "❌ Connection test failed"
|
20
|
+
end
|
21
|
+
rescue RuntimeError => e
|
22
|
+
puts "❌ Failed to connect to Gmail: #{e.message}\n\n#{e.backtrace.join("\n")}"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "list", "List recent emails"
|
27
|
+
method_option :start_date, type: :string, aliases: "-s", desc: "Start date (YYYY-MM-DD)"
|
28
|
+
method_option :end_date, type: :string, aliases: "-e", desc: "End date (YYYY-MM-DD)"
|
29
|
+
method_option :max_results, type: :numeric, default: 20, aliases: "-n", desc: "Max number of emails"
|
30
|
+
method_option :sender, type: :string, aliases: "-f", desc: "Filter by sender email"
|
31
|
+
method_option :subject, type: :string, aliases: "-j", desc: "Filter by subject"
|
32
|
+
method_option :labels, type: :string, aliases: "-l", desc: "Filter by labels (comma-separated)"
|
33
|
+
method_option :read_status, type: :string, aliases: "-r", desc: "Filter by read status (read/unread)"
|
34
|
+
def list
|
35
|
+
labels = options[:labels]&.split(",")&.map(&:strip)
|
36
|
+
|
37
|
+
result = tool.list_emails(
|
38
|
+
start_date: options[:start_date],
|
39
|
+
end_date: options[:end_date],
|
40
|
+
max_results: options[:max_results],
|
41
|
+
sender: options[:sender],
|
42
|
+
subject: options[:subject],
|
43
|
+
labels: labels,
|
44
|
+
read_status: options[:read_status]
|
45
|
+
)
|
46
|
+
emails = result[:emails]
|
47
|
+
|
48
|
+
if emails.empty?
|
49
|
+
puts "📧 No emails found for the specified criteria"
|
50
|
+
else
|
51
|
+
puts "📧 Found #{result[:count]} email(s):"
|
52
|
+
emails.each_with_index do |email, index|
|
53
|
+
puts " #{index + 1}. #{email[:subject] || "No subject"}"
|
54
|
+
puts " From: #{email[:from]}"
|
55
|
+
puts " Date: #{email[:date]}"
|
56
|
+
puts " Snippet: #{email[:snippet]}" if email[:snippet]
|
57
|
+
puts " Labels: #{email[:labels].join(", ")}" if email[:labels]&.any?
|
58
|
+
puts " ID: #{email[:id]}"
|
59
|
+
puts
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue RuntimeError => e
|
63
|
+
warn "❌ Failed to list emails: #{e.message}"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "search QUERY", "Search emails by text content"
|
68
|
+
method_option :max_results, type: :numeric, default: 10, aliases: "-n", desc: "Max number of emails"
|
69
|
+
def search(query)
|
70
|
+
result = tool.search_emails(
|
71
|
+
query,
|
72
|
+
max_results: options[:max_results]
|
73
|
+
)
|
74
|
+
emails = result[:emails]
|
75
|
+
|
76
|
+
if emails.empty?
|
77
|
+
puts "🔍 No emails found matching '#{query}'"
|
78
|
+
else
|
79
|
+
puts "🔍 Found #{result[:count]} email(s) matching '#{query}':"
|
80
|
+
emails.each_with_index do |email, index|
|
81
|
+
puts " #{index + 1}. #{email[:subject] || "No subject"}"
|
82
|
+
puts " From: #{email[:from]}"
|
83
|
+
puts " Date: #{email[:date]}"
|
84
|
+
puts " Snippet: #{email[:snippet]}" if email[:snippet]
|
85
|
+
puts " ID: #{email[:id]}"
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue RuntimeError => e
|
90
|
+
warn "❌ Failed to search emails: #{e.message}"
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "read EMAIL_ID", "Read a specific email"
|
95
|
+
def read(email_id)
|
96
|
+
email = tool.get_email_content(email_id)
|
97
|
+
|
98
|
+
puts "📧 Email Details:"
|
99
|
+
puts " ID: #{email[:id]}"
|
100
|
+
puts " Thread ID: #{email[:thread_id]}"
|
101
|
+
puts " Subject: #{email[:subject] || "No subject"}"
|
102
|
+
puts " From: #{email[:from]}"
|
103
|
+
puts " To: #{email[:to]}"
|
104
|
+
puts " CC: #{email[:cc]}" if email[:cc]
|
105
|
+
puts " BCC: #{email[:bcc]}" if email[:bcc]
|
106
|
+
puts " Date: #{email[:date]}"
|
107
|
+
puts " Labels: #{email[:labels].join(", ")}" if email[:labels]&.any?
|
108
|
+
|
109
|
+
if email[:attachments]&.any?
|
110
|
+
puts " Attachments:"
|
111
|
+
email[:attachments].each do |attachment|
|
112
|
+
puts " - #{attachment[:filename]} (#{attachment[:mime_type]}, #{attachment[:size]} bytes)"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
puts "\n📄 Body:"
|
117
|
+
puts email[:body]
|
118
|
+
rescue RuntimeError => e
|
119
|
+
warn "❌ Failed to read email: #{e.message}"
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "send", "Send a new email"
|
124
|
+
method_option :to, type: :string, required: true, aliases: "-t", desc: "Recipient email address"
|
125
|
+
method_option :subject, type: :string, required: true, aliases: "-s", desc: "Email subject"
|
126
|
+
method_option :body, type: :string, required: true, aliases: "-b", desc: "Email body"
|
127
|
+
method_option :cc, type: :string, aliases: "-c", desc: "CC email address"
|
128
|
+
method_option :bcc, type: :string, aliases: "-B", desc: "BCC email address"
|
129
|
+
method_option :reply_to, type: :string, aliases: "-r", desc: "Reply-to email address"
|
130
|
+
def send
|
131
|
+
result = tool.send_email(
|
132
|
+
to: options[:to],
|
133
|
+
subject: options[:subject],
|
134
|
+
body: options[:body],
|
135
|
+
cc: options[:cc],
|
136
|
+
bcc: options[:bcc],
|
137
|
+
reply_to: options[:reply_to]
|
138
|
+
)
|
139
|
+
|
140
|
+
if result[:success]
|
141
|
+
puts "✅ Email sent successfully"
|
142
|
+
puts " Message ID: #{result[:message_id]}"
|
143
|
+
puts " Thread ID: #{result[:thread_id]}"
|
144
|
+
else
|
145
|
+
puts "❌ Failed to send email"
|
146
|
+
end
|
147
|
+
rescue RuntimeError => e
|
148
|
+
warn "❌ Failed to send email: #{e.message}"
|
149
|
+
exit 1
|
150
|
+
end
|
151
|
+
|
152
|
+
desc "reply EMAIL_ID", "Reply to an email"
|
153
|
+
method_option :body, type: :string, required: true, aliases: "-b", desc: "Reply body"
|
154
|
+
method_option :include_quoted, type: :boolean, default: true, aliases: "-q", desc: "Include quoted original message"
|
155
|
+
def reply(email_id)
|
156
|
+
result = tool.reply_to_email(
|
157
|
+
email_id: email_id,
|
158
|
+
body: options[:body],
|
159
|
+
include_quoted: options[:include_quoted]
|
160
|
+
)
|
161
|
+
|
162
|
+
if result[:success]
|
163
|
+
puts "✅ Reply sent successfully"
|
164
|
+
puts " Message ID: #{result[:message_id]}"
|
165
|
+
puts " Thread ID: #{result[:thread_id]}"
|
166
|
+
else
|
167
|
+
puts "❌ Failed to send reply"
|
168
|
+
end
|
169
|
+
rescue RuntimeError => e
|
170
|
+
warn "❌ Failed to send reply: #{e.message}"
|
171
|
+
exit 1
|
172
|
+
end
|
173
|
+
|
174
|
+
desc "mark_read EMAIL_ID", "Mark an email as read"
|
175
|
+
def mark_read(email_id)
|
176
|
+
result = tool.mark_as_read(email_id)
|
177
|
+
|
178
|
+
if result[:success]
|
179
|
+
puts "✅ Email marked as read"
|
180
|
+
else
|
181
|
+
puts "❌ Failed to mark email as read"
|
182
|
+
end
|
183
|
+
rescue RuntimeError => e
|
184
|
+
warn "❌ Failed to mark email as read: #{e.message}"
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
|
188
|
+
desc "mark_unread EMAIL_ID", "Mark an email as unread"
|
189
|
+
def mark_unread(email_id)
|
190
|
+
result = tool.mark_as_unread(email_id)
|
191
|
+
|
192
|
+
if result[:success]
|
193
|
+
puts "✅ Email marked as unread"
|
194
|
+
else
|
195
|
+
puts "❌ Failed to mark email as unread"
|
196
|
+
end
|
197
|
+
rescue RuntimeError => e
|
198
|
+
warn "❌ Failed to mark email as unread: #{e.message}"
|
199
|
+
exit 1
|
200
|
+
end
|
201
|
+
|
202
|
+
desc "add_label EMAIL_ID LABEL", "Add a label to an email"
|
203
|
+
def add_label(email_id, label)
|
204
|
+
result = tool.add_label(email_id, label)
|
205
|
+
|
206
|
+
if result[:success]
|
207
|
+
puts "✅ Label '#{label}' added to email"
|
208
|
+
else
|
209
|
+
puts "❌ Failed to add label to email"
|
210
|
+
end
|
211
|
+
rescue RuntimeError => e
|
212
|
+
warn "❌ Failed to add label to email: #{e.message}"
|
213
|
+
exit 1
|
214
|
+
end
|
215
|
+
|
216
|
+
desc "remove_label EMAIL_ID LABEL", "Remove a label from an email"
|
217
|
+
def remove_label(email_id, label)
|
218
|
+
result = tool.remove_label(email_id, label)
|
219
|
+
|
220
|
+
if result[:success]
|
221
|
+
puts "✅ Label '#{label}' removed from email"
|
222
|
+
else
|
223
|
+
puts "❌ Failed to remove label from email"
|
224
|
+
end
|
225
|
+
rescue RuntimeError => e
|
226
|
+
warn "❌ Failed to remove label from email: #{e.message}"
|
227
|
+
exit 1
|
228
|
+
end
|
229
|
+
|
230
|
+
desc "archive EMAIL_ID", "Archive an email"
|
231
|
+
def archive(email_id)
|
232
|
+
result = tool.archive_email(email_id)
|
233
|
+
|
234
|
+
if result[:success]
|
235
|
+
puts "✅ Email archived"
|
236
|
+
else
|
237
|
+
puts "❌ Failed to archive email"
|
238
|
+
end
|
239
|
+
rescue RuntimeError => e
|
240
|
+
warn "❌ Failed to archive email: #{e.message}"
|
241
|
+
exit 1
|
242
|
+
end
|
243
|
+
|
244
|
+
desc "trash EMAIL_ID", "Move an email to trash"
|
245
|
+
def trash(email_id)
|
246
|
+
result = tool.trash_email(email_id)
|
247
|
+
|
248
|
+
if result[:success]
|
249
|
+
puts "✅ Email moved to trash"
|
250
|
+
else
|
251
|
+
puts "❌ Failed to move email to trash"
|
252
|
+
end
|
253
|
+
rescue RuntimeError => e
|
254
|
+
warn "❌ Failed to move email to trash: #{e.message}"
|
255
|
+
exit 1
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def tool
|
261
|
+
@tool ||= Service.new
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|