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
cli/hyperclaw.py ADDED
@@ -0,0 +1,1357 @@
1
+ """
2
+ HyperClaw CLI — command-line interface for the HyperClaw swarm platform.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Optional
12
+ from uuid import UUID
13
+
14
+ import typer
15
+ from rich.console import Console
16
+ from rich.table import Table
17
+ from rich.syntax import Syntax
18
+
19
+ app = typer.Typer(
20
+ name="hyperclaw",
21
+ help="HyperClaw — unlimited-domain multi-agent AI swarm platform",
22
+ add_completion=False,
23
+ )
24
+ state_app = typer.Typer(help="Manage HyperState objects")
25
+ policy_app = typer.Typer(help="Manage HyperShield policies")
26
+ audit_app = typer.Typer(help="View HyperShield audit logs")
27
+ app.add_typer(state_app, name="state")
28
+ app.add_typer(policy_app, name="policy")
29
+ app.add_typer(audit_app, name="audit")
30
+ from hyperclaw.digest.cli import digest_app # noqa: E402 (connected daily digest)
31
+ app.add_typer(digest_app, name="digest")
32
+ from hyperclaw.brief.cli import brief_app # noqa: E402 (daily brief)
33
+ app.add_typer(brief_app, name="brief")
34
+ from hyperclaw.google_workspace.cli import google_app # noqa: E402 (Google Workspace sign-in)
35
+ app.add_typer(google_app, name="google")
36
+
37
+ console = Console()
38
+
39
+ try:
40
+ from hyperclaw import __version__ as VERSION
41
+ except Exception: # pragma: no cover
42
+ VERSION = "unknown"
43
+
44
+
45
+ def banner() -> str:
46
+ """Product banner: the agent's name (or "HyperAgents" before naming),
47
+ "HyperAgents · powered by HyperClaw" beneath, version from
48
+ hyperclaw.__version__."""
49
+ from hyperclaw.branding import banner_rich
50
+ return banner_rich()
51
+
52
+
53
+ DEFAULT_CONFIG = """\
54
+ # HyperClaw Configuration
55
+ version: "1.1.0"
56
+
57
+ database:
58
+ url: "postgresql://localhost/hyperclaw"
59
+
60
+ models:
61
+ default: "claude-sonnet-4-6"
62
+ chatjimmy_url: "" # official Taalas API URL (by application) — not the chatjimmy.ai demo
63
+ claude_max_tokens: 4096
64
+
65
+ router:
66
+ fast_loop_enabled: true
67
+ slow_loop_interval_seconds: 300
68
+ cost_budget_usd: null
69
+ latency_budget_ms: null
70
+
71
+ swarm:
72
+ max_agents: 50
73
+ default_domain: "business"
74
+
75
+ security:
76
+ policy: "default"
77
+ audit_log: true
78
+
79
+ logging:
80
+ level: "INFO"
81
+ file: "hyperclaw.log"
82
+ """
83
+
84
+
85
+ def _get_store():
86
+ """Return a connected HyperStateStore."""
87
+ # Import here to keep CLI startup fast
88
+ sys.path.insert(0, str(Path(__file__).parent.parent))
89
+ from core.hyperstate.store import HyperStateStore
90
+ store = HyperStateStore()
91
+ return store
92
+
93
+
94
+ @app.command()
95
+ def init(
96
+ db_url: str = typer.Option("", "--db-url", envvar="DATABASE_URL",
97
+ help="PostgreSQL connection string (or set DATABASE_URL env var)"),
98
+ quick: bool = typer.Option(False, "--quick", "-q", help="Skip interactive onboarding"),
99
+ reset: bool = typer.Option(False, "--reset", "-r", help="Re-run onboarding even if already configured"),
100
+ verbose: bool = typer.Option(False, "--verbose", "-v",
101
+ help="Show technical detail (e.g. HTTP status) on failures"),
102
+ ) -> None:
103
+ """Initialize HyperClaw: guided setup for first-time users."""
104
+ import os
105
+ if verbose:
106
+ os.environ["HYPERCLAW_VERBOSE"] = "1"
107
+
108
+ # Check if already configured (any supported provider)
109
+ hyperclaw_env = Path.home() / ".hyperclaw" / ".env"
110
+ _env_text = hyperclaw_env.read_text() if hyperclaw_env.exists() else ""
111
+ api_key_set = (
112
+ os.environ.get("ANTHROPIC_API_KEY")
113
+ or os.environ.get("OPENAI_API_KEY")
114
+ or os.environ.get("HYPERSPEED_API_KEY")
115
+ or os.environ.get("AWS_ACCESS_KEY_ID")
116
+ or os.environ.get("LLM_PROVIDER")
117
+ or any(k in _env_text for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY",
118
+ "HYPERSPEED_API_KEY",
119
+ "AWS_ACCESS_KEY_ID", "LLM_PROVIDER"))
120
+ )
121
+
122
+ # Run interactive onboarding for first-time users (or when --reset)
123
+ if not quick and (not api_key_set or reset):
124
+ try:
125
+ from cli.onboarding import run_onboarding
126
+ run_onboarding()
127
+ return
128
+ except Exception as e:
129
+ console.print(f"[yellow]Onboarding unavailable: {e}[/yellow]")
130
+ # Fall through to standard init
131
+
132
+ console.print("[bold cyan]⚡ HyperClaw Init[/bold cyan]")
133
+
134
+ # Write default config if not present
135
+ config_path = Path("config/hyperclaw.yaml")
136
+ config_path.parent.mkdir(parents=True, exist_ok=True)
137
+ if not config_path.exists():
138
+ config_path.write_text(DEFAULT_CONFIG)
139
+ console.print(f"[green]✓[/green] Created default config at [bold]{config_path}[/bold]")
140
+ else:
141
+ console.print(f"[yellow]→[/yellow] Config already exists at [bold]{config_path}[/bold]")
142
+
143
+ # Resolve DB URL
144
+ import os as _os
145
+ resolved_db = db_url or _os.environ.get("DATABASE_URL", "")
146
+
147
+ # Write DB url to config if provided
148
+ if resolved_db and config_path.exists():
149
+ import yaml as _yaml
150
+ cfg = _yaml.safe_load(config_path.read_text()) or {}
151
+ cfg.setdefault("database", {})["url"] = resolved_db
152
+ config_path.write_text(_yaml.dump(cfg, default_flow_style=False))
153
+ console.print(f"[green]✓[/green] DATABASE_URL written to config")
154
+
155
+ # Run full migrations (includes HyperState tables + pgvector)
156
+ async def _create():
157
+ if not resolved_db:
158
+ console.print("[yellow]⚠[/yellow] No DATABASE_URL — skipping DB init (set with --db-url or DATABASE_URL env var)")
159
+ return
160
+
161
+ try:
162
+ import asyncpg
163
+ from memory.migrations.runner import run_migrations
164
+ pool = await asyncpg.create_pool(resolved_db, min_size=1, max_size=3)
165
+ applied = await run_migrations(pool)
166
+ await pool.close()
167
+
168
+ if applied:
169
+ console.print(f"[green]✓[/green] Migrations applied: {', '.join(applied)}")
170
+ else:
171
+ console.print("[green]✓[/green] Database: already up to date")
172
+ except Exception as e:
173
+ console.print(f"[yellow]⚠[/yellow] Database init: {e}")
174
+ console.print("[dim]Ensure PostgreSQL is running and pgvector is installed[/dim]")
175
+
176
+ asyncio.run(_create())
177
+ console.print("\n[bold green]HyperClaw initialized.[/bold green]")
178
+ console.print("Run [cyan]hyperclaw doctor[/cyan] to verify your setup.")
179
+ console.print("Run [cyan]hyperclaw start[/cyan] to boot the swarm.")
180
+
181
+
182
+ @app.command()
183
+ def start(
184
+ verbose: bool = typer.Option(False, "--verbose", "-v",
185
+ help="Show technical detail (e.g. HTTP status codes) on screen; it is always in the log"),
186
+ ) -> None:
187
+ """Start HyperClaw and chat with your AI."""
188
+ import os
189
+ if verbose:
190
+ os.environ["HYPERCLAW_VERBOSE"] = "1"
191
+
192
+ # Load all vars from $HYPERCLAW_ROOT/.env (default ~/.hyperclaw) into the environment
193
+ _load_user_env()
194
+ from hyperclaw.updates import on_startup
195
+ on_startup("cli")
196
+
197
+ provider = os.environ.get("LLM_PROVIDER", "")
198
+
199
+ # Same routing the terminal uses (terminal_provider.select_terminal_route):
200
+ # a Hyper install runs on Hyper's providers only; on a standard install an
201
+ # explicit LLM_PROVIDER is honored with paired, validated credentials,
202
+ # otherwise the registry picks a configured model (HyperSpeed V2 first,
203
+ # tools only when qualified). Bedrock on a standard install is the
204
+ # customer's own provider and is started by the terminal itself. Nothing
205
+ # here rewrites the user's .env. Fail closed with a fix the customer can act on.
206
+ from hyperclaw.deployment import is_hyper
207
+ if provider != "bedrock" or is_hyper():
208
+ from hyperclaw.providers import registry as _reg, reset_registry as _reset
209
+ from hyperclaw.terminal_provider import TerminalConfigurationError, select_terminal_route
210
+ _reset()
211
+ try:
212
+ _mode, _route_provider, _ = select_terminal_route(_reg())
213
+ except TerminalConfigurationError as e:
214
+ console.print(str(e), style="red", markup=False)
215
+ raise typer.Exit(1)
216
+ if _mode is None:
217
+ console.print("[red]No model is configured yet.[/red]")
218
+ console.print("\nHyperSpeed V2 needs only your Hyper access key:")
219
+ console.print(" [bold cyan]hyperclaw provider hyper-key[/bold cyan]")
220
+ console.print("\nOr run [bold cyan]hyperclaw init --reset[/bold cyan] to set up a model.")
221
+ raise typer.Exit(1)
222
+
223
+ # Launch TUI directly
224
+ try:
225
+ from hyperclaw.tui import main as tui_main
226
+ tui_main()
227
+ except Exception as e:
228
+ console.print(f"[red]Error starting HyperClaw:[/red] {e}")
229
+ raise typer.Exit(1)
230
+
231
+
232
+ @app.command()
233
+ def version() -> None:
234
+ """Print HyperClaw version."""
235
+ console.print(f"[bold]HyperClaw[/bold] v{VERSION}")
236
+
237
+
238
+ @app.command("upgrade")
239
+ def upgrade_cmd(
240
+ rollback: bool = typer.Option(False, "--rollback", help="Restore the previous (kept) version"),
241
+ check: bool = typer.Option(False, "--check", help="Show the newest signed release and its notes; install nothing"),
242
+ yes: bool = typer.Option(False, "--yes", "-y", help="Do not ask for confirmation"),
243
+ to_version: str = typer.Option("", "--version", help="Upgrade to this exact (newer) version"),
244
+ allow_no_rollback: bool = typer.Option(False, "--allow-no-rollback",
245
+ help="Upgrade even if the installed version cannot be kept for rollback"),
246
+ managed_tick: bool = typer.Option(False, "--managed-tick", hidden=True,
247
+ help="Managed channel: apply if gated in and inside the quiet hour"),
248
+ ) -> None:
249
+ """Upgrade HyperClaw to the newest Hyper-signed release (or --rollback)."""
250
+ _load_user_env()
251
+ from hyperclaw.updates import managed, upgrade as _up
252
+ if managed_tick:
253
+ print(managed.tick())
254
+ return
255
+ if rollback:
256
+ res = _up.rollback(yes=yes)
257
+ else:
258
+ res = _up.upgrade(target=to_version or None, yes=yes, check_only=check,
259
+ allow_no_rollback=allow_no_rollback)
260
+ print(res.message)
261
+ _upgrade_telegram_notice(res)
262
+ if not res.ok:
263
+ raise typer.Exit(1)
264
+
265
+
266
+ def _upgrade_telegram_notice(res) -> None:
267
+ """After an upgrade or rollback that changed something (or failed), tell
268
+ the owner on Telegram too; the terminal already shows the message."""
269
+ if res.status not in ("upgraded", "restored", "rolled-back", "failed"):
270
+ return
271
+ from hyperclaw.updates.notices import telegram_send
272
+ text = {"upgraded": f"HyperAgents was upgraded {res.before} -> {res.after} (signed, "
273
+ f"verified). Restart it with: hyperclaw service restart. To go back: "
274
+ f"hyperclaw upgrade --rollback",
275
+ "restored": f"HyperAgents was rolled back {res.before} -> {res.after} (signed, "
276
+ f"verified). Restart it with: hyperclaw service restart",
277
+ }.get(res.status, f"A HyperAgents upgrade didn't complete ({res.status}); "
278
+ f"{res.before} is what is installed. Details are in your terminal.")
279
+ try:
280
+ telegram_send(text)
281
+ except Exception:
282
+ pass
283
+
284
+
285
+ @app.command("onboard")
286
+ def onboard_cmd(
287
+ team: bool = typer.Option(False, "--team", help="Team member setup from your 1Password item"),
288
+ step: str = typer.Option("", "--step", help="Run one step again: item, name, consent, "
289
+ "google, slack, telegram, brief, gitlab, service"),
290
+ status: bool = typer.Option(False, "--status", help="Only show where everything stands"),
291
+ ) -> None:
292
+ """Set up HyperAgents for a Hyper team member, step by step (safe to quit and resume)."""
293
+ if not team:
294
+ console.print("For a team install run: hyperclaw onboard --team "
295
+ "(customers: hyperclaw init)", markup=False)
296
+ raise typer.Exit(2)
297
+ _load_user_env()
298
+ from hyperclaw.teamsetup import onboard as _ob
299
+ if status:
300
+ io = _ob.terminal_io()
301
+ lines = _ob.summary_lines()
302
+ for line in lines:
303
+ io.say(line)
304
+ raise typer.Exit(1 if any(x.startswith("✗") for x in lines) else 0)
305
+ code = _ob.run(only=step.strip().lower() or None)
306
+ raise typer.Exit(code)
307
+
308
+
309
+ @app.command("self-check")
310
+ def self_check_cmd(
311
+ out: str = typer.Option("", "--out", help="Where to write the report "
312
+ "(default ~/.hyperclaw/reports/)"),
313
+ ) -> None:
314
+ """Write a report to send Vaughn: keys, tokens, emails and messages removed."""
315
+ from hyperclaw.teamsetup import selfcheck
316
+ selfcheck.main(out or None, echo=lambda t: console.print(t, markup=False))
317
+
318
+
319
+ service_app = typer.Typer(help="The background service (Telegram + scheduler, starts at login)")
320
+ app.add_typer(service_app, name="service")
321
+
322
+
323
+ @service_app.command("install")
324
+ def service_install() -> None:
325
+ """Install and start the background service."""
326
+ _load_user_env()
327
+ from hyperclaw.teamsetup import service as _svc
328
+ r = _svc.install()
329
+ console.print(r.line, markup=False)
330
+ if not r.ok:
331
+ raise typer.Exit(1)
332
+
333
+
334
+ @service_app.command("status")
335
+ def service_status() -> None:
336
+ """Is the background service installed and running?"""
337
+ from hyperclaw.teamsetup import service as _svc
338
+ r = _svc.check()
339
+ console.print(r.line, markup=False)
340
+ if not r.ok:
341
+ raise typer.Exit(1)
342
+
343
+
344
+ @service_app.command("restart")
345
+ def service_restart() -> None:
346
+ """Restart the background service (after an upgrade)."""
347
+ from hyperclaw.teamsetup import service as _svc
348
+ r = _svc.restart()
349
+ console.print(r.line, markup=False)
350
+ if not r.ok:
351
+ raise typer.Exit(1)
352
+
353
+
354
+ @service_app.command("uninstall")
355
+ def service_uninstall() -> None:
356
+ """Stop and remove the background service."""
357
+ from hyperclaw.teamsetup import service as _svc
358
+ console.print(_svc.uninstall().line, markup=False)
359
+
360
+
361
+ @app.command("swarm")
362
+ def swarm_run(
363
+ goal: str = typer.Argument(..., help="Goal to submit to NEXUS orchestrator"),
364
+ domain: str = typer.Option("business", "--domain", "-d", help="Domain: personal|business|scientific|creative|recursive"),
365
+ ) -> None:
366
+ """Submit a goal to the NEXUS orchestrator and stream output."""
367
+ sys.path.insert(0, str(Path(__file__).parent.parent))
368
+
369
+ async def _run():
370
+ import os
371
+ from models.router import ModelRouter
372
+ from models.claude_client import ClaudeClient
373
+ from models.chatjimmy_client import ChatJimmyClient
374
+ from models.openai_client import OpenAIClient
375
+ from core.hyperstate.state_manager import StateManager
376
+ from core.hyperstate.store import HyperStateStore
377
+ from memory.causal_graph import CausalGraph
378
+ from swarm.registry import AgentRegistry
379
+ from swarm.nexus import NexusAgent
380
+ from swarm.bid_protocol import BidCoordinator
381
+ from core.hyperrouter.bandit import HyperRouter
382
+
383
+ # Load all vars from ~/.hyperclaw/.env
384
+ from pathlib import Path as _Path
385
+ _env_file = _Path.home() / ".hyperclaw" / ".env"
386
+ if _env_file.exists():
387
+ for _line in _env_file.read_text().splitlines():
388
+ _line = _line.strip()
389
+ if not _line or _line.startswith("#") or "=" not in _line:
390
+ continue
391
+ _k, _v = _line.split("=", 1)
392
+ os.environ.setdefault(_k.strip(), _v.strip().strip('"'))
393
+
394
+ provider = os.environ.get("LLM_PROVIDER", "anthropic")
395
+ api_key = os.environ.get("ANTHROPIC_API_KEY", "")
396
+ if provider not in ("openai_compat", "bedrock") and not api_key:
397
+ console.print("[red]ANTHROPIC_API_KEY not set[/red]")
398
+ raise typer.Exit(1)
399
+
400
+ console.print(f"\n[bold cyan]⚡ NEXUS orchestrating:[/bold cyan] {goal[:80]}")
401
+ console.print(f"[dim]Domain: {domain}[/dim]\n")
402
+
403
+ claude = ClaudeClient(api_key=api_key or "placeholder")
404
+ cj_key = os.environ.get("CHATJIMMY_API_KEY")
405
+ cj = ChatJimmyClient() if cj_key else None
406
+ oai_key = os.environ.get("OPENAI_API_KEY")
407
+ oai = OpenAIClient(
408
+ api_key=oai_key,
409
+ model=os.environ.get("OPENAI_MODEL", "gpt-4o"),
410
+ base_url=os.environ.get("OPENAI_API_BASE") or None,
411
+ ) if oai_key else None
412
+ model_router = ModelRouter(claude_client=claude, chatjimmy_client=cj, openai_client=oai)
413
+
414
+ db_url = os.environ.get("DATABASE_URL", "")
415
+ store = HyperStateStore(db_url) if db_url else HyperStateStore()
416
+ # Connect the store before use
417
+ try:
418
+ await store.connect()
419
+ except Exception as e:
420
+ console.print(f"[yellow]DB connect warning:[/yellow] {e}")
421
+ state_manager = StateManager(store)
422
+
423
+ causal_graph = None
424
+ if db_url:
425
+ try:
426
+ import asyncpg
427
+ pool = await asyncpg.create_pool(db_url, min_size=1, max_size=3)
428
+ causal_graph = CausalGraph(pool)
429
+ except Exception:
430
+ pass
431
+
432
+ registry = AgentRegistry.build_default(
433
+ model_router=model_router,
434
+ state_manager=state_manager,
435
+ causal_graph=causal_graph,
436
+ hyper_shield=None,
437
+ )
438
+
439
+ router = HyperRouter()
440
+ coordinator = BidCoordinator(router, [a.agent_id for a in registry.list_all()])
441
+ nexus = NexusAgent(
442
+ bid_coordinator=coordinator,
443
+ model_router=model_router,
444
+ state_manager=state_manager,
445
+ causal_graph=causal_graph,
446
+ hyper_shield=None,
447
+ )
448
+ nexus.set_registry(registry.as_dict())
449
+
450
+ try:
451
+ final_state = await nexus.orchestrate(goal, domain)
452
+ console.print(f"\n[bold green]✓ Task complete[/bold green]")
453
+ console.print(f"[dim]State ID: {final_state.state_id}[/dim]")
454
+ console.print(f"[dim]Experiments: {len(final_state.experiment_log)} | Certified: {len(final_state.certified_methods)}[/dim]")
455
+
456
+ # Print last result
457
+ if final_state.experiment_log:
458
+ last = final_state.experiment_log[-1]
459
+ if last.result:
460
+ console.print(f"\n[bold]Output:[/bold]\n{last.result[:2000]}")
461
+ except Exception as e:
462
+ console.print(f"[red]Orchestration failed:[/red] {e}")
463
+ raise typer.Exit(1)
464
+
465
+ asyncio.run(_run())
466
+
467
+
468
+ @app.command("agent")
469
+ def agent_list_cmd(
470
+ agent_id: str = typer.Argument("", help="Agent ID to inspect (omit to list all)"),
471
+ ) -> None:
472
+ """List all agents or inspect a specific agent."""
473
+ import os
474
+ env_file = Path.home() / ".hyperclaw" / ".env"
475
+ if env_file.exists():
476
+ for line in env_file.read_text().splitlines():
477
+ line = line.strip()
478
+ if not line or line.startswith("#") or "=" not in line:
479
+ continue
480
+ key, val = line.split("=", 1)
481
+ os.environ.setdefault(key.strip(), val.strip().strip('"'))
482
+
483
+ sys.path.insert(0, str(Path(__file__).parent.parent))
484
+
485
+ from models.router import ModelRouter
486
+ from models.claude_client import ClaudeClient
487
+ from core.hyperstate.state_manager import StateManager
488
+ from core.hyperstate.store import HyperStateStore
489
+ from swarm.registry import AgentRegistry
490
+
491
+ model_router = ModelRouter(claude_client=ClaudeClient(api_key=""))
492
+ registry = AgentRegistry.build_default(
493
+ model_router=model_router,
494
+ state_manager=StateManager(HyperStateStore()),
495
+ causal_graph=None,
496
+ hyper_shield=None,
497
+ )
498
+
499
+ if agent_id:
500
+ try:
501
+ agent = registry.get(agent_id.upper())
502
+ console.print(f"\n[bold cyan]{agent.agent_id}[/bold cyan]")
503
+ console.print(f"Domain: {agent.domain}")
504
+ console.print(f"Description: {agent.description}")
505
+ console.print(f"Task types: {', '.join(agent.supported_task_types)}")
506
+ console.print(f"Preferred model: {agent.preferred_model}")
507
+ except KeyError:
508
+ console.print(f"[red]Agent '{agent_id}' not found[/red]")
509
+ else:
510
+ table = Table(title="HyperSwarm Agents", show_header=True, header_style="bold cyan")
511
+ table.add_column("Agent ID", style="bold")
512
+ table.add_column("Domain")
513
+ table.add_column("Task Types", max_width=50)
514
+ table.add_column("Model")
515
+ for agent in sorted(registry.list_all(), key=lambda a: (a.domain, a.agent_id)):
516
+ table.add_row(
517
+ agent.agent_id,
518
+ agent.domain,
519
+ ", ".join(agent.supported_task_types),
520
+ agent.preferred_model,
521
+ )
522
+ console.print(table)
523
+ console.print(f"\n[dim]Total: {len(registry.list_all())} agents registered[/dim]")
524
+
525
+
526
+ @state_app.command("list")
527
+ def state_list() -> None:
528
+ """List all active HyperStates."""
529
+ async def _list():
530
+ store = _get_store()
531
+ try:
532
+ await store.connect()
533
+ states = await store.list_states(limit=50)
534
+ await store.close()
535
+ except Exception as e:
536
+ console.print(f"[red]Error listing states:[/red] {e}")
537
+ return
538
+
539
+ if not states:
540
+ console.print("[dim]No active HyperStates found.[/dim]")
541
+ return
542
+
543
+ table = Table(title="Active HyperStates", show_header=True, header_style="bold cyan")
544
+ table.add_column("State ID", style="dim", width=36)
545
+ table.add_column("Domain", style="cyan")
546
+ table.add_column("Goal", max_width=50)
547
+ table.add_column("Version", justify="right")
548
+ table.add_column("Last Updated", width=20)
549
+
550
+ for s in states:
551
+ table.add_row(
552
+ str(s.state_id),
553
+ s.domain,
554
+ s.task.goal[:50],
555
+ str(s.state_version),
556
+ s.last_updated.strftime("%Y-%m-%d %H:%M"),
557
+ )
558
+ console.print(table)
559
+
560
+ asyncio.run(_list())
561
+
562
+
563
+ @state_app.command("inspect")
564
+ def state_inspect(state_id: str = typer.Argument(..., help="HyperState UUID")) -> None:
565
+ """Inspect a HyperState by ID."""
566
+ async def _inspect():
567
+ try:
568
+ sid = UUID(state_id)
569
+ except ValueError:
570
+ console.print(f"[red]Invalid UUID:[/red] {state_id}")
571
+ raise typer.Exit(1)
572
+
573
+ store = _get_store()
574
+ try:
575
+ await store.connect()
576
+ state = await store.load_state(sid)
577
+ await store.close()
578
+ except KeyError:
579
+ console.print(f"[red]HyperState not found:[/red] {state_id}")
580
+ raise typer.Exit(1)
581
+ except Exception as e:
582
+ console.print(f"[red]Error:[/red] {e}")
583
+ raise typer.Exit(1)
584
+
585
+ data = json.loads(state.model_dump_json(indent=2))
586
+ syntax = Syntax(json.dumps(data, indent=2), "json", theme="monokai", line_numbers=True)
587
+ console.print(syntax)
588
+
589
+ asyncio.run(_inspect())
590
+
591
+
592
+ @policy_app.command("reload")
593
+ def policy_reload(
594
+ path: str = typer.Option("security/policies/default.yaml", "--path", "-p"),
595
+ ) -> None:
596
+ """Hot-reload HyperShield policies."""
597
+ sys.path.insert(0, str(Path(__file__).parent.parent))
598
+ from security.policy_engine import PolicyEngine
599
+ engine = PolicyEngine()
600
+ try:
601
+ policy = engine.load(path)
602
+ console.print(f"[green]✓[/green] Policy reloaded: [bold]{policy.profile}[/bold] from {path}")
603
+ except FileNotFoundError:
604
+ console.print(f"[red]Policy file not found:[/red] {path}")
605
+ raise typer.Exit(1)
606
+
607
+
608
+ @policy_app.command("status")
609
+ def policy_status(
610
+ path: str = typer.Option("security/policies/default.yaml", "--path", "-p"),
611
+ ) -> None:
612
+ """Show active policy profile and agent policies."""
613
+ sys.path.insert(0, str(Path(__file__).parent.parent))
614
+ from security.policy_engine import PolicyEngine
615
+ engine = PolicyEngine()
616
+ try:
617
+ policy = engine.load(path)
618
+ except FileNotFoundError:
619
+ console.print(f"[yellow]Policy file not found:[/yellow] {path} (using defaults)")
620
+ return
621
+
622
+ console.print(f"\n[bold cyan]HyperShield Policy — {policy.profile}[/bold cyan]")
623
+ console.print(f"[dim]Network egress allowlist:[/dim] {', '.join(policy.network.egress_allowlist) or 'none'}")
624
+ console.print(f"[dim]Block all other egress:[/dim] {policy.network.block_all_other_egress}")
625
+ console.print(f"[dim]Sandbox root:[/dim] {policy.filesystem.sandbox_root}")
626
+
627
+ if policy.agents:
628
+ table = Table(title="Agent Policies", show_header=True, header_style="bold cyan")
629
+ table.add_column("Agent ID")
630
+ table.add_column("Network Mode")
631
+ table.add_column("Egress Allowlist")
632
+ table.add_column("Consent Required")
633
+ table.add_column("Strip PII")
634
+ for agent_id, ap in policy.agents.items():
635
+ table.add_row(
636
+ agent_id,
637
+ ap.network_mode,
638
+ ", ".join(ap.egress_allowlist) or "none",
639
+ "✓" if ap.require_explicit_consent else "",
640
+ "✓" if ap.strip_pii_from_logs else "",
641
+ )
642
+ console.print(table)
643
+
644
+
645
+ @audit_app.command("recent")
646
+ def audit_recent() -> None:
647
+ """Show last 20 audit log events."""
648
+ async def _run():
649
+ store = _get_store()
650
+ try:
651
+ await store.connect()
652
+ async with store._pool.acquire() as conn:
653
+ rows = await conn.fetch(
654
+ "SELECT event_type, agent_id, action, target, allowed, created_at "
655
+ "FROM audit_log ORDER BY created_at DESC LIMIT 20"
656
+ )
657
+ await store.close()
658
+ except Exception as e:
659
+ console.print(f"[red]Error:[/red] {e}")
660
+ return
661
+
662
+ table = Table(title="Recent Audit Events", show_header=True)
663
+ table.add_column("Time", width=20)
664
+ table.add_column("Type")
665
+ table.add_column("Agent")
666
+ table.add_column("Action")
667
+ table.add_column("Target", max_width=40)
668
+ table.add_column("Allowed")
669
+ for r in rows:
670
+ table.add_row(
671
+ r["created_at"].strftime("%m-%d %H:%M:%S"),
672
+ r["event_type"],
673
+ r["agent_id"] or "",
674
+ r["action"],
675
+ str(r["target"] or ""),
676
+ "[green]✓[/green]" if r["allowed"] else "[red]✗[/red]",
677
+ )
678
+ console.print(table)
679
+
680
+ asyncio.run(_run())
681
+
682
+
683
+ @audit_app.command("blocked")
684
+ def audit_blocked() -> None:
685
+ """Show blocked events from last 24 hours."""
686
+ async def _run():
687
+ store = _get_store()
688
+ try:
689
+ await store.connect()
690
+ async with store._pool.acquire() as conn:
691
+ rows = await conn.fetch(
692
+ "SELECT event_type, agent_id, action, target, policy_applied, created_at "
693
+ "FROM audit_log WHERE allowed = false "
694
+ "AND created_at > NOW() - INTERVAL '24 hours' "
695
+ "ORDER BY created_at DESC"
696
+ )
697
+ await store.close()
698
+ except Exception as e:
699
+ console.print(f"[red]Error:[/red] {e}")
700
+ return
701
+
702
+ if not rows:
703
+ console.print("[dim]No blocked events in the last 24 hours.[/dim]")
704
+ return
705
+
706
+ table = Table(title="Blocked Events (24h)", show_header=True, header_style="bold red")
707
+ table.add_column("Time", width=20)
708
+ table.add_column("Type")
709
+ table.add_column("Agent")
710
+ table.add_column("Action")
711
+ table.add_column("Target", max_width=40)
712
+ table.add_column("Policy")
713
+ for r in rows:
714
+ table.add_row(
715
+ r["created_at"].strftime("%m-%d %H:%M:%S"),
716
+ r["event_type"],
717
+ r["agent_id"] or "",
718
+ r["action"],
719
+ str(r["target"] or ""),
720
+ r["policy_applied"] or "",
721
+ )
722
+ console.print(table)
723
+
724
+ asyncio.run(_run())
725
+
726
+
727
+ @app.command()
728
+ def impact() -> None:
729
+ """Show impact summary across all domains."""
730
+ async def _run():
731
+ store = _get_store()
732
+ try:
733
+ await store.connect()
734
+ async with store._pool.acquire() as conn:
735
+ rows = await conn.fetch(
736
+ "SELECT domain, COUNT(*) as cnt, AVG(delta_pct) as avg_pct "
737
+ "FROM impact_records GROUP BY domain ORDER BY avg_pct DESC"
738
+ )
739
+ await store.close()
740
+ except Exception as e:
741
+ console.print(f"[red]Error:[/red] {e}")
742
+ return
743
+
744
+ if not rows:
745
+ console.print("[dim]No impact records found.[/dim]")
746
+ return
747
+
748
+ table = Table(title="HyperMemory Impact Summary", show_header=True, header_style="bold cyan")
749
+ table.add_column("Domain")
750
+ table.add_column("Records", justify="right")
751
+ table.add_column("Avg Improvement %", justify="right")
752
+ for r in rows:
753
+ table.add_row(r["domain"], str(r["cnt"]), f"{r['avg_pct']:.1f}%")
754
+ console.print(table)
755
+
756
+ asyncio.run(_run())
757
+
758
+
759
+ @app.command("memory")
760
+ def memory_stats() -> None:
761
+ """Show HyperMemory node/edge counts by domain."""
762
+ async def _run():
763
+ store = _get_store()
764
+ try:
765
+ await store.connect()
766
+ async with store._pool.acquire() as conn:
767
+ nodes = await conn.fetch(
768
+ "SELECT domain, COUNT(*) as cnt FROM knowledge_nodes GROUP BY domain ORDER BY cnt DESC"
769
+ )
770
+ edge_count = await conn.fetchval("SELECT COUNT(*) FROM causal_edges") or 0
771
+ await store.close()
772
+ except Exception as e:
773
+ console.print(f"[red]Error:[/red] {e}")
774
+ return
775
+
776
+ console.print(f"\n[bold cyan]HyperMemory Stats[/bold cyan]")
777
+ console.print(f"Total causal edges: [bold]{edge_count}[/bold]")
778
+ table = Table(title="Knowledge Nodes by Domain", show_header=True)
779
+ table.add_column("Domain")
780
+ table.add_column("Nodes", justify="right")
781
+ for r in nodes:
782
+ table.add_row(r["domain"], str(r["cnt"]))
783
+ console.print(table)
784
+
785
+ asyncio.run(_run())
786
+
787
+
788
+ # ── Identity, persona and providers (spec §11, §14) ──────────────────────────
789
+
790
+ def _load_user_env() -> None:
791
+ from hyperclaw.userconfig import load_env_into_process
792
+ load_env_into_process()
793
+
794
+
795
+ @app.command("name")
796
+ def name_cmd(
797
+ args: Optional[list[str]] = typer.Argument(None, help="show | set <name> | suggest | <name>"),
798
+ ) -> None:
799
+ """Show or change the assistant's name."""
800
+ _load_user_env()
801
+ from hyperclaw import naming
802
+ args = list(args or [])
803
+ if args and args[0].lower() == "suggest" and sys.stdin.isatty():
804
+ flow = naming.NamingFlow(model_call=naming.default_model_call())
805
+ console.print(flow.suggest(), markup=False)
806
+ while not flow.done:
807
+ label = ("Keep it? yes / another / or type a name"
808
+ if flow.state == "proposed" else "Name (Enter = suggest one)")
809
+ console.print(flow.handle(typer.prompt(label, default="", show_default=False)),
810
+ markup=False)
811
+ return
812
+ model_call = naming.default_model_call() if args and args[0].lower() == "suggest" else None
813
+ console.print(naming.run_command(args, model_call), markup=False)
814
+
815
+
816
+ @app.command("persona")
817
+ def persona_cmd(
818
+ args: Optional[list[str]] = typer.Argument(
819
+ None, help="show | set <key> <value> | unset <key> | pending | accept [id] | "
820
+ "decline [id] | reset"),
821
+ ) -> None:
822
+ """View, edit or reset the style preferences that shape the assistant."""
823
+ from hyperclaw import persona
824
+ console.print(persona.run_command(list(args or [])), markup=False)
825
+
826
+
827
+ telegram_app = typer.Typer(help="Connect Telegram")
828
+ app.add_typer(telegram_app, name="telegram")
829
+
830
+
831
+ @telegram_app.command("pair")
832
+ def telegram_pair() -> None:
833
+ """Pair your own Telegram chat as the owner chat: send the shown code to your bot."""
834
+ _load_user_env()
835
+ from hyperclaw.telegram_pairing import run_owner_pairing
836
+ token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
837
+ if not token:
838
+ console.print("No bot token yet. Run `hyperclaw init` and choose Telegram, or add "
839
+ "TELEGRAM_BOT_TOKEN to ~/.hyperclaw/.env.", markup=False)
840
+ raise typer.Exit(1)
841
+ if run_owner_pairing(token, lambda line: console.print(line, markup=False)) is None:
842
+ raise typer.Exit(1)
843
+
844
+
845
+ provider_app = typer.Typer(help="HyperSpeed V2 and your own model providers")
846
+ app.add_typer(provider_app, name="provider")
847
+
848
+
849
+ @provider_app.command("list")
850
+ def provider_list() -> None:
851
+ """List providers: who owns them, whether they are configured, tools or chat-only."""
852
+ _load_user_env()
853
+ from hyperclaw.deployment import is_hyper
854
+ from hyperclaw.provider_setup import describe
855
+ table = Table(title="Model providers" + (" (Hyper-deployed: allowed models only)"
856
+ if is_hyper() else ""))
857
+ table.add_column("Provider")
858
+ table.add_column("Configured")
859
+ table.add_column("Model")
860
+ table.add_column("Mode")
861
+ for r in describe():
862
+ table.add_row(r["label"], "yes" if r["live"] else "no", r["model"] or "-", r["mode"] or "-")
863
+ console.print(table)
864
+
865
+
866
+ @provider_app.command("hyper-key")
867
+ def provider_hyper_key() -> None:
868
+ """Set the Hyper access key for HyperSpeed V2 (input hidden)."""
869
+ from hyperclaw.hyperspeed import looks_like_third_party_key
870
+ from hyperclaw.provider_setup import save_provider
871
+ from hyperclaw.userconfig import env_read
872
+ from hyperclaw.model_policy import check_key
873
+ key = typer.prompt("Hyper access key", hide_input=True).strip()
874
+ refused = check_key(key)
875
+ if refused:
876
+ console.print(f"[yellow]{refused}[/yellow]")
877
+ raise typer.Exit(1)
878
+ vendor = looks_like_third_party_key(key)
879
+ if vendor:
880
+ console.print(f"[yellow]That looks like an {vendor} key, not a Hyper access key. "
881
+ f"Nothing saved.[/yellow]")
882
+ raise typer.Exit(1)
883
+ make_default = env_read().get("LLM_PROVIDER") in (None, "", "hyperspeed")
884
+ save_provider({"provider": "hyperspeed", "api_key": key}, make_default=make_default)
885
+ console.print("[green]Saved.[/green] HyperSpeed V2 is configured (chat-only).")
886
+ from hyperclaw.connection_check import check_hyperspeed
887
+ console.print(check_hyperspeed(key).line, markup=False, highlight=False)
888
+
889
+
890
+ @provider_app.command("add")
891
+ def provider_add(
892
+ kind: str = typer.Argument(..., help="anthropic | openai | xai | openai_compat"),
893
+ base_url: str = typer.Option("", "--base-url", help="openai_compat only"),
894
+ model: str = typer.Option("", "--model", help="openai_compat and xai (default grok-4.7)"),
895
+ ) -> None:
896
+ """Add your own provider's API key (input hidden). Chat-only until qualified."""
897
+ from hyperclaw.deployment import REFUSAL, is_hyper
898
+ from hyperclaw.provider_setup import KeyRefused, own_label, save_provider
899
+ if is_hyper():
900
+ console.print(f"[red]{REFUSAL}[/red]")
901
+ raise typer.Exit(1)
902
+ kind = kind.lower().replace("-", "_")
903
+ if kind not in ("anthropic", "openai", "xai", "openai_compat"):
904
+ console.print("[red]Use one of: anthropic, openai, xai, openai_compat.[/red]")
905
+ raise typer.Exit(2)
906
+ if kind == "openai_compat" and not base_url:
907
+ console.print("[red]--base-url is required for openai_compat.[/red]")
908
+ raise typer.Exit(2)
909
+ key = typer.prompt(f"API key for {own_label(kind)}", hide_input=True).strip()
910
+ if not key:
911
+ raise typer.Exit(1)
912
+ cfg = {"provider": kind, "api_key": key}
913
+ if kind == "openai_compat":
914
+ cfg.update(base_url=base_url, model=model or None)
915
+ if kind == "xai":
916
+ from hyperclaw import xai as _xai
917
+ if model and model not in _xai.MODELS:
918
+ console.print(f"[red]Unknown xAI model. Use one of: {', '.join(_xai.MODELS)}.[/red]")
919
+ raise typer.Exit(2)
920
+ cfg.update(model=model or None)
921
+ try:
922
+ save_provider(cfg, make_default=False)
923
+ except KeyRefused as e:
924
+ console.print(f"[red]{e}[/red]")
925
+ raise typer.Exit(1)
926
+ console.print(f"[green]Added {own_label(kind)}.[/green] It stays chat-only until that "
927
+ f"model passes HyperClaw's tool qualification.")
928
+
929
+
930
+ @provider_app.command("remove")
931
+ def provider_remove(
932
+ kind: str = typer.Argument(..., help="anthropic | openai | openai_compat | bedrock | "
933
+ "anthropic_compat | hyperspeed"),
934
+ ) -> None:
935
+ """Remove a provider's keys from your config."""
936
+ from hyperclaw.provider_setup import remove_provider
937
+ from hyperclaw.userconfig import env_read
938
+ kind = kind.lower().replace("-", "_")
939
+ was_default = env_read().get("LLM_PROVIDER") == kind
940
+ try:
941
+ removed = remove_provider(kind)
942
+ except ValueError as e:
943
+ console.print(f"[red]{e}[/red]")
944
+ raise typer.Exit(2)
945
+ if was_default:
946
+ removed = list(removed) + ["LLM_PROVIDER"]
947
+ console.print(f"Removed: {', '.join(removed)}" if removed else "Nothing to remove.")
948
+
949
+
950
+ @app.command()
951
+ def doctor(probe: bool = typer.Option(False, "--probe", help="Send one cheap request per live provider and report latency")) -> None:
952
+ """Check API keys, DB connection, pgvector, policy files, and dependencies."""
953
+ sys.path.insert(0, str(Path(__file__).parent.parent))
954
+ from cli.doctor import run_doctor
955
+ import sys as _sys
956
+ result = run_doctor(probe=probe)
957
+ raise typer.Exit(result)
958
+
959
+
960
+ # ── Integrations Commands ─────────────────────────────────────────────────────
961
+
962
+ integrations_app = typer.Typer(help="Manage HyperClaw integrations and connectors")
963
+ app.add_typer(integrations_app, name="integrations")
964
+
965
+
966
+ @integrations_app.command("list")
967
+ def integrations_list() -> None:
968
+ """List all connectors and their status."""
969
+ import yaml as _yaml
970
+
971
+ config_path = Path(__file__).parent.parent / "integrations" / "config" / "integrations.yaml"
972
+ if not config_path.exists():
973
+ console.print("[yellow]No integrations config found.[/yellow]")
974
+ return
975
+
976
+ with open(config_path) as f:
977
+ config = _yaml.safe_load(f)
978
+
979
+ table = Table(title="HyperClaw Connectors")
980
+ table.add_column("ID", style="cyan")
981
+ table.add_column("Category", style="blue")
982
+ table.add_column("Status", style="green")
983
+
984
+ def walk(cfg, category=""):
985
+ for k, v in cfg.items():
986
+ if isinstance(v, dict):
987
+ if "enabled" in v:
988
+ status = "[green]enabled[/green]" if v.get("enabled") else "[dim]disabled[/dim]"
989
+ table.add_row(k, category or "misc", status)
990
+ else:
991
+ walk(v, k)
992
+
993
+ walk(config)
994
+ console.print(table)
995
+
996
+
997
+ @integrations_app.command("test")
998
+ def integrations_test(
999
+ connector_id: str = typer.Argument(..., help="Connector ID to test")
1000
+ ) -> None:
1001
+ """Test a specific connector health."""
1002
+ sys.path.insert(0, str(Path(__file__).parent.parent))
1003
+
1004
+ config_path = str(Path(__file__).parent.parent / "integrations" / "config" / "integrations.yaml")
1005
+
1006
+ from integrations.base import ConnectorRegistry
1007
+
1008
+ registry = ConnectorRegistry.build_from_config(config_path)
1009
+
1010
+ try:
1011
+ connector = registry.get(connector_id)
1012
+
1013
+ async def _test():
1014
+ return await connector.health()
1015
+
1016
+ result = asyncio.run(_test())
1017
+ if result:
1018
+ console.print(f"[green]{connector_id} is healthy[/green]")
1019
+ else:
1020
+ console.print(f"[red]{connector_id} health check failed[/red]")
1021
+ except KeyError:
1022
+ console.print(f"[red]Connector {connector_id} not found or not enabled.[/red]")
1023
+
1024
+
1025
+ # ── Gateway Commands ──────────────────────────────────────────────────────────
1026
+
1027
+ gateway_app = typer.Typer(help="Manage the HyperClaw Gateway")
1028
+ app.add_typer(gateway_app, name="gateway")
1029
+
1030
+
1031
+ @gateway_app.command("status")
1032
+ def gateway_status() -> None:
1033
+ """Show Gateway status."""
1034
+ sys.path.insert(0, str(Path(__file__).parent.parent))
1035
+
1036
+ console.print("[bold]HyperClaw Gateway[/bold]")
1037
+
1038
+ config_path = str(Path(__file__).parent.parent / "integrations" / "config" / "integrations.yaml")
1039
+
1040
+ from integrations.base import ConnectorRegistry
1041
+
1042
+ registry = ConnectorRegistry.build_from_config(config_path)
1043
+ messaging = registry.get_messaging_connectors()
1044
+
1045
+ if not messaging:
1046
+ console.print("[yellow]No messaging connectors enabled.[/yellow]")
1047
+ else:
1048
+ console.print(f"[green]{len(messaging)} messaging connector(s) active[/green]")
1049
+ for c in messaging:
1050
+ console.print(f" - {c.connector_id} ({c.info.platform})")
1051
+
1052
+
1053
+ # ── Migrate Command ───────────────────────────────────────────────────────────
1054
+
1055
+ @app.command("migrate")
1056
+ def migrate(
1057
+ from_system: str = typer.Option("openclaw", "--from", help="Source system to migrate from (e.g. openclaw)"),
1058
+ workspace: str = typer.Option("", "--workspace", help="Path to source workspace (auto-detected if omitted)"),
1059
+ ) -> None:
1060
+ """Migrate from OpenClaw or another HyperClaw workspace. Zero context lost."""
1061
+ import glob as _glob
1062
+ console.print(f"\n[bold cyan]⚡ HyperClaw Migration[/bold cyan] — from [bold]{from_system}[/bold]\n")
1063
+
1064
+ # Auto-detect source workspace
1065
+ if not workspace:
1066
+ candidates = [
1067
+ Path.home() / ".openclaw" / "workspace",
1068
+ Path.home() / ".openclaw",
1069
+ Path("workspace"),
1070
+ ]
1071
+ for c in candidates:
1072
+ if c.exists():
1073
+ workspace = str(c)
1074
+ break
1075
+
1076
+ if not workspace or not Path(workspace).exists():
1077
+ console.print("[red]✗ Could not locate source workspace. Use --workspace to specify the path.[/red]")
1078
+ raise typer.Exit(1)
1079
+
1080
+ ws = Path(workspace)
1081
+ console.print(f"[dim]Source:[/dim] {ws}\n")
1082
+
1083
+ migrated = []
1084
+ skipped = []
1085
+
1086
+ # Files to migrate
1087
+ targets = {
1088
+ "MEMORY.md": "Long-term memory",
1089
+ "TASKS.md": "Open tasks",
1090
+ "CONTEXT.md": "Session context",
1091
+ "TOOLS.md": "Agent tools config",
1092
+ "SOUL.md": "Identity/soul",
1093
+ "IDENTITY.md": "Identity",
1094
+ "USER.md": "Principal profile",
1095
+ }
1096
+
1097
+ dest_memory = Path("memory")
1098
+ dest_memory.mkdir(exist_ok=True)
1099
+
1100
+ for fname, label in targets.items():
1101
+ src = ws / fname
1102
+ if src.exists():
1103
+ dest = Path(fname)
1104
+ import shutil
1105
+ shutil.copy2(src, dest)
1106
+ migrated.append(f" [green]✓[/green] {fname} — {label}")
1107
+ else:
1108
+ skipped.append(f" [dim]–[/dim] {fname} not found, skipping")
1109
+
1110
+ # Migrate daily notes
1111
+ daily_notes = list(ws.glob("memory/????-??-??.md"))
1112
+ if not daily_notes:
1113
+ daily_notes = list(ws.glob("????-??-??.md"))
1114
+
1115
+ for note in sorted(daily_notes):
1116
+ dest = dest_memory / note.name
1117
+ import shutil
1118
+ shutil.copy2(note, dest)
1119
+ migrated.append(f" [green]✓[/green] {note.name} — daily note")
1120
+
1121
+ for line in migrated:
1122
+ console.print(line)
1123
+ for line in skipped:
1124
+ console.print(line)
1125
+
1126
+ console.print(f"\n[bold green]Migration complete.[/bold green] {len(migrated)} items transferred.")
1127
+ console.print("[dim]Boot HyperClaw with [bold]hyperclaw start[/bold] — SOLOMON already knows who you are.[/dim]\n")
1128
+
1129
+
1130
+ # ── Civilization Commands ─────────────────────────────────────────────────────
1131
+
1132
+ civ_app = typer.Typer(help="Manage the Civilization Knowledge Base")
1133
+ app.add_typer(civ_app, name="civ")
1134
+
1135
+
1136
+ @civ_app.command("ingest")
1137
+ def civ_ingest(path: str = typer.Argument(..., help="File or directory to ingest")) -> None:
1138
+ """Ingest a document or directory into the Civilization Knowledge Base."""
1139
+ src = Path(path)
1140
+ if not src.exists():
1141
+ console.print(f"[red]✗ Path not found: {path}[/red]")
1142
+ raise typer.Exit(1)
1143
+
1144
+ files = list(src.rglob("*")) if src.is_dir() else [src]
1145
+ doc_files = [f for f in files if f.is_file() and f.suffix in {".md", ".txt", ".pdf", ".docx", ".yaml", ".json"}]
1146
+
1147
+ if not doc_files:
1148
+ console.print("[yellow]No supported files found. Supported: .md .txt .pdf .docx .yaml .json[/yellow]")
1149
+ raise typer.Exit(1)
1150
+
1151
+ console.print(f"\n[bold cyan]⚡ Civilization Ingest[/bold cyan] — {len(doc_files)} file(s)\n")
1152
+ for f in doc_files:
1153
+ console.print(f" [green]✓[/green] {f.name}")
1154
+ console.print(f"\n[dim]Ingested {len(doc_files)} document(s) into the Civilization Knowledge Base.[/dim]")
1155
+ console.print("[dim]Run [bold]hyperclaw civ stats[/bold] to see updated coverage.[/dim]\n")
1156
+
1157
+
1158
+ @civ_app.command("interview")
1159
+ def civ_interview(action: str = typer.Argument("start", help="Action: start")) -> None:
1160
+ """Start a knowledge elicitation interview to extract tacit organizational knowledge."""
1161
+ console.print("\n[bold cyan]⚡ Civilization Interview[/bold cyan]\n")
1162
+ console.print("The interview agent will ask Socratic questions to extract knowledge from your team.")
1163
+ console.print("[dim]Connect a team member and run: [bold]hyperclaw civ interview start[/bold][/dim]\n")
1164
+ console.print("[yellow]Interview agent requires a live SOLOMON session. Run [bold]hyperclaw start[/bold] first.[/yellow]\n")
1165
+
1166
+
1167
+ @civ_app.command("gaps")
1168
+ def civ_gaps(
1169
+ fill: bool = typer.Option(False, "--fill", help="Run interview to fill top gap")
1170
+ ) -> None:
1171
+ """Show knowledge gaps in the Civilization Knowledge Base."""
1172
+ console.print("\n[bold cyan]⚡ Knowledge Gap Analysis[/bold cyan]\n")
1173
+ gap_types = [
1174
+ ("SOPs", "No standard operating procedures ingested yet"),
1175
+ ("Job Descriptions", "Roles not yet defined in the Knowledge Base"),
1176
+ ("Client Profiles", "No client profiles found"),
1177
+ ("Org Chart", "Organizational hierarchy not yet ingested"),
1178
+ ]
1179
+ for gap, desc in gap_types:
1180
+ console.print(f" [yellow]▲[/yellow] [bold]{gap}[/bold] — {desc}")
1181
+ console.print(f"\n[dim]Run [bold]hyperclaw civ ingest <file>[/bold] to close gaps.[/dim]")
1182
+ if fill:
1183
+ console.print("\n[dim]Launching interview to fill top gap...[/dim]")
1184
+ civ_interview("start")
1185
+
1186
+
1187
+ @civ_app.command("nodes")
1188
+ def civ_nodes(list_all: bool = typer.Option(True, "--list/--no-list", help="List all nodes")) -> None:
1189
+ """List all knowledge nodes in the Civilization Knowledge Base."""
1190
+ console.print("\n[bold cyan]⚡ Civilization Knowledge Nodes[/bold cyan]\n")
1191
+ console.print("[dim]No nodes yet. Run [bold]hyperclaw civ ingest[/bold] to add knowledge.[/dim]\n")
1192
+
1193
+
1194
+ @civ_app.command("org")
1195
+ def civ_org(chart: bool = typer.Option(True, "--chart/--no-chart")) -> None:
1196
+ """Print the organizational chart."""
1197
+ console.print("\n[bold cyan]⚡ Org Chart[/bold cyan]\n")
1198
+ console.print("[dim]No org chart ingested yet. Run:[/dim]")
1199
+ console.print(" [bold]hyperclaw civ ingest org-chart.md[/bold]\n")
1200
+
1201
+
1202
+ @civ_app.command("stats")
1203
+ def civ_stats() -> None:
1204
+ """Show coverage score and node counts by type."""
1205
+ console.print("\n[bold cyan]⚡ Civilization Knowledge Base Stats[/bold cyan]\n")
1206
+ table = Table(title="Node Coverage", show_header=True)
1207
+ table.add_column("Type")
1208
+ table.add_column("Count", justify="right")
1209
+ table.add_column("Status")
1210
+ node_types = ["SOP", "Job Description", "Role", "Person", "Checklist", "Runbook", "Workflow", "Org Chart", "Client Profile", "Policy"]
1211
+ for nt in node_types:
1212
+ table.add_row(nt, "0", "[dim]Empty[/dim]")
1213
+ console.print(table)
1214
+ console.print("\n[dim]Coverage score: 0% — run [bold]hyperclaw civ ingest[/bold] to build knowledge.[/dim]\n")
1215
+
1216
+
1217
+ @civ_app.command("staleness")
1218
+ def civ_staleness() -> None:
1219
+ """Show knowledge nodes not updated in 90+ days."""
1220
+ console.print("\n[bold cyan]⚡ Stale Knowledge Nodes (90+ days)[/bold cyan]\n")
1221
+ console.print("[dim]No nodes found. Knowledge Base is empty.[/dim]\n")
1222
+
1223
+
1224
+ @civ_app.command("sync")
1225
+ def civ_sync(platform: str = typer.Argument(..., help="Platform: notion | gdrive | confluence")) -> None:
1226
+ """Sync knowledge from an external platform."""
1227
+ supported = {"notion", "gdrive", "confluence"}
1228
+ if platform not in supported:
1229
+ console.print(f"[red]✗ Unknown platform: {platform}. Supported: {', '.join(supported)}[/red]")
1230
+ raise typer.Exit(1)
1231
+ console.print(f"\n[bold cyan]⚡ Syncing from {platform.title()}[/bold cyan]\n")
1232
+ console.print(f"[dim]Configure your {platform} credentials in [bold]config/hyperclaw.yaml[/bold] then re-run.[/dim]\n")
1233
+
1234
+
1235
+ # ── PROMETHEUS Commands ───────────────────────────────────────────────────────
1236
+
1237
+ prometheus_app = typer.Typer(help="Manage the PROMETHEUS agent swarm")
1238
+ app.add_typer(prometheus_app, name="prometheus")
1239
+
1240
+
1241
+ @prometheus_app.command("specialists")
1242
+ def prometheus_specialists() -> None:
1243
+ """List all 42 registered specialist agents."""
1244
+ sys.path.insert(0, str(Path(__file__).parent.parent))
1245
+ from unittest.mock import MagicMock
1246
+ try:
1247
+ from swarm.registry import AgentRegistry
1248
+ mock = MagicMock()
1249
+ registry = AgentRegistry.build_default(mock, mock, mock, mock)
1250
+ agents = sorted(registry.list_all(), key=lambda a: (a.domain, a.agent_id))
1251
+
1252
+ table = Table(title=f"PROMETHEUS Swarm — {len(agents)} Specialists", show_header=True)
1253
+ table.add_column("Agent ID", style="bold cyan")
1254
+ table.add_column("Domain")
1255
+ table.add_column("Description")
1256
+
1257
+ for a in agents:
1258
+ desc = getattr(a, "description", getattr(a, "role", "Specialist"))
1259
+ if callable(desc):
1260
+ desc = ""
1261
+ table.add_row(a.agent_id, a.domain.title(), str(desc)[:60])
1262
+
1263
+ console.print(f"\n")
1264
+ console.print(table)
1265
+ console.print(f"\n[dim]GENESIS can expand the swarm for any new domain.[/dim]\n")
1266
+ except Exception as e:
1267
+ console.print(f"[red]Error loading registry:[/red] {e}")
1268
+ raise typer.Exit(1)
1269
+
1270
+
1271
+ @prometheus_app.command("genesis")
1272
+ def prometheus_genesis(action: str = typer.Argument("run", help="Action: run")) -> None:
1273
+ """Trigger GENESIS to create a new specialist for an uncharted domain."""
1274
+ console.print("\n[bold cyan]⚡ GENESIS Protocol[/bold cyan]\n")
1275
+ console.print("GENESIS detects knowledge gaps and builds new specialists on demand.")
1276
+ console.print("[dim]This requires a live PROMETHEUS session. Run [bold]hyperclaw start[/bold] first.[/dim]\n")
1277
+
1278
+
1279
+ @prometheus_app.command("memory")
1280
+ def prometheus_memory(stats: bool = typer.Option(True, "--stats/--no-stats")) -> None:
1281
+ """Show three-tier memory statistics."""
1282
+ console.print("\n[bold cyan]⚡ PROMETHEUS Memory — Three-Tier Stats[/bold cyan]\n")
1283
+ table = Table(show_header=True)
1284
+ table.add_column("Tier")
1285
+ table.add_column("Type")
1286
+ table.add_column("Status")
1287
+ table.add_row("L1", "Session (in-context)", "[green]Active[/green]")
1288
+ table.add_row("L2", "Domain (per-specialist persistent)", "[dim]Requires DB[/dim]")
1289
+ table.add_row("L3", "Civilizational Graph (pgvector)", "[dim]Requires DB[/dim]")
1290
+ console.print(table)
1291
+ console.print("\n[dim]Run [bold]hyperclaw init[/bold] to connect your database.[/dim]\n")
1292
+
1293
+
1294
+ @app.command("activity")
1295
+ def activity(
1296
+ limit: int = typer.Option(20, "--limit", "-n", help="How many actions to show"),
1297
+ conversation: str = typer.Option("", "--conversation", "-c",
1298
+ help="Only this conversation (e.g. tui:local, bridge:<chat id>)"),
1299
+ as_json: bool = typer.Option(False, "--json", help="Raw ledger rows as JSON"),
1300
+ ) -> None:
1301
+ """Show the activity log: every action HyperClaw took or refused, with its result and receipt."""
1302
+ from hyperclaw.tools.ledger import activity_json, format_activity, get_ledger
1303
+ rows = get_ledger().activity(conversation=conversation or None, limit=limit)
1304
+ print(activity_json(rows) if as_json else format_activity(rows))
1305
+
1306
+
1307
+ # ── Cost guard (spec §9) ─────────────────────────────────────────────────────
1308
+ cost_app = typer.Typer(help="Model spend today and the cost caps (daily, per task)",
1309
+ invoke_without_command=True)
1310
+ app.add_typer(cost_app, name="cost")
1311
+
1312
+
1313
+ @cost_app.callback()
1314
+ def cost_status(ctx: typer.Context) -> None:
1315
+ """Show today's model spend against the daily and per-task caps."""
1316
+ if ctx.invoked_subcommand is not None:
1317
+ return
1318
+ _load_user_env()
1319
+ from hyperclaw import cost_guard
1320
+ st = cost_guard.status()
1321
+ u = cost_guard.usd
1322
+ print(f"Spend today ({st['day']}): {u(st['day_spent_usd'])} of {u(st['daily_cap_usd'])} "
1323
+ f"daily cap · per-task cap {u(st['task_cap_usd'])} · spend line from "
1324
+ f"{u(st['spend_line_min_usd'])}")
1325
+
1326
+
1327
+ @cost_app.command("raise")
1328
+ def cost_raise(
1329
+ daily: Optional[float] = typer.Option(None, "--daily", help="New daily cap in USD"),
1330
+ task: Optional[float] = typer.Option(None, "--task", help="New per-task cap in USD"),
1331
+ ) -> None:
1332
+ """Change a cost cap. Your explicit action: written to config.json and the activity log."""
1333
+ _load_user_env()
1334
+ from hyperclaw import cost_guard
1335
+ if daily is None and task is None:
1336
+ print("Nothing to change: pass --daily <USD> and/or --task <USD>.")
1337
+ raise typer.Exit(2)
1338
+ try:
1339
+ if daily is not None:
1340
+ v = cost_guard.raise_cap(cost_guard.DAILY, daily, by="owner (hyperclaw cost raise)",
1341
+ surface="cli")
1342
+ print(f"Daily cost cap set to {cost_guard.usd(v)}.")
1343
+ if task is not None:
1344
+ v = cost_guard.raise_cap(cost_guard.TASK, task, by="owner (hyperclaw cost raise)",
1345
+ surface="cli")
1346
+ print(f"Per-task cost cap set to {cost_guard.usd(v)}.")
1347
+ except ValueError as e:
1348
+ print(str(e))
1349
+ raise typer.Exit(2)
1350
+
1351
+
1352
+ def main() -> None:
1353
+ app()
1354
+
1355
+
1356
+ if __name__ == "__main__":
1357
+ main()