hyperagents 1.2.3rc5.post3__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.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- ui/tui/app.py +214 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""GmailConnector — Gmail API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
from typing import AsyncIterator
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from integrations.base import (
|
|
12
|
+
BaseConnector,
|
|
13
|
+
ConnectorCapability,
|
|
14
|
+
ConnectorInfo,
|
|
15
|
+
InboundMessage,
|
|
16
|
+
OutboundMessage,
|
|
17
|
+
)
|
|
18
|
+
from integrations.productivity.google.base import GoogleOAuthBase
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("hyperclaw.integrations.gmail")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GmailConnector(BaseConnector, GoogleOAuthBase):
|
|
24
|
+
"""
|
|
25
|
+
Gmail API connector.
|
|
26
|
+
|
|
27
|
+
Required config (via GoogleOAuthBase):
|
|
28
|
+
- access_token or credentials_file
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
BASE_URL = "https://gmail.googleapis.com/gmail/v1"
|
|
32
|
+
|
|
33
|
+
def __init__(self, config: dict) -> None:
|
|
34
|
+
self.config = config
|
|
35
|
+
self._init_google_auth(config)
|
|
36
|
+
self._validate_google_config(config)
|
|
37
|
+
self._client: httpx.AsyncClient | None = None
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def info(self) -> ConnectorInfo:
|
|
41
|
+
return ConnectorInfo(
|
|
42
|
+
connector_id="gmail",
|
|
43
|
+
platform="gmail",
|
|
44
|
+
category="productivity",
|
|
45
|
+
capabilities=frozenset(
|
|
46
|
+
[
|
|
47
|
+
ConnectorCapability.SEND_MESSAGE,
|
|
48
|
+
ConnectorCapability.RECEIVE_MESSAGE,
|
|
49
|
+
ConnectorCapability.READ_DATA,
|
|
50
|
+
ConnectorCapability.WRITE_DATA,
|
|
51
|
+
ConnectorCapability.DELETE_DATA,
|
|
52
|
+
ConnectorCapability.LIST_DATA,
|
|
53
|
+
ConnectorCapability.SEARCH,
|
|
54
|
+
ConnectorCapability.FILE_UPLOAD,
|
|
55
|
+
ConnectorCapability.FILE_DOWNLOAD,
|
|
56
|
+
]
|
|
57
|
+
),
|
|
58
|
+
auth_type="oauth2",
|
|
59
|
+
rate_limit_per_minute=250,
|
|
60
|
+
supports_threads=True,
|
|
61
|
+
supports_attachments=True,
|
|
62
|
+
supports_reactions=False,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
66
|
+
return await self._get_google_client()
|
|
67
|
+
|
|
68
|
+
async def health(self) -> bool:
|
|
69
|
+
"""Check if Gmail API is accessible."""
|
|
70
|
+
try:
|
|
71
|
+
client = await self._get_client()
|
|
72
|
+
response = await client.get(f"{self.BASE_URL}/users/me/profile")
|
|
73
|
+
return response.status_code == 200
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Gmail health check failed: {e}")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
async def _send_impl(self, message: OutboundMessage) -> str:
|
|
79
|
+
"""Send an email via Gmail."""
|
|
80
|
+
return await self.send_email(
|
|
81
|
+
to=message.recipient_id,
|
|
82
|
+
subject="Message",
|
|
83
|
+
body=message.content,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
|
|
87
|
+
"""Receive emails from inbox."""
|
|
88
|
+
messages = await self.list_inbox(max_results=20)
|
|
89
|
+
|
|
90
|
+
client = await self._get_client()
|
|
91
|
+
|
|
92
|
+
for msg_ref in messages:
|
|
93
|
+
response = await client.get(
|
|
94
|
+
f"{self.BASE_URL}/users/me/messages/{msg_ref['id']}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if response.status_code != 200:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
msg = response.json()
|
|
101
|
+
headers = {
|
|
102
|
+
h["name"]: h["value"]
|
|
103
|
+
for h in msg.get("payload", {}).get("headers", [])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
yield InboundMessage(
|
|
107
|
+
message_id=msg.get("id", ""),
|
|
108
|
+
platform="gmail",
|
|
109
|
+
sender_id=headers.get("From", ""),
|
|
110
|
+
sender_name=headers.get("From", ""),
|
|
111
|
+
content=msg.get("snippet", ""),
|
|
112
|
+
thread_id=msg.get("threadId"),
|
|
113
|
+
raw=msg,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
async def send_email(
|
|
117
|
+
self,
|
|
118
|
+
to: str,
|
|
119
|
+
subject: str,
|
|
120
|
+
body: str,
|
|
121
|
+
attachments: list[str] | None = None,
|
|
122
|
+
) -> str:
|
|
123
|
+
"""Send an email."""
|
|
124
|
+
client = await self._get_client()
|
|
125
|
+
|
|
126
|
+
# Build raw email
|
|
127
|
+
email_lines = [
|
|
128
|
+
f"To: {to}",
|
|
129
|
+
f"Subject: {subject}",
|
|
130
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
131
|
+
"",
|
|
132
|
+
body,
|
|
133
|
+
]
|
|
134
|
+
email_content = "\r\n".join(email_lines)
|
|
135
|
+
raw = base64.urlsafe_b64encode(email_content.encode()).decode()
|
|
136
|
+
|
|
137
|
+
response = await client.post(
|
|
138
|
+
f"{self.BASE_URL}/users/me/messages/send",
|
|
139
|
+
json={"raw": raw},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if response.status_code not in (200, 201):
|
|
143
|
+
raise Exception(f"Gmail send failed: {response.text}")
|
|
144
|
+
|
|
145
|
+
return response.json().get("id", "")
|
|
146
|
+
|
|
147
|
+
async def list_inbox(
|
|
148
|
+
self,
|
|
149
|
+
max_results: int = 20,
|
|
150
|
+
query: str = "",
|
|
151
|
+
) -> list[dict]:
|
|
152
|
+
"""List inbox messages."""
|
|
153
|
+
client = await self._get_client()
|
|
154
|
+
|
|
155
|
+
params = {"maxResults": max_results, "labelIds": "INBOX"}
|
|
156
|
+
if query:
|
|
157
|
+
params["q"] = query
|
|
158
|
+
|
|
159
|
+
response = await client.get(
|
|
160
|
+
f"{self.BASE_URL}/users/me/messages",
|
|
161
|
+
params=params,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if response.status_code != 200:
|
|
165
|
+
raise Exception(f"Gmail list failed: {response.text}")
|
|
166
|
+
|
|
167
|
+
return response.json().get("messages", [])
|
|
168
|
+
|
|
169
|
+
async def search_emails(self, query: str) -> list[dict]:
|
|
170
|
+
"""Search emails."""
|
|
171
|
+
return await self.list_inbox(max_results=50, query=query)
|
|
172
|
+
|
|
173
|
+
async def get_thread(self, thread_id: str) -> dict:
|
|
174
|
+
"""Get an email thread."""
|
|
175
|
+
client = await self._get_client()
|
|
176
|
+
|
|
177
|
+
response = await client.get(
|
|
178
|
+
f"{self.BASE_URL}/users/me/threads/{thread_id}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if response.status_code != 200:
|
|
182
|
+
raise Exception(f"Gmail get thread failed: {response.text}")
|
|
183
|
+
|
|
184
|
+
return response.json()
|
|
185
|
+
|
|
186
|
+
async def create_draft(
|
|
187
|
+
self,
|
|
188
|
+
to: str,
|
|
189
|
+
subject: str,
|
|
190
|
+
body: str,
|
|
191
|
+
) -> str:
|
|
192
|
+
"""Create an email draft."""
|
|
193
|
+
client = await self._get_client()
|
|
194
|
+
|
|
195
|
+
email_lines = [f"To: {to}", f"Subject: {subject}", "", body]
|
|
196
|
+
email_content = "\r\n".join(email_lines)
|
|
197
|
+
raw = base64.urlsafe_b64encode(email_content.encode()).decode()
|
|
198
|
+
|
|
199
|
+
response = await client.post(
|
|
200
|
+
f"{self.BASE_URL}/users/me/drafts",
|
|
201
|
+
json={"message": {"raw": raw}},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if response.status_code not in (200, 201):
|
|
205
|
+
raise Exception(f"Gmail create draft failed: {response.text}")
|
|
206
|
+
|
|
207
|
+
return response.json().get("id", "")
|
|
208
|
+
|
|
209
|
+
async def mark_read(self, message_id: str) -> bool:
|
|
210
|
+
"""Mark a message as read."""
|
|
211
|
+
client = await self._get_client()
|
|
212
|
+
|
|
213
|
+
response = await client.post(
|
|
214
|
+
f"{self.BASE_URL}/users/me/messages/{message_id}/modify",
|
|
215
|
+
json={"removeLabelIds": ["UNREAD"]},
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return response.status_code == 200
|
|
219
|
+
|
|
220
|
+
async def add_label(self, message_id: str, label: str) -> bool:
|
|
221
|
+
"""Add a label to a message."""
|
|
222
|
+
client = await self._get_client()
|
|
223
|
+
|
|
224
|
+
response = await client.post(
|
|
225
|
+
f"{self.BASE_URL}/users/me/messages/{message_id}/modify",
|
|
226
|
+
json={"addLabelIds": [label]},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return response.status_code == 200
|
|
230
|
+
|
|
231
|
+
async def create_filter(self, criteria: dict, action: dict) -> str:
|
|
232
|
+
"""Create an email filter."""
|
|
233
|
+
client = await self._get_client()
|
|
234
|
+
|
|
235
|
+
response = await client.post(
|
|
236
|
+
f"{self.BASE_URL}/users/me/settings/filters",
|
|
237
|
+
json={"criteria": criteria, "action": action},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if response.status_code not in (200, 201):
|
|
241
|
+
raise Exception(f"Gmail create filter failed: {response.text}")
|
|
242
|
+
|
|
243
|
+
return response.json().get("id", "")
|
|
244
|
+
|
|
245
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
246
|
+
"""Read a message."""
|
|
247
|
+
client = await self._get_client()
|
|
248
|
+
|
|
249
|
+
response = await client.get(
|
|
250
|
+
f"{self.BASE_URL}/users/me/messages/{resource_id}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if response.status_code != 200:
|
|
254
|
+
raise Exception(f"Gmail read failed: {response.text}")
|
|
255
|
+
|
|
256
|
+
return response.json()
|
|
257
|
+
|
|
258
|
+
async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
|
|
259
|
+
"""Delete a message."""
|
|
260
|
+
client = await self._get_client()
|
|
261
|
+
|
|
262
|
+
response = await client.delete(
|
|
263
|
+
f"{self.BASE_URL}/users/me/messages/{resource_id}"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return response.status_code == 204
|
|
267
|
+
|
|
268
|
+
async def _search_impl(self, query: str, **kwargs) -> list[dict]:
|
|
269
|
+
"""Search implementation."""
|
|
270
|
+
return await self.search_emails(query)
|
|
271
|
+
|
|
272
|
+
async def _list_impl(
|
|
273
|
+
self, resource_type: str, filters: dict, **kwargs
|
|
274
|
+
) -> list[dict]:
|
|
275
|
+
"""List implementation."""
|
|
276
|
+
return await self.list_inbox(
|
|
277
|
+
max_results=filters.get("max_results", 20),
|
|
278
|
+
query=filters.get("query", ""),
|
|
279
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""GoogleMeetConnector — Google Meet API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from integrations.base import (
|
|
11
|
+
BaseConnector,
|
|
12
|
+
ConnectorCapability,
|
|
13
|
+
ConnectorInfo,
|
|
14
|
+
)
|
|
15
|
+
from integrations.productivity.google.base import GoogleOAuthBase
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("hyperclaw.integrations.google_meet")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GoogleMeetConnector(BaseConnector, GoogleOAuthBase):
|
|
21
|
+
"""
|
|
22
|
+
Google Meet API connector.
|
|
23
|
+
|
|
24
|
+
Uses Google Calendar API to create Meet events.
|
|
25
|
+
|
|
26
|
+
Required config (via GoogleOAuthBase):
|
|
27
|
+
- access_token or credentials_file
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
CALENDAR_URL = "https://www.googleapis.com/calendar/v3"
|
|
31
|
+
|
|
32
|
+
def __init__(self, config: dict) -> None:
|
|
33
|
+
self.config = config
|
|
34
|
+
self._init_google_auth(config)
|
|
35
|
+
self._validate_google_config(config)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def info(self) -> ConnectorInfo:
|
|
39
|
+
return ConnectorInfo(
|
|
40
|
+
connector_id="google_meet",
|
|
41
|
+
platform="google_meet",
|
|
42
|
+
category="productivity",
|
|
43
|
+
capabilities=frozenset(
|
|
44
|
+
[
|
|
45
|
+
ConnectorCapability.READ_DATA,
|
|
46
|
+
ConnectorCapability.WRITE_DATA,
|
|
47
|
+
ConnectorCapability.ACTION,
|
|
48
|
+
]
|
|
49
|
+
),
|
|
50
|
+
auth_type="oauth2",
|
|
51
|
+
rate_limit_per_minute=60,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
55
|
+
return await self._get_google_client()
|
|
56
|
+
|
|
57
|
+
async def health(self) -> bool:
|
|
58
|
+
"""Check if Meet/Calendar API is accessible."""
|
|
59
|
+
try:
|
|
60
|
+
client = await self._get_client()
|
|
61
|
+
response = await client.get(
|
|
62
|
+
f"{self.CALENDAR_URL}/users/me/calendarList",
|
|
63
|
+
)
|
|
64
|
+
return response.status_code == 200
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(f"Google Meet health check failed: {e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
async def create_meeting(
|
|
70
|
+
self,
|
|
71
|
+
title: str,
|
|
72
|
+
start: datetime,
|
|
73
|
+
end: datetime,
|
|
74
|
+
attendees: list[str] | None = None,
|
|
75
|
+
) -> dict:
|
|
76
|
+
"""Create a Google Meet meeting via Calendar."""
|
|
77
|
+
client = await self._get_client()
|
|
78
|
+
|
|
79
|
+
event = {
|
|
80
|
+
"summary": title,
|
|
81
|
+
"start": {"dateTime": start.isoformat(), "timeZone": "UTC"},
|
|
82
|
+
"end": {"dateTime": end.isoformat(), "timeZone": "UTC"},
|
|
83
|
+
"conferenceData": {
|
|
84
|
+
"createRequest": {
|
|
85
|
+
"requestId": f"meet-{datetime.utcnow().timestamp()}",
|
|
86
|
+
"conferenceSolutionKey": {"type": "hangoutsMeet"},
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if attendees:
|
|
92
|
+
event["attendees"] = [{"email": email} for email in attendees]
|
|
93
|
+
|
|
94
|
+
response = await client.post(
|
|
95
|
+
f"{self.CALENDAR_URL}/calendars/primary/events",
|
|
96
|
+
params={"conferenceDataVersion": 1},
|
|
97
|
+
json=event,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if response.status_code not in (200, 201):
|
|
101
|
+
raise Exception(f"Meet create meeting failed: {response.text}")
|
|
102
|
+
|
|
103
|
+
data = response.json()
|
|
104
|
+
|
|
105
|
+
# Extract Meet link
|
|
106
|
+
conference_data = data.get("conferenceData", {})
|
|
107
|
+
entry_points = conference_data.get("entryPoints", [])
|
|
108
|
+
meet_link = next(
|
|
109
|
+
(ep.get("uri") for ep in entry_points if ep.get("entryPointType") == "video"),
|
|
110
|
+
None,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"event_id": data.get("id"),
|
|
115
|
+
"meet_link": meet_link,
|
|
116
|
+
"calendar_link": data.get("htmlLink"),
|
|
117
|
+
"start": data.get("start"),
|
|
118
|
+
"end": data.get("end"),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async def get_meeting_transcript(self, meeting_id: str) -> dict:
|
|
122
|
+
"""
|
|
123
|
+
Get meeting transcript.
|
|
124
|
+
Note: This requires Meet transcription to be enabled and
|
|
125
|
+
uses the Google Cloud Speech-to-Text API or Meet add-ons.
|
|
126
|
+
This is a placeholder for the interface.
|
|
127
|
+
"""
|
|
128
|
+
# Meet transcripts are stored in Drive or via external services
|
|
129
|
+
# This would require additional API integration
|
|
130
|
+
return {
|
|
131
|
+
"meeting_id": meeting_id,
|
|
132
|
+
"transcript": None,
|
|
133
|
+
"status": "Transcript retrieval requires additional configuration",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async def list_upcoming_meetings(
|
|
137
|
+
self,
|
|
138
|
+
max_results: int = 10,
|
|
139
|
+
) -> list[dict]:
|
|
140
|
+
"""List upcoming meetings with Meet links."""
|
|
141
|
+
client = await self._get_client()
|
|
142
|
+
|
|
143
|
+
now = datetime.utcnow().isoformat() + "Z"
|
|
144
|
+
|
|
145
|
+
response = await client.get(
|
|
146
|
+
f"{self.CALENDAR_URL}/calendars/primary/events",
|
|
147
|
+
params={
|
|
148
|
+
"timeMin": now,
|
|
149
|
+
"maxResults": max_results,
|
|
150
|
+
"singleEvents": True,
|
|
151
|
+
"orderBy": "startTime",
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if response.status_code != 200:
|
|
156
|
+
raise Exception(f"Meet list meetings failed: {response.text}")
|
|
157
|
+
|
|
158
|
+
events = response.json().get("items", [])
|
|
159
|
+
|
|
160
|
+
# Filter to only events with Meet links
|
|
161
|
+
meetings = []
|
|
162
|
+
for event in events:
|
|
163
|
+
conference_data = event.get("conferenceData", {})
|
|
164
|
+
entry_points = conference_data.get("entryPoints", [])
|
|
165
|
+
meet_link = next(
|
|
166
|
+
(ep.get("uri") for ep in entry_points if ep.get("entryPointType") == "video"),
|
|
167
|
+
None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if meet_link:
|
|
171
|
+
meetings.append({
|
|
172
|
+
"event_id": event.get("id"),
|
|
173
|
+
"title": event.get("summary"),
|
|
174
|
+
"meet_link": meet_link,
|
|
175
|
+
"start": event.get("start"),
|
|
176
|
+
"end": event.get("end"),
|
|
177
|
+
"attendees": event.get("attendees", []),
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
return meetings
|
|
181
|
+
|
|
182
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
183
|
+
"""Read a meeting event."""
|
|
184
|
+
client = await self._get_client()
|
|
185
|
+
|
|
186
|
+
response = await client.get(
|
|
187
|
+
f"{self.CALENDAR_URL}/calendars/primary/events/{resource_id}"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if response.status_code != 200:
|
|
191
|
+
raise Exception(f"Meet read failed: {response.text}")
|
|
192
|
+
|
|
193
|
+
return response.json()
|
|
194
|
+
|
|
195
|
+
async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
|
|
196
|
+
"""Create a meeting."""
|
|
197
|
+
return await self.create_meeting(
|
|
198
|
+
title=data.get("title", "Meeting"),
|
|
199
|
+
start=data.get("start"),
|
|
200
|
+
end=data.get("end"),
|
|
201
|
+
attendees=data.get("attendees"),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
async def _action_impl(self, action_name: str, params: dict) -> dict:
|
|
205
|
+
"""Execute Meet-specific actions."""
|
|
206
|
+
if action_name == "create_meeting":
|
|
207
|
+
return await self.create_meeting(
|
|
208
|
+
title=params.get("title", "Meeting"),
|
|
209
|
+
start=params["start"],
|
|
210
|
+
end=params["end"],
|
|
211
|
+
attendees=params.get("attendees"),
|
|
212
|
+
)
|
|
213
|
+
elif action_name == "get_transcript":
|
|
214
|
+
return await self.get_meeting_transcript(params["meeting_id"])
|
|
215
|
+
elif action_name == "list_upcoming":
|
|
216
|
+
return {"meetings": await self.list_upcoming_meetings(params.get("max_results", 10))}
|
|
217
|
+
|
|
218
|
+
raise ValueError(f"Unknown action: {action_name}")
|