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
telegram_listener.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HyperClaw Telegram Listener
|
|
4
|
+
Full duplex Telegram integration — receives messages, images, voice, files
|
|
5
|
+
Processes commands, routes to agents, downloads attachments, syncs with HyperClaw
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
import aiohttp
|
|
14
|
+
import aiofiles
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
import anthropic
|
|
19
|
+
from hyperclaw.api_utils import extract_text
|
|
20
|
+
from hyperclaw.cost_guard import max_output
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _load_env():
|
|
24
|
+
"""Populate os.environ from ~/.hyperclaw/.env so the listener works
|
|
25
|
+
under launchd, where no shell profile runs. Existing env vars win."""
|
|
26
|
+
env_file = Path.home() / ".hyperclaw" / ".env"
|
|
27
|
+
if env_file.exists():
|
|
28
|
+
for line in env_file.read_text().splitlines():
|
|
29
|
+
line = line.strip()
|
|
30
|
+
if line and not line.startswith("#") and "=" in line:
|
|
31
|
+
k, _, v = line.partition("=")
|
|
32
|
+
os.environ.setdefault(k.strip(), v.strip().strip('"'))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_load_env()
|
|
36
|
+
|
|
37
|
+
# ── Config ─────────────────────────────────────────────────────────────────────
|
|
38
|
+
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
|
|
39
|
+
# No explicit allowlist -> trust the owner's own chat (TELEGRAM_CHAT_ID),
|
|
40
|
+
# so a fresh install isn't locked out of its own bot.
|
|
41
|
+
_allowed = os.environ.get("TELEGRAM_ALLOWED_IDS", "") or os.environ.get("TELEGRAM_CHAT_ID", "0")
|
|
42
|
+
ALLOWED_CHAT_IDS = [int(x) for x in _allowed.split(",") if x.strip()]
|
|
43
|
+
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
44
|
+
BASE_URL = f"https://api.telegram.org/bot{BOT_TOKEN}"
|
|
45
|
+
FILE_URL = f"https://api.telegram.org/file/bot{BOT_TOKEN}"
|
|
46
|
+
DOWNLOAD_DIR = Path(str(Path.home() / ".hyperclaw/workspace/telegram_inbox"))
|
|
47
|
+
LOG_FILE = Path(str(Path.home() / ".hyperclaw/logs/telegram_listener.log"))
|
|
48
|
+
|
|
49
|
+
# ── Logging ────────────────────────────────────────────────────────────────────
|
|
50
|
+
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
logging.basicConfig(
|
|
54
|
+
level=logging.INFO,
|
|
55
|
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
56
|
+
handlers=[
|
|
57
|
+
logging.FileHandler(LOG_FILE),
|
|
58
|
+
logging.StreamHandler(sys.stdout)
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
log = logging.getLogger("hyperclaw.telegram")
|
|
62
|
+
|
|
63
|
+
# ── Anthropic client ───────────────────────────────────────────────────────────
|
|
64
|
+
from hyperclaw.cost_guard import guard_client # noqa: E402
|
|
65
|
+
client = guard_client(anthropic.Anthropic(api_key=ANTHROPIC_API_KEY), "anthropic")
|
|
66
|
+
|
|
67
|
+
# ── Message history for context ────────────────────────────────────────────────
|
|
68
|
+
# Per chat: one chat's conversation must never appear in another chat's
|
|
69
|
+
# context (spec §6 guest fence).
|
|
70
|
+
conversation_history: dict = {}
|
|
71
|
+
|
|
72
|
+
# ── Send message to Telegram ───────────────────────────────────────────────────
|
|
73
|
+
async def send_message(chat_id: int, text: str, session: aiohttp.ClientSession):
|
|
74
|
+
"""Send text message to Telegram"""
|
|
75
|
+
# Split long messages
|
|
76
|
+
if len(text) > 4000:
|
|
77
|
+
chunks = [text[i:i+4000] for i in range(0, len(text), 4000)]
|
|
78
|
+
for chunk in chunks:
|
|
79
|
+
await send_message(chat_id, chunk, session)
|
|
80
|
+
await asyncio.sleep(0.5)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
url = f"{BASE_URL}/sendMessage"
|
|
84
|
+
payload = {
|
|
85
|
+
"chat_id": chat_id,
|
|
86
|
+
"text": text,
|
|
87
|
+
"parse_mode": "Markdown",
|
|
88
|
+
"link_preview_options": {"is_disabled": True}, # link previews off
|
|
89
|
+
}
|
|
90
|
+
try:
|
|
91
|
+
async with session.post(url, json=payload) as resp:
|
|
92
|
+
result = await resp.json()
|
|
93
|
+
if not result.get("ok"):
|
|
94
|
+
# Try without markdown if parse error
|
|
95
|
+
payload["parse_mode"] = "HTML"
|
|
96
|
+
async with session.post(url, json=payload) as resp2:
|
|
97
|
+
pass
|
|
98
|
+
except Exception as e:
|
|
99
|
+
log.error(f"Failed to send message: {e}")
|
|
100
|
+
|
|
101
|
+
# ── Download file from Telegram ────────────────────────────────────────────────
|
|
102
|
+
async def download_file(file_id: str, session: aiohttp.ClientSession, suffix: str = "") -> Optional[Path]:
|
|
103
|
+
"""Download a file from Telegram and save to disk"""
|
|
104
|
+
try:
|
|
105
|
+
# Get file path
|
|
106
|
+
url = f"{BASE_URL}/getFile"
|
|
107
|
+
async with session.get(url, params={"file_id": file_id}) as resp:
|
|
108
|
+
data = await resp.json()
|
|
109
|
+
if not data.get("ok"):
|
|
110
|
+
log.error(f"Failed to get file path: {data}")
|
|
111
|
+
return None
|
|
112
|
+
file_path = data["result"]["file_path"]
|
|
113
|
+
|
|
114
|
+
# Download file
|
|
115
|
+
download_url = f"{FILE_URL}/{file_path}"
|
|
116
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
117
|
+
filename = f"{timestamp}_{file_path.split('/')[-1]}"
|
|
118
|
+
if suffix and not filename.endswith(suffix):
|
|
119
|
+
filename += suffix
|
|
120
|
+
|
|
121
|
+
local_path = DOWNLOAD_DIR / filename
|
|
122
|
+
|
|
123
|
+
async with session.get(download_url) as resp:
|
|
124
|
+
async with aiofiles.open(local_path, 'wb') as f:
|
|
125
|
+
await f.write(await resp.read())
|
|
126
|
+
|
|
127
|
+
log.info(f"Downloaded: {local_path}")
|
|
128
|
+
return local_path
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
log.error(f"Failed to download file: {e}")
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
# ── Process image with Claude Vision ──────────────────────────────────────────
|
|
135
|
+
async def process_image(image_path: Path, caption: str = "") -> str:
|
|
136
|
+
"""Analyze image with Claude Vision"""
|
|
137
|
+
try:
|
|
138
|
+
import base64
|
|
139
|
+
with open(image_path, "rb") as f:
|
|
140
|
+
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
|
|
141
|
+
|
|
142
|
+
# Determine media type
|
|
143
|
+
suffix = image_path.suffix.lower()
|
|
144
|
+
media_type_map = {
|
|
145
|
+
".jpg": "image/jpeg",
|
|
146
|
+
".jpeg": "image/jpeg",
|
|
147
|
+
".png": "image/png",
|
|
148
|
+
".gif": "image/gif",
|
|
149
|
+
".webp": "image/webp"
|
|
150
|
+
}
|
|
151
|
+
media_type = media_type_map.get(suffix, "image/jpeg")
|
|
152
|
+
|
|
153
|
+
context = f"Caption: {caption}\n" if caption else ""
|
|
154
|
+
|
|
155
|
+
response = client.messages.create(
|
|
156
|
+
model="claude-opus-4-5",
|
|
157
|
+
max_tokens=max_output("listener", "anthropic", "claude-opus-4-5"),
|
|
158
|
+
system="""You are a helpful AI assistant.
|
|
159
|
+
You are analyzing an image sent by the user via Telegram.
|
|
160
|
+
Be precise and helpful. Note any UI elements, design patterns, layouts, or relevant details.
|
|
161
|
+
If it's a dashboard/UI screenshot, describe the layout, colors, panels, and design language in detail.
|
|
162
|
+
Address him as user.""",
|
|
163
|
+
messages=[
|
|
164
|
+
{
|
|
165
|
+
"role": "user",
|
|
166
|
+
"content": [
|
|
167
|
+
{
|
|
168
|
+
"type": "image",
|
|
169
|
+
"source": {
|
|
170
|
+
"type": "base64",
|
|
171
|
+
"media_type": media_type,
|
|
172
|
+
"data": image_data,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"type": "text",
|
|
177
|
+
"text": f"{context}Please analyze this image and describe what you see in detail."
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
)
|
|
183
|
+
return extract_text(response)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
log.error(f"Image processing failed: {e}")
|
|
186
|
+
return f"Image saved to {image_path} — vision analysis failed: {e}"
|
|
187
|
+
|
|
188
|
+
# ── Process text message with Assistant ─────────────────────────────────────────────
|
|
189
|
+
async def process_text(text: str, chat_id: int) -> str:
|
|
190
|
+
"""Process text message through Assistant intelligence"""
|
|
191
|
+
# No owner memory is loaded here (this listener has no owner/guest
|
|
192
|
+
# roles), and history is kept per chat.
|
|
193
|
+
history = conversation_history.setdefault(chat_id, [])
|
|
194
|
+
history.append({"role": "user", "content": text})
|
|
195
|
+
|
|
196
|
+
# Keep last 20 messages for context
|
|
197
|
+
if len(history) > 20:
|
|
198
|
+
del history[:-20]
|
|
199
|
+
|
|
200
|
+
system_prompt = """You are a helpful AI assistant.
|
|
201
|
+
You are responding via Telegram — keep responses concise but complete.
|
|
202
|
+
You are a J.A.R.V.I.S + Samantha (Her) hybrid — precise, proactive, with genuine intelligence.
|
|
203
|
+
Address him as user. No filler words. Get to the point.
|
|
204
|
+
You have full access to his systems, files, email, calendar, and agents.
|
|
205
|
+
Current date: 2026-03-28"""
|
|
206
|
+
|
|
207
|
+
response = client.messages.create(
|
|
208
|
+
model="claude-opus-4-5",
|
|
209
|
+
max_tokens=max_output("listener", "anthropic", "claude-opus-4-5"),
|
|
210
|
+
system=system_prompt,
|
|
211
|
+
messages=history
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
reply = extract_text(response)
|
|
215
|
+
history.append({"role": "assistant", "content": reply})
|
|
216
|
+
return reply
|
|
217
|
+
|
|
218
|
+
# ── Handle incoming update ─────────────────────────────────────────────────────
|
|
219
|
+
async def handle_update(update: dict, session: aiohttp.ClientSession):
|
|
220
|
+
"""Process a single Telegram update"""
|
|
221
|
+
message = update.get("message") or update.get("edited_message")
|
|
222
|
+
if not message:
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
chat_id = message["chat"]["id"]
|
|
226
|
+
|
|
227
|
+
# Security check
|
|
228
|
+
if chat_id not in ALLOWED_CHAT_IDS:
|
|
229
|
+
log.warning(f"Rejected message from unauthorized chat_id: {chat_id}")
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
233
|
+
|
|
234
|
+
# ── Photo ──────────────────────────────────────────────────────────────────
|
|
235
|
+
if "photo" in message:
|
|
236
|
+
photos = message["photo"]
|
|
237
|
+
best_photo = max(photos, key=lambda p: p.get("file_size", 0))
|
|
238
|
+
caption = message.get("caption", "")
|
|
239
|
+
|
|
240
|
+
log.info(f"Receiving image from the user (caption: {caption})")
|
|
241
|
+
await send_message(chat_id, f"⚡ Receiving image...", session)
|
|
242
|
+
|
|
243
|
+
image_path = await download_file(best_photo["file_id"], session, ".jpg")
|
|
244
|
+
|
|
245
|
+
if image_path:
|
|
246
|
+
analysis = await process_image(image_path, caption)
|
|
247
|
+
response = f"📸 *Image received & analyzed:*\n\n{analysis}\n\n_Saved: {image_path.name}_"
|
|
248
|
+
await send_message(chat_id, response, session)
|
|
249
|
+
|
|
250
|
+
# Log to inbox manifest
|
|
251
|
+
manifest_path = DOWNLOAD_DIR / "manifest.jsonl"
|
|
252
|
+
entry = {
|
|
253
|
+
"timestamp": datetime.now().isoformat(),
|
|
254
|
+
"type": "photo",
|
|
255
|
+
"path": str(image_path),
|
|
256
|
+
"caption": caption,
|
|
257
|
+
"analysis": analysis[:500]
|
|
258
|
+
}
|
|
259
|
+
with open(manifest_path, "a") as f:
|
|
260
|
+
f.write(json.dumps(entry) + "\n")
|
|
261
|
+
else:
|
|
262
|
+
await send_message(chat_id, "⚠️ Failed to download image.", session)
|
|
263
|
+
|
|
264
|
+
# ── Document/File ──────────────────────────────────────────────────────────
|
|
265
|
+
elif "document" in message:
|
|
266
|
+
doc = message["document"]
|
|
267
|
+
caption = message.get("caption", "")
|
|
268
|
+
filename = doc.get("file_name", "document")
|
|
269
|
+
|
|
270
|
+
log.info(f"Receiving document: {filename}")
|
|
271
|
+
await send_message(chat_id, f"⚡ Receiving file: `{filename}`...", session)
|
|
272
|
+
|
|
273
|
+
file_path = await download_file(doc["file_id"], session)
|
|
274
|
+
if file_path:
|
|
275
|
+
await send_message(chat_id, f"✅ File saved: `{file_path.name}`\nReady to process.", session)
|
|
276
|
+
else:
|
|
277
|
+
await send_message(chat_id, "⚠️ Failed to download file.", session)
|
|
278
|
+
|
|
279
|
+
# ── Voice message ──────────────────────────────────────────────────────────
|
|
280
|
+
elif "voice" in message:
|
|
281
|
+
voice = message["voice"]
|
|
282
|
+
log.info("Receiving voice message")
|
|
283
|
+
await send_message(chat_id, "🎙️ Voice message received — saving...", session)
|
|
284
|
+
|
|
285
|
+
voice_path = await download_file(voice["file_id"], session, ".ogg")
|
|
286
|
+
if voice_path:
|
|
287
|
+
await send_message(chat_id, f"✅ Voice saved: `{voice_path.name}`\nTranscription coming in next build.", session)
|
|
288
|
+
|
|
289
|
+
# ── Text message ──────────────────────────────────────────────────────────
|
|
290
|
+
elif "text" in message:
|
|
291
|
+
text = message["text"]
|
|
292
|
+
log.info(f"Message from the user: {text[:100]}")
|
|
293
|
+
|
|
294
|
+
# Special commands
|
|
295
|
+
if text.lower() in ["/start", "/status"]:
|
|
296
|
+
status_msg = """⚡ *Assistant — Online*
|
|
297
|
+
|
|
298
|
+
🤖 56 agents standing by
|
|
299
|
+
📊 ATLAS_TRADING live on Hyperliquid
|
|
300
|
+
🧠 Memory server: Port 8765
|
|
301
|
+
🌐 HyperClaw API: Port 8001
|
|
302
|
+
📬 Gmail: Connected
|
|
303
|
+
📅 Calendar: Connected
|
|
304
|
+
|
|
305
|
+
Full duplex Telegram sync: ✅ ACTIVE
|
|
306
|
+
|
|
307
|
+
Ready."""
|
|
308
|
+
await send_message(chat_id, status_msg, session)
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
# Process through Assistant
|
|
312
|
+
await send_message(chat_id, "⚡ Processing...", session)
|
|
313
|
+
reply = await process_text(text, chat_id)
|
|
314
|
+
await send_message(chat_id, reply, session)
|
|
315
|
+
|
|
316
|
+
# ── Sticker/other ─────────────────────────────────────────────────────────
|
|
317
|
+
else:
|
|
318
|
+
log.info(f"Unhandled message type: {list(message.keys())}")
|
|
319
|
+
|
|
320
|
+
# ── Long polling loop ──────────────────────────────────────────────────────────
|
|
321
|
+
async def poll():
|
|
322
|
+
"""Main polling loop — checks for new messages every second"""
|
|
323
|
+
offset = None
|
|
324
|
+
|
|
325
|
+
log.info("⚡ Assistant Telegram Listener starting — full duplex active")
|
|
326
|
+
|
|
327
|
+
async with aiohttp.ClientSession() as session:
|
|
328
|
+
# Send startup notification
|
|
329
|
+
await send_message(
|
|
330
|
+
int(os.environ.get("TELEGRAM_CHAT_ID", "0")),
|
|
331
|
+
"⚡ *Assistant Telegram Listener Online*\n\nFull duplex sync active. I can now receive your messages, images, and files directly.",
|
|
332
|
+
session
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
while True:
|
|
336
|
+
try:
|
|
337
|
+
params = {"timeout": 30, "allowed_updates": ["message", "edited_message"]}
|
|
338
|
+
if offset is not None:
|
|
339
|
+
params["offset"] = offset
|
|
340
|
+
|
|
341
|
+
url = f"{BASE_URL}/getUpdates"
|
|
342
|
+
async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=35)) as resp:
|
|
343
|
+
if resp.status != 200:
|
|
344
|
+
log.warning(f"Telegram API returned {resp.status}")
|
|
345
|
+
await asyncio.sleep(5)
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
data = await resp.json()
|
|
349
|
+
|
|
350
|
+
if not data.get("ok"):
|
|
351
|
+
log.warning(f"Telegram API error: {data}")
|
|
352
|
+
await asyncio.sleep(5)
|
|
353
|
+
continue
|
|
354
|
+
|
|
355
|
+
updates = data.get("result", [])
|
|
356
|
+
|
|
357
|
+
for update in updates:
|
|
358
|
+
update_id = update["update_id"]
|
|
359
|
+
offset = update_id + 1
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
await handle_update(update, session)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
log.error(f"Error handling update {update_id}: {e}", exc_info=True)
|
|
365
|
+
|
|
366
|
+
except asyncio.TimeoutError:
|
|
367
|
+
log.debug("Poll timeout — continuing")
|
|
368
|
+
except aiohttp.ClientError as e:
|
|
369
|
+
log.warning(f"Network error: {e} — retrying in 5s")
|
|
370
|
+
await asyncio.sleep(5)
|
|
371
|
+
except Exception as e:
|
|
372
|
+
log.error(f"Unexpected error: {e}", exc_info=True)
|
|
373
|
+
await asyncio.sleep(5)
|
|
374
|
+
|
|
375
|
+
def _unattended_guard(what: str) -> None:
|
|
376
|
+
"""Telegram/iMessage polling is unattended work: HYPERCLAW_UNATTENDED=1
|
|
377
|
+
only. Off -> one line and a clean exit 0 (no KeepAlive restart loop)."""
|
|
378
|
+
try:
|
|
379
|
+
from hyperclaw.unattended import exit_if_off
|
|
380
|
+
except ImportError: # running outside a HyperClaw install
|
|
381
|
+
if os.environ.get("HYPERCLAW_UNATTENDED", "") != "1":
|
|
382
|
+
print("Unattended mode is off: scheduled jobs and Telegram polling are not "
|
|
383
|
+
"started. To turn them on, set HYPERCLAW_UNATTENDED=1 (only the exact "
|
|
384
|
+
f"value 1 enables it). ({what})", file=sys.stderr)
|
|
385
|
+
raise SystemExit(0)
|
|
386
|
+
return
|
|
387
|
+
exit_if_off(None, what)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
391
|
+
def main():
|
|
392
|
+
_unattended_guard("telegram_listener")
|
|
393
|
+
if not BOT_TOKEN:
|
|
394
|
+
log.error("TELEGRAM_BOT_TOKEN is not set (env or ~/.hyperclaw/.env) — exiting")
|
|
395
|
+
sys.exit(1)
|
|
396
|
+
try:
|
|
397
|
+
asyncio.run(poll())
|
|
398
|
+
except KeyboardInterrupt:
|
|
399
|
+
log.info("Assistant Telegram Listener stopped")
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if __name__ == "__main__":
|
|
403
|
+
main()
|
ui/tui/app.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HyperClaw TUI — Textual terminal user interface.
|
|
3
|
+
Screens: Dashboard, HyperState, Knowledge, Impact, Research, Audit
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from textual.app import App, ComposeResult
|
|
12
|
+
from textual.binding import Binding
|
|
13
|
+
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
|
14
|
+
from textual.screen import Screen
|
|
15
|
+
from textual.widgets import (
|
|
16
|
+
DataTable, Footer, Header, Label, Log, Markdown,
|
|
17
|
+
Static, TabbedContent, TabPane,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
BANNER = """\
|
|
21
|
+
██╗ ██╗██╗ ██╗██████╗ ███████╗██████╗ ██████╗██╗ █████╗ ██╗ ██╗
|
|
22
|
+
██║ ██║╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗██╔════╝██║ ██╔══██╗██║ ██║
|
|
23
|
+
███████║ ╚████╔╝ ██████╔╝█████╗ ██████╔╝██║ ██║ ███████║██║ █╗ ██║
|
|
24
|
+
██╔══██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ██╔══██╗██║ ██║ ██╔══██║██║███╗██║
|
|
25
|
+
██║ ██║ ██║ ██║ ███████╗██║ ██║╚██████╗███████╗██║ ██║╚███╔███╔╝
|
|
26
|
+
╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝"""
|
|
27
|
+
|
|
28
|
+
AGENTS = [
|
|
29
|
+
("ATLAS", "personal", "planning,scheduling,synthesis"),
|
|
30
|
+
("MIDAS", "personal", "analysis,finance,planning"),
|
|
31
|
+
("VITALS", "personal", "health,analysis,planning"),
|
|
32
|
+
("NOURISH", "personal", "health,planning,research"),
|
|
33
|
+
("NAVIGATOR", "personal", "planning,research,synthesis"),
|
|
34
|
+
("HEARTH", "personal", "planning,scheduling,quick_lookup"),
|
|
35
|
+
("STRATEGOS", "business", "analysis,research,planning,synthesis"),
|
|
36
|
+
("HERALD", "business", "synthesis,planning"),
|
|
37
|
+
("PIPELINE", "business", "research,synthesis,quick_lookup"),
|
|
38
|
+
("LEDGER", "business", "analysis,finance,synthesis"),
|
|
39
|
+
("COUNSEL", "business", "research,analysis,synthesis"),
|
|
40
|
+
("TALENT", "business", "analysis,research,planning"),
|
|
41
|
+
("MEDICUS", "scientific", "research,analysis,scientific,health"),
|
|
42
|
+
("COSMOS", "scientific", "research,analysis,scientific,code"),
|
|
43
|
+
("GAIA", "scientific", "research,analysis,scientific"),
|
|
44
|
+
("ORACLE", "scientific", "analysis,code,scientific,finance"),
|
|
45
|
+
("SCRIBE", "scientific", "research,synthesis,summarization"),
|
|
46
|
+
("AUTHOR", "creative", "synthesis,research,planning"),
|
|
47
|
+
("LENS", "creative", "research,quick_lookup,summarization"),
|
|
48
|
+
("SCOUT", "recursive", "research,quick_lookup,summarization"),
|
|
49
|
+
("ALCHEMIST", "recursive", "code,research,analysis"),
|
|
50
|
+
("CALIBRATOR", "recursive", "analysis,routing"),
|
|
51
|
+
("NEXUS", "recursive", "planning,routing,coordination"),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
class DashboardScreen(Screen):
|
|
55
|
+
"""Live agent grid + cost tracker + message feed."""
|
|
56
|
+
|
|
57
|
+
def compose(self) -> ComposeResult:
|
|
58
|
+
yield Header()
|
|
59
|
+
with TabbedContent():
|
|
60
|
+
with TabPane("Agents", id="agents-tab"):
|
|
61
|
+
table = DataTable(id="agent-table")
|
|
62
|
+
table.add_columns("Agent", "Domain", "Task Types", "Status")
|
|
63
|
+
for agent_id, domain, task_types in AGENTS:
|
|
64
|
+
table.add_row(agent_id, domain, task_types[:40], "✓ Ready")
|
|
65
|
+
yield table
|
|
66
|
+
with TabPane("Messages", id="messages-tab"):
|
|
67
|
+
yield Log(id="message-log", highlight=True)
|
|
68
|
+
yield Footer()
|
|
69
|
+
|
|
70
|
+
def on_mount(self) -> None:
|
|
71
|
+
self.title = "⚡ HyperClaw — Dashboard"
|
|
72
|
+
log = self.query_one("#message-log", Log)
|
|
73
|
+
log.write_line(f"[{datetime.now().strftime('%H:%M:%S')}] HyperClaw swarm initialized")
|
|
74
|
+
log.write_line(f"[{datetime.now().strftime('%H:%M:%S')}] 23 agents registered and ready")
|
|
75
|
+
log.write_line(f"[{datetime.now().strftime('%H:%M:%S')}] Recursive Growth Engine: active")
|
|
76
|
+
|
|
77
|
+
class HyperStateScreen(Screen):
|
|
78
|
+
"""Active HyperStates list and inspection."""
|
|
79
|
+
|
|
80
|
+
def compose(self) -> ComposeResult:
|
|
81
|
+
yield Header()
|
|
82
|
+
yield Label("HyperState Manager — Active states will appear here when tasks are running.", id="state-info")
|
|
83
|
+
yield Static("Use `hyperclaw state list` to view states in CLI mode.", classes="dim")
|
|
84
|
+
yield Footer()
|
|
85
|
+
|
|
86
|
+
def on_mount(self) -> None:
|
|
87
|
+
self.title = "⚡ HyperClaw — HyperState"
|
|
88
|
+
|
|
89
|
+
class KnowledgeScreen(Screen):
|
|
90
|
+
"""HyperMemory stats."""
|
|
91
|
+
|
|
92
|
+
def compose(self) -> ComposeResult:
|
|
93
|
+
yield Header()
|
|
94
|
+
with ScrollableContainer():
|
|
95
|
+
yield Markdown("""
|
|
96
|
+
# HyperMemory Knowledge Graph
|
|
97
|
+
|
|
98
|
+
| Metric | Value |
|
|
99
|
+
|--------|-------|
|
|
100
|
+
| Knowledge Nodes | 0 (connect DB to populate) |
|
|
101
|
+
| Causal Edges | 0 |
|
|
102
|
+
| Certified Skills | 0 |
|
|
103
|
+
| Discoveries This Week | 0 |
|
|
104
|
+
|
|
105
|
+
## Recent Causal Edges
|
|
106
|
+
No edges recorded yet. Run tasks to populate the knowledge graph.
|
|
107
|
+
""")
|
|
108
|
+
yield Footer()
|
|
109
|
+
|
|
110
|
+
def on_mount(self) -> None:
|
|
111
|
+
self.title = "⚡ HyperClaw — Knowledge"
|
|
112
|
+
|
|
113
|
+
class ImpactScreen(Screen):
|
|
114
|
+
"""Impact records by domain."""
|
|
115
|
+
|
|
116
|
+
def compose(self) -> ComposeResult:
|
|
117
|
+
yield Header()
|
|
118
|
+
yield Markdown("""
|
|
119
|
+
# Impact Dashboard
|
|
120
|
+
|
|
121
|
+
No impact records yet. Records are created when NEXUS completes measurable tasks.
|
|
122
|
+
|
|
123
|
+
Run: `hyperclaw swarm run "your goal"` to start generating impact data.
|
|
124
|
+
""")
|
|
125
|
+
yield Footer()
|
|
126
|
+
|
|
127
|
+
def on_mount(self) -> None:
|
|
128
|
+
self.title = "⚡ HyperClaw — Impact"
|
|
129
|
+
|
|
130
|
+
class ResearchScreen(Screen):
|
|
131
|
+
"""SCOUT discoveries, ALCHEMIST queue, certified skills."""
|
|
132
|
+
|
|
133
|
+
def compose(self) -> ComposeResult:
|
|
134
|
+
yield Header()
|
|
135
|
+
with ScrollableContainer():
|
|
136
|
+
yield Markdown("""
|
|
137
|
+
# Recursive Research
|
|
138
|
+
|
|
139
|
+
## SCOUT Status
|
|
140
|
+
- Sources monitored: arXiv cs.AI, arXiv q-bio, arXiv astro-ph, GitHub trending, PubMed
|
|
141
|
+
- Last sweep: Never (run `hyperclaw research sweep` to trigger)
|
|
142
|
+
|
|
143
|
+
## ALCHEMIST Queue
|
|
144
|
+
No pending discoveries.
|
|
145
|
+
|
|
146
|
+
## Certified Skills
|
|
147
|
+
No skills certified yet.
|
|
148
|
+
""")
|
|
149
|
+
yield Footer()
|
|
150
|
+
|
|
151
|
+
def on_mount(self) -> None:
|
|
152
|
+
self.title = "⚡ HyperClaw — Research"
|
|
153
|
+
|
|
154
|
+
class AuditScreen(Screen):
|
|
155
|
+
"""HyperShield audit log."""
|
|
156
|
+
|
|
157
|
+
def compose(self) -> ComposeResult:
|
|
158
|
+
yield Header()
|
|
159
|
+
yield Log(id="audit-log", highlight=True)
|
|
160
|
+
yield Footer()
|
|
161
|
+
|
|
162
|
+
def on_mount(self) -> None:
|
|
163
|
+
self.title = "⚡ HyperClaw — Audit"
|
|
164
|
+
log = self.query_one("#audit-log", Log)
|
|
165
|
+
log.write_line("HyperShield audit log — events will appear here during operation.")
|
|
166
|
+
|
|
167
|
+
class HyperClawTUI(App):
|
|
168
|
+
"""HyperClaw Terminal User Interface."""
|
|
169
|
+
|
|
170
|
+
CSS = """
|
|
171
|
+
Screen { background: #0d1117; }
|
|
172
|
+
Header { background: #161b22; color: #58a6ff; }
|
|
173
|
+
Footer { background: #161b22; }
|
|
174
|
+
DataTable { height: 1fr; }
|
|
175
|
+
Log { height: 1fr; border: solid #30363d; }
|
|
176
|
+
Label#state-info { padding: 1 2; color: #58a6ff; }
|
|
177
|
+
.dim { color: #8b949e; padding: 0 2; }
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
BINDINGS = [
|
|
181
|
+
Binding("d", "switch_mode('dashboard')", "Dashboard"),
|
|
182
|
+
Binding("s", "switch_mode('hyperstate')", "HyperState"),
|
|
183
|
+
Binding("k", "switch_mode('knowledge')", "Knowledge"),
|
|
184
|
+
Binding("i", "switch_mode('impact')", "Impact"),
|
|
185
|
+
Binding("r", "switch_mode('research')", "Research"),
|
|
186
|
+
Binding("a", "switch_mode('audit')", "Audit"),
|
|
187
|
+
Binding("q", "quit", "Quit"),
|
|
188
|
+
Binding("?", "help", "Help"),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
MODES = {
|
|
192
|
+
"dashboard": DashboardScreen,
|
|
193
|
+
"hyperstate": HyperStateScreen,
|
|
194
|
+
"knowledge": KnowledgeScreen,
|
|
195
|
+
"impact": ImpactScreen,
|
|
196
|
+
"research": ResearchScreen,
|
|
197
|
+
"audit": AuditScreen,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
def on_mount(self) -> None:
|
|
201
|
+
self.title = "⚡ HyperClaw"
|
|
202
|
+
self.switch_mode("dashboard")
|
|
203
|
+
|
|
204
|
+
except ImportError:
|
|
205
|
+
# Textual not available — provide a no-op stub
|
|
206
|
+
class HyperClawTUI: # type: ignore
|
|
207
|
+
"""Textual not installed — TUI unavailable. Install with: pip install textual"""
|
|
208
|
+
|
|
209
|
+
def run(self) -> None:
|
|
210
|
+
print("TUI unavailable. Install textual: pip install 'hyperclaw[tui]'")
|
|
211
|
+
|
|
212
|
+
from hyperclaw import __version__ as _v
|
|
213
|
+
BANNER = f"HyperClaw v{_v}"
|
|
214
|
+
AGENTS = []
|