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,182 @@
|
|
|
1
|
+
"""PostgresConnector — PostgreSQL database integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from integrations.base import (
|
|
8
|
+
BaseConnector,
|
|
9
|
+
ConnectorCapability,
|
|
10
|
+
ConnectorInfo,
|
|
11
|
+
ConnectorUnavailableError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("hyperclaw.integrations.postgres")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PostgresConnector(BaseConnector):
|
|
18
|
+
"""
|
|
19
|
+
PostgreSQL database connector using asyncpg.
|
|
20
|
+
|
|
21
|
+
Required config:
|
|
22
|
+
- connection_string: PostgreSQL connection URL
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, config: dict) -> None:
|
|
26
|
+
self.config = config
|
|
27
|
+
self.connection_string = config.get("connection_string", "")
|
|
28
|
+
|
|
29
|
+
if config.get("enabled", False) and not self.connection_string:
|
|
30
|
+
raise ValueError("PostgresConnector requires connection_string")
|
|
31
|
+
|
|
32
|
+
self._pool = None
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def info(self) -> ConnectorInfo:
|
|
36
|
+
return ConnectorInfo(
|
|
37
|
+
connector_id="postgres",
|
|
38
|
+
platform="postgres",
|
|
39
|
+
category="data",
|
|
40
|
+
capabilities=frozenset(
|
|
41
|
+
[
|
|
42
|
+
ConnectorCapability.READ_DATA,
|
|
43
|
+
ConnectorCapability.WRITE_DATA,
|
|
44
|
+
ConnectorCapability.DELETE_DATA,
|
|
45
|
+
ConnectorCapability.LIST_DATA,
|
|
46
|
+
ConnectorCapability.SEARCH,
|
|
47
|
+
]
|
|
48
|
+
),
|
|
49
|
+
auth_type="api_key",
|
|
50
|
+
rate_limit_per_minute=1000,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
async def _ensure_pool(self):
|
|
54
|
+
"""Ensure connection pool is created."""
|
|
55
|
+
if self._pool is None:
|
|
56
|
+
try:
|
|
57
|
+
import asyncpg
|
|
58
|
+
except ImportError:
|
|
59
|
+
raise ConnectorUnavailableError("asyncpg not installed")
|
|
60
|
+
|
|
61
|
+
self._pool = await asyncpg.create_pool(
|
|
62
|
+
self.connection_string,
|
|
63
|
+
min_size=1,
|
|
64
|
+
max_size=10,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def health(self) -> bool:
|
|
68
|
+
"""Check if PostgreSQL connection is available."""
|
|
69
|
+
try:
|
|
70
|
+
import asyncpg # noqa: F401
|
|
71
|
+
except ImportError:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
await self._ensure_pool()
|
|
76
|
+
async with self._pool.acquire() as conn:
|
|
77
|
+
await conn.fetchval("SELECT 1")
|
|
78
|
+
return True
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"PostgreSQL health check failed: {e}")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
async def query(self, sql: str, params: list | None = None) -> list[dict]:
|
|
84
|
+
"""Execute a SQL query and return results."""
|
|
85
|
+
try:
|
|
86
|
+
import asyncpg # noqa: F401
|
|
87
|
+
except ImportError:
|
|
88
|
+
raise ConnectorUnavailableError("asyncpg not installed")
|
|
89
|
+
|
|
90
|
+
await self._ensure_pool()
|
|
91
|
+
|
|
92
|
+
async with self._pool.acquire() as conn:
|
|
93
|
+
if params:
|
|
94
|
+
rows = await conn.fetch(sql, *params)
|
|
95
|
+
else:
|
|
96
|
+
rows = await conn.fetch(sql)
|
|
97
|
+
|
|
98
|
+
return [dict(row) for row in rows]
|
|
99
|
+
|
|
100
|
+
async def execute(self, sql: str, params: list | None = None) -> dict:
|
|
101
|
+
"""Execute a SQL statement (INSERT/UPDATE/DELETE)."""
|
|
102
|
+
try:
|
|
103
|
+
import asyncpg # noqa: F401
|
|
104
|
+
except ImportError:
|
|
105
|
+
raise ConnectorUnavailableError("asyncpg not installed")
|
|
106
|
+
|
|
107
|
+
await self._ensure_pool()
|
|
108
|
+
|
|
109
|
+
async with self._pool.acquire() as conn:
|
|
110
|
+
if params:
|
|
111
|
+
result = await conn.execute(sql, *params)
|
|
112
|
+
else:
|
|
113
|
+
result = await conn.execute(sql)
|
|
114
|
+
|
|
115
|
+
return {"status": result}
|
|
116
|
+
|
|
117
|
+
async def list_tables(self) -> list[dict]:
|
|
118
|
+
"""List all tables in the database."""
|
|
119
|
+
sql = """
|
|
120
|
+
SELECT table_name, table_type
|
|
121
|
+
FROM information_schema.tables
|
|
122
|
+
WHERE table_schema = 'public'
|
|
123
|
+
ORDER BY table_name
|
|
124
|
+
"""
|
|
125
|
+
return await self.query(sql)
|
|
126
|
+
|
|
127
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
128
|
+
"""Read a row by ID."""
|
|
129
|
+
table = kwargs.get("table", "")
|
|
130
|
+
if not table:
|
|
131
|
+
raise ValueError("table parameter required")
|
|
132
|
+
|
|
133
|
+
result = await self.query(
|
|
134
|
+
f"SELECT * FROM {table} WHERE id = $1",
|
|
135
|
+
[resource_id],
|
|
136
|
+
)
|
|
137
|
+
return result[0] if result else {}
|
|
138
|
+
|
|
139
|
+
async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
|
|
140
|
+
"""Insert a row."""
|
|
141
|
+
columns = ", ".join(data.keys())
|
|
142
|
+
placeholders = ", ".join(f"${i+1}" for i in range(len(data)))
|
|
143
|
+
values = list(data.values())
|
|
144
|
+
|
|
145
|
+
result = await self.query(
|
|
146
|
+
f"INSERT INTO {resource_type} ({columns}) VALUES ({placeholders}) RETURNING *",
|
|
147
|
+
values,
|
|
148
|
+
)
|
|
149
|
+
return result[0] if result else {}
|
|
150
|
+
|
|
151
|
+
async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
|
|
152
|
+
"""Delete a row."""
|
|
153
|
+
table = kwargs.get("table", "")
|
|
154
|
+
if not table:
|
|
155
|
+
raise ValueError("table parameter required")
|
|
156
|
+
|
|
157
|
+
await self.execute(
|
|
158
|
+
f"DELETE FROM {table} WHERE id = $1",
|
|
159
|
+
[resource_id],
|
|
160
|
+
)
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
async def _list_impl(
|
|
164
|
+
self, resource_type: str, filters: dict, **kwargs
|
|
165
|
+
) -> list[dict]:
|
|
166
|
+
"""List rows from a table."""
|
|
167
|
+
sql = f"SELECT * FROM {resource_type}"
|
|
168
|
+
|
|
169
|
+
if filters:
|
|
170
|
+
conditions = []
|
|
171
|
+
params = []
|
|
172
|
+
for i, (key, value) in enumerate(filters.items()):
|
|
173
|
+
conditions.append(f"{key} = ${i+1}")
|
|
174
|
+
params.append(value)
|
|
175
|
+
sql += " WHERE " + " AND ".join(conditions)
|
|
176
|
+
return await self.query(sql, params)
|
|
177
|
+
|
|
178
|
+
return await self.query(sql)
|
|
179
|
+
|
|
180
|
+
async def _search_impl(self, query: str, **kwargs) -> list[dict]:
|
|
181
|
+
"""Execute a raw SQL query."""
|
|
182
|
+
return await self.query(query)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""SupabaseConnector — Supabase database 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.supabase")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SupabaseConnector(BaseConnector):
|
|
19
|
+
"""
|
|
20
|
+
Supabase database connector.
|
|
21
|
+
|
|
22
|
+
Required config:
|
|
23
|
+
- url: Supabase project URL
|
|
24
|
+
- service_role_key: Supabase service role key
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: dict) -> None:
|
|
28
|
+
self.config = config
|
|
29
|
+
self.url = config.get("url", "").rstrip("/")
|
|
30
|
+
self.service_role_key = config.get("service_role_key", "")
|
|
31
|
+
|
|
32
|
+
if config.get("enabled", False):
|
|
33
|
+
if not self.url or not self.service_role_key:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"SupabaseConnector requires url and service_role_key"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self._client: httpx.AsyncClient | None = None
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def info(self) -> ConnectorInfo:
|
|
42
|
+
return ConnectorInfo(
|
|
43
|
+
connector_id="supabase",
|
|
44
|
+
platform="supabase",
|
|
45
|
+
category="data",
|
|
46
|
+
capabilities=frozenset(
|
|
47
|
+
[
|
|
48
|
+
ConnectorCapability.READ_DATA,
|
|
49
|
+
ConnectorCapability.WRITE_DATA,
|
|
50
|
+
ConnectorCapability.DELETE_DATA,
|
|
51
|
+
ConnectorCapability.LIST_DATA,
|
|
52
|
+
ConnectorCapability.SEARCH,
|
|
53
|
+
]
|
|
54
|
+
),
|
|
55
|
+
auth_type="api_key",
|
|
56
|
+
rate_limit_per_minute=60,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
60
|
+
if self._client is None:
|
|
61
|
+
self._client = httpx.AsyncClient(
|
|
62
|
+
timeout=30.0,
|
|
63
|
+
headers={
|
|
64
|
+
"apikey": self.service_role_key,
|
|
65
|
+
"Authorization": f"Bearer {self.service_role_key}",
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
"Prefer": "return=representation",
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
return self._client
|
|
71
|
+
|
|
72
|
+
async def health(self) -> bool:
|
|
73
|
+
"""Check if Supabase API is accessible."""
|
|
74
|
+
try:
|
|
75
|
+
client = await self._get_client()
|
|
76
|
+
response = await client.get(f"{self.url}/rest/v1/")
|
|
77
|
+
return response.status_code == 200
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Supabase health check failed: {e}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
async def query(self, table: str, filters: dict | None = None) -> list[dict]:
|
|
83
|
+
"""Query a table with optional filters."""
|
|
84
|
+
client = await self._get_client()
|
|
85
|
+
|
|
86
|
+
url = f"{self.url}/rest/v1/{table}"
|
|
87
|
+
params = {}
|
|
88
|
+
|
|
89
|
+
if filters:
|
|
90
|
+
for key, value in filters.items():
|
|
91
|
+
params[key] = f"eq.{value}"
|
|
92
|
+
|
|
93
|
+
response = await client.get(url, params=params)
|
|
94
|
+
|
|
95
|
+
if response.status_code != 200:
|
|
96
|
+
raise Exception(f"Supabase query failed: {response.text}")
|
|
97
|
+
|
|
98
|
+
return response.json()
|
|
99
|
+
|
|
100
|
+
async def insert(self, table: str, data: dict) -> dict:
|
|
101
|
+
"""Insert a row into a table."""
|
|
102
|
+
client = await self._get_client()
|
|
103
|
+
|
|
104
|
+
response = await client.post(
|
|
105
|
+
f"{self.url}/rest/v1/{table}",
|
|
106
|
+
json=data,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if response.status_code not in (200, 201):
|
|
110
|
+
raise Exception(f"Supabase insert failed: {response.text}")
|
|
111
|
+
|
|
112
|
+
result = response.json()
|
|
113
|
+
return result[0] if isinstance(result, list) else result
|
|
114
|
+
|
|
115
|
+
async def update(self, table: str, id: str, data: dict) -> dict:
|
|
116
|
+
"""Update a row in a table."""
|
|
117
|
+
client = await self._get_client()
|
|
118
|
+
|
|
119
|
+
response = await client.patch(
|
|
120
|
+
f"{self.url}/rest/v1/{table}",
|
|
121
|
+
params={"id": f"eq.{id}"},
|
|
122
|
+
json=data,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if response.status_code not in (200, 204):
|
|
126
|
+
raise Exception(f"Supabase update failed: {response.text}")
|
|
127
|
+
|
|
128
|
+
result = response.json()
|
|
129
|
+
return result[0] if isinstance(result, list) and result else {}
|
|
130
|
+
|
|
131
|
+
async def delete(self, table: str, id: str) -> bool:
|
|
132
|
+
"""Delete a row from a table."""
|
|
133
|
+
client = await self._get_client()
|
|
134
|
+
|
|
135
|
+
response = await client.delete(
|
|
136
|
+
f"{self.url}/rest/v1/{table}",
|
|
137
|
+
params={"id": f"eq.{id}"},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if response.status_code not in (200, 204):
|
|
141
|
+
raise Exception(f"Supabase delete failed: {response.text}")
|
|
142
|
+
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
async def rpc(self, function_name: str, params: dict | None = None) -> dict:
|
|
146
|
+
"""Call a Supabase RPC function."""
|
|
147
|
+
client = await self._get_client()
|
|
148
|
+
|
|
149
|
+
response = await client.post(
|
|
150
|
+
f"{self.url}/rest/v1/rpc/{function_name}",
|
|
151
|
+
json=params or {},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if response.status_code != 200:
|
|
155
|
+
raise Exception(f"Supabase rpc failed: {response.text}")
|
|
156
|
+
|
|
157
|
+
return response.json()
|
|
158
|
+
|
|
159
|
+
async def _read_impl(self, resource_id: str, **kwargs) -> dict:
|
|
160
|
+
"""Read a row by ID."""
|
|
161
|
+
table = kwargs.get("table", "")
|
|
162
|
+
if not table:
|
|
163
|
+
raise ValueError("table parameter required")
|
|
164
|
+
|
|
165
|
+
result = await self.query(table, {"id": resource_id})
|
|
166
|
+
return result[0] if result else {}
|
|
167
|
+
|
|
168
|
+
async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
|
|
169
|
+
"""Insert a row."""
|
|
170
|
+
return await self.insert(resource_type, data)
|
|
171
|
+
|
|
172
|
+
async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
|
|
173
|
+
"""Delete a row."""
|
|
174
|
+
table = kwargs.get("table", "")
|
|
175
|
+
if not table:
|
|
176
|
+
raise ValueError("table parameter required")
|
|
177
|
+
|
|
178
|
+
return await self.delete(table, resource_id)
|
|
179
|
+
|
|
180
|
+
async def _list_impl(
|
|
181
|
+
self, resource_type: str, filters: dict, **kwargs
|
|
182
|
+
) -> list[dict]:
|
|
183
|
+
"""List rows from a table."""
|
|
184
|
+
return await self.query(resource_type, filters)
|
|
185
|
+
|
|
186
|
+
async def _search_impl(self, query: str, **kwargs) -> list[dict]:
|
|
187
|
+
"""Search using RPC or full-text search."""
|
|
188
|
+
table = kwargs.get("table", "")
|
|
189
|
+
column = kwargs.get("column", "")
|
|
190
|
+
|
|
191
|
+
if table and column:
|
|
192
|
+
# Use PostgREST full-text search
|
|
193
|
+
client = await self._get_client()
|
|
194
|
+
response = await client.get(
|
|
195
|
+
f"{self.url}/rest/v1/{table}",
|
|
196
|
+
params={column: f"ilike.*{query}*"},
|
|
197
|
+
)
|
|
198
|
+
if response.status_code == 200:
|
|
199
|
+
return response.json()
|
|
200
|
+
|
|
201
|
+
# Fallback to RPC if available
|
|
202
|
+
return await self.rpc("search", {"query": query})
|