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.
Files changed (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,730 @@
1
+ """
2
+ Social Media Connectors.
3
+ Integration with Twitter/X, LinkedIn, Instagram, Facebook, YouTube, Bluesky, Mastodon, Reddit, TikTok.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import logging
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from typing import Any
13
+
14
+ import httpx
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class SocialPost:
21
+ """Social media post."""
22
+ id: str
23
+ text: str
24
+ author: str | None = None
25
+ created_at: datetime | None = None
26
+ likes: int = 0
27
+ shares: int = 0
28
+ comments: int = 0
29
+ url: str | None = None
30
+ media: list[str] = field(default_factory=list)
31
+ raw: dict = field(default_factory=dict)
32
+
33
+
34
+ class SocialConnector(ABC):
35
+ """Abstract base for social media connectors."""
36
+
37
+ name: str = "base"
38
+
39
+ @abstractmethod
40
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
41
+ """Create a new post."""
42
+ pass
43
+
44
+ @abstractmethod
45
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
46
+ """Get recent timeline posts."""
47
+ pass
48
+
49
+
50
+ # ═══════════════════════════════════════════════════════════════════════════════
51
+ # TWITTER/X
52
+ # ═══════════════════════════════════════════════════════════════════════════════
53
+
54
+ class TwitterConnector(SocialConnector):
55
+ """Twitter/X API connector."""
56
+
57
+ name = "twitter"
58
+
59
+ def __init__(
60
+ self,
61
+ bearer_token: str | None = None,
62
+ api_key: str | None = None,
63
+ api_secret: str | None = None,
64
+ access_token: str | None = None,
65
+ access_secret: str | None = None,
66
+ ):
67
+ self.bearer_token = bearer_token or os.environ.get("TWITTER_BEARER_TOKEN")
68
+ self.api_key = api_key or os.environ.get("TWITTER_API_KEY")
69
+ self.api_secret = api_secret or os.environ.get("TWITTER_API_SECRET")
70
+ self.access_token = access_token or os.environ.get("TWITTER_ACCESS_TOKEN")
71
+ self.access_secret = access_secret or os.environ.get("TWITTER_ACCESS_SECRET")
72
+ self.base_url = "https://api.twitter.com/2"
73
+
74
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
75
+ # Twitter API v2 requires OAuth 1.0a for posting
76
+ # Using requests-oauthlib in production
77
+ async with httpx.AsyncClient() as client:
78
+ response = await client.post(
79
+ f"{self.base_url}/tweets",
80
+ headers={"Authorization": f"Bearer {self.bearer_token}"},
81
+ json={"text": text},
82
+ timeout=30.0,
83
+ )
84
+ response.raise_for_status()
85
+ data = response.json()
86
+
87
+ return SocialPost(
88
+ id=data["data"]["id"],
89
+ text=text,
90
+ url=f"https://twitter.com/i/status/{data['data']['id']}",
91
+ raw=data,
92
+ )
93
+
94
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
95
+ async with httpx.AsyncClient() as client:
96
+ response = await client.get(
97
+ f"{self.base_url}/users/me/timelines/reverse_chronological",
98
+ headers={"Authorization": f"Bearer {self.bearer_token}"},
99
+ params={"max_results": min(limit, 100)},
100
+ timeout=30.0,
101
+ )
102
+ response.raise_for_status()
103
+ data = response.json()
104
+
105
+ return [
106
+ SocialPost(
107
+ id=tweet["id"],
108
+ text=tweet.get("text", ""),
109
+ created_at=datetime.fromisoformat(tweet["created_at"].replace("Z", "+00:00"))
110
+ if tweet.get("created_at") else None,
111
+ raw=tweet,
112
+ )
113
+ for tweet in data.get("data", [])
114
+ ]
115
+
116
+ async def search(self, query: str, limit: int = 20) -> list[SocialPost]:
117
+ """Search tweets."""
118
+ async with httpx.AsyncClient() as client:
119
+ response = await client.get(
120
+ f"{self.base_url}/tweets/search/recent",
121
+ headers={"Authorization": f"Bearer {self.bearer_token}"},
122
+ params={"query": query, "max_results": min(limit, 100)},
123
+ timeout=30.0,
124
+ )
125
+ response.raise_for_status()
126
+ data = response.json()
127
+
128
+ return [
129
+ SocialPost(id=tweet["id"], text=tweet.get("text", ""), raw=tweet)
130
+ for tweet in data.get("data", [])
131
+ ]
132
+
133
+
134
+ # ═══════════════════════════════════════════════════════════════════════════════
135
+ # LINKEDIN
136
+ # ═══════════════════════════════════════════════════════════════════════════════
137
+
138
+ class LinkedInConnector(SocialConnector):
139
+ """LinkedIn API connector."""
140
+
141
+ name = "linkedin"
142
+
143
+ def __init__(self, access_token: str | None = None):
144
+ self.access_token = access_token or os.environ.get("LINKEDIN_ACCESS_TOKEN")
145
+ self.base_url = "https://api.linkedin.com/v2"
146
+
147
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
148
+ # Get user URN first
149
+ async with httpx.AsyncClient() as client:
150
+ me_response = await client.get(
151
+ f"{self.base_url}/me",
152
+ headers={"Authorization": f"Bearer {self.access_token}"},
153
+ timeout=30.0,
154
+ )
155
+ me_response.raise_for_status()
156
+ user_urn = f"urn:li:person:{me_response.json()['id']}"
157
+
158
+ # Create post
159
+ response = await client.post(
160
+ f"{self.base_url}/ugcPosts",
161
+ headers={
162
+ "Authorization": f"Bearer {self.access_token}",
163
+ "X-Restli-Protocol-Version": "2.0.0",
164
+ },
165
+ json={
166
+ "author": user_urn,
167
+ "lifecycleState": "PUBLISHED",
168
+ "specificContent": {
169
+ "com.linkedin.ugc.ShareContent": {
170
+ "shareCommentary": {"text": text},
171
+ "shareMediaCategory": "NONE",
172
+ }
173
+ },
174
+ "visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"},
175
+ },
176
+ timeout=30.0,
177
+ )
178
+ response.raise_for_status()
179
+ data = response.json()
180
+
181
+ return SocialPost(id=data["id"], text=text, raw=data)
182
+
183
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
184
+ # LinkedIn doesn't provide a public timeline API
185
+ return []
186
+
187
+
188
+ # ═══════════════════════════════════════════════════════════════════════════════
189
+ # INSTAGRAM
190
+ # ═══════════════════════════════════════════════════════════════════════════════
191
+
192
+ class InstagramConnector(SocialConnector):
193
+ """Instagram Graph API connector (requires Facebook Business account)."""
194
+
195
+ name = "instagram"
196
+
197
+ def __init__(self, access_token: str | None = None, account_id: str | None = None):
198
+ self.access_token = access_token or os.environ.get("INSTAGRAM_ACCESS_TOKEN")
199
+ self.account_id = account_id or os.environ.get("INSTAGRAM_ACCOUNT_ID")
200
+ self.base_url = "https://graph.facebook.com/v18.0"
201
+
202
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
203
+ if not media:
204
+ raise ValueError("Instagram requires at least one media item")
205
+
206
+ async with httpx.AsyncClient() as client:
207
+ # Create media container
208
+ container_response = await client.post(
209
+ f"{self.base_url}/{self.account_id}/media",
210
+ params={
211
+ "access_token": self.access_token,
212
+ "image_url": media[0],
213
+ "caption": text,
214
+ },
215
+ timeout=30.0,
216
+ )
217
+ container_response.raise_for_status()
218
+ container_id = container_response.json()["id"]
219
+
220
+ # Publish the container
221
+ publish_response = await client.post(
222
+ f"{self.base_url}/{self.account_id}/media_publish",
223
+ params={
224
+ "access_token": self.access_token,
225
+ "creation_id": container_id,
226
+ },
227
+ timeout=30.0,
228
+ )
229
+ publish_response.raise_for_status()
230
+ data = publish_response.json()
231
+
232
+ return SocialPost(id=data["id"], text=text, media=media, raw=data)
233
+
234
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
235
+ async with httpx.AsyncClient() as client:
236
+ response = await client.get(
237
+ f"{self.base_url}/{self.account_id}/media",
238
+ params={
239
+ "access_token": self.access_token,
240
+ "fields": "id,caption,media_type,media_url,timestamp,like_count,comments_count",
241
+ "limit": limit,
242
+ },
243
+ timeout=30.0,
244
+ )
245
+ response.raise_for_status()
246
+ data = response.json()
247
+
248
+ return [
249
+ SocialPost(
250
+ id=post["id"],
251
+ text=post.get("caption", ""),
252
+ created_at=datetime.fromisoformat(post["timestamp"].replace("Z", "+00:00"))
253
+ if post.get("timestamp") else None,
254
+ likes=post.get("like_count", 0),
255
+ comments=post.get("comments_count", 0),
256
+ media=[post.get("media_url")] if post.get("media_url") else [],
257
+ raw=post,
258
+ )
259
+ for post in data.get("data", [])
260
+ ]
261
+
262
+
263
+ # ═══════════════════════════════════════════════════════════════════════════════
264
+ # FACEBOOK
265
+ # ═══════════════════════════════════════════════════════════════════════════════
266
+
267
+ class FacebookConnector(SocialConnector):
268
+ """Facebook Graph API connector."""
269
+
270
+ name = "facebook"
271
+
272
+ def __init__(self, access_token: str | None = None, page_id: str | None = None):
273
+ self.access_token = access_token or os.environ.get("FACEBOOK_ACCESS_TOKEN")
274
+ self.page_id = page_id or os.environ.get("FACEBOOK_PAGE_ID")
275
+ self.base_url = "https://graph.facebook.com/v18.0"
276
+
277
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
278
+ async with httpx.AsyncClient() as client:
279
+ endpoint = f"{self.base_url}/{self.page_id}/feed"
280
+ params: dict[str, Any] = {"access_token": self.access_token, "message": text}
281
+
282
+ if media:
283
+ params["link"] = media[0]
284
+
285
+ response = await client.post(endpoint, params=params, timeout=30.0)
286
+ response.raise_for_status()
287
+ data = response.json()
288
+
289
+ return SocialPost(
290
+ id=data["id"],
291
+ text=text,
292
+ url=f"https://facebook.com/{data['id']}",
293
+ raw=data,
294
+ )
295
+
296
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
297
+ async with httpx.AsyncClient() as client:
298
+ response = await client.get(
299
+ f"{self.base_url}/{self.page_id}/feed",
300
+ params={
301
+ "access_token": self.access_token,
302
+ "fields": "id,message,created_time,shares,likes.summary(true),comments.summary(true)",
303
+ "limit": limit,
304
+ },
305
+ timeout=30.0,
306
+ )
307
+ response.raise_for_status()
308
+ data = response.json()
309
+
310
+ return [
311
+ SocialPost(
312
+ id=post["id"],
313
+ text=post.get("message", ""),
314
+ created_at=datetime.fromisoformat(post["created_time"].replace("+0000", "+00:00"))
315
+ if post.get("created_time") else None,
316
+ likes=post.get("likes", {}).get("summary", {}).get("total_count", 0),
317
+ shares=post.get("shares", {}).get("count", 0),
318
+ comments=post.get("comments", {}).get("summary", {}).get("total_count", 0),
319
+ raw=post,
320
+ )
321
+ for post in data.get("data", [])
322
+ ]
323
+
324
+
325
+ # ═══════════════════════════════════════════════════════════════════════════════
326
+ # YOUTUBE
327
+ # ═══════════════════════════════════════════════════════════════════════════════
328
+
329
+ class YouTubeConnector(SocialConnector):
330
+ """YouTube Data API connector."""
331
+
332
+ name = "youtube"
333
+
334
+ def __init__(self, api_key: str | None = None, access_token: str | None = None):
335
+ self.api_key = api_key or os.environ.get("YOUTUBE_API_KEY")
336
+ self.access_token = access_token or os.environ.get("YOUTUBE_ACCESS_TOKEN")
337
+ self.base_url = "https://www.googleapis.com/youtube/v3"
338
+
339
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
340
+ # YouTube posting requires video upload which is more complex
341
+ raise NotImplementedError("YouTube video upload requires resumable upload API")
342
+
343
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
344
+ """Get channel's recent videos."""
345
+ channel_id = kwargs.get("channel_id")
346
+ if not channel_id:
347
+ return []
348
+
349
+ async with httpx.AsyncClient() as client:
350
+ response = await client.get(
351
+ f"{self.base_url}/search",
352
+ params={
353
+ "key": self.api_key,
354
+ "channelId": channel_id,
355
+ "part": "snippet",
356
+ "order": "date",
357
+ "maxResults": limit,
358
+ "type": "video",
359
+ },
360
+ timeout=30.0,
361
+ )
362
+ response.raise_for_status()
363
+ data = response.json()
364
+
365
+ return [
366
+ SocialPost(
367
+ id=item["id"]["videoId"],
368
+ text=item["snippet"]["title"],
369
+ author=item["snippet"]["channelTitle"],
370
+ created_at=datetime.fromisoformat(
371
+ item["snippet"]["publishedAt"].replace("Z", "+00:00")
372
+ ),
373
+ url=f"https://youtube.com/watch?v={item['id']['videoId']}",
374
+ raw=item,
375
+ )
376
+ for item in data.get("items", [])
377
+ ]
378
+
379
+ async def search(self, query: str, limit: int = 20) -> list[SocialPost]:
380
+ """Search YouTube videos."""
381
+ async with httpx.AsyncClient() as client:
382
+ response = await client.get(
383
+ f"{self.base_url}/search",
384
+ params={
385
+ "key": self.api_key,
386
+ "q": query,
387
+ "part": "snippet",
388
+ "maxResults": limit,
389
+ "type": "video",
390
+ },
391
+ timeout=30.0,
392
+ )
393
+ response.raise_for_status()
394
+ data = response.json()
395
+
396
+ return [
397
+ SocialPost(
398
+ id=item["id"]["videoId"],
399
+ text=item["snippet"]["title"],
400
+ author=item["snippet"]["channelTitle"],
401
+ url=f"https://youtube.com/watch?v={item['id']['videoId']}",
402
+ raw=item,
403
+ )
404
+ for item in data.get("items", [])
405
+ ]
406
+
407
+
408
+ # ═══════════════════════════════════════════════════════════════════════════════
409
+ # BLUESKY
410
+ # ═══════════════════════════════════════════════════════════════════════════════
411
+
412
+ class BlueskyConnector(SocialConnector):
413
+ """Bluesky AT Protocol connector."""
414
+
415
+ name = "bluesky"
416
+
417
+ def __init__(
418
+ self,
419
+ handle: str | None = None,
420
+ password: str | None = None,
421
+ access_jwt: str | None = None,
422
+ ):
423
+ self.handle = handle or os.environ.get("BLUESKY_HANDLE")
424
+ self.password = password or os.environ.get("BLUESKY_PASSWORD")
425
+ self.access_jwt = access_jwt
426
+ self.base_url = "https://bsky.social/xrpc"
427
+
428
+ async def _ensure_auth(self) -> str:
429
+ """Ensure we have a valid access token."""
430
+ if self.access_jwt:
431
+ return self.access_jwt
432
+
433
+ async with httpx.AsyncClient() as client:
434
+ response = await client.post(
435
+ f"{self.base_url}/com.atproto.server.createSession",
436
+ json={"identifier": self.handle, "password": self.password},
437
+ timeout=30.0,
438
+ )
439
+ response.raise_for_status()
440
+ data = response.json()
441
+ self.access_jwt = data["accessJwt"]
442
+ return self.access_jwt
443
+
444
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
445
+ token = await self._ensure_auth()
446
+
447
+ async with httpx.AsyncClient() as client:
448
+ response = await client.post(
449
+ f"{self.base_url}/com.atproto.repo.createRecord",
450
+ headers={"Authorization": f"Bearer {token}"},
451
+ json={
452
+ "repo": self.handle,
453
+ "collection": "app.bsky.feed.post",
454
+ "record": {
455
+ "$type": "app.bsky.feed.post",
456
+ "text": text,
457
+ "createdAt": datetime.utcnow().isoformat() + "Z",
458
+ },
459
+ },
460
+ timeout=30.0,
461
+ )
462
+ response.raise_for_status()
463
+ data = response.json()
464
+
465
+ return SocialPost(id=data["uri"], text=text, raw=data)
466
+
467
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
468
+ token = await self._ensure_auth()
469
+
470
+ async with httpx.AsyncClient() as client:
471
+ response = await client.get(
472
+ f"{self.base_url}/app.bsky.feed.getTimeline",
473
+ headers={"Authorization": f"Bearer {token}"},
474
+ params={"limit": limit},
475
+ timeout=30.0,
476
+ )
477
+ response.raise_for_status()
478
+ data = response.json()
479
+
480
+ return [
481
+ SocialPost(
482
+ id=item["post"]["uri"],
483
+ text=item["post"]["record"].get("text", ""),
484
+ author=item["post"]["author"].get("handle"),
485
+ raw=item,
486
+ )
487
+ for item in data.get("feed", [])
488
+ ]
489
+
490
+
491
+ # ═══════════════════════════════════════════════════════════════════════════════
492
+ # MASTODON
493
+ # ═══════════════════════════════════════════════════════════════════════════════
494
+
495
+ class MastodonConnector(SocialConnector):
496
+ """Mastodon API connector."""
497
+
498
+ name = "mastodon"
499
+
500
+ def __init__(
501
+ self,
502
+ instance_url: str | None = None,
503
+ access_token: str | None = None,
504
+ ):
505
+ self.instance_url = (instance_url or os.environ.get("MASTODON_INSTANCE", "https://mastodon.social")).rstrip("/")
506
+ self.access_token = access_token or os.environ.get("MASTODON_ACCESS_TOKEN")
507
+
508
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
509
+ async with httpx.AsyncClient() as client:
510
+ response = await client.post(
511
+ f"{self.instance_url}/api/v1/statuses",
512
+ headers={"Authorization": f"Bearer {self.access_token}"},
513
+ json={"status": text},
514
+ timeout=30.0,
515
+ )
516
+ response.raise_for_status()
517
+ data = response.json()
518
+
519
+ return SocialPost(
520
+ id=data["id"],
521
+ text=text,
522
+ url=data.get("url"),
523
+ raw=data,
524
+ )
525
+
526
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
527
+ async with httpx.AsyncClient() as client:
528
+ response = await client.get(
529
+ f"{self.instance_url}/api/v1/timelines/home",
530
+ headers={"Authorization": f"Bearer {self.access_token}"},
531
+ params={"limit": limit},
532
+ timeout=30.0,
533
+ )
534
+ response.raise_for_status()
535
+ data = response.json()
536
+
537
+ return [
538
+ SocialPost(
539
+ id=status["id"],
540
+ text=status.get("content", ""),
541
+ author=status["account"]["username"],
542
+ created_at=datetime.fromisoformat(status["created_at"].replace("Z", "+00:00"))
543
+ if status.get("created_at") else None,
544
+ likes=status.get("favourites_count", 0),
545
+ shares=status.get("reblogs_count", 0),
546
+ comments=status.get("replies_count", 0),
547
+ url=status.get("url"),
548
+ raw=status,
549
+ )
550
+ for status in data
551
+ ]
552
+
553
+
554
+ # ═══════════════════════════════════════════════════════════════════════════════
555
+ # REDDIT
556
+ # ═══════════════════════════════════════════════════════════════════════════════
557
+
558
+ class RedditConnector(SocialConnector):
559
+ """Reddit API connector."""
560
+
561
+ name = "reddit"
562
+
563
+ def __init__(
564
+ self,
565
+ client_id: str | None = None,
566
+ client_secret: str | None = None,
567
+ username: str | None = None,
568
+ password: str | None = None,
569
+ ):
570
+ self.client_id = client_id or os.environ.get("REDDIT_CLIENT_ID")
571
+ self.client_secret = client_secret or os.environ.get("REDDIT_CLIENT_SECRET")
572
+ self.username = username or os.environ.get("REDDIT_USERNAME")
573
+ self.password = password or os.environ.get("REDDIT_PASSWORD")
574
+ self.access_token: str | None = None
575
+ self.base_url = "https://oauth.reddit.com"
576
+
577
+ async def _ensure_auth(self) -> str:
578
+ if self.access_token:
579
+ return self.access_token
580
+
581
+ async with httpx.AsyncClient() as client:
582
+ response = await client.post(
583
+ "https://www.reddit.com/api/v1/access_token",
584
+ auth=(self.client_id, self.client_secret),
585
+ data={
586
+ "grant_type": "password",
587
+ "username": self.username,
588
+ "password": self.password,
589
+ },
590
+ headers={"User-Agent": "HyperClaw/1.0"},
591
+ timeout=30.0,
592
+ )
593
+ response.raise_for_status()
594
+ data = response.json()
595
+ self.access_token = data["access_token"]
596
+ return self.access_token
597
+
598
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
599
+ token = await self._ensure_auth()
600
+ subreddit = kwargs.get("subreddit", "test")
601
+ title = kwargs.get("title", text[:100])
602
+
603
+ async with httpx.AsyncClient() as client:
604
+ response = await client.post(
605
+ f"{self.base_url}/api/submit",
606
+ headers={
607
+ "Authorization": f"Bearer {token}",
608
+ "User-Agent": "HyperClaw/1.0",
609
+ },
610
+ data={
611
+ "sr": subreddit,
612
+ "kind": "self",
613
+ "title": title,
614
+ "text": text,
615
+ },
616
+ timeout=30.0,
617
+ )
618
+ response.raise_for_status()
619
+ data = response.json()
620
+
621
+ return SocialPost(
622
+ id=data["json"]["data"]["id"],
623
+ text=text,
624
+ url=data["json"]["data"]["url"],
625
+ raw=data,
626
+ )
627
+
628
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
629
+ token = await self._ensure_auth()
630
+ subreddit = kwargs.get("subreddit", "all")
631
+
632
+ async with httpx.AsyncClient() as client:
633
+ response = await client.get(
634
+ f"{self.base_url}/r/{subreddit}/hot",
635
+ headers={
636
+ "Authorization": f"Bearer {token}",
637
+ "User-Agent": "HyperClaw/1.0",
638
+ },
639
+ params={"limit": limit},
640
+ timeout=30.0,
641
+ )
642
+ response.raise_for_status()
643
+ data = response.json()
644
+
645
+ return [
646
+ SocialPost(
647
+ id=post["data"]["id"],
648
+ text=post["data"].get("selftext", ""),
649
+ author=post["data"].get("author"),
650
+ likes=post["data"].get("ups", 0),
651
+ comments=post["data"].get("num_comments", 0),
652
+ url=f"https://reddit.com{post['data']['permalink']}",
653
+ raw=post["data"],
654
+ )
655
+ for post in data["data"]["children"]
656
+ ]
657
+
658
+
659
+ # ═══════════════════════════════════════════════════════════════════════════════
660
+ # TIKTOK
661
+ # ═══════════════════════════════════════════════════════════════════════════════
662
+
663
+ class TikTokConnector(SocialConnector):
664
+ """TikTok API connector (Content Posting API)."""
665
+
666
+ name = "tiktok"
667
+
668
+ def __init__(self, access_token: str | None = None):
669
+ self.access_token = access_token or os.environ.get("TIKTOK_ACCESS_TOKEN")
670
+ self.base_url = "https://open.tiktokapis.com/v2"
671
+
672
+ async def post(self, text: str, media: list[str] | None = None, **kwargs) -> SocialPost:
673
+ # TikTok requires video content
674
+ if not media:
675
+ raise ValueError("TikTok requires video content")
676
+
677
+ async with httpx.AsyncClient() as client:
678
+ # Initialize video upload
679
+ response = await client.post(
680
+ f"{self.base_url}/post/publish/content/init/",
681
+ headers={"Authorization": f"Bearer {self.access_token}"},
682
+ json={
683
+ "post_info": {
684
+ "title": text[:150],
685
+ "privacy_level": "PUBLIC_TO_EVERYONE",
686
+ },
687
+ "source_info": {
688
+ "source": "PULL_FROM_URL",
689
+ "video_url": media[0],
690
+ },
691
+ },
692
+ timeout=60.0,
693
+ )
694
+ response.raise_for_status()
695
+ data = response.json()
696
+
697
+ return SocialPost(id=data.get("publish_id", ""), text=text, raw=data)
698
+
699
+ async def get_timeline(self, limit: int = 20, **kwargs) -> list[SocialPost]:
700
+ # TikTok doesn't provide a timeline API for third-party apps
701
+ return []
702
+
703
+
704
+ # ═══════════════════════════════════════════════════════════════════════════════
705
+ # PROVIDER FACTORY
706
+ # ═══════════════════════════════════════════════════════════════════════════════
707
+
708
+ SOCIAL_CONNECTORS: dict[str, type[SocialConnector]] = {
709
+ "twitter": TwitterConnector,
710
+ "x": TwitterConnector,
711
+ "linkedin": LinkedInConnector,
712
+ "instagram": InstagramConnector,
713
+ "facebook": FacebookConnector,
714
+ "youtube": YouTubeConnector,
715
+ "bluesky": BlueskyConnector,
716
+ "mastodon": MastodonConnector,
717
+ "reddit": RedditConnector,
718
+ "tiktok": TikTokConnector,
719
+ }
720
+
721
+
722
+ def get_social_connector(name: str, **kwargs) -> SocialConnector:
723
+ """Get a social media connector by name."""
724
+ name = name.lower()
725
+
726
+ if name not in SOCIAL_CONNECTORS:
727
+ available = ", ".join(sorted(SOCIAL_CONNECTORS.keys()))
728
+ raise ValueError(f"Unknown social connector: {name}. Available: {available}")
729
+
730
+ return SOCIAL_CONNECTORS[name](**kwargs)