scripts/config.py
scripts/config.pyBrowse 56 files
1,440 tokens
6,108 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1"""Install-wide configuration with easiest-first defaults.2 3Everything works zero-config. `setup --auto` (the autonomous path) detects what4this environment can do and picks the MOST AUTONOMOUS valid configuration without5asking anyone; plain `setup` keeps the easiest-first defaults and only upgrades a6setting when a flag opts in.7 8`autonomy` is policy, orthogonal to capability:9 full - intake consent is standing authorization; the agent submits T0-T210 opt-outs without pausing per submission (default).11 assisted - the agent pauses for operator confirmation before each submission.12"""13from __future__ import annotations14 15import os16from pathlib import Path17from shutil import which18 19import emailer20import paths21import storage22 23DEFAULT_CONFIG = {24 "autonomy": "full", # hands-off after intake+consent25 "email_mode": "draft_only", # zero credentials26 "browser_backend": "auto", # auto = Browserbase when BROWSERBASE_API_KEY is set27 # (recommended default; clears soft CAPTCHAs), else plain browser28 "tracker_backend": "local-json", # no external dependency29 "encryption": "none", # files still written 060030 "default_rescan_interval_days": 120,31 "email_min_interval_seconds": 20, # pace SMTP sends so a run can't torch the account32}33 34VALID = {35 "autonomy": {"full", "assisted"},36 # email_mode:37 # draft_only - render drafts; the operator sends + clicks verify links (zero setup)38 # browser - the agent sends + opens verify links through the operator's logged-in39 # webmail via browser_* tools (NO password stored; needs a browser the40 # operator's inbox is signed into)41 # programmatic - CLI sends via SMTP + reads verify links via IMAP (needs EMAIL_* creds)42 # alias - AgentMail agent-owned inboxes / per-broker aliases43 "email_mode": {"draft_only", "browser", "programmatic", "alias"},44 "browser_backend": {"auto", "browserbase", "agent-browser", "camofox"},45 "tracker_backend": {"local-json", "google-sheets"},46 "encryption": {"none", "age"},47}48 49 50def load_config() -> dict:51 cfg = dict(DEFAULT_CONFIG)52 cfg.update(storage.read_json(paths.config_path(), {}) or {})53 return cfg54 55 56def save_config(cfg: dict) -> Path:57 merged = dict(DEFAULT_CONFIG)58 merged.update(cfg)59 for key, allowed in VALID.items():60 if merged.get(key) not in allowed:61 raise ValueError(f"invalid {key!r}: {merged.get(key)!r} (allowed: {sorted(allowed)})")62 return storage.write_json(paths.config_path(), merged)63 64 65def dotenv_env() -> dict:66 """Shell env overlaid on `$HERMES_HOME/.env`, so capability detection sees the creds Hermes67 loads for its own tools (BROWSERBASE_API_KEY, EMAIL_*, AGENTMAIL_API_KEY, ...) even though the68 terminal-tool shell doesn't export them. Shell env wins; the .env only fills gaps."""69 merged: dict = {}70 p = paths.hermes_home() / ".env"71 if p.exists():72 try:73 for line in p.read_text(encoding="utf-8", errors="replace").splitlines():74 line = line.strip()75 if not line or line.startswith("#") or "=" not in line:76 continue77 k, v = line.split("=", 1)78 merged[k.strip()] = v.strip().strip('"').strip("'")79 except OSError:80 pass81 merged.update(os.environ)82 return merged83 84 85def detect_capabilities(env: dict | None = None) -> dict:86 """Report which opt-in upgrades are available without extra setup."""87 env = os.environ if env is None else env88 home = paths.hermes_home()89 google = (90 (home / "google_token.json").exists()91 or (home / "skills" / "productivity" / "google-workspace").exists()92 or (home / "skills" / "google-workspace").exists()93 )94 mail = emailer.available(env)95 return {96 "browserbase": bool(env.get("BROWSERBASE_API_KEY")),97 "agentmail": bool(env.get("AGENTMAIL_API_KEY")),98 "email_imap_smtp": bool(env.get("EMAIL_ADDRESS") and env.get("EMAIL_PASSWORD")),99 "smtp_send": mail["smtp"], # CLI can SEND opt-out emails itself100 "imap_read": mail["imap"], # CLI can POLL verification links itself101 "google_workspace": google,102 "age": which("age") is not None,103 }104 105 106def auto_configure(env: dict | None = None) -> dict:107 """Pick the most autonomous configuration this environment supports (no questions).108 109 - email: programmatic when SMTP creds exist (CLI sends + IMAP-verifies itself);110 alias mode when only AgentMail exists; draft_only as the capability floor.111 - browser: browserbase when the key exists (clears soft CAPTCHAs -> more T1).112 - encryption: age when the binary is installed (free privacy, zero human cost).113 - tracker: stays local-json (google-sheets needs a sheet id -> a human choice).114 """115 caps = detect_capabilities(env)116 cfg = load_config()117 cfg["autonomy"] = "full"118 if caps["smtp_send"]:119 cfg["email_mode"] = "programmatic"120 elif caps["agentmail"]:121 cfg["email_mode"] = "alias"122 else:123 cfg["email_mode"] = "draft_only"124 cfg["browser_backend"] = "browserbase" if caps["browserbase"] else "auto"125 if caps["age"]:126 cfg["encryption"] = "age"127 return cfg128 129 130def browser_clears_captcha(cfg: dict, env: dict | None = None) -> bool:131 """True if the chosen browser backend can clear soft CAPTCHAs (shifts T2 -> T1).132 133 Browserbase is the recommended default: a real residential-IP cloud browser passes134 soft/managed challenges (Turnstile, hCaptcha/reCAPTCHA checkbox) as normal operation.135 This is NOT solving/spoofing - hard interactive challenges still escalate to a human.136 `auto` inherits this whenever BROWSERBASE_API_KEY is present.137 """138 backend = cfg.get("browser_backend", "auto")139 if backend == "browserbase":140 return True141 if backend == "auto":142 env = os.environ if env is None else env143 return bool(env.get("BROWSERBASE_API_KEY"))144 return False145