hermes-gsuite-cli 0.2.0__py3-none-any.whl
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.
- gsuite_cli/__init__.py +7 -0
- gsuite_cli/ai/__init__.py +38 -0
- gsuite_cli/ai/analytics.py +460 -0
- gsuite_cli/ai/chatbot.py +48 -0
- gsuite_cli/ai/commands.py +667 -0
- gsuite_cli/ai/date_parser.py +234 -0
- gsuite_cli/ai/gemini_client.py +62 -0
- gsuite_cli/ai/intent_schema.py +482 -0
- gsuite_cli/ai/nlp.py +677 -0
- gsuite_cli/ai/query_engine.py +1584 -0
- gsuite_cli/ai/summarizer.py +385 -0
- gsuite_cli/ai/tool_registry.py +1230 -0
- gsuite_cli/auth/__init__.py +7 -0
- gsuite_cli/auth/oauth.py +391 -0
- gsuite_cli/cli.py +6086 -0
- gsuite_cli/config/__init__.py +5 -0
- gsuite_cli/config/hermes_config.py +125 -0
- gsuite_cli/config/manager.py +349 -0
- gsuite_cli/profile/__init__.py +35 -0
- gsuite_cli/profile/commands.py +188 -0
- gsuite_cli/profile/models.py +190 -0
- gsuite_cli/profile/resolver.py +440 -0
- gsuite_cli/profile/service.py +357 -0
- gsuite_cli/profile/storage.py +125 -0
- gsuite_cli/profile/validators.py +86 -0
- gsuite_cli/services/__init__.py +3 -0
- gsuite_cli/services/admin.py +268 -0
- gsuite_cli/services/apps_script.py +206 -0
- gsuite_cli/services/booking.py +735 -0
- gsuite_cli/services/calendar.py +846 -0
- gsuite_cli/services/calendar_advanced.py +534 -0
- gsuite_cli/services/calendar_resolver.py +510 -0
- gsuite_cli/services/calendar_validators.py +207 -0
- gsuite_cli/services/chat.py +457 -0
- gsuite_cli/services/chat_commands.py +423 -0
- gsuite_cli/services/cloud_identity.py +187 -0
- gsuite_cli/services/cloud_search.py +75 -0
- gsuite_cli/services/diagnostics.py +250 -0
- gsuite_cli/services/docs.py +472 -0
- gsuite_cli/services/docs_advanced.py +601 -0
- gsuite_cli/services/drive.py +377 -0
- gsuite_cli/services/drive_activity.py +76 -0
- gsuite_cli/services/forms.py +364 -0
- gsuite_cli/services/gmail.py +1252 -0
- gsuite_cli/services/gmail_advanced.py +513 -0
- gsuite_cli/services/gmail_commands.py +737 -0
- gsuite_cli/services/meet.py +800 -0
- gsuite_cli/services/people.py +122 -0
- gsuite_cli/services/profile_resolver.py +15 -0
- gsuite_cli/services/reminders.py +243 -0
- gsuite_cli/services/resource_resolver.py +706 -0
- gsuite_cli/services/sheets.py +517 -0
- gsuite_cli/services/sheets_advanced.py +638 -0
- gsuite_cli/services/tasks.py +258 -0
- gsuite_cli/services/workspace_commands.py +98 -0
- gsuite_cli/services/workspace_events.py +122 -0
- gsuite_cli/ui/__init__.py +7 -0
- gsuite_cli/ui/human_flows.py +1366 -0
- gsuite_cli/ui/interactive.py +2825 -0
- gsuite_cli/ui/interactive_calendar.py +708 -0
- gsuite_cli/ui/interactive_labels.py +1016 -0
- gsuite_cli/ui/interactive_profile.py +1243 -0
- gsuite_cli/utils/__init__.py +3 -0
- gsuite_cli/utils/cache.py +258 -0
- gsuite_cli/utils/errors.py +147 -0
- gsuite_cli/utils/formatters.py +412 -0
- gsuite_cli/utils/validator.py +266 -0
- hermes_gsuite_cli-0.2.0.dist-info/METADATA +333 -0
- hermes_gsuite_cli-0.2.0.dist-info/RECORD +73 -0
- hermes_gsuite_cli-0.2.0.dist-info/WHEEL +5 -0
- hermes_gsuite_cli-0.2.0.dist-info/entry_points.txt +2 -0
- hermes_gsuite_cli-0.2.0.dist-info/licenses/LICENSE +22 -0
- hermes_gsuite_cli-0.2.0.dist-info/top_level.txt +1 -0
gsuite_cli/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI-powered features for GSuite CLI
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .commands import ai
|
|
6
|
+
from .nlp import NaturalLanguageProcessor
|
|
7
|
+
from .analytics import AIAnalytics
|
|
8
|
+
from .summarizer import EmailSummarizer
|
|
9
|
+
from .query_engine import NaturalQueryEngine, ConversationContext
|
|
10
|
+
from .intent_schema import StructuredIntent, validate_structured_intent
|
|
11
|
+
from .date_parser import NaturalDateParser
|
|
12
|
+
from .tool_registry import (
|
|
13
|
+
ToolDefinition,
|
|
14
|
+
ToolRegistry,
|
|
15
|
+
ToolValidator,
|
|
16
|
+
ToolExecutor,
|
|
17
|
+
ToolExecutionResult,
|
|
18
|
+
get_default_registry,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'ai',
|
|
23
|
+
'NaturalLanguageProcessor',
|
|
24
|
+
'AIAnalytics',
|
|
25
|
+
'EmailSummarizer',
|
|
26
|
+
'NaturalQueryEngine',
|
|
27
|
+
'ConversationContext',
|
|
28
|
+
'StructuredIntent',
|
|
29
|
+
'validate_structured_intent',
|
|
30
|
+
'NaturalDateParser',
|
|
31
|
+
'ToolDefinition',
|
|
32
|
+
'ToolRegistry',
|
|
33
|
+
'ToolValidator',
|
|
34
|
+
'ToolExecutor',
|
|
35
|
+
'ToolExecutionResult',
|
|
36
|
+
'get_default_registry',
|
|
37
|
+
]
|
|
38
|
+
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI-powered productivity analytics
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Dict, List, Any, Optional
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from collections import Counter, defaultdict
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
from .gemini_client import GeminiClient
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AIAnalytics:
|
|
17
|
+
"""AI-powered productivity analytics and insights"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config_manager=None):
|
|
20
|
+
self.config_manager = config_manager
|
|
21
|
+
self.gemini_client = None
|
|
22
|
+
|
|
23
|
+
if config_manager:
|
|
24
|
+
ai_config = config_manager.get('ai')
|
|
25
|
+
if ai_config and ai_config.gemini_api_key:
|
|
26
|
+
self.gemini_client = GeminiClient(
|
|
27
|
+
api_key=ai_config.gemini_api_key,
|
|
28
|
+
model_name=ai_config.model_name
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self.productivity_metrics = {
|
|
32
|
+
'email_response_rate': 0.0,
|
|
33
|
+
'meeting_frequency': 0.0,
|
|
34
|
+
'peak_productivity_hours': [],
|
|
35
|
+
'communication_patterns': {},
|
|
36
|
+
'workload_trends': {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def analyze_productivity(self,
|
|
40
|
+
emails: List[Dict[str, Any]],
|
|
41
|
+
events: List[Dict[str, Any]],
|
|
42
|
+
period: str = 'week') -> Dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Analyze productivity patterns from emails and calendar events
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
emails: List of email dictionaries
|
|
48
|
+
events: List of calendar event dictionaries
|
|
49
|
+
period: Analysis period ('day', 'week', 'month')
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Comprehensive productivity analysis
|
|
53
|
+
"""
|
|
54
|
+
if not emails and not events:
|
|
55
|
+
return {'message': 'No data available for analysis'}
|
|
56
|
+
|
|
57
|
+
# Time-based analysis
|
|
58
|
+
time_analysis = self._analyze_time_patterns(emails, events, period)
|
|
59
|
+
|
|
60
|
+
# Email analysis
|
|
61
|
+
email_analysis = self._analyze_email_patterns(emails, period)
|
|
62
|
+
|
|
63
|
+
# Calendar analysis
|
|
64
|
+
calendar_analysis = self._analyze_calendar_patterns(events, period)
|
|
65
|
+
|
|
66
|
+
# Generate insights
|
|
67
|
+
insights = self._generate_productivity_insights(
|
|
68
|
+
time_analysis, email_analysis, calendar_analysis
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Calculate productivity score
|
|
72
|
+
productivity_score = self._calculate_productivity_score(
|
|
73
|
+
email_analysis, calendar_analysis, time_analysis
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
recommendations = self._generate_recommendations(insights)
|
|
77
|
+
|
|
78
|
+
# Enhance with Gemini if available
|
|
79
|
+
if self.gemini_client and self.gemini_client.is_available:
|
|
80
|
+
try:
|
|
81
|
+
gemini_analysis = self._analyze_with_gemini(
|
|
82
|
+
email_analysis, calendar_analysis, time_analysis, period
|
|
83
|
+
)
|
|
84
|
+
if gemini_analysis:
|
|
85
|
+
# Append Gemini insights and recommendations
|
|
86
|
+
if 'insights' in gemini_analysis:
|
|
87
|
+
insights.extend(gemini_analysis['insights'])
|
|
88
|
+
if 'recommendations' in gemini_analysis:
|
|
89
|
+
# Prioritize Gemini recommendations
|
|
90
|
+
recommendations = gemini_analysis['recommendations'] + recommendations
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Gemini analytics failed: {e}")
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
'period': period,
|
|
96
|
+
'productivity_score': productivity_score,
|
|
97
|
+
'time_analysis': time_analysis,
|
|
98
|
+
'email_analysis': email_analysis,
|
|
99
|
+
'calendar_analysis': calendar_analysis,
|
|
100
|
+
'insights': insights,
|
|
101
|
+
'recommendations': recommendations[:5] # Limit to top 5
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def _analyze_with_gemini(self,
|
|
105
|
+
email_stats: Dict[str, Any],
|
|
106
|
+
calendar_stats: Dict[str, Any],
|
|
107
|
+
time_stats: Dict[str, Any],
|
|
108
|
+
period: str) -> Optional[Dict[str, Any]]:
|
|
109
|
+
"""Analyze productivity stats using Gemini"""
|
|
110
|
+
prompt = f'''
|
|
111
|
+
Analyze the following productivity statistics for the {period}.
|
|
112
|
+
|
|
113
|
+
Email Stats: {json.dumps(email_stats, default=str)}
|
|
114
|
+
Calendar Stats: {json.dumps(calendar_stats, default=str)}
|
|
115
|
+
Time Stats: {json.dumps(time_stats, default=str)}
|
|
116
|
+
|
|
117
|
+
Return ONLY a JSON object with:
|
|
118
|
+
- insights: list of deep insights about working habits and productivity
|
|
119
|
+
- recommendations: list of specific, actionable recommendations to improve productivity
|
|
120
|
+
'''
|
|
121
|
+
|
|
122
|
+
response = self.gemini_client.generate_content(prompt)
|
|
123
|
+
if not response:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Clean up response
|
|
128
|
+
text = response.replace('```json', '').replace('```', '').strip()
|
|
129
|
+
start_idx = text.find('{')
|
|
130
|
+
end_idx = text.rfind('}') + 1
|
|
131
|
+
if start_idx != -1 and end_idx != -1:
|
|
132
|
+
return json.loads(text[start_idx:end_idx])
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(f"Error parsing Gemini response: {e}")
|
|
135
|
+
return None
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def _analyze_time_patterns(self,
|
|
139
|
+
emails: List[Dict[str, Any]],
|
|
140
|
+
events: List[Dict[str, Any]],
|
|
141
|
+
period: str) -> Dict[str, Any]:
|
|
142
|
+
"""Analyze time-based patterns"""
|
|
143
|
+
hour_activity = defaultdict(int)
|
|
144
|
+
day_activity = defaultdict(int)
|
|
145
|
+
|
|
146
|
+
# Analyze email timestamps
|
|
147
|
+
for email in emails:
|
|
148
|
+
date_str = email.get('date', '')
|
|
149
|
+
if date_str:
|
|
150
|
+
try:
|
|
151
|
+
# Parse date (simplified)
|
|
152
|
+
if 'T' in date_str:
|
|
153
|
+
datetime_part = date_str.split('T')[1]
|
|
154
|
+
hour = int(datetime_part.split(':')[0])
|
|
155
|
+
else:
|
|
156
|
+
hour = 12 # Default to noon if no time
|
|
157
|
+
|
|
158
|
+
hour_activity[hour] += 1
|
|
159
|
+
day_activity['email'] += 1
|
|
160
|
+
except:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Analyze event times
|
|
164
|
+
for event in events:
|
|
165
|
+
start_str = event.get('start', '')
|
|
166
|
+
if start_str:
|
|
167
|
+
try:
|
|
168
|
+
if 'T' in start_str:
|
|
169
|
+
datetime_part = start_str.split('T')[1]
|
|
170
|
+
hour = int(datetime_part.split(':')[0])
|
|
171
|
+
else:
|
|
172
|
+
hour = 12 # Default to noon if no time
|
|
173
|
+
|
|
174
|
+
hour_activity[hour] += 1
|
|
175
|
+
day_activity['events'] += 1
|
|
176
|
+
except:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Find peak hours
|
|
180
|
+
if hour_activity:
|
|
181
|
+
peak_hours = sorted(hour_activity.items(), key=lambda x: x[1], reverse=True)[:3]
|
|
182
|
+
peak_hours = [f"{hour}:00" for hour, count in peak_hours]
|
|
183
|
+
else:
|
|
184
|
+
peak_hours = []
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
'peak_hours': peak_hours,
|
|
188
|
+
'hourly_distribution': dict(hour_activity),
|
|
189
|
+
'daily_breakdown': dict(day_activity)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
def _analyze_email_patterns(self, emails: List[Dict[str, Any]], period: str) -> Dict[str, Any]:
|
|
193
|
+
"""Analyze email communication patterns"""
|
|
194
|
+
if not emails:
|
|
195
|
+
return {'total': 0}
|
|
196
|
+
|
|
197
|
+
total_emails = len(emails)
|
|
198
|
+
|
|
199
|
+
# Sender analysis
|
|
200
|
+
senders = [email.get('from', '') for email in emails]
|
|
201
|
+
sender_counts = Counter(senders)
|
|
202
|
+
top_senders = sender_counts.most_common(5)
|
|
203
|
+
|
|
204
|
+
# Subject analysis
|
|
205
|
+
subjects = [email.get('subject', '') for email in emails]
|
|
206
|
+
subject_lengths = [len(s) for s in subjects if s]
|
|
207
|
+
avg_subject_length = sum(subject_lengths) / len(subject_lengths) if subject_lengths else 0
|
|
208
|
+
|
|
209
|
+
# Time-based patterns
|
|
210
|
+
recent_emails = 0
|
|
211
|
+
older_emails = 0
|
|
212
|
+
|
|
213
|
+
for email in emails:
|
|
214
|
+
date_str = email.get('date', '')
|
|
215
|
+
if date_str:
|
|
216
|
+
try:
|
|
217
|
+
# Simple date parsing
|
|
218
|
+
if '2026' in date_str: # Current year
|
|
219
|
+
recent_emails += 1
|
|
220
|
+
else:
|
|
221
|
+
older_emails += 1
|
|
222
|
+
except:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
'total': total_emails,
|
|
227
|
+
'top_senders': top_senders,
|
|
228
|
+
'avg_subject_length': round(avg_subject_length, 1),
|
|
229
|
+
'recent_emails': recent_emails,
|
|
230
|
+
'older_emails': older_emails,
|
|
231
|
+
'emails_per_day': round(total_emails / 7, 1) if period == 'week' else total_emails
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
def _analyze_calendar_patterns(self, events: List[Dict[str, Any]], period: str) -> Dict[str, Any]:
|
|
235
|
+
"""Analyze calendar event patterns"""
|
|
236
|
+
if not events:
|
|
237
|
+
return {'total': 0}
|
|
238
|
+
|
|
239
|
+
total_events = len(events)
|
|
240
|
+
|
|
241
|
+
# Event types by keywords
|
|
242
|
+
meeting_events = 0
|
|
243
|
+
personal_events = 0
|
|
244
|
+
other_events = 0
|
|
245
|
+
|
|
246
|
+
for event in events:
|
|
247
|
+
title = event.get('summary', '').lower()
|
|
248
|
+
if any(keyword in title for keyword in ['meeting', 'call', 'conference', 'sync']):
|
|
249
|
+
meeting_events += 1
|
|
250
|
+
elif any(keyword in title for keyword in ['birthday', 'personal', 'holiday']):
|
|
251
|
+
personal_events += 1
|
|
252
|
+
else:
|
|
253
|
+
other_events += 1
|
|
254
|
+
|
|
255
|
+
# Duration analysis (simplified)
|
|
256
|
+
avg_duration_hours = 1.0 # Default assumption
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
'total': total_events,
|
|
260
|
+
'meetings': meeting_events,
|
|
261
|
+
'personal': personal_events,
|
|
262
|
+
'other': other_events,
|
|
263
|
+
'avg_duration_hours': avg_duration_hours,
|
|
264
|
+
'events_per_day': round(total_events / 7, 1) if period == 'week' else total_events
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
def _generate_productivity_insights(self,
|
|
268
|
+
time_analysis: Dict[str, Any],
|
|
269
|
+
email_analysis: Dict[str, Any],
|
|
270
|
+
calendar_analysis: Dict[str, Any]) -> List[str]:
|
|
271
|
+
"""Generate AI-powered insights from data"""
|
|
272
|
+
insights = []
|
|
273
|
+
|
|
274
|
+
# Email insights
|
|
275
|
+
if email_analysis.get('total', 0) > 50:
|
|
276
|
+
insights.append("High email volume - consider batching responses")
|
|
277
|
+
|
|
278
|
+
if email_analysis.get('top_senders'):
|
|
279
|
+
top_sender = email_analysis['top_senders'][0]
|
|
280
|
+
insights.append(f"Most communication with {top_sender[0]} ({top_sender[1]} emails)")
|
|
281
|
+
|
|
282
|
+
# Calendar insights
|
|
283
|
+
if calendar_analysis.get('meetings', 0) > 10:
|
|
284
|
+
insights.append("Heavy meeting schedule - ensure focus time")
|
|
285
|
+
|
|
286
|
+
if calendar_analysis.get('personal', 0) > 5:
|
|
287
|
+
insights.append("Good work-life balance with personal events")
|
|
288
|
+
|
|
289
|
+
# Time insights
|
|
290
|
+
if time_analysis.get('peak_hours'):
|
|
291
|
+
peak_hour = time_analysis['peak_hours'][0]
|
|
292
|
+
insights.append(f"Peak activity around {peak_hour}")
|
|
293
|
+
|
|
294
|
+
# Communication patterns
|
|
295
|
+
total_emails = email_analysis.get('total', 0)
|
|
296
|
+
total_events = calendar_analysis.get('total', 0)
|
|
297
|
+
|
|
298
|
+
if total_emails > total_events * 5:
|
|
299
|
+
insights.append("Email-heavy communication pattern")
|
|
300
|
+
elif total_events > total_emails * 2:
|
|
301
|
+
insights.append("Meeting-heavy schedule")
|
|
302
|
+
else:
|
|
303
|
+
insights.append("Balanced communication approach")
|
|
304
|
+
|
|
305
|
+
return insights
|
|
306
|
+
|
|
307
|
+
def _calculate_productivity_score(self,
|
|
308
|
+
email_analysis: Dict[str, Any],
|
|
309
|
+
calendar_analysis: Dict[str, Any],
|
|
310
|
+
time_analysis: Dict[str, Any]) -> float:
|
|
311
|
+
"""Calculate overall productivity score (0-100)"""
|
|
312
|
+
score = 50 # Base score
|
|
313
|
+
|
|
314
|
+
# Email productivity factors
|
|
315
|
+
email_total = email_analysis.get('total', 0)
|
|
316
|
+
if 10 <= email_total <= 30: # Optimal range
|
|
317
|
+
score += 10
|
|
318
|
+
elif email_total > 50: # Too many emails
|
|
319
|
+
score -= 10
|
|
320
|
+
|
|
321
|
+
# Meeting productivity factors
|
|
322
|
+
meetings = calendar_analysis.get('meetings', 0)
|
|
323
|
+
if 5 <= meetings <= 15: # Optimal range
|
|
324
|
+
score += 10
|
|
325
|
+
elif meetings > 20: # Too many meetings
|
|
326
|
+
score -= 15
|
|
327
|
+
|
|
328
|
+
# Work-life balance
|
|
329
|
+
personal_events = calendar_analysis.get('personal', 0)
|
|
330
|
+
if personal_events > 0:
|
|
331
|
+
score += 5
|
|
332
|
+
|
|
333
|
+
# Time distribution
|
|
334
|
+
peak_hours = time_analysis.get('peak_hours', [])
|
|
335
|
+
if len(peak_hours) > 0:
|
|
336
|
+
score += 5
|
|
337
|
+
|
|
338
|
+
return max(0, min(100, score))
|
|
339
|
+
|
|
340
|
+
def _generate_recommendations(self, insights: List[str]) -> List[str]:
|
|
341
|
+
"""Generate AI-powered recommendations"""
|
|
342
|
+
recommendations = []
|
|
343
|
+
|
|
344
|
+
insight_text = ' '.join(insights).lower()
|
|
345
|
+
|
|
346
|
+
if 'high email volume' in insight_text:
|
|
347
|
+
recommendations.append("Schedule specific email checking times (2-3x per day)")
|
|
348
|
+
recommendations.append("Use email templates for common responses")
|
|
349
|
+
|
|
350
|
+
if 'heavy meeting schedule' in insight_text:
|
|
351
|
+
recommendations.append("Block focus time between meetings")
|
|
352
|
+
recommendations.append("Evaluate if all meetings are necessary")
|
|
353
|
+
|
|
354
|
+
if 'peak activity' in insight_text:
|
|
355
|
+
recommendations.append("Schedule important tasks during peak hours")
|
|
356
|
+
|
|
357
|
+
if 'email-heavy' in insight_text:
|
|
358
|
+
recommendations.append("Consider more direct communication methods")
|
|
359
|
+
recommendations.append("Use chat or calls for quick discussions")
|
|
360
|
+
|
|
361
|
+
if 'meeting-heavy' in insight_text:
|
|
362
|
+
recommendations.append("Send agendas in advance to reduce meeting time")
|
|
363
|
+
recommendations.append("Consider async updates instead of meetings")
|
|
364
|
+
|
|
365
|
+
if not recommendations:
|
|
366
|
+
recommendations.append("Maintain current productivity patterns")
|
|
367
|
+
recommendations.append("Continue balancing email and meeting communication")
|
|
368
|
+
|
|
369
|
+
return recommendations[:5] # Return top 5 recommendations
|
|
370
|
+
|
|
371
|
+
def generate_weekly_report(self,
|
|
372
|
+
emails: List[Dict[str, Any]],
|
|
373
|
+
events: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
374
|
+
"""Generate comprehensive weekly productivity report"""
|
|
375
|
+
analysis = self.analyze_productivity(emails, events, 'week')
|
|
376
|
+
|
|
377
|
+
# Add weekly-specific metrics
|
|
378
|
+
week_start = datetime.now() - timedelta(days=datetime.now().weekday())
|
|
379
|
+
week_end = week_start + timedelta(days=6)
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
'report_period': f"{week_start.strftime('%Y-%m-%d')} to {week_end.strftime('%Y-%m-%d')}",
|
|
383
|
+
'executive_summary': self._generate_executive_summary(analysis),
|
|
384
|
+
'detailed_analysis': analysis,
|
|
385
|
+
'achievements': self._extract_achievements(events),
|
|
386
|
+
'challenges': self._identify_challenges(emails, events),
|
|
387
|
+
'next_week_priorities': self._suggest_priorities(analysis)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
def _generate_executive_summary(self, analysis: Dict[str, Any]) -> str:
|
|
391
|
+
"""Generate executive summary for weekly report"""
|
|
392
|
+
score = analysis.get('productivity_score', 50)
|
|
393
|
+
insights = analysis.get('insights', [])
|
|
394
|
+
|
|
395
|
+
if score >= 80:
|
|
396
|
+
performance = "excellent"
|
|
397
|
+
elif score >= 60:
|
|
398
|
+
performance = "good"
|
|
399
|
+
elif score >= 40:
|
|
400
|
+
performance = "moderate"
|
|
401
|
+
else:
|
|
402
|
+
performance = "needs improvement"
|
|
403
|
+
|
|
404
|
+
summary = f"Weekly productivity was {performance} (score: {score}/100). "
|
|
405
|
+
|
|
406
|
+
if insights:
|
|
407
|
+
summary += f"Key insights: {insights[0].lower()}"
|
|
408
|
+
if len(insights) > 1:
|
|
409
|
+
summary += f". {insights[1].lower()}"
|
|
410
|
+
|
|
411
|
+
return summary
|
|
412
|
+
|
|
413
|
+
def _extract_achievements(self, events: List[Dict[str, Any]]) -> List[str]:
|
|
414
|
+
"""Extract achievements from calendar events"""
|
|
415
|
+
achievements = []
|
|
416
|
+
|
|
417
|
+
for event in events:
|
|
418
|
+
title = event.get('summary', '').lower()
|
|
419
|
+
if any(keyword in title for keyword in ['completed', 'finished', 'done', 'achieved']):
|
|
420
|
+
achievements.append(event.get('summary', ''))
|
|
421
|
+
|
|
422
|
+
return achievements[:3] # Top 3 achievements
|
|
423
|
+
|
|
424
|
+
def _identify_challenges(self, emails: List[Dict[str, Any]], events: List[Dict[str, Any]]) -> List[str]:
|
|
425
|
+
"""Identify challenges from emails and events"""
|
|
426
|
+
challenges = []
|
|
427
|
+
|
|
428
|
+
# Look for challenge indicators in emails
|
|
429
|
+
for email in emails:
|
|
430
|
+
subject = email.get('subject', '').lower()
|
|
431
|
+
if any(keyword in subject for keyword in ['problem', 'issue', 'delay', 'urgent']):
|
|
432
|
+
challenges.append(email.get('subject', ''))
|
|
433
|
+
|
|
434
|
+
# Look for schedule conflicts
|
|
435
|
+
if len(events) > 20:
|
|
436
|
+
challenges.append("Heavy schedule with many commitments")
|
|
437
|
+
|
|
438
|
+
return challenges[:3] # Top 3 challenges
|
|
439
|
+
|
|
440
|
+
def _suggest_priorities(self, analysis: Dict[str, Any]) -> List[str]:
|
|
441
|
+
"""Suggest priorities for next week"""
|
|
442
|
+
priorities = []
|
|
443
|
+
|
|
444
|
+
recommendations = analysis.get('recommendations', [])
|
|
445
|
+
insights = analysis.get('insights', [])
|
|
446
|
+
|
|
447
|
+
# Convert recommendations to priorities
|
|
448
|
+
for rec in recommendations[:3]:
|
|
449
|
+
if 'schedule' in rec.lower():
|
|
450
|
+
priorities.append(f"Schedule: {rec}")
|
|
451
|
+
elif 'consider' in rec.lower():
|
|
452
|
+
priorities.append(f"Consider: {rec}")
|
|
453
|
+
else:
|
|
454
|
+
priorities.append(f"Action: {rec}")
|
|
455
|
+
|
|
456
|
+
if not priorities:
|
|
457
|
+
priorities.append("Maintain current productivity habits")
|
|
458
|
+
priorities.append("Focus on high-impact activities")
|
|
459
|
+
|
|
460
|
+
return priorities
|
gsuite_cli/ai/chatbot.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI Chatbot service for GSuite CLI - Gemini Powered (v3)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from google import genai
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class AIChatBot:
|
|
12
|
+
"""Chatbot service powered by Google Gemini (New SDK)"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, gemini_key: str = '', model_name: str = 'gemini-3.6-flash'):
|
|
15
|
+
self.gemini_key = gemini_key
|
|
16
|
+
self.model_name = model_name
|
|
17
|
+
self._client = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def client(self):
|
|
21
|
+
"""Lazy initialization of the Gemini client"""
|
|
22
|
+
if self._client is None and self.gemini_key:
|
|
23
|
+
self._client = genai.Client(api_key=self.gemini_key)
|
|
24
|
+
return self._client
|
|
25
|
+
|
|
26
|
+
def chat(self, message: str) -> str:
|
|
27
|
+
"""Send a message to Gemini and get a response using the new SDK"""
|
|
28
|
+
if not self.gemini_key:
|
|
29
|
+
return "❌ Gemini API key not configured. Set it with 'hermes config set ai.gemini_api_key YOUR_KEY'"
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if not self.client:
|
|
33
|
+
return "❌ Failed to initialize Gemini client."
|
|
34
|
+
|
|
35
|
+
response = self.client.models.generate_content(
|
|
36
|
+
model=self.model_name,
|
|
37
|
+
contents=message
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if response and response.text:
|
|
41
|
+
return response.text
|
|
42
|
+
return "❌ No response from Gemini"
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error(f"Gemini SDK error: {e}")
|
|
46
|
+
# If the specific v3 preview model isn't available, try a fallback if it makes sense,
|
|
47
|
+
# but usually for a preview user, we want to show the specific error.
|
|
48
|
+
return f"❌ Gemini AI Error: {str(e)}"
|