hyperagents 1.2.3rc5.post3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- ui/tui/app.py +214 -0
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()
|