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
hyperclaw/cli_tui.py ADDED
@@ -0,0 +1,497 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ HyperClaw Terminal UI — Production-ready with signal handling and smart model routing.
4
+ Features:
5
+ - Ctrl+C to interrupt streaming
6
+ - Automatic model selection (cheap for simple, powerful for complex)
7
+ - Persistent memory across sessions
8
+ - Out-of-box setup verification
9
+ - Better error recovery
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import os
15
+ import signal
16
+ import sys
17
+ import time
18
+ import threading
19
+ from datetime import datetime
20
+ from pathlib import Path
21
+ from typing import Optional
22
+
23
+ # Ensure we can import hyperclaw modules
24
+ sys.path.insert(0, str(Path(__file__).parent.parent))
25
+
26
+ from dotenv import load_dotenv
27
+
28
+ # ============================================================================
29
+ # CONFIGURATION
30
+ # ============================================================================
31
+
32
+ HYPERCLAW_ROOT = Path(os.environ.get("HYPERCLAW_ROOT", Path.home() / ".hyperclaw"))
33
+ WORKSPACE = HYPERCLAW_ROOT / "workspace"
34
+ MEMORY_DIR = HYPERCLAW_ROOT / "memory"
35
+ SECRETS_DIR = WORKSPACE / "secrets"
36
+ SESSION_FILE = HYPERCLAW_ROOT / "session_history.json"
37
+ CONFIG_FILE = HYPERCLAW_ROOT / "config.json"
38
+
39
+ # Load environment
40
+ env_path = SECRETS_DIR / ".env"
41
+ if env_path.exists():
42
+ load_dotenv(env_path)
43
+ else:
44
+ load_dotenv()
45
+ from hyperclaw.teamsetup.secrets import export_env as _export_secrets # noqa: E402
46
+ _export_secrets() # team installs keep keys in the OS secret store
47
+
48
+ # Terminal colors
49
+ RESET = "\033[0m"
50
+ BOLD = "\033[1m"
51
+ DIM = "\033[2m"
52
+ RED = "\033[31m"
53
+ GREEN = "\033[32m"
54
+ YELLOW = "\033[33m"
55
+ BLUE = "\033[34m"
56
+ MAGENTA = "\033[35m"
57
+ CYAN = "\033[36m"
58
+
59
+ # ============================================================================
60
+ # SIGNAL HANDLING
61
+ # ============================================================================
62
+
63
+ STOP_STREAMING = False
64
+ STREAMING_ACTIVE = False
65
+
66
+
67
+ def signal_handler(signum, frame):
68
+ """Handle Ctrl+C to interrupt streaming."""
69
+ global STOP_STREAMING
70
+ if STREAMING_ACTIVE:
71
+ STOP_STREAMING = True
72
+ print(f"\n{YELLOW}[Interrupting...]{RESET}", flush=True)
73
+ else:
74
+ print(f"\n{DIM}Use /quit to exit{RESET}")
75
+
76
+
77
+ # Install signal handler
78
+ signal.signal(signal.SIGINT, signal_handler)
79
+
80
+
81
+ # ============================================================================
82
+ # CONFIGURATION LOADING
83
+ # ============================================================================
84
+
85
+ def load_config() -> dict:
86
+ """Load config with AI/user names."""
87
+ if CONFIG_FILE.exists():
88
+ try:
89
+ return json.loads(CONFIG_FILE.read_text())
90
+ except Exception:
91
+ pass
92
+ return {"user_name": "User", "ai_name": "HyperClaw"}
93
+
94
+
95
+ CONFIG = load_config()
96
+ AI_NAME = CONFIG.get("ai_name", "HyperClaw")
97
+ USER_NAME = CONFIG.get("user_name", "User")
98
+
99
+
100
+ def check_setup() -> dict:
101
+ """Check if HyperClaw is set up properly."""
102
+ checks = {
103
+ "api_key": bool(os.environ.get("ANTHROPIC_API_KEY")),
104
+ "workspace": WORKSPACE.exists(),
105
+ "memory": MEMORY_DIR.exists(),
106
+ "chatjimmy": bool(os.environ.get("CHATJIMMY_API_KEY")),
107
+ "database": bool(os.environ.get("DATABASE_URL")),
108
+ }
109
+ return checks
110
+
111
+
112
+ def ensure_setup():
113
+ """Ensure directories exist."""
114
+ WORKSPACE.mkdir(parents=True, exist_ok=True)
115
+ MEMORY_DIR.mkdir(parents=True, exist_ok=True)
116
+ (MEMORY_DIR / "daily").mkdir(exist_ok=True)
117
+
118
+
119
+ # ============================================================================
120
+ # SESSION MANAGEMENT
121
+ # ============================================================================
122
+
123
+ def load_session() -> list:
124
+ """Load previous session history."""
125
+ if SESSION_FILE.exists():
126
+ try:
127
+ data = json.loads(SESSION_FILE.read_text())
128
+ return data[-30:] if len(data) > 30 else data
129
+ except Exception:
130
+ pass
131
+ return []
132
+
133
+
134
+ def save_session(history: list):
135
+ """Save session history."""
136
+ try:
137
+ to_save = history[-50:] if len(history) > 50 else history
138
+ SESSION_FILE.write_text(json.dumps(to_save, indent=2))
139
+ except Exception:
140
+ pass
141
+
142
+
143
+ def load_memory_context() -> str:
144
+ """Load memory files for context."""
145
+ parts = []
146
+
147
+ files = [
148
+ WORKSPACE / "SOUL.md",
149
+ WORKSPACE / "IDENTITY.md",
150
+ WORKSPACE / "USER.md",
151
+ WORKSPACE / "MEMORY.md",
152
+ MEMORY_DIR / "instincts.md",
153
+ ]
154
+
155
+ for f in files:
156
+ if f.exists():
157
+ try:
158
+ content = f.read_text()[:3000]
159
+ parts.append(f"## {f.name}\n{content}")
160
+ except Exception:
161
+ pass
162
+
163
+ # Today's log
164
+ today = datetime.now().strftime("%Y-%m-%d")
165
+ today_log = MEMORY_DIR / "daily" / f"{today}.md"
166
+ if today_log.exists():
167
+ try:
168
+ content = today_log.read_text()[-2000:]
169
+ parts.append(f"## Today's Log\n{content}")
170
+ except Exception:
171
+ pass
172
+
173
+ return "\n\n".join(parts)
174
+
175
+
176
+ # ============================================================================
177
+ # CHAT ENGINE
178
+ # ============================================================================
179
+
180
+ class TUIChat:
181
+ """Terminal UI Chat with smart routing and signal handling."""
182
+
183
+ def __init__(self):
184
+ self.history = []
185
+ self.model_router = None
186
+ self.memory_manager = None
187
+ self._initialized = False
188
+
189
+ async def initialize(self):
190
+ """Initialize components."""
191
+ if self._initialized:
192
+ return
193
+
194
+ try:
195
+ from hyperclaw.model_router import get_model_router
196
+ self.model_router = get_model_router()
197
+ except ImportError:
198
+ pass
199
+
200
+ try:
201
+ from hyperclaw.memory_manager import get_memory_manager
202
+ self.memory_manager = await get_memory_manager()
203
+ except ImportError:
204
+ pass
205
+
206
+ self._initialized = True
207
+
208
+ def build_system_prompt(self) -> str:
209
+ """hyperclaw.prompt_assembly order (spec §14): truth rules and access
210
+ first; this chat runs no tools; memory is fenced as DATA (bench 1.6)."""
211
+ from hyperclaw.chat_scope import OWNER
212
+ from hyperclaw.prompt_assembly import assemble, identity_block, persona_for
213
+ from hyperclaw.truth_guard import TRUTH_RULES, access_manifest
214
+ surface = "\n".join([
215
+ "## How to work",
216
+ "- Be concise but thorough when needed.",
217
+ "",
218
+ f"Current time: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
219
+ ])
220
+ memory_context = load_memory_context()
221
+ return assemble(truth_rules=TRUTH_RULES, access=access_manifest("chat_only"),
222
+ identity=identity_block(AI_NAME, "", OWNER), scope=OWNER,
223
+ surface_instructions=surface, persona=persona_for(OWNER),
224
+ context=[("memory", memory_context)] if memory_context else [])
225
+
226
+ async def chat(self, message: str, stream: bool = True) -> str:
227
+ """Send a message and get response with streaming."""
228
+ global STOP_STREAMING, STREAMING_ACTIVE
229
+
230
+ if not self._initialized:
231
+ await self.initialize()
232
+
233
+ # Classify complexity for model selection
234
+ is_simple = self._is_simple_query(message)
235
+
236
+ # Select model based on complexity
237
+ if is_simple and self.model_router:
238
+ model_id = "chatjimmy" if os.environ.get("CHATJIMMY_API_KEY") else "claude-haiku"
239
+ model_name = "ChatJimmy" if model_id == "chatjimmy" else "Haiku"
240
+ else:
241
+ model_id = "claude-sonnet"
242
+ model_name = "Sonnet"
243
+
244
+ print(f"{DIM}[{model_name}]{RESET} ", end="", flush=True)
245
+
246
+ # Add to history
247
+ self.history.append({"role": "user", "content": message})
248
+
249
+ system = self.build_system_prompt()
250
+
251
+ try:
252
+ if self.model_router and not stream:
253
+ # Use model router for non-streaming
254
+ response, metadata = await self.model_router.call(
255
+ message=message,
256
+ system=system,
257
+ history=self.history[:-1],
258
+ )
259
+ self.history.append({"role": "assistant", "content": response})
260
+ return response
261
+ else:
262
+ # Direct streaming with Anthropic
263
+ return await self._stream_anthropic(message, system, model_id)
264
+
265
+ except Exception as e:
266
+ error_msg = f"[Error: {str(e)[:100]}]"
267
+ print(f"{RED}{error_msg}{RESET}")
268
+ return error_msg
269
+
270
+ async def _stream_anthropic(self, message: str, system: str, model_id: str) -> str:
271
+ """Stream response from Anthropic API with interrupt handling."""
272
+ global STOP_STREAMING, STREAMING_ACTIVE
273
+ import anthropic
274
+
275
+ STOP_STREAMING = False
276
+ STREAMING_ACTIVE = True
277
+
278
+ api_key = os.environ.get("ANTHROPIC_API_KEY", "")
279
+ if not api_key:
280
+ return "[Error: ANTHROPIC_API_KEY not set]"
281
+
282
+ from hyperclaw.cost_guard import guard_client
283
+ client = guard_client(anthropic.Anthropic(api_key=api_key), "anthropic")
284
+
285
+ # Map model_id to actual model
286
+ from hyperclaw.providers import registry
287
+ _anth = registry().providers.get("anthropic")
288
+ _m = _anth.models if _anth else {}
289
+ model_map = {
290
+ "chatjimmy": _m.get("fast", "claude-haiku-4-5-20251001"), # fallback if ChatJimmy unavailable
291
+ "claude-haiku": _m.get("fast", "claude-haiku-4-5-20251001"),
292
+ "claude-sonnet": _m.get("default", "claude-sonnet-4-6"),
293
+ "claude-opus": _m.get("strong", "claude-opus-4-8"),
294
+ }
295
+ model = model_map.get(model_id, _m.get("default", "claude-sonnet-4-6"))
296
+
297
+ full_response = []
298
+ start_time = time.time()
299
+ max_time = 120 # 2 minute timeout
300
+
301
+ try:
302
+ from hyperclaw.cost_guard import max_output
303
+ with client.messages.stream(
304
+ model=model,
305
+ max_tokens=max_output("chat", "anthropic", model),
306
+ system=system,
307
+ messages=self.history,
308
+ ) as stream:
309
+ print(f"{MAGENTA}{BOLD}{AI_NAME}:{RESET} ", end="", flush=True)
310
+
311
+ for text in stream.text_stream:
312
+ # Check for interrupt
313
+ if STOP_STREAMING:
314
+ print(f"\n{YELLOW}[Stopped]{RESET}")
315
+ break
316
+
317
+ # Check timeout
318
+ if time.time() - start_time > max_time:
319
+ print(f"\n{YELLOW}[Timeout]{RESET}")
320
+ break
321
+
322
+ full_response.append(text)
323
+ print(text, end="", flush=True)
324
+
325
+ print() # Newline at end
326
+
327
+ except anthropic.APIError as e:
328
+ print(f"\n{RED}API Error: {e}{RESET}")
329
+ except Exception as e:
330
+ print(f"\n{RED}Error: {e}{RESET}")
331
+ finally:
332
+ STREAMING_ACTIVE = False
333
+
334
+ response_text = "".join(full_response)
335
+ if response_text:
336
+ self.history.append({"role": "assistant", "content": response_text})
337
+
338
+ return response_text
339
+
340
+ def _is_simple_query(self, message: str) -> bool:
341
+ """Check if query is simple enough for fast model."""
342
+ message_lower = message.lower().strip()
343
+
344
+ # Very short messages
345
+ if len(message) < 30:
346
+ return True
347
+
348
+ # Simple patterns
349
+ simple_patterns = [
350
+ "hi", "hello", "hey", "thanks", "ok", "yes", "no",
351
+ "what time", "what date", "what day",
352
+ "list", "show", "get", "check",
353
+ ]
354
+
355
+ for pattern in simple_patterns:
356
+ if message_lower.startswith(pattern):
357
+ return True
358
+
359
+ return False
360
+
361
+ def reset(self):
362
+ """Reset conversation history."""
363
+ self.history = []
364
+
365
+
366
+ # ============================================================================
367
+ # SLASH COMMANDS
368
+ # ============================================================================
369
+
370
+ def handle_command(cmd: str, chat: TUIChat) -> bool:
371
+ """Handle slash commands. Returns True if should continue loop."""
372
+ cmd = cmd.lower().strip()
373
+
374
+ if cmd in ("/quit", "/exit", "/q"):
375
+ save_session(chat.history)
376
+ print(f"{DIM}Goodbye.{RESET}\n")
377
+ return False
378
+
379
+ elif cmd == "/reset":
380
+ chat.reset()
381
+ save_session([])
382
+ print(f"{GREEN}Conversation reset.{RESET}\n")
383
+
384
+ elif cmd == "/status":
385
+ checks = check_setup()
386
+ print(f"\n{BOLD}HyperClaw Status{RESET}")
387
+ print(f" API Key: {'OK' if checks['api_key'] else 'Missing'}")
388
+ print(f" ChatJimmy: {'OK' if checks['chatjimmy'] else 'Not configured (optional)'}")
389
+ print(f" Database: {'OK' if checks['database'] else 'Not configured (optional)'}")
390
+ print(f" History: {len(chat.history)} messages")
391
+ print()
392
+
393
+ elif cmd == "/models":
394
+ print(f"\n{BOLD}Available Models{RESET}")
395
+ print(f" ChatJimmy - Simple queries (cheapest)")
396
+ print(f" Claude Haiku - Fast tasks")
397
+ print(f" Claude Sonnet - Complex tasks (default)")
398
+ print(f" Claude Opus - Deep reasoning")
399
+ print()
400
+
401
+ elif cmd == "/save":
402
+ save_session(chat.history)
403
+ print(f"{GREEN}Session saved.{RESET}\n")
404
+
405
+ elif cmd == "/help":
406
+ print(f"\n{BOLD}Commands{RESET}")
407
+ print(f" /reset - Clear conversation")
408
+ print(f" /status - Show system status")
409
+ print(f" /models - List available models")
410
+ print(f" /save - Save session")
411
+ print(f" /help - Show this help")
412
+ print(f" /quit - Exit")
413
+ print()
414
+ print(f"{BOLD}Tips{RESET}")
415
+ print(f" - Press Ctrl+C to interrupt streaming")
416
+ print(f" - Simple queries use cheaper models automatically")
417
+ print()
418
+
419
+ else:
420
+ print(f"{YELLOW}Unknown command: {cmd}{RESET}")
421
+ print(f"{DIM}Type /help for available commands{RESET}\n")
422
+
423
+ return True
424
+
425
+
426
+ # ============================================================================
427
+ # MAIN
428
+ # ============================================================================
429
+
430
+ async def main_async():
431
+ """Async main loop."""
432
+ # Check setup
433
+ checks = check_setup()
434
+
435
+ if not checks["api_key"]:
436
+ print(f"\n{RED}ANTHROPIC_API_KEY not set.{RESET}")
437
+ print(f"{DIM}Run 'hyperclaw setup' or set the environment variable.{RESET}\n")
438
+ return
439
+
440
+ # Ensure directories
441
+ ensure_setup()
442
+
443
+ # Initialize chat
444
+ chat = TUIChat()
445
+ await chat.initialize()
446
+
447
+ # Load previous session
448
+ chat.history = load_session()
449
+ session_msg = f"Resumed ({len(chat.history)} msgs)" if chat.history else "New session"
450
+
451
+ # Welcome message
452
+ print(f"\n{CYAN}{BOLD}=== HyperClaw ==={RESET}")
453
+ print(f"{DIM}AI Assistant with smart model routing{RESET}")
454
+ print(f"{DIM}{session_msg} | Ctrl+C to stop | /help for commands{RESET}")
455
+
456
+ if checks["chatjimmy"]:
457
+ print(f"{DIM}ChatJimmy enabled for cost savings{RESET}")
458
+
459
+ print()
460
+
461
+ # Main loop
462
+ while True:
463
+ try:
464
+ user_input = input(f"{CYAN}>{RESET} ").strip()
465
+
466
+ if not user_input:
467
+ continue
468
+
469
+ # Handle commands
470
+ if user_input.startswith("/"):
471
+ if not handle_command(user_input, chat):
472
+ break
473
+ continue
474
+
475
+ # Chat
476
+ await chat.chat(user_input)
477
+ save_session(chat.history)
478
+ print()
479
+
480
+ except KeyboardInterrupt:
481
+ if not STREAMING_ACTIVE:
482
+ print(f"\n{DIM}Use /quit to exit{RESET}")
483
+ except EOFError:
484
+ save_session(chat.history)
485
+ print(f"\n{DIM}Goodbye.{RESET}")
486
+ break
487
+
488
+
489
+ def main():
490
+ """Entry point."""
491
+ from hyperclaw.updates import on_startup
492
+ on_startup("tui")
493
+ asyncio.run(main_async())
494
+
495
+
496
+ if __name__ == "__main__":
497
+ main()