mcpeasy 0.1.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 +7 -0
- data/.claude/settings.json +15 -0
- data/.claudeignore +4 -0
- data/.envrc +2 -0
- data/.mcp.json +40 -0
- data/CLAUDE.md +170 -0
- data/README.md +161 -0
- data/bin/mcpz +6 -0
- data/env.template +11 -0
- data/ext/setup.rb +7 -0
- data/lib/mcpeasy/cli.rb +154 -0
- data/lib/mcpeasy/config.rb +102 -0
- data/lib/mcpeasy/setup.rb +22 -0
- data/lib/mcpeasy/version.rb +5 -0
- data/lib/mcpeasy.rb +9 -0
- data/lib/utilities/_google/auth_server.rb +149 -0
- data/lib/utilities/gcal/README.md +237 -0
- data/lib/utilities/gcal/cli.rb +134 -0
- data/lib/utilities/gcal/gcal_tool.rb +308 -0
- data/lib/utilities/gcal/mcp.rb +381 -0
- data/lib/utilities/gdrive/README.md +269 -0
- data/lib/utilities/gdrive/cli.rb +118 -0
- data/lib/utilities/gdrive/gdrive_tool.rb +291 -0
- data/lib/utilities/gdrive/mcp.rb +347 -0
- data/lib/utilities/gmeet/README.md +133 -0
- data/lib/utilities/gmeet/cli.rb +157 -0
- data/lib/utilities/gmeet/gmeet_tool.rb +407 -0
- data/lib/utilities/gmeet/mcp.rb +438 -0
- data/lib/utilities/slack/README.md +211 -0
- data/lib/utilities/slack/cli.rb +74 -0
- data/lib/utilities/slack/mcp.rb +280 -0
- data/lib/utilities/slack/slack_tool.rb +119 -0
- data/logs/.keep +0 -0
- data/mcpeasy.gemspec +47 -0
- metadata +191 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "json"
|
6
|
+
require_relative "gdrive_tool"
|
7
|
+
|
8
|
+
class MCPServer
|
9
|
+
def initialize
|
10
|
+
@tools = {
|
11
|
+
"test_connection" => {
|
12
|
+
name: "test_connection",
|
13
|
+
description: "Test the Google Drive API connection",
|
14
|
+
inputSchema: {
|
15
|
+
type: "object",
|
16
|
+
properties: {},
|
17
|
+
required: []
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"search_files" => {
|
21
|
+
name: "search_files",
|
22
|
+
description: "Search for files in Google Drive by content or name",
|
23
|
+
inputSchema: {
|
24
|
+
type: "object",
|
25
|
+
properties: {
|
26
|
+
query: {
|
27
|
+
type: "string",
|
28
|
+
description: "Search query to find files"
|
29
|
+
},
|
30
|
+
max_results: {
|
31
|
+
type: "number",
|
32
|
+
description: "Maximum number of results to return (default: 10)"
|
33
|
+
}
|
34
|
+
},
|
35
|
+
required: ["query"]
|
36
|
+
}
|
37
|
+
},
|
38
|
+
"get_file_content" => {
|
39
|
+
name: "get_file_content",
|
40
|
+
description: "Get the content of a specific Google Drive file",
|
41
|
+
inputSchema: {
|
42
|
+
type: "object",
|
43
|
+
properties: {
|
44
|
+
file_id: {
|
45
|
+
type: "string",
|
46
|
+
description: "The Google Drive file ID"
|
47
|
+
}
|
48
|
+
},
|
49
|
+
required: ["file_id"]
|
50
|
+
}
|
51
|
+
},
|
52
|
+
"list_files" => {
|
53
|
+
name: "list_files",
|
54
|
+
description: "List recent files in Google Drive",
|
55
|
+
inputSchema: {
|
56
|
+
type: "object",
|
57
|
+
properties: {
|
58
|
+
max_results: {
|
59
|
+
type: "number",
|
60
|
+
description: "Maximum number of files to return (default: 20)"
|
61
|
+
}
|
62
|
+
},
|
63
|
+
required: []
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def run
|
70
|
+
# Disable stdout buffering for immediate response
|
71
|
+
$stdout.sync = true
|
72
|
+
|
73
|
+
# Log startup to file instead of stdout to avoid protocol interference
|
74
|
+
Mcpeasy::Config.ensure_config_dirs
|
75
|
+
File.write(Mcpeasy::Config.log_file_path("gdrive", "startup"), "#{Time.now}: Google Drive MCP Server starting on stdio\n", mode: "a")
|
76
|
+
while (line = $stdin.gets)
|
77
|
+
handle_request(line.strip)
|
78
|
+
end
|
79
|
+
rescue Interrupt
|
80
|
+
# Silent shutdown
|
81
|
+
rescue => e
|
82
|
+
# Log to a file instead of stderr to avoid protocol interference
|
83
|
+
File.write(Mcpeasy::Config.log_file_path("gdrive", "error"), "#{Time.now}: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def handle_request(line)
|
89
|
+
return if line.empty?
|
90
|
+
|
91
|
+
begin
|
92
|
+
request = JSON.parse(line)
|
93
|
+
response = process_request(request)
|
94
|
+
if response
|
95
|
+
puts JSON.generate(response)
|
96
|
+
$stdout.flush
|
97
|
+
end
|
98
|
+
rescue JSON::ParserError => e
|
99
|
+
error_response = {
|
100
|
+
jsonrpc: "2.0",
|
101
|
+
id: nil,
|
102
|
+
error: {
|
103
|
+
code: -32700,
|
104
|
+
message: "Parse error",
|
105
|
+
data: e.message
|
106
|
+
}
|
107
|
+
}
|
108
|
+
puts JSON.generate(error_response)
|
109
|
+
$stdout.flush
|
110
|
+
rescue => e
|
111
|
+
File.write(Mcpeasy::Config.log_file_path("gdrive", "error"), "#{Time.now}: Error handling request: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
112
|
+
error_response = {
|
113
|
+
jsonrpc: "2.0",
|
114
|
+
id: request&.dig("id"),
|
115
|
+
error: {
|
116
|
+
code: -32603,
|
117
|
+
message: "Internal error",
|
118
|
+
data: e.message
|
119
|
+
}
|
120
|
+
}
|
121
|
+
puts JSON.generate(error_response)
|
122
|
+
$stdout.flush
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def process_request(request)
|
127
|
+
id = request["id"]
|
128
|
+
method = request["method"]
|
129
|
+
params = request["params"] || {}
|
130
|
+
|
131
|
+
case method
|
132
|
+
when "notifications/initialized"
|
133
|
+
# Client acknowledgment - no response needed
|
134
|
+
nil
|
135
|
+
when "initialize"
|
136
|
+
initialize_response(id, params)
|
137
|
+
when "tools/list"
|
138
|
+
tools_list_response(id, params)
|
139
|
+
when "tools/call"
|
140
|
+
tools_call_response(id, params)
|
141
|
+
else
|
142
|
+
{
|
143
|
+
jsonrpc: "2.0",
|
144
|
+
id: id,
|
145
|
+
error: {
|
146
|
+
code: -32601,
|
147
|
+
message: "Method not found",
|
148
|
+
data: "Unknown method: #{method}"
|
149
|
+
}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize_response(id, params)
|
155
|
+
{
|
156
|
+
jsonrpc: "2.0",
|
157
|
+
id: id,
|
158
|
+
result: {
|
159
|
+
protocolVersion: "2024-11-05",
|
160
|
+
capabilities: {
|
161
|
+
tools: {}
|
162
|
+
},
|
163
|
+
serverInfo: {
|
164
|
+
name: "gdrive-mcp-server",
|
165
|
+
version: "1.0.0"
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
def tools_list_response(id, params)
|
172
|
+
{
|
173
|
+
jsonrpc: "2.0",
|
174
|
+
id: id,
|
175
|
+
result: {
|
176
|
+
tools: @tools.values
|
177
|
+
}
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
def tools_call_response(id, params)
|
182
|
+
tool_name = params["name"]
|
183
|
+
arguments = params["arguments"] || {}
|
184
|
+
|
185
|
+
unless @tools.key?(tool_name)
|
186
|
+
return {
|
187
|
+
jsonrpc: "2.0",
|
188
|
+
id: id,
|
189
|
+
error: {
|
190
|
+
code: -32602,
|
191
|
+
message: "Unknown tool",
|
192
|
+
data: "Tool '#{tool_name}' not found"
|
193
|
+
}
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
begin
|
198
|
+
result = call_tool(tool_name, arguments)
|
199
|
+
{
|
200
|
+
jsonrpc: "2.0",
|
201
|
+
id: id,
|
202
|
+
result: {
|
203
|
+
content: [
|
204
|
+
{
|
205
|
+
type: "text",
|
206
|
+
text: result
|
207
|
+
}
|
208
|
+
],
|
209
|
+
isError: false
|
210
|
+
}
|
211
|
+
}
|
212
|
+
rescue => e
|
213
|
+
File.write(Mcpeasy::Config.log_file_path("gdrive", "error"), "#{Time.now}: Tool error: #{e.message}\n#{e.backtrace.join("\n")}\n", mode: "a")
|
214
|
+
{
|
215
|
+
jsonrpc: "2.0",
|
216
|
+
id: id,
|
217
|
+
result: {
|
218
|
+
content: [
|
219
|
+
{
|
220
|
+
type: "text",
|
221
|
+
text: "❌ Error: #{e.message}"
|
222
|
+
}
|
223
|
+
],
|
224
|
+
isError: true
|
225
|
+
}
|
226
|
+
}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def call_tool(tool_name, arguments)
|
231
|
+
# Initialize GdriveTool only when needed
|
232
|
+
@gdrive_tool ||= GdriveTool.new
|
233
|
+
|
234
|
+
case tool_name
|
235
|
+
when "test_connection"
|
236
|
+
test_connection
|
237
|
+
when "search_files"
|
238
|
+
search_files(arguments)
|
239
|
+
when "get_file_content"
|
240
|
+
get_file_content(arguments)
|
241
|
+
when "list_files"
|
242
|
+
list_files(arguments)
|
243
|
+
else
|
244
|
+
raise "Unknown tool: #{tool_name}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_connection
|
249
|
+
tool = GdriveTool.new
|
250
|
+
response = tool.test_connection
|
251
|
+
if response[:ok]
|
252
|
+
"✅ Successfully connected to Google Drive.\n" \
|
253
|
+
" User: #{response[:user]} (#{response[:email]})\n" \
|
254
|
+
" Storage: #{format_bytes(response[:storage_used])} / #{format_bytes(response[:storage_limit])}"
|
255
|
+
else
|
256
|
+
raise "Connection test failed"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def search_files(arguments)
|
261
|
+
unless arguments["query"]
|
262
|
+
raise "Missing required argument: query"
|
263
|
+
end
|
264
|
+
|
265
|
+
query = arguments["query"].to_s
|
266
|
+
max_results = arguments["max_results"]&.to_i || 10
|
267
|
+
|
268
|
+
tool = GdriveTool.new
|
269
|
+
result = tool.search_files(query, max_results: max_results)
|
270
|
+
files = result[:files]
|
271
|
+
|
272
|
+
if files.empty?
|
273
|
+
"🔍 No files found matching '#{query}'"
|
274
|
+
else
|
275
|
+
output = "🔍 Found #{result[:count]} file(s) matching '#{query}':\n\n"
|
276
|
+
files.each_with_index do |file, index|
|
277
|
+
output << "#{index + 1}. **#{file[:name]}**\n"
|
278
|
+
output << " - ID: `#{file[:id]}`\n"
|
279
|
+
output << " - Type: #{file[:mime_type]}\n"
|
280
|
+
output << " - Size: #{format_bytes(file[:size])}\n"
|
281
|
+
output << " - Modified: #{file[:modified_time]}\n"
|
282
|
+
output << " - Link: #{file[:web_view_link]}\n\n"
|
283
|
+
end
|
284
|
+
output
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def get_file_content(arguments)
|
289
|
+
unless arguments["file_id"]
|
290
|
+
raise "Missing required argument: file_id"
|
291
|
+
end
|
292
|
+
|
293
|
+
file_id = arguments["file_id"].to_s
|
294
|
+
tool = GdriveTool.new
|
295
|
+
result = tool.get_file_content(file_id)
|
296
|
+
|
297
|
+
output = "📄 **#{result[:name]}**\n"
|
298
|
+
output << " - Type: #{result[:mime_type]}\n"
|
299
|
+
output << " - Size: #{format_bytes(result[:size])}\n\n"
|
300
|
+
output << "**Content:**\n"
|
301
|
+
output << "```\n#{result[:content]}\n```"
|
302
|
+
output
|
303
|
+
end
|
304
|
+
|
305
|
+
def list_files(arguments)
|
306
|
+
max_results = arguments["max_results"]&.to_i || 20
|
307
|
+
tool = GdriveTool.new
|
308
|
+
result = tool.list_files(max_results: max_results)
|
309
|
+
files = result[:files]
|
310
|
+
|
311
|
+
if files.empty?
|
312
|
+
"📂 No files found in Google Drive"
|
313
|
+
else
|
314
|
+
output = "📂 Recent #{result[:count]} file(s):\n\n"
|
315
|
+
files.each_with_index do |file, index|
|
316
|
+
output << "#{index + 1}. **#{file[:name]}**\n"
|
317
|
+
output << " - ID: `#{file[:id]}`\n"
|
318
|
+
output << " - Type: #{file[:mime_type]}\n"
|
319
|
+
output << " - Size: #{format_bytes(file[:size])}\n"
|
320
|
+
output << " - Modified: #{file[:modified_time]}\n"
|
321
|
+
output << " - Link: #{file[:web_view_link]}\n\n"
|
322
|
+
end
|
323
|
+
output
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
|
329
|
+
def format_bytes(bytes)
|
330
|
+
return "Unknown" unless bytes
|
331
|
+
|
332
|
+
units = %w[B KB MB GB TB]
|
333
|
+
size = bytes.to_f
|
334
|
+
unit_index = 0
|
335
|
+
|
336
|
+
while size >= 1024 && unit_index < units.length - 1
|
337
|
+
size /= 1024
|
338
|
+
unit_index += 1
|
339
|
+
end
|
340
|
+
|
341
|
+
"#{size.round(1)} #{units[unit_index]}"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
if __FILE__ == $0
|
346
|
+
MCPServer.new.run
|
347
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Google Meet MCP Server
|
2
|
+
|
3
|
+
A Model Context Protocol (MCP) server for Google Meet integration. This tool allows you to list, search, and get direct links to Google Meet meetings from your Google Calendar.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **List Google Meet meetings** with date filtering
|
8
|
+
- **Search for meetings** by title or description
|
9
|
+
- **Get upcoming meetings** in the next 24 hours
|
10
|
+
- **Extract Google Meet URLs** for direct browser access
|
11
|
+
- **CLI and MCP server modes** for flexible usage
|
12
|
+
|
13
|
+
## Setup
|
14
|
+
|
15
|
+
### 1. Google API Credentials
|
16
|
+
|
17
|
+
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
|
18
|
+
2. Create a new project or select an existing one
|
19
|
+
3. Enable the Google Calendar API
|
20
|
+
4. Create credentials (OAuth 2.0 Client ID) for a "Desktop application"
|
21
|
+
5. Download the credentials and note the Client ID and Client Secret
|
22
|
+
|
23
|
+
### 2. OAuth Credentials Setup
|
24
|
+
|
25
|
+
The Google OAuth credentials will be configured during the authentication process. You'll need the Client ID and Client Secret from your Google Cloud Console setup.
|
26
|
+
|
27
|
+
### 3. Install Dependencies
|
28
|
+
|
29
|
+
```bash
|
30
|
+
bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
### 4. Authentication
|
34
|
+
|
35
|
+
Run the authentication flow to get access to your Google Calendar:
|
36
|
+
|
37
|
+
```bash
|
38
|
+
mcpz google auth
|
39
|
+
```
|
40
|
+
|
41
|
+
This will open a browser for Google OAuth authorization and save credentials to `~/.config/mcpeasy/google/token.json`. The credentials are shared with all Google services.
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
### CLI Mode
|
46
|
+
|
47
|
+
**Test connection:**
|
48
|
+
```bash
|
49
|
+
mcpz gmeet test
|
50
|
+
```
|
51
|
+
|
52
|
+
**List Google Meet meetings:**
|
53
|
+
```bash
|
54
|
+
mcpz gmeet meetings
|
55
|
+
mcpz gmeet meetings --start_date 2024-01-01 --end_date 2024-01-07
|
56
|
+
mcpz gmeet meetings --max_results 10
|
57
|
+
```
|
58
|
+
|
59
|
+
**List upcoming meetings:**
|
60
|
+
```bash
|
61
|
+
mcpz gmeet upcoming
|
62
|
+
mcpz gmeet upcoming --max_results 5
|
63
|
+
```
|
64
|
+
|
65
|
+
**Search for meetings:**
|
66
|
+
```bash
|
67
|
+
mcpz gmeet search "standup"
|
68
|
+
mcpz gmeet search "team meeting" --start_date 2024-01-01
|
69
|
+
```
|
70
|
+
|
71
|
+
**Get meeting URL by event ID:**
|
72
|
+
```bash
|
73
|
+
mcpz gmeet url event_id_here
|
74
|
+
```
|
75
|
+
|
76
|
+
### MCP Server Mode
|
77
|
+
|
78
|
+
Configure in your `.mcp.json`:
|
79
|
+
|
80
|
+
```json
|
81
|
+
{
|
82
|
+
"mcpServers": {
|
83
|
+
"gmeet": {
|
84
|
+
"command": "mcpz",
|
85
|
+
"args": ["gmeet", "mcp"]
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
```
|
90
|
+
|
91
|
+
Available MCP tools:
|
92
|
+
- `test_connection` - Test Google Calendar API connection
|
93
|
+
- `list_meetings` - List Google Meet meetings with date filtering
|
94
|
+
- `upcoming_meetings` - List upcoming meetings in next 24 hours
|
95
|
+
- `search_meetings` - Search meetings by text content
|
96
|
+
- `get_meeting_url` - Get Google Meet URL for specific event
|
97
|
+
|
98
|
+
## How It Works
|
99
|
+
|
100
|
+
The tool uses the Google Calendar API to:
|
101
|
+
|
102
|
+
1. **Fetch calendar events** from your Google Calendar
|
103
|
+
2. **Filter for Google Meet meetings** by detecting:
|
104
|
+
- Conference data with Google Meet
|
105
|
+
- Hangout links (legacy)
|
106
|
+
- Meet.google.com URLs in descriptions
|
107
|
+
- Meet.google.com URLs in location fields
|
108
|
+
3. **Extract meeting URLs** for direct browser access
|
109
|
+
4. **Format and present** meeting information
|
110
|
+
|
111
|
+
## Troubleshooting
|
112
|
+
|
113
|
+
**Authentication Issues:**
|
114
|
+
- Re-run the auth flow: `mcpz google auth`
|
115
|
+
- Check that the Google Calendar API is enabled in your Google Cloud project
|
116
|
+
- Verify your Google OAuth credentials are configured correctly
|
117
|
+
|
118
|
+
**No meetings found:**
|
119
|
+
- Verify you have Google Meet meetings in your calendar
|
120
|
+
- Check the date range (default is 7 days from today)
|
121
|
+
- Ensure meetings have Google Meet links attached
|
122
|
+
|
123
|
+
**MCP Server Issues:**
|
124
|
+
- Check logs in `./logs/mcp_gmeet_error.log`
|
125
|
+
- Verify the server path in `.mcp.json` is correct
|
126
|
+
- Ensure all dependencies are installed with `bundle install`
|
127
|
+
|
128
|
+
## API Permissions
|
129
|
+
|
130
|
+
This tool requires:
|
131
|
+
- `https://www.googleapis.com/auth/calendar.readonly` - Read access to your Google Calendar
|
132
|
+
|
133
|
+
The tool only reads calendar data and never modifies your calendar or meetings.
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "thor"
|
5
|
+
require_relative "gmeet_tool"
|
6
|
+
|
7
|
+
class GmeetCLI < Thor
|
8
|
+
desc "test", "Test the Google Calendar API connection"
|
9
|
+
def test
|
10
|
+
response = tool.test_connection
|
11
|
+
|
12
|
+
if response[:ok]
|
13
|
+
puts "✅ Successfully connected to Google Calendar"
|
14
|
+
puts " User: #{response[:user]} (#{response[:email]})"
|
15
|
+
else
|
16
|
+
warn "❌ Connection test failed"
|
17
|
+
end
|
18
|
+
rescue RuntimeError => e
|
19
|
+
puts "❌ Failed to connect to Google Calendar: #{e.message}"
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "meetings", "List Google Meet meetings"
|
24
|
+
method_option :start_date, type: :string, aliases: "-s", desc: "Start date (YYYY-MM-DD)"
|
25
|
+
method_option :end_date, type: :string, aliases: "-e", desc: "End date (YYYY-MM-DD)"
|
26
|
+
method_option :max_results, type: :numeric, default: 20, aliases: "-n", desc: "Max number of meetings"
|
27
|
+
method_option :calendar_id, type: :string, aliases: "-c", desc: "Calendar ID (default: primary)"
|
28
|
+
def meetings
|
29
|
+
result = tool.list_meetings(
|
30
|
+
start_date: options[:start_date],
|
31
|
+
end_date: options[:end_date],
|
32
|
+
max_results: options[:max_results],
|
33
|
+
calendar_id: options[:calendar_id] || "primary"
|
34
|
+
)
|
35
|
+
meetings = result[:meetings]
|
36
|
+
|
37
|
+
if meetings.empty?
|
38
|
+
puts "🎥 No Google Meet meetings found for the specified date range"
|
39
|
+
else
|
40
|
+
puts "🎥 Found #{result[:count]} Google Meet meeting(s):"
|
41
|
+
meetings.each_with_index do |meeting, index|
|
42
|
+
puts " #{index + 1}. #{meeting[:summary] || "No title"}"
|
43
|
+
puts " Start: #{format_datetime(meeting[:start])}"
|
44
|
+
puts " End: #{format_datetime(meeting[:end])}"
|
45
|
+
puts " Description: #{meeting[:description]}" if meeting[:description]
|
46
|
+
puts " Location: #{meeting[:location]}" if meeting[:location]
|
47
|
+
puts " Attendees: #{meeting[:attendees].join(", ")}" if meeting[:attendees]&.any?
|
48
|
+
puts " Meet Link: #{meeting[:meet_link]}"
|
49
|
+
puts " Calendar Link: #{meeting[:html_link]}"
|
50
|
+
puts
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue RuntimeError => e
|
54
|
+
warn "❌ Failed to list meetings: #{e.message}"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "upcoming", "List upcoming Google Meet meetings"
|
59
|
+
method_option :max_results, type: :numeric, default: 10, aliases: "-n", desc: "Max number of meetings"
|
60
|
+
method_option :calendar_id, type: :string, aliases: "-c", desc: "Calendar ID (default: primary)"
|
61
|
+
def upcoming
|
62
|
+
result = tool.upcoming_meetings(
|
63
|
+
max_results: options[:max_results],
|
64
|
+
calendar_id: options[:calendar_id] || "primary"
|
65
|
+
)
|
66
|
+
meetings = result[:meetings]
|
67
|
+
|
68
|
+
if meetings.empty?
|
69
|
+
puts "🎥 No upcoming Google Meet meetings found in the next 24 hours"
|
70
|
+
else
|
71
|
+
puts "🎥 Found #{result[:count]} upcoming Google Meet meeting(s):"
|
72
|
+
meetings.each_with_index do |meeting, index|
|
73
|
+
puts " #{index + 1}. #{meeting[:summary] || "No title"}"
|
74
|
+
puts " Start: #{format_datetime(meeting[:start])} (#{meeting[:time_until_start]})"
|
75
|
+
puts " End: #{format_datetime(meeting[:end])}"
|
76
|
+
puts " Description: #{meeting[:description]}" if meeting[:description]
|
77
|
+
puts " Location: #{meeting[:location]}" if meeting[:location]
|
78
|
+
puts " Attendees: #{meeting[:attendees].join(", ")}" if meeting[:attendees]&.any?
|
79
|
+
puts " Meet Link: #{meeting[:meet_link]}"
|
80
|
+
puts " Calendar Link: #{meeting[:html_link]}"
|
81
|
+
puts
|
82
|
+
end
|
83
|
+
end
|
84
|
+
rescue RuntimeError => e
|
85
|
+
warn "❌ Failed to list upcoming meetings: #{e.message}"
|
86
|
+
exit 1
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "search QUERY", "Search for Google Meet meetings by text content"
|
90
|
+
method_option :start_date, type: :string, aliases: "-s", desc: "Start date (YYYY-MM-DD)"
|
91
|
+
method_option :end_date, type: :string, aliases: "-e", desc: "End date (YYYY-MM-DD)"
|
92
|
+
method_option :max_results, type: :numeric, default: 10, aliases: "-n", desc: "Max number of meetings"
|
93
|
+
def search(query)
|
94
|
+
result = tool.search_meetings(
|
95
|
+
query,
|
96
|
+
start_date: options[:start_date],
|
97
|
+
end_date: options[:end_date],
|
98
|
+
max_results: options[:max_results]
|
99
|
+
)
|
100
|
+
meetings = result[:meetings]
|
101
|
+
|
102
|
+
if meetings.empty?
|
103
|
+
puts "🔍 No Google Meet meetings found matching '#{query}'"
|
104
|
+
else
|
105
|
+
puts "🔍 Found #{result[:count]} Google Meet meeting(s) matching '#{query}':"
|
106
|
+
meetings.each_with_index do |meeting, index|
|
107
|
+
puts " #{index + 1}. #{meeting[:summary] || "No title"}"
|
108
|
+
puts " Start: #{format_datetime(meeting[:start])}"
|
109
|
+
puts " End: #{format_datetime(meeting[:end])}"
|
110
|
+
puts " Description: #{meeting[:description]}" if meeting[:description]
|
111
|
+
puts " Location: #{meeting[:location]}" if meeting[:location]
|
112
|
+
puts " Meet Link: #{meeting[:meet_link]}"
|
113
|
+
puts " Calendar Link: #{meeting[:html_link]}"
|
114
|
+
puts
|
115
|
+
end
|
116
|
+
end
|
117
|
+
rescue RuntimeError => e
|
118
|
+
warn "❌ Failed to search meetings: #{e.message}"
|
119
|
+
exit 1
|
120
|
+
end
|
121
|
+
|
122
|
+
desc "url EVENT_ID", "Get the Google Meet URL for a specific event"
|
123
|
+
method_option :calendar_id, type: :string, aliases: "-c", desc: "Calendar ID (default: primary)"
|
124
|
+
def url(event_id)
|
125
|
+
result = tool.get_meeting_url(event_id, calendar_id: options[:calendar_id] || "primary")
|
126
|
+
|
127
|
+
puts "🎥 #{result[:summary] || "Meeting"}"
|
128
|
+
puts " Start: #{format_datetime(result[:start])}"
|
129
|
+
puts " End: #{format_datetime(result[:end])}"
|
130
|
+
puts " Meet Link: #{result[:meet_link]}"
|
131
|
+
puts " Event ID: #{result[:event_id]}"
|
132
|
+
rescue RuntimeError => e
|
133
|
+
warn "❌ Failed to get meeting URL: #{e.message}"
|
134
|
+
exit 1
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def tool
|
140
|
+
@tool ||= GmeetTool.new
|
141
|
+
end
|
142
|
+
|
143
|
+
def format_datetime(datetime_info)
|
144
|
+
return "Unknown" unless datetime_info
|
145
|
+
|
146
|
+
if datetime_info[:date]
|
147
|
+
# All-day event
|
148
|
+
datetime_info[:date]
|
149
|
+
elsif datetime_info[:date_time]
|
150
|
+
# Specific time event
|
151
|
+
time = Time.parse(datetime_info[:date_time])
|
152
|
+
time.strftime("%Y-%m-%d %H:%M")
|
153
|
+
else
|
154
|
+
"Unknown"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|