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,241 @@
|
|
|
1
|
+
"""OutlookConnector — Microsoft Outlook/Mail API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import AsyncIterator
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from integrations.base import (
|
|
11
|
+
BaseConnector,
|
|
12
|
+
ConnectorCapability,
|
|
13
|
+
ConnectorInfo,
|
|
14
|
+
InboundMessage,
|
|
15
|
+
OutboundMessage,
|
|
16
|
+
)
|
|
17
|
+
from integrations.productivity.microsoft.base import MicrosoftOAuthBase
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("hyperclaw.integrations.outlook")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OutlookConnector(BaseConnector, MicrosoftOAuthBase):
|
|
23
|
+
"""
|
|
24
|
+
Microsoft Outlook connector via Graph API.
|
|
25
|
+
|
|
26
|
+
Required config (via MicrosoftOAuthBase):
|
|
27
|
+
- access_token OR (client_id + client_secret + tenant_id)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, config: dict) -> None:
|
|
31
|
+
self.config = config
|
|
32
|
+
self._init_microsoft_auth(config)
|
|
33
|
+
self._validate_microsoft_config(config)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def info(self) -> ConnectorInfo:
|
|
37
|
+
return ConnectorInfo(
|
|
38
|
+
connector_id="outlook",
|
|
39
|
+
platform="outlook",
|
|
40
|
+
category="productivity",
|
|
41
|
+
capabilities=frozenset(
|
|
42
|
+
[
|
|
43
|
+
ConnectorCapability.SEND_MESSAGE,
|
|
44
|
+
ConnectorCapability.RECEIVE_MESSAGE,
|
|
45
|
+
ConnectorCapability.READ_DATA,
|
|
46
|
+
ConnectorCapability.WRITE_DATA,
|
|
47
|
+
ConnectorCapability.LIST_DATA,
|
|
48
|
+
ConnectorCapability.SEARCH,
|
|
49
|
+
ConnectorCapability.FILE_UPLOAD,
|
|
50
|
+
ConnectorCapability.FILE_DOWNLOAD,
|
|
51
|
+
]
|
|
52
|
+
),
|
|
53
|
+
auth_type="oauth2",
|
|
54
|
+
rate_limit_per_minute=120,
|
|
55
|
+
supports_threads=True,
|
|
56
|
+
supports_attachments=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
60
|
+
return await self._get_microsoft_client()
|
|
61
|
+
|
|
62
|
+
async def health(self) -> bool:
|
|
63
|
+
"""Check if Outlook API is accessible."""
|
|
64
|
+
try:
|
|
65
|
+
client = await self._get_client()
|
|
66
|
+
response = await client.get(f"{self.GRAPH_URL}/me")
|
|
67
|
+
return response.status_code == 200
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error(f"Outlook health check failed: {e}")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
async def _send_impl(self, message: OutboundMessage) -> str:
|
|
73
|
+
"""Send an email via Outlook."""
|
|
74
|
+
return await self.send_email(
|
|
75
|
+
to=message.recipient_id,
|
|
76
|
+
subject="Message",
|
|
77
|
+
body=message.content,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
|
|
81
|
+
"""Receive emails from inbox."""
|
|
82
|
+
messages = await self.list_inbox(max_results=20)
|
|
83
|
+
|
|
84
|
+
for msg in messages:
|
|
85
|
+
sender = msg.get("from", {}).get("emailAddress", {})
|
|
86
|
+
|
|
87
|
+
yield InboundMessage(
|
|
88
|
+
message_id=msg.get("id", ""),
|
|
89
|
+
platform="outlook",
|
|
90
|
+
sender_id=sender.get("address", ""),
|
|
91
|
+
sender_name=sender.get("name", sender.get("address", "")),
|
|
92
|
+
content=msg.get("bodyPreview", ""),
|
|
93
|
+
thread_id=msg.get("conversationId"),
|
|
94
|
+
raw=msg,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async def send_email(
|
|
98
|
+
self,
|
|
99
|
+
to: str,
|
|
100
|
+
subject: str,
|
|
101
|
+
body: str,
|
|
102
|
+
is_html: bool = False,
|
|
103
|
+
) -> str:
|
|
104
|
+
"""Send an email."""
|
|
105
|
+
client = await self._get_client()
|
|
106
|
+
|
|
107
|
+
payload = {
|
|
108
|
+
"message": {
|
|
109
|
+
"subject": subject,
|
|
110
|
+
"body": {
|
|
111
|
+
"contentType": "HTML" if is_html else "Text",
|
|
112
|
+
"content": body,
|
|
113
|
+
},
|
|
114
|
+
"toRecipients": [{"emailAddress": {"address": to}}],
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
response = await client.post(
|
|
119
|
+
f"{self.GRAPH_URL}/me/sendMail",
|
|
120
|
+
json=payload,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if response.status_code not in (200, 202):
|
|
124
|
+
raise Exception(f"Outlook send failed: {response.text}")
|
|
125
|
+
|
|
126
|
+
return "sent"
|
|
127
|
+
|
|
128
|
+
async def list_inbox(
|
|
129
|
+
self,
|
|
130
|
+
max_results: int = 20,
|
|
131
|
+
filter_query: str = "",
|
|
132
|
+
) -> list[dict]:
|
|
133
|
+
"""List inbox messages."""
|
|
134
|
+
client = await self._get_client()
|
|
135
|
+
|
|
136
|
+
params = {"$top": max_results, "$orderby": "receivedDateTime desc"}
|
|
137
|
+
if filter_query:
|
|
138
|
+
params["$filter"] = filter_query
|
|
139
|
+
|
|
140
|
+
response = await client.get(
|
|
141
|
+
f"{self.GRAPH_URL}/me/messages",
|
|
142
|
+
params=params,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if response.status_code != 200:
|
|
146
|
+
raise Exception(f"Outlook list inbox failed: {response.text}")
|
|
147
|
+
|
|
148
|
+
return response.json().get("value", [])
|
|
149
|
+
|
|
150
|
+
async def search_emails(self, query: str) -> list[dict]:
|
|
151
|
+
"""Search emails."""
|
|
152
|
+
client = await self._get_client()
|
|
153
|
+
|
|
154
|
+
response = await client.get(
|
|
155
|
+
f"{self.GRAPH_URL}/me/messages",
|
|
156
|
+
params={"$search": f'"{query}"', "$top": 50},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if response.status_code != 200:
|
|
160
|
+
raise Exception(f"Outlook search failed: {response.text}")
|
|
161
|
+
|
|
162
|
+
return response.json().get("value", [])
|
|
163
|
+
|
|
164
|
+
async def get_message(self, message_id: str) -> dict:
|
|
165
|
+
"""Get a specific message."""
|
|
166
|
+
client = await self._get_client()
|
|
167
|
+
|
|
168
|
+
response = await client.get(f"{self.GRAPH_URL}/me/messages/{message_id}")
|
|
169
|
+
|
|
170
|
+
if response.status_code != 200:
|
|
171
|
+
raise Exception(f"Outlook get message failed: {response.text}")
|
|
172
|
+
|
|
173
|
+
return response.json()
|
|
174
|
+
|
|
175
|
+
async def create_draft(
|
|
176
|
+
self,
|
|
177
|
+
to: str,
|
|
178
|
+
subject: str,
|
|
179
|
+
body: str,
|
|
180
|
+
) -> str:
|
|
181
|
+
"""Create an email draft."""
|
|
182
|
+
client = await self._get_client()
|
|
183
|
+
|
|
184
|
+
payload = {
|
|
185
|
+
"subject": subject,
|
|
186
|
+
"body": {"contentType": "Text", "content": body},
|
|
187
|
+
"toRecipients": [{"emailAddress": {"address": to}}],
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
response = await client.post(
|
|
191
|
+
f"{self.GRAPH_URL}/me/messages",
|
|
192
|
+
json=payload,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if response.status_code not in (200, 201):
|
|
196
|
+
raise Exception(f"Outlook create draft failed: {response.text}")
|
|
197
|
+
|
|
198
|
+
return response.json().get("id", "")
|
|
199
|
+
|
|
200
|
+
async def move_to_folder(
|
|
201
|
+
self,
|
|
202
|
+
message_id: str,
|
|
203
|
+
folder_id: str,
|
|
204
|
+
) -> bool:
|
|
205
|
+
"""Move a message to a folder."""
|
|
206
|
+
client = await self._get_client()
|
|
207
|
+
|
|
208
|
+
response = await client.post(
|
|
209
|
+
f"{self.GRAPH_URL}/me/messages/{message_id}/move",
|
|
210
|
+
json={"destinationId": folder_id},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return response.status_code == 201
|
|
214
|
+
|
|
215
|
+
async def flag_message(self, message_id: str) -> bool:
|
|
216
|
+
"""Flag a message for follow-up."""
|
|
217
|
+
client = await self._get_client()
|
|
218
|
+
|
|
219
|
+
response = await client.patch(
|
|
220
|
+
f"{self.GRAPH_URL}/me/messages/{message_id}",
|
|
221
|
+
json={"flag": {"flagStatus": "flagged"}},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return response.status_code == 200
|
|
225
|
+
|
|
226
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
227
|
+
"""Read a message."""
|
|
228
|
+
return await self.get_message(resource_id)
|
|
229
|
+
|
|
230
|
+
async def _search_impl(self, query: str, **kwargs) -> list[dict]:
|
|
231
|
+
"""Search implementation."""
|
|
232
|
+
return await self.search_emails(query)
|
|
233
|
+
|
|
234
|
+
async def _list_impl(
|
|
235
|
+
self, resource_type: str, filters: dict, **kwargs
|
|
236
|
+
) -> list[dict]:
|
|
237
|
+
"""List implementation."""
|
|
238
|
+
return await self.list_inbox(
|
|
239
|
+
max_results=filters.get("max_results", 20),
|
|
240
|
+
filter_query=filters.get("filter", ""),
|
|
241
|
+
)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""SharePointConnector — Microsoft SharePoint API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from integrations.base import (
|
|
11
|
+
BaseConnector,
|
|
12
|
+
ConnectorCapability,
|
|
13
|
+
ConnectorInfo,
|
|
14
|
+
)
|
|
15
|
+
from integrations.productivity.microsoft.base import MicrosoftOAuthBase
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("hyperclaw.integrations.sharepoint")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SharePointConnector(BaseConnector, MicrosoftOAuthBase):
|
|
21
|
+
"""
|
|
22
|
+
Microsoft SharePoint connector via Graph API.
|
|
23
|
+
|
|
24
|
+
Required config (via MicrosoftOAuthBase):
|
|
25
|
+
- access_token OR (client_id + client_secret + tenant_id)
|
|
26
|
+
- site_id (optional): SharePoint site ID
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: dict) -> None:
|
|
30
|
+
self.config = config
|
|
31
|
+
self._init_microsoft_auth(config)
|
|
32
|
+
self._validate_microsoft_config(config)
|
|
33
|
+
self.site_id = config.get("site_id", "")
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def info(self) -> ConnectorInfo:
|
|
37
|
+
return ConnectorInfo(
|
|
38
|
+
connector_id="sharepoint",
|
|
39
|
+
platform="sharepoint",
|
|
40
|
+
category="productivity",
|
|
41
|
+
capabilities=frozenset(
|
|
42
|
+
[
|
|
43
|
+
ConnectorCapability.READ_DATA,
|
|
44
|
+
ConnectorCapability.WRITE_DATA,
|
|
45
|
+
ConnectorCapability.LIST_DATA,
|
|
46
|
+
ConnectorCapability.SEARCH,
|
|
47
|
+
ConnectorCapability.FILE_UPLOAD,
|
|
48
|
+
]
|
|
49
|
+
),
|
|
50
|
+
auth_type="oauth2",
|
|
51
|
+
rate_limit_per_minute=100,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
55
|
+
return await self._get_microsoft_client()
|
|
56
|
+
|
|
57
|
+
async def health(self) -> bool:
|
|
58
|
+
"""Check if SharePoint API is accessible."""
|
|
59
|
+
try:
|
|
60
|
+
client = await self._get_client()
|
|
61
|
+
response = await client.get(f"{self.GRAPH_URL}/sites/root")
|
|
62
|
+
return response.status_code == 200
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error(f"SharePoint health check failed: {e}")
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
async def list_sites(self) -> list[dict]:
|
|
68
|
+
"""List SharePoint sites."""
|
|
69
|
+
client = await self._get_client()
|
|
70
|
+
|
|
71
|
+
response = await client.get(
|
|
72
|
+
f"{self.GRAPH_URL}/sites",
|
|
73
|
+
params={"search": "*"},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if response.status_code != 200:
|
|
77
|
+
raise Exception(f"SharePoint list sites failed: {response.text}")
|
|
78
|
+
|
|
79
|
+
return response.json().get("value", [])
|
|
80
|
+
|
|
81
|
+
async def get_site(self, site_id: str) -> dict:
|
|
82
|
+
"""Get site details."""
|
|
83
|
+
client = await self._get_client()
|
|
84
|
+
|
|
85
|
+
response = await client.get(f"{self.GRAPH_URL}/sites/{site_id}")
|
|
86
|
+
|
|
87
|
+
if response.status_code != 200:
|
|
88
|
+
raise Exception(f"SharePoint get site failed: {response.text}")
|
|
89
|
+
|
|
90
|
+
return response.json()
|
|
91
|
+
|
|
92
|
+
async def list_drives(self, site_id: str | None = None) -> list[dict]:
|
|
93
|
+
"""List document libraries in a site."""
|
|
94
|
+
client = await self._get_client()
|
|
95
|
+
site = site_id or self.site_id
|
|
96
|
+
|
|
97
|
+
if not site:
|
|
98
|
+
raise ValueError("site_id is required")
|
|
99
|
+
|
|
100
|
+
response = await client.get(f"{self.GRAPH_URL}/sites/{site}/drives")
|
|
101
|
+
|
|
102
|
+
if response.status_code != 200:
|
|
103
|
+
raise Exception(f"SharePoint list drives failed: {response.text}")
|
|
104
|
+
|
|
105
|
+
return response.json().get("value", [])
|
|
106
|
+
|
|
107
|
+
async def list_items(
|
|
108
|
+
self,
|
|
109
|
+
drive_id: str,
|
|
110
|
+
folder_path: str = "root",
|
|
111
|
+
) -> list[dict]:
|
|
112
|
+
"""List items in a document library folder."""
|
|
113
|
+
client = await self._get_client()
|
|
114
|
+
|
|
115
|
+
if folder_path == "root":
|
|
116
|
+
url = f"{self.GRAPH_URL}/drives/{drive_id}/root/children"
|
|
117
|
+
else:
|
|
118
|
+
url = f"{self.GRAPH_URL}/drives/{drive_id}/root:/{folder_path}:/children"
|
|
119
|
+
|
|
120
|
+
response = await client.get(url)
|
|
121
|
+
|
|
122
|
+
if response.status_code != 200:
|
|
123
|
+
raise Exception(f"SharePoint list items failed: {response.text}")
|
|
124
|
+
|
|
125
|
+
return response.json().get("value", [])
|
|
126
|
+
|
|
127
|
+
async def upload_file(
|
|
128
|
+
self,
|
|
129
|
+
drive_id: str,
|
|
130
|
+
local_path: str,
|
|
131
|
+
remote_path: str,
|
|
132
|
+
) -> dict:
|
|
133
|
+
"""Upload a file to SharePoint."""
|
|
134
|
+
client = await self._get_client()
|
|
135
|
+
|
|
136
|
+
with open(local_path, "rb") as f:
|
|
137
|
+
content = f.read()
|
|
138
|
+
|
|
139
|
+
response = await client.put(
|
|
140
|
+
f"{self.GRAPH_URL}/drives/{drive_id}/root:/{remote_path}:/content",
|
|
141
|
+
content=content,
|
|
142
|
+
headers={"Content-Type": "application/octet-stream"},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if response.status_code not in (200, 201):
|
|
146
|
+
raise Exception(f"SharePoint upload failed: {response.text}")
|
|
147
|
+
|
|
148
|
+
return response.json()
|
|
149
|
+
|
|
150
|
+
async def search_content(self, query: str) -> list[dict]:
|
|
151
|
+
"""Search across SharePoint content."""
|
|
152
|
+
client = await self._get_client()
|
|
153
|
+
|
|
154
|
+
response = await client.get(
|
|
155
|
+
f"{self.GRAPH_URL}/search/query",
|
|
156
|
+
params={"query": query},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if response.status_code != 200:
|
|
160
|
+
raise Exception(f"SharePoint search failed: {response.text}")
|
|
161
|
+
|
|
162
|
+
return response.json().get("value", [])
|
|
163
|
+
|
|
164
|
+
async def get_list_items(
|
|
165
|
+
self,
|
|
166
|
+
site_id: str,
|
|
167
|
+
list_id: str,
|
|
168
|
+
) -> list[dict]:
|
|
169
|
+
"""Get items from a SharePoint list."""
|
|
170
|
+
client = await self._get_client()
|
|
171
|
+
|
|
172
|
+
response = await client.get(
|
|
173
|
+
f"{self.GRAPH_URL}/sites/{site_id}/lists/{list_id}/items",
|
|
174
|
+
params={"expand": "fields"},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if response.status_code != 200:
|
|
178
|
+
raise Exception(f"SharePoint get list items failed: {response.text}")
|
|
179
|
+
|
|
180
|
+
return response.json().get("value", [])
|
|
181
|
+
|
|
182
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
183
|
+
"""Read a site or drive item."""
|
|
184
|
+
return await self.get_site(resource_id)
|
|
185
|
+
|
|
186
|
+
async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
|
|
187
|
+
"""Upload a file."""
|
|
188
|
+
return await self.upload_file(
|
|
189
|
+
drive_id=data.get("drive_id"),
|
|
190
|
+
local_path=data.get("local_path"),
|
|
191
|
+
remote_path=data.get("remote_path"),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
async def _list_impl(
|
|
195
|
+
self, resource_type: str, filters: dict, **kwargs
|
|
196
|
+
) -> list[dict]:
|
|
197
|
+
"""List resources."""
|
|
198
|
+
if resource_type == "sites":
|
|
199
|
+
return await self.list_sites()
|
|
200
|
+
elif resource_type == "drives":
|
|
201
|
+
return await self.list_drives(filters.get("site_id"))
|
|
202
|
+
elif resource_type == "items":
|
|
203
|
+
return await self.list_items(
|
|
204
|
+
drive_id=filters.get("drive_id"),
|
|
205
|
+
folder_path=filters.get("folder_path", "root"),
|
|
206
|
+
)
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
async def _search_impl(self, query: str, **kwargs) -> list[dict]:
|
|
210
|
+
"""Search SharePoint content."""
|
|
211
|
+
return await self.search_content(query)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""NotionConnector — Notion API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from integrations.base import (
|
|
10
|
+
BaseConnector,
|
|
11
|
+
ConnectorCapability,
|
|
12
|
+
ConnectorInfo,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("hyperclaw.integrations.notion")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NotionConnector(BaseConnector):
|
|
19
|
+
"""
|
|
20
|
+
Notion API connector.
|
|
21
|
+
|
|
22
|
+
Required config:
|
|
23
|
+
- integration_token: Notion integration token
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
BASE_URL = "https://api.notion.com/v1"
|
|
27
|
+
NOTION_VERSION = "2022-06-28"
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: dict) -> None:
|
|
30
|
+
self.config = config
|
|
31
|
+
self.integration_token = config.get("integration_token", "")
|
|
32
|
+
|
|
33
|
+
if config.get("enabled", False) and not self.integration_token:
|
|
34
|
+
raise ValueError("NotionConnector requires integration_token")
|
|
35
|
+
|
|
36
|
+
self._client: httpx.AsyncClient | None = None
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def info(self) -> ConnectorInfo:
|
|
40
|
+
return ConnectorInfo(
|
|
41
|
+
connector_id="notion",
|
|
42
|
+
platform="notion",
|
|
43
|
+
category="productivity",
|
|
44
|
+
capabilities=frozenset(
|
|
45
|
+
[
|
|
46
|
+
ConnectorCapability.READ_DATA,
|
|
47
|
+
ConnectorCapability.WRITE_DATA,
|
|
48
|
+
ConnectorCapability.DELETE_DATA,
|
|
49
|
+
ConnectorCapability.LIST_DATA,
|
|
50
|
+
ConnectorCapability.SEARCH,
|
|
51
|
+
]
|
|
52
|
+
),
|
|
53
|
+
auth_type="bearer",
|
|
54
|
+
rate_limit_per_minute=100,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
58
|
+
if self._client is None:
|
|
59
|
+
self._client = httpx.AsyncClient(
|
|
60
|
+
timeout=30.0,
|
|
61
|
+
headers={
|
|
62
|
+
"Authorization": f"Bearer {self.integration_token}",
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
"Notion-Version": self.NOTION_VERSION,
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
return self._client
|
|
68
|
+
|
|
69
|
+
async def health(self) -> bool:
|
|
70
|
+
"""Check if Notion API is accessible."""
|
|
71
|
+
try:
|
|
72
|
+
client = await self._get_client()
|
|
73
|
+
response = await client.get(f"{self.BASE_URL}/users/me")
|
|
74
|
+
return response.status_code == 200
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Notion health check failed: {e}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
async def get_page(self, page_id: str) -> dict:
|
|
80
|
+
"""Get a page by ID."""
|
|
81
|
+
client = await self._get_client()
|
|
82
|
+
|
|
83
|
+
response = await client.get(f"{self.BASE_URL}/pages/{page_id}")
|
|
84
|
+
|
|
85
|
+
if response.status_code != 200:
|
|
86
|
+
raise Exception(f"Notion get page failed: {response.text}")
|
|
87
|
+
|
|
88
|
+
return response.json()
|
|
89
|
+
|
|
90
|
+
async def create_page(
|
|
91
|
+
self,
|
|
92
|
+
parent_id: str,
|
|
93
|
+
title: str,
|
|
94
|
+
content_blocks: list[dict] | None = None,
|
|
95
|
+
) -> dict:
|
|
96
|
+
"""Create a new page."""
|
|
97
|
+
client = await self._get_client()
|
|
98
|
+
|
|
99
|
+
body = {
|
|
100
|
+
"parent": {"page_id": parent_id},
|
|
101
|
+
"properties": {
|
|
102
|
+
"title": {"title": [{"text": {"content": title}}]}
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if content_blocks:
|
|
107
|
+
body["children"] = content_blocks
|
|
108
|
+
|
|
109
|
+
response = await client.post(
|
|
110
|
+
f"{self.BASE_URL}/pages",
|
|
111
|
+
json=body,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if response.status_code not in (200, 201):
|
|
115
|
+
raise Exception(f"Notion create page failed: {response.text}")
|
|
116
|
+
|
|
117
|
+
return response.json()
|
|
118
|
+
|
|
119
|
+
async def update_page(
|
|
120
|
+
self,
|
|
121
|
+
page_id: str,
|
|
122
|
+
properties: dict,
|
|
123
|
+
) -> dict:
|
|
124
|
+
"""Update page properties."""
|
|
125
|
+
client = await self._get_client()
|
|
126
|
+
|
|
127
|
+
response = await client.patch(
|
|
128
|
+
f"{self.BASE_URL}/pages/{page_id}",
|
|
129
|
+
json={"properties": properties},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if response.status_code != 200:
|
|
133
|
+
raise Exception(f"Notion update page failed: {response.text}")
|
|
134
|
+
|
|
135
|
+
return response.json()
|
|
136
|
+
|
|
137
|
+
async def query_database(
|
|
138
|
+
self,
|
|
139
|
+
database_id: str,
|
|
140
|
+
filter: dict | None = None,
|
|
141
|
+
sort: list[dict] | None = None,
|
|
142
|
+
) -> list[dict]:
|
|
143
|
+
"""Query a database."""
|
|
144
|
+
client = await self._get_client()
|
|
145
|
+
|
|
146
|
+
body = {}
|
|
147
|
+
if filter:
|
|
148
|
+
body["filter"] = filter
|
|
149
|
+
if sort:
|
|
150
|
+
body["sorts"] = sort
|
|
151
|
+
|
|
152
|
+
response = await client.post(
|
|
153
|
+
f"{self.BASE_URL}/databases/{database_id}/query",
|
|
154
|
+
json=body,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if response.status_code != 200:
|
|
158
|
+
raise Exception(f"Notion query database failed: {response.text}")
|
|
159
|
+
|
|
160
|
+
return response.json().get("results", [])
|
|
161
|
+
|
|
162
|
+
async def create_database_entry(
|
|
163
|
+
self,
|
|
164
|
+
database_id: str,
|
|
165
|
+
properties: dict,
|
|
166
|
+
) -> dict:
|
|
167
|
+
"""Create a new entry in a database."""
|
|
168
|
+
client = await self._get_client()
|
|
169
|
+
|
|
170
|
+
body = {
|
|
171
|
+
"parent": {"database_id": database_id},
|
|
172
|
+
"properties": properties,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
response = await client.post(
|
|
176
|
+
f"{self.BASE_URL}/pages",
|
|
177
|
+
json=body,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if response.status_code not in (200, 201):
|
|
181
|
+
raise Exception(f"Notion create entry failed: {response.text}")
|
|
182
|
+
|
|
183
|
+
return response.json()
|
|
184
|
+
|
|
185
|
+
async def search(self, query: str) -> list[dict]:
|
|
186
|
+
"""Search Notion workspace."""
|
|
187
|
+
client = await self._get_client()
|
|
188
|
+
|
|
189
|
+
response = await client.post(
|
|
190
|
+
f"{self.BASE_URL}/search",
|
|
191
|
+
json={"query": query},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if response.status_code != 200:
|
|
195
|
+
raise Exception(f"Notion search failed: {response.text}")
|
|
196
|
+
|
|
197
|
+
return response.json().get("results", [])
|
|
198
|
+
|
|
199
|
+
async def archive_page(self, page_id: str) -> bool:
|
|
200
|
+
"""Archive (soft delete) a page."""
|
|
201
|
+
client = await self._get_client()
|
|
202
|
+
|
|
203
|
+
response = await client.patch(
|
|
204
|
+
f"{self.BASE_URL}/pages/{page_id}",
|
|
205
|
+
json={"archived": True},
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return response.status_code == 200
|
|
209
|
+
|
|
210
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
211
|
+
"""Read a page."""
|
|
212
|
+
return await self.get_page(resource_id)
|
|
213
|
+
|
|
214
|
+
async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
|
|
215
|
+
"""Create a page or database entry."""
|
|
216
|
+
if resource_type == "database_entry":
|
|
217
|
+
return await self.create_database_entry(
|
|
218
|
+
database_id=data.get("database_id"),
|
|
219
|
+
properties=data.get("properties", {}),
|
|
220
|
+
)
|
|
221
|
+
return await self.create_page(
|
|
222
|
+
parent_id=data.get("parent_id"),
|
|
223
|
+
title=data.get("title", "Untitled"),
|
|
224
|
+
content_blocks=data.get("content_blocks"),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
|
|
228
|
+
"""Archive a page."""
|
|
229
|
+
return await self.archive_page(resource_id)
|
|
230
|
+
|
|
231
|
+
async def _list_impl(
|
|
232
|
+
self, resource_type: str, filters: dict, **kwargs
|
|
233
|
+
) -> list[dict]:
|
|
234
|
+
"""Query a database."""
|
|
235
|
+
return await self.query_database(
|
|
236
|
+
database_id=filters.get("database_id"),
|
|
237
|
+
filter=filters.get("filter"),
|
|
238
|
+
sort=filters.get("sort"),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
async def _search_impl(self, query: str, **kwargs) -> list[dict]:
|
|
242
|
+
"""Search implementation."""
|
|
243
|
+
return await self.search(query)
|