darkfire514's picture
Upload 160 files
399b80c verified
"""OpenClaw host-agent config reader.
Reads ``~/.openclaw/openclaw.json`` to auto-detect:
- LLM provider credentials (via ``auth-profiles`` — not yet implemented)
- Skill-level env block (``skills.entries.openspace.env``)
- OpenAI API key for embedding generation
Config path resolution mirrors OpenClaw's own logic:
1. ``OPENCLAW_CONFIG_PATH`` env var
2. ``OPENCLAW_STATE_DIR/openclaw.json``
3. ``~/.openclaw/openclaw.json`` (default)
Fallback legacy dirs: ``~/.clawdbot``, ``~/.moldbot``, ``~/.moltbot``.
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any, Dict, Optional
logger = logging.getLogger("openspace.host_detection")
_STATE_DIRNAMES = [".openclaw", ".clawdbot", ".moldbot", ".moltbot"]
_CONFIG_FILENAMES = ["openclaw.json", "clawdbot.json", "moldbot.json", "moltbot.json"]
def _resolve_openclaw_config_path() -> Optional[Path]:
"""Find the OpenClaw config file on disk."""
import os
# 1. Explicit env override
explicit = os.environ.get("OPENCLAW_CONFIG_PATH", "").strip()
if explicit:
p = Path(explicit).expanduser()
if p.is_file():
return p
return None
# 2. State dir override
state_dir = os.environ.get("OPENCLAW_STATE_DIR", "").strip()
if state_dir:
for fname in _CONFIG_FILENAMES:
p = Path(state_dir) / fname
if p.is_file():
return p
# 3. Default locations
home = Path.home()
for dirname in _STATE_DIRNAMES:
for fname in _CONFIG_FILENAMES:
p = home / dirname / fname
if p.is_file():
return p
return None
def _load_openclaw_config() -> Optional[Dict[str, Any]]:
"""Load and parse the OpenClaw config file. Returns None on failure."""
config_path = _resolve_openclaw_config_path()
if config_path is None:
return None
try:
with open(config_path, encoding="utf-8") as f:
data = json.load(f)
return data if isinstance(data, dict) else None
except (json.JSONDecodeError, OSError) as e:
logger.warning("Failed to read OpenClaw config %s: %s", config_path, e)
return None
def read_openclaw_skill_env(skill_name: str = "openspace") -> Dict[str, str]:
"""Read ``skills.entries.<skill_name>.env`` from OpenClaw config.
This is the OpenClaw equivalent of nanobot's
``tools.mcpServers.openspace.env``.
Returns the env dict (empty if not found / parse error).
"""
data = _load_openclaw_config()
if data is None:
return {}
skills = data.get("skills", {})
if not isinstance(skills, dict):
return {}
entries = skills.get("entries", {})
if not isinstance(entries, dict):
return {}
skill_cfg = entries.get(skill_name, {})
if not isinstance(skill_cfg, dict):
return {}
env_block = skill_cfg.get("env", {})
return env_block if isinstance(env_block, dict) else {}
def get_openclaw_openai_api_key() -> Optional[str]:
"""Get OpenAI API key from OpenClaw config.
Checks ``skills.entries.openspace.env.OPENAI_API_KEY`` first,
then any top-level env vars in the config.
Returns the key string, or None.
"""
# Try skill-level env
env = read_openclaw_skill_env("openspace")
key = env.get("OPENAI_API_KEY", "").strip()
if key:
logger.debug("Using OpenAI API key from OpenClaw skill env config")
return key
# Try top-level config env.vars
data = _load_openclaw_config()
if data:
env_section = data.get("env", {})
if isinstance(env_section, dict):
vars_block = env_section.get("vars", {})
if isinstance(vars_block, dict):
key = vars_block.get("OPENAI_API_KEY", "").strip()
if key:
logger.debug("Using OpenAI API key from OpenClaw env.vars config")
return key
return None
def is_openclaw_host() -> bool:
"""Detect if the current environment is running under OpenClaw."""
import os
# Check OpenClaw-specific env vars
if os.environ.get("OPENCLAW_STATE_DIR") or os.environ.get("OPENCLAW_CONFIG_PATH"):
return True
# Check if config exists
return _resolve_openclaw_config_path() is not None