File size: 4,333 Bytes
399b80c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""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