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
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
"""``hyperclaw onboard --team``: a team member sets up HyperAgents alone.
|
|
2
|
+
|
|
3
|
+
Steps, in order (each can be re-run alone with ``--step <id>``)::
|
|
4
|
+
|
|
5
|
+
item paste the 1Password item (hidden): team profile + personal keys
|
|
6
|
+
name name the agent
|
|
7
|
+
consent the one consent line for the outside model, then a live check
|
|
8
|
+
google sign in to Google in their own browser (read-only Docs, Sheets)
|
|
9
|
+
slack sign in to Slack in their own browser (PKCE; reads, posts only on Send)
|
|
10
|
+
telegram create the bot (named after the agent) and pair it by code
|
|
11
|
+
brief pick the morning brief time (and, optionally, its Doc and Sheet)
|
|
12
|
+
gitlab add GitLab with a read_api token (optional)
|
|
13
|
+
service the background service at login (Telegram + scheduler)
|
|
14
|
+
|
|
15
|
+
It ends with one line per piece: ``✓ Connected: ...`` or the plain reason
|
|
16
|
+
and the fix. Progress is saved after every step in
|
|
17
|
+
``$HYPERCLAW_ROOT/config/onboard-team.json`` (no secrets), so quitting
|
|
18
|
+
halfway is safe: running it again carries on where they left off.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import datetime as _dt
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Callable, Optional
|
|
29
|
+
|
|
30
|
+
STEPS = ("item", "name", "consent", "google", "slack", "telegram", "brief", "gitlab",
|
|
31
|
+
"service")
|
|
32
|
+
TITLES = {
|
|
33
|
+
"item": "Your 1Password item", "name": "Name your agent", "consent": "Your model",
|
|
34
|
+
"google": "Google sign-in", "slack": "Slack sign-in", "telegram": "Telegram",
|
|
35
|
+
"brief": "Morning brief", "gitlab": "GitLab (optional)", "service": "Background service",
|
|
36
|
+
}
|
|
37
|
+
DONE, FAILED, SKIPPED = "done", "failed", "skipped"
|
|
38
|
+
STOPPED = ("Stopped. Everything finished so far is saved. Run 'hyperclaw onboard --team' "
|
|
39
|
+
"to carry on from where you left off.")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ── state (non-secret) ────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
def state_path() -> Path:
|
|
45
|
+
from hyperclaw.userconfig import root
|
|
46
|
+
return root() / "config" / "onboard-team.json"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_state() -> dict:
|
|
50
|
+
try:
|
|
51
|
+
d = json.loads(state_path().read_text(encoding="utf-8"))
|
|
52
|
+
if isinstance(d, dict) and isinstance(d.get("steps"), dict):
|
|
53
|
+
return d
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
return {"steps": {}}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def mark(step: str, status: str, code: str = "") -> None:
|
|
60
|
+
d = load_state()
|
|
61
|
+
d["steps"][step] = {"status": status, "code": code,
|
|
62
|
+
"at": _dt.datetime.now(_dt.timezone.utc).isoformat(timespec="seconds")}
|
|
63
|
+
p = state_path()
|
|
64
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
tmp = p.with_suffix(".tmp")
|
|
66
|
+
fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
67
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
68
|
+
json.dump(d, f, indent=1)
|
|
69
|
+
os.replace(tmp, p)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def status_of(step: str) -> str:
|
|
73
|
+
return (load_state()["steps"].get(step) or {}).get("status", "")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def next_steps() -> list:
|
|
77
|
+
"""Steps still to do, in order (failed ones are tried again)."""
|
|
78
|
+
st = load_state()["steps"]
|
|
79
|
+
return [s for s in STEPS if (st.get(s) or {}).get("status") not in (DONE, SKIPPED)]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── terminal I/O (tests pass a fake) ──────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class IO:
|
|
86
|
+
say: Callable[[str], None]
|
|
87
|
+
ask: Callable[..., str] # ask(label, default="") -> str
|
|
88
|
+
hidden: Callable[[str], str] # hidden(label) -> str (never echoed)
|
|
89
|
+
confirm: Callable[..., bool] # confirm(label, default=False) -> bool
|
|
90
|
+
open_browser: Optional[Callable[[str], object]] = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def terminal_io() -> IO:
|
|
94
|
+
import webbrowser
|
|
95
|
+
|
|
96
|
+
from rich.console import Console
|
|
97
|
+
from rich.prompt import Confirm, Prompt
|
|
98
|
+
|
|
99
|
+
from hyperclaw.teamsetup.tty import read_hidden
|
|
100
|
+
con = Console()
|
|
101
|
+
return IO(
|
|
102
|
+
say=lambda t: con.print(t, markup=False, highlight=False),
|
|
103
|
+
ask=lambda label, default="": Prompt.ask(label, default=default,
|
|
104
|
+
show_default=bool(default)),
|
|
105
|
+
hidden=lambda label: read_hidden(label + ": "),
|
|
106
|
+
confirm=lambda label, default=False: Confirm.ask(label, default=default),
|
|
107
|
+
open_browser=webbrowser.open,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class StepResult:
|
|
113
|
+
status: str
|
|
114
|
+
line: str = ""
|
|
115
|
+
code: str = ""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ── the steps ─────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
def _profile() -> dict:
|
|
121
|
+
from hyperclaw.teamsetup import profile
|
|
122
|
+
return profile.load()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _need_profile() -> Optional[StepResult]:
|
|
126
|
+
if not _profile():
|
|
127
|
+
return StepResult(FAILED, "✗ This step needs your team item first. Fix: hyperclaw "
|
|
128
|
+
"onboard --team --step item", "no_item")
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def step_item(io: IO) -> StepResult:
|
|
133
|
+
from hyperclaw.teamsetup import profile, secrets
|
|
134
|
+
from hyperclaw.teamsetup.item import ItemError, parse
|
|
135
|
+
from hyperclaw.userconfig import update_config
|
|
136
|
+
io.say("Open 1Password, find the HyperAgents item Vaughn shared with you, and copy its "
|
|
137
|
+
"text. Paste it here and press Enter. Nothing will show on screen as you paste; "
|
|
138
|
+
"that is on purpose.")
|
|
139
|
+
item = None
|
|
140
|
+
for _ in range(3):
|
|
141
|
+
raw = io.hidden("Paste your 1Password item (hidden)")
|
|
142
|
+
try:
|
|
143
|
+
item = parse(raw)
|
|
144
|
+
break
|
|
145
|
+
except ItemError as e:
|
|
146
|
+
io.say(e.text)
|
|
147
|
+
finally:
|
|
148
|
+
raw = ""
|
|
149
|
+
if item is None:
|
|
150
|
+
return StepResult(FAILED, "✗ Team item: not saved. Fix: copy the item from 1Password "
|
|
151
|
+
"again, then hyperclaw onboard --team --step item", "item")
|
|
152
|
+
profile.save(item.envelope)
|
|
153
|
+
where = secrets.put("model_key", item.model_key)
|
|
154
|
+
if item.hyper_key:
|
|
155
|
+
secrets.put("hyper_key", item.hyper_key, env="HYPERSPEED_API_KEY")
|
|
156
|
+
if item.google_client_secret:
|
|
157
|
+
secrets.put("google_client_secret", item.google_client_secret)
|
|
158
|
+
if item.profile["google"]["client_id"]:
|
|
159
|
+
write_google_client(item.profile["google"]["client_id"], item.google_client_secret)
|
|
160
|
+
update_config(deployment="hyper" if item.profile["install_mode"] == "hyper" else "standard",
|
|
161
|
+
team_profile=item.profile["name"])
|
|
162
|
+
if secrets.env_map()["env"].get("ANTHROPIC_API_KEY") == "model_key":
|
|
163
|
+
secrets.link_env("model_key", "ANTHROPIC_API_KEY") # re-paste: keep it current
|
|
164
|
+
del item
|
|
165
|
+
return StepResult(DONE, f"✓ Team item saved. Your keys are kept in {where}.")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def step_name(io: IO) -> StepResult:
|
|
169
|
+
from hyperclaw import naming
|
|
170
|
+
current = naming.current_name()
|
|
171
|
+
if current and io.confirm(f"Your agent is called {current}. Keep that name?", default=True):
|
|
172
|
+
return StepResult(DONE, f"✓ Your agent is called {current}.")
|
|
173
|
+
name = naming.run_interactive(lambda label: io.ask(label, ""), io.say, model_call=None)
|
|
174
|
+
if not name:
|
|
175
|
+
return StepResult(FAILED, "✗ No name chosen. Fix: hyperclaw onboard --team --step "
|
|
176
|
+
"name", "no_name")
|
|
177
|
+
return StepResult(DONE, f"✓ Your agent is called {name}.")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def step_consent(io: IO, *, http=None) -> StepResult:
|
|
181
|
+
from hyperclaw.model_policy import consent_line, has_consent, record_consent
|
|
182
|
+
from hyperclaw.teamsetup import checks, secrets
|
|
183
|
+
need = _need_profile()
|
|
184
|
+
if need:
|
|
185
|
+
return need
|
|
186
|
+
provider, _, model = _profile()["default_model"].partition("/")
|
|
187
|
+
label = checks.model_label(provider, model)
|
|
188
|
+
if provider == "hyperspeed":
|
|
189
|
+
return StepResult(DONE, f"✓ Your model is {label} (Hyper's own; no consent needed).")
|
|
190
|
+
io.say(f"Your model: {label}.")
|
|
191
|
+
io.say(consent_line(provider))
|
|
192
|
+
if not io.confirm("Continue?", default=False):
|
|
193
|
+
secrets.unlink_env("ANTHROPIC_API_KEY")
|
|
194
|
+
return StepResult(FAILED, f"✗ {label}: not switched on, because you didn't agree. "
|
|
195
|
+
"Fix: when you're ready, hyperclaw onboard --team --step "
|
|
196
|
+
"consent", "declined")
|
|
197
|
+
if not has_consent(provider):
|
|
198
|
+
record_consent(provider, model)
|
|
199
|
+
if not secrets.link_env("model_key", "ANTHROPIC_API_KEY"):
|
|
200
|
+
return StepResult(FAILED, f"✗ {label}: no model key is stored. Fix: hyperclaw onboard "
|
|
201
|
+
"--team --step item", "no_key")
|
|
202
|
+
try:
|
|
203
|
+
from hyperclaw.providers import reset_registry
|
|
204
|
+
reset_registry()
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
c = checks.check_model(provider, model, http=http)
|
|
208
|
+
return StepResult(DONE if c.ok else FAILED, c.line, c.code)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def write_google_client(client_id: str, client_secret: str) -> Path:
|
|
212
|
+
"""The install's Desktop client file for the Google sign-in
|
|
213
|
+
(hyperclaw.google_workspace.auth.client_path), owner-only."""
|
|
214
|
+
from hyperclaw.google_workspace import auth as ga
|
|
215
|
+
p = ga.client_path()
|
|
216
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
try:
|
|
218
|
+
os.chmod(p.parent, 0o700)
|
|
219
|
+
except OSError:
|
|
220
|
+
pass
|
|
221
|
+
body = {"installed": {"client_id": client_id, "client_secret": client_secret,
|
|
222
|
+
"auth_uri": ga.AUTH_URI, "token_uri": ga.TOKEN_URI,
|
|
223
|
+
"redirect_uris": ["http://127.0.0.1"]}}
|
|
224
|
+
tmp = p.with_suffix(".tmp")
|
|
225
|
+
fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
226
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
227
|
+
json.dump(body, f)
|
|
228
|
+
os.replace(tmp, p)
|
|
229
|
+
return p
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def step_google(io: IO, *, http=None, connect=None) -> StepResult:
|
|
233
|
+
"""`hyperclaw google connect` (hyperclaw.google_workspace): one sign-in in
|
|
234
|
+
their own browser, loopback PKCE, the full team scopes (Gmail read,
|
|
235
|
+
compose and send; read-only Calendar, Meet, Drive, Docs, Sheets, Slides;
|
|
236
|
+
Calendar write only when the profile allows it); token in the OS secret
|
|
237
|
+
store."""
|
|
238
|
+
from hyperclaw.google_workspace import auth as ga
|
|
239
|
+
from hyperclaw.google_workspace.cli import _whoami
|
|
240
|
+
from hyperclaw.google_workspace.errors import GoogleError
|
|
241
|
+
from hyperclaw.teamsetup import checks, profile
|
|
242
|
+
need = _need_profile()
|
|
243
|
+
if need:
|
|
244
|
+
return need
|
|
245
|
+
client = profile.google_client()
|
|
246
|
+
if client:
|
|
247
|
+
write_google_client(client["client_id"], client["client_secret"])
|
|
248
|
+
elif not ga.client_path().is_file():
|
|
249
|
+
return StepResult(FAILED, "✗ Google: your team item has no Google sign-in details. "
|
|
250
|
+
"Fix: ask Vaughn for an updated item, then hyperclaw onboard "
|
|
251
|
+
"--team --step item", "no_client")
|
|
252
|
+
io.say("Next, Google. Your browser will open: sign in with your work Google account and "
|
|
253
|
+
"choose Allow. Your agent can then read your email and draft or send mail you "
|
|
254
|
+
"approve, and read your Calendar, Meet notes, Drive, Docs, Sheets and Slides.")
|
|
255
|
+
try:
|
|
256
|
+
(connect or ga.connect)(show=io.say, open_browser=io.open_browser, http=http,
|
|
257
|
+
whoami=_whoami)
|
|
258
|
+
except GoogleError as e:
|
|
259
|
+
return StepResult(FAILED, f"✗ Google: {e.text.split('. ')[0].rstrip('.')}. Nothing was "
|
|
260
|
+
"saved. Fix: hyperclaw onboard --team --step google",
|
|
261
|
+
e.reason.value)
|
|
262
|
+
c = checks.check_google(http=http)
|
|
263
|
+
return StepResult(DONE if c.ok else FAILED, c.line, c.code)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def step_slack(io: IO, *, http=None, sign_in=None) -> StepResult:
|
|
267
|
+
from hyperclaw.teamsetup import checks, slack_oauth
|
|
268
|
+
need = _need_profile()
|
|
269
|
+
if need:
|
|
270
|
+
return need
|
|
271
|
+
cfg = _profile().get("slack") or {}
|
|
272
|
+
if not cfg.get("client_id"):
|
|
273
|
+
return StepResult(SKIPPED, "– Slack: not part of your team profile.", "no_app")
|
|
274
|
+
io.say("Next, Slack.")
|
|
275
|
+
try:
|
|
276
|
+
(sign_in or slack_oauth.sign_in)(cfg, show=io.say, open_browser=io.open_browser,
|
|
277
|
+
http=http)
|
|
278
|
+
except slack_oauth.SlackAuthError as e:
|
|
279
|
+
saved = "" if "nothing was saved" in e.text else " Nothing was saved."
|
|
280
|
+
return StepResult(FAILED, f"✗ Slack: {e.text}.{saved} Fix: {e.fix}", e.code)
|
|
281
|
+
c = checks.check_slack(cfg, http=http)
|
|
282
|
+
return StepResult(DONE if c.ok else FAILED, c.line, c.code)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def bot_username_hint(agent: str) -> str:
|
|
286
|
+
base = re.sub(r"[^a-z0-9]+", "_", agent.lower()).strip("_") or "my"
|
|
287
|
+
return f"{base[:20]}_hyperagents_bot"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def step_telegram(io: IO, *, client=None, pair=None, paused=None) -> StepResult:
|
|
291
|
+
from hyperclaw import naming
|
|
292
|
+
from hyperclaw import telegram_pairing as tp
|
|
293
|
+
from hyperclaw.teamsetup import checks, secrets, service
|
|
294
|
+
need = _need_profile()
|
|
295
|
+
if need:
|
|
296
|
+
return need
|
|
297
|
+
if not _profile().get("telegram"):
|
|
298
|
+
return StepResult(SKIPPED, "– Telegram: not part of your team profile.", "off")
|
|
299
|
+
agent = naming.current_name() or "HyperAgents"
|
|
300
|
+
io.say("Next, Telegram: your agent gets its own bot, and only your chat can use it.")
|
|
301
|
+
io.say("1. In Telegram, open @BotFather and send /newbot.")
|
|
302
|
+
io.say(f"2. When it asks for a name, send: {agent}")
|
|
303
|
+
io.say(f"3. When it asks for a username, send one ending in 'bot', for example "
|
|
304
|
+
f"{bot_username_hint(agent)}")
|
|
305
|
+
io.say("4. BotFather replies with a token. Copy it and paste it here.")
|
|
306
|
+
token = ""
|
|
307
|
+
for _ in range(3):
|
|
308
|
+
token = "".join(io.hidden("Bot token (hidden)").split())
|
|
309
|
+
if re.fullmatch(tp._TOKEN_SHAPE, token):
|
|
310
|
+
break
|
|
311
|
+
io.say("That doesn't look like a Telegram bot token, so it wasn't saved. Copy the "
|
|
312
|
+
"whole token from BotFather's message and paste it again.")
|
|
313
|
+
token = ""
|
|
314
|
+
if not token:
|
|
315
|
+
return StepResult(FAILED, "✗ Telegram: no bot token saved. Fix: hyperclaw onboard "
|
|
316
|
+
"--team --step telegram", "token")
|
|
317
|
+
secrets.put("telegram_bot_token", token, env="TELEGRAM_BOT_TOKEN")
|
|
318
|
+
with (paused or service.paused)():
|
|
319
|
+
chat = (pair or tp.run_owner_pairing)(token, io.say, client=client)
|
|
320
|
+
if chat is None:
|
|
321
|
+
return StepResult(FAILED, "✗ Telegram: not paired. Fix: hyperclaw onboard --team "
|
|
322
|
+
"--step telegram", "unpaired")
|
|
323
|
+
try:
|
|
324
|
+
tp._call(client or tp._client(), token, "setMyName", {"name": agent[:64]}, timeout=15)
|
|
325
|
+
except Exception:
|
|
326
|
+
io.say(f"Telegram didn't take the display name {agent} just now; the bot works anyway, "
|
|
327
|
+
"and you can try again later with /name.")
|
|
328
|
+
c = checks.check_telegram(client=client)
|
|
329
|
+
return StepResult(DONE if c.ok else FAILED, c.line, c.code)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def local_timezone() -> str:
|
|
333
|
+
tz = os.environ.get("TZ", "").strip().lstrip(":")
|
|
334
|
+
if tz and "/" in tz:
|
|
335
|
+
return tz
|
|
336
|
+
try:
|
|
337
|
+
target = os.path.realpath("/etc/localtime")
|
|
338
|
+
if "zoneinfo/" in target:
|
|
339
|
+
return target.split("zoneinfo/", 1)[1]
|
|
340
|
+
except OSError:
|
|
341
|
+
pass
|
|
342
|
+
return ""
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _brief_ready_line(when: str, tz: str) -> StepResult:
|
|
346
|
+
from hyperclaw.brief import compose, store
|
|
347
|
+
from hyperclaw.brief.errors import BriefError
|
|
348
|
+
from hyperclaw.digest.errors import DigestError
|
|
349
|
+
try:
|
|
350
|
+
from hyperclaw.providers import reset_registry
|
|
351
|
+
reset_registry()
|
|
352
|
+
except Exception:
|
|
353
|
+
pass
|
|
354
|
+
try:
|
|
355
|
+
compose.v2_route()
|
|
356
|
+
except (BriefError, DigestError):
|
|
357
|
+
return StepResult(FAILED, f"✗ Morning brief: set for {when} {tz}, but it is written only "
|
|
358
|
+
"by HyperSpeed V2 and this install has no Hyper access key. "
|
|
359
|
+
"Fix: ask Vaughn for an item that includes one, then "
|
|
360
|
+
"hyperclaw onboard --team --step item", "no_v2")
|
|
361
|
+
from hyperclaw.brief import google_auth
|
|
362
|
+
if store.source_id("doc") and not google_auth.connected():
|
|
363
|
+
return StepResult(FAILED, f"✗ Morning brief: set for {when} {tz}, but it can't read its "
|
|
364
|
+
"Doc yet (no Google sign-in for the brief). Fix: hyperclaw "
|
|
365
|
+
"onboard --team --step brief", "brief_google")
|
|
366
|
+
if not (store.source_id("doc") and store.source_id("sheet")):
|
|
367
|
+
return StepResult(FAILED, f"✗ Morning brief: set for {when} {tz}, but its Google Doc "
|
|
368
|
+
"and Sheet aren't set. Fix: hyperclaw onboard --team --step "
|
|
369
|
+
"brief", "no_sources")
|
|
370
|
+
return StepResult(DONE, f"✓ Morning brief: every day at {when} {tz}")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def step_brief(io: IO, *, brief_connect=None, http=None) -> StepResult:
|
|
374
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
375
|
+
|
|
376
|
+
from hyperclaw.brief import links, store
|
|
377
|
+
when = None
|
|
378
|
+
for _ in range(3):
|
|
379
|
+
raw = io.ask("What time should your morning brief arrive? (24-hour, e.g. 07:30)",
|
|
380
|
+
str(store.config().get("time") or store.DEFAULT_TIME))
|
|
381
|
+
when = store.parse_hhmm(raw)
|
|
382
|
+
if when:
|
|
383
|
+
break
|
|
384
|
+
io.say("Use the 24-hour form HH:MM, for example 07:30.")
|
|
385
|
+
if not when:
|
|
386
|
+
return StepResult(FAILED, "✗ Morning brief: no time set. Fix: hyperclaw onboard --team "
|
|
387
|
+
"--step brief", "time")
|
|
388
|
+
tz = ""
|
|
389
|
+
for _ in range(3):
|
|
390
|
+
tz = io.ask("Your time zone", str(store.config().get("tz") or local_timezone()
|
|
391
|
+
or "America/Chicago")).strip()
|
|
392
|
+
try:
|
|
393
|
+
ZoneInfo(tz)
|
|
394
|
+
break
|
|
395
|
+
except (ZoneInfoNotFoundError, ValueError):
|
|
396
|
+
io.say("That isn't a time zone name. Use one like America/Chicago or Europe/London.")
|
|
397
|
+
tz = ""
|
|
398
|
+
if not tz:
|
|
399
|
+
return StepResult(FAILED, "✗ Morning brief: no time zone set. Fix: hyperclaw onboard "
|
|
400
|
+
"--team --step brief", "tz")
|
|
401
|
+
hm = f"{when[0]:02d}:{when[1]:02d}"
|
|
402
|
+
store.set_config(scheduled=True, time=hm, tz=tz,
|
|
403
|
+
scheduled_at=_dt.datetime.now(ZoneInfo(tz)).isoformat(timespec="seconds"))
|
|
404
|
+
have = bool(store.source_id("doc") and store.source_id("sheet"))
|
|
405
|
+
if io.confirm("Set the Google Doc and Sheet your brief reads now? (you can do it later)",
|
|
406
|
+
default=not have):
|
|
407
|
+
for kind, want, label in (("doc", links.DOC, "Doc"), ("sheet", links.SHEET, "Sheet")):
|
|
408
|
+
raw = io.hidden(f"Link to your {label} (hidden; Enter to keep the current one)")
|
|
409
|
+
if not raw.strip():
|
|
410
|
+
continue
|
|
411
|
+
got, fid = links.parse(raw.strip())
|
|
412
|
+
raw = ""
|
|
413
|
+
if got != want or not fid:
|
|
414
|
+
io.say(f"That isn't a Google {label} link, so the {label} wasn't changed.")
|
|
415
|
+
continue
|
|
416
|
+
store.set_source(kind, fid, "")
|
|
417
|
+
io.say(f"{label}: set.")
|
|
418
|
+
from hyperclaw.brief import google_auth
|
|
419
|
+
from hyperclaw.brief.errors import BriefError
|
|
420
|
+
if store.source_id("doc") and not google_auth.connected():
|
|
421
|
+
# The brief reads its Doc and Sheet with its own read-only sign-in.
|
|
422
|
+
io.say("Your brief reads that Doc and Sheet with its own read-only Google sign-in. Your "
|
|
423
|
+
"browser will open once more: choose the same account and Allow.")
|
|
424
|
+
try:
|
|
425
|
+
(brief_connect or google_auth.connect)(show=io.say, open_browser=io.open_browser,
|
|
426
|
+
http=http)
|
|
427
|
+
except BriefError as e:
|
|
428
|
+
return StepResult(FAILED, f"✗ Morning brief: set for {hm} {tz}, but {e.text}. Fix: "
|
|
429
|
+
"hyperclaw onboard --team --step brief", e.reason.value)
|
|
430
|
+
return _brief_ready_line(hm, tz)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def step_gitlab(io: IO, *, client=None) -> StepResult:
|
|
434
|
+
from hyperclaw.digest.errors import DigestError
|
|
435
|
+
from hyperclaw.digest.service import connect
|
|
436
|
+
from hyperclaw.teamsetup import checks
|
|
437
|
+
if not io.confirm("Add GitLab, so your agent can tell you about your merge requests and "
|
|
438
|
+
"to-dos? (optional, read-only)", default=False):
|
|
439
|
+
return StepResult(SKIPPED, "– GitLab: not added (optional). Add it any time: hyperclaw "
|
|
440
|
+
"onboard --team --step gitlab", "declined")
|
|
441
|
+
url = (_profile().get("gitlab") or {}).get("url") or "https://gitlab.com"
|
|
442
|
+
io.say(f"In GitLab ({url}): click your avatar, then Edit profile, then Access tokens, then "
|
|
443
|
+
"Add new token. Tick only read_api, pick an expiry date, create it and copy it.")
|
|
444
|
+
token = io.hidden("GitLab token (hidden)")
|
|
445
|
+
try:
|
|
446
|
+
connect("gitlab", token, gitlab_url=url, client=client)
|
|
447
|
+
except DigestError as e:
|
|
448
|
+
return StepResult(FAILED, f"✗ GitLab: {e.text}. Nothing was saved. Fix: "
|
|
449
|
+
+ (e.fix or "hyperclaw onboard --team --step gitlab"),
|
|
450
|
+
e.reason.value)
|
|
451
|
+
finally:
|
|
452
|
+
token = ""
|
|
453
|
+
c = checks.check_gitlab(client=client)
|
|
454
|
+
return StepResult(DONE if c.ok else FAILED, c.line, c.code)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def step_service(io: IO, *, run=None) -> StepResult:
|
|
458
|
+
from hyperclaw.teamsetup import service
|
|
459
|
+
io.say("Last, the background service: it starts when you log in and keeps your agent "
|
|
460
|
+
"on Telegram and your brief on time, even after a restart or sleep.")
|
|
461
|
+
r = service.install(**({"run": run} if run else {}))
|
|
462
|
+
return StepResult(DONE if r.ok else FAILED, r.line, "" if r.ok else "service")
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
RUNNERS = {"item": step_item, "name": step_name, "consent": step_consent,
|
|
466
|
+
"google": step_google, "slack": step_slack, "telegram": step_telegram,
|
|
467
|
+
"brief": step_brief, "gitlab": step_gitlab, "service": step_service}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
# ── summary ───────────────────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
def summary_lines(*, http=None, client=None, run=None) -> list:
|
|
473
|
+
"""One line per piece, from live checks (and the saved state for skipped
|
|
474
|
+
optional pieces)."""
|
|
475
|
+
from hyperclaw.teamsetup import checks, service
|
|
476
|
+
steps = load_state()["steps"]
|
|
477
|
+
lines = []
|
|
478
|
+
prof = _profile()
|
|
479
|
+
if not prof:
|
|
480
|
+
return ["✗ Team item: not set up. Fix: hyperclaw onboard --team --step item"]
|
|
481
|
+
provider, _, model = prof["default_model"].partition("/")
|
|
482
|
+
if (steps.get("consent") or {}).get("code") == "declined":
|
|
483
|
+
lines.append(f"✗ {checks.model_label(provider, model)}: not switched on, because you "
|
|
484
|
+
"didn't agree. Fix: hyperclaw onboard --team --step consent")
|
|
485
|
+
else:
|
|
486
|
+
lines.append(checks.check_model(provider, model, http=http).line)
|
|
487
|
+
lines.append(checks.check_google(http=http).line)
|
|
488
|
+
if (prof.get("slack") or {}).get("client_id"):
|
|
489
|
+
lines.append(checks.check_slack(prof["slack"], http=http).line)
|
|
490
|
+
if prof.get("telegram"):
|
|
491
|
+
lines.append(checks.check_telegram(client=client).line)
|
|
492
|
+
from hyperclaw.brief import store
|
|
493
|
+
if store.opted_in_by_config():
|
|
494
|
+
lines.append(_brief_ready_line(str(store.config().get("time")), store.tz_name()).line)
|
|
495
|
+
else:
|
|
496
|
+
lines.append("✗ Morning brief: no time set. Fix: hyperclaw onboard --team --step brief")
|
|
497
|
+
if (steps.get("gitlab") or {}).get("status") == SKIPPED:
|
|
498
|
+
lines.append("– GitLab: not added (optional)")
|
|
499
|
+
else:
|
|
500
|
+
lines.append(checks.check_gitlab(client=client).line)
|
|
501
|
+
lines.append(service.check(**({"run": run} if run else {})).line)
|
|
502
|
+
return lines
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# ── runner ────────────────────────────────────────────────────────────────
|
|
506
|
+
|
|
507
|
+
def run(io: Optional[IO] = None, *, only: Optional[str] = None, deps: Optional[dict] = None,
|
|
508
|
+
summary: bool = True) -> int:
|
|
509
|
+
"""Run the remaining steps (or one, with ``only``). Returns an exit code:
|
|
510
|
+
0 when every line is ✓ (or an optional piece was skipped)."""
|
|
511
|
+
io = io or terminal_io()
|
|
512
|
+
deps = deps or {}
|
|
513
|
+
todo = [only] if only else next_steps()
|
|
514
|
+
if only and only not in RUNNERS:
|
|
515
|
+
io.say(f"Unknown step '{only}'. Steps: {', '.join(STEPS)}")
|
|
516
|
+
return 2
|
|
517
|
+
if not only:
|
|
518
|
+
done = [s for s in STEPS if s not in todo]
|
|
519
|
+
if done and todo:
|
|
520
|
+
io.say(f"Picking up where you left off: {TITLES[todo[0]]}.")
|
|
521
|
+
try:
|
|
522
|
+
for step in todo:
|
|
523
|
+
io.say("")
|
|
524
|
+
io.say(f"── {TITLES[step]} ──")
|
|
525
|
+
res = RUNNERS[step](io, **deps.get(step, {}))
|
|
526
|
+
mark(step, res.status, res.code)
|
|
527
|
+
if res.line:
|
|
528
|
+
io.say(res.line)
|
|
529
|
+
except (KeyboardInterrupt, EOFError):
|
|
530
|
+
io.say("")
|
|
531
|
+
io.say(STOPPED)
|
|
532
|
+
return 130
|
|
533
|
+
if only and status_of("service") == DONE and only != "service":
|
|
534
|
+
from hyperclaw.teamsetup import service
|
|
535
|
+
service.install(**({"run": deps["service"]["run"]} if deps.get("service") else {}))
|
|
536
|
+
if not summary:
|
|
537
|
+
return 0
|
|
538
|
+
io.say("")
|
|
539
|
+
io.say("Where everything stands:")
|
|
540
|
+
lines = summary_lines(**deps.get("summary", {}))
|
|
541
|
+
for line in lines:
|
|
542
|
+
io.say(" " + line)
|
|
543
|
+
bad = [x for x in lines if x.startswith("✗")]
|
|
544
|
+
io.say("")
|
|
545
|
+
if bad:
|
|
546
|
+
io.say("Follow each Fix above. If you're stuck, run 'hyperclaw self-check' and send "
|
|
547
|
+
"Vaughn the report it writes (it has no keys, tokens, emails or messages in it).")
|
|
548
|
+
else:
|
|
549
|
+
io.say("All set. Say hello to your agent on Telegram.")
|
|
550
|
+
return 1 if bad else 0
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""The team profile on this install: the Hyper-signed policy from the team
|
|
2
|
+
item (hyperclaw.teamsetup.item), kept as issued in
|
|
3
|
+
``$HYPERCLAW_ROOT/config/team-profile.json`` (0600).
|
|
4
|
+
|
|
5
|
+
It is verified again every time it is read, so editing the file only makes
|
|
6
|
+
it invalid (then the strictest defaults apply). ``hyperclaw.managed_config``
|
|
7
|
+
reads it as the install's managed settings when no system managed file is
|
|
8
|
+
present (docs/MODEL-POLICY.md, "Team install profile").
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger("hyperclaw.teamsetup.profile")
|
|
18
|
+
|
|
19
|
+
_cache: dict = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def path() -> Path:
|
|
23
|
+
from hyperclaw.userconfig import root
|
|
24
|
+
return root() / "config" / "team-profile.json"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def save(envelope: dict) -> None:
|
|
28
|
+
p = path()
|
|
29
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
tmp = p.with_suffix(".tmp")
|
|
31
|
+
fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
32
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
33
|
+
json.dump(envelope, f, indent=1, sort_keys=True)
|
|
34
|
+
os.replace(tmp, p)
|
|
35
|
+
_cache.clear()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load() -> dict:
|
|
39
|
+
"""The verified profile, or {} (no file, or it doesn't verify)."""
|
|
40
|
+
from hyperclaw.teamsetup.item import ItemError, verify_profile
|
|
41
|
+
p = path()
|
|
42
|
+
try:
|
|
43
|
+
st = p.stat()
|
|
44
|
+
except OSError:
|
|
45
|
+
return {}
|
|
46
|
+
key = (str(p), st.st_mtime_ns, st.st_size)
|
|
47
|
+
if _cache.get("key") == key:
|
|
48
|
+
return dict(_cache["profile"])
|
|
49
|
+
try:
|
|
50
|
+
prof = verify_profile(json.loads(p.read_text(encoding="utf-8")))
|
|
51
|
+
except (ItemError, ValueError, OSError) as e:
|
|
52
|
+
log.warning("team profile %s ignored (%s); defaults apply", p,
|
|
53
|
+
getattr(e, "code", type(e).__name__))
|
|
54
|
+
prof = {}
|
|
55
|
+
_cache.update(key=key, profile=prof)
|
|
56
|
+
return dict(prof)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def managed_view() -> dict:
|
|
60
|
+
"""The profile's managed-config fields (same shape as the managed config file)."""
|
|
61
|
+
prof = load()
|
|
62
|
+
if not prof:
|
|
63
|
+
return {}
|
|
64
|
+
return {"allowed_models": list(prof["allowed_models"]),
|
|
65
|
+
"default_model": prof["default_model"],
|
|
66
|
+
"hyper_issued_keys": list(prof["hyper_issued_keys"]),
|
|
67
|
+
"model_names": dict(prof["model_names"]),
|
|
68
|
+
"telegram": prof["telegram"] is True,
|
|
69
|
+
"restricted_clients": [dict(c) for c in prof["restricted_clients"]],
|
|
70
|
+
"restricted_clients_required": prof["restricted_clients_required"] is True,
|
|
71
|
+
"google_calendar_write": prof["google_calendar_write"] is True}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def google_client() -> dict:
|
|
75
|
+
"""{"client_id", "client_secret"} for the team Google client, or {}."""
|
|
76
|
+
prof = load()
|
|
77
|
+
cid = (prof.get("google") or {}).get("client_id") or ""
|
|
78
|
+
if not cid:
|
|
79
|
+
return {}
|
|
80
|
+
from hyperclaw.teamsetup import secrets
|
|
81
|
+
sec = secrets.get("google_client_secret")
|
|
82
|
+
return {"client_id": cid, "client_secret": sec} if sec else {}
|