Files

1 line
34 KiB
JSON

{"repo": "cabezno/bmb-encover-agent", "files": {"bmb_cli/dump.py": {"f154a6d2d101b596b5ed67226d31af36e0bee4ad": {"t": 1785745421.1906853, "n": 10780, "h": "7b2380d2072d02b8", "c": "\"\"\"\nDump command for bmb CLI.\n\nOutputs a compact, plain-text summary of the user's BMB setup\nthat can be copy-pasted into Discord/GitHub/Telegram for support context.\nNo ANSI colors, no checkmarks — just data.\n\"\"\"\n\nimport json\nimport os\nimport platform\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nfrom bmb_cli.config import get_bmb_home, get_env_path, get_project_root, load_config\nfrom bmb_constants import display_bmb_home\n\n\ndef _get_git_commit(project_root: Path) -> str:\n \"\"\"Return short git commit hash, or '(unknown)'.\"\"\"\n try:\n result = subprocess.run(\n [\"git\", \"rev-parse\", \"--short=8\", \"HEAD\"],\n capture_output=True, text=True, timeout=5,\n cwd=str(project_root),\n )\n if result.returncode == 0:\n return result.stdout.strip()\n except Exception:\n pass\n return \"(unknown)\"\n\n\ndef _redact(value: str) -> str:\n \"\"\"Redact all but first 4 and last 4 chars.\n\n Thin wrapper over :func:`agent.redact.mask_secret`. Returns ``\"\"`` for\n an empty value (matches the historical behavior of this helper —\n ``bmb dump`` formats empty values as blank, not as ``\"(not set)\"``).\n \"\"\"\n from agent.redact import mask_secret\n return mask_secret(value)\n\n\ndef _gateway_status() -> str:\n \"\"\"Return a short gateway status string.\"\"\"\n try:\n from bmb_cli.gateway import get_gateway_runtime_snapshot\n\n snapshot = get_gateway_runtime_snapshot()\n if snapshot.running:\n mode = snapshot.manager\n if snapshot.has_process_service_mismatch:\n mode = \"manual\"\n return f\"running ({mode}, pid {snapshot.gateway_pids[0]})\"\n if snapshot.service_installed and not snapshot.service_running:\n return f\"stopped ({snapshot.manager})\"\n return f\"stopped ({snapshot.manager})\"\n except Exception:\n return \"unknown\" if sys.platform.startswith((\"linux\", \"darwin\")) else \"N/A\"\n\n\ndef _count_skills(bmb_home: Path) -> int:\n \"\"\"Count installed skills.\"\"\"\n skills_dir = bmb_home / \"skills\"\n if not skills_dir.is_dir():\n return 0\n count = 0\n for item in skills_dir.rglob(\"SKILL.md\"):\n count += 1\n return count\n\n\ndef _count_mcp_servers(config: dict) -> int:\n \"\"\"Count configured MCP servers.\"\"\"\n mcp = config.get(\"mcp\", {})\n servers = mcp.get(\"servers\", {})\n return len(servers)\n\n\ndef _cron_summary(bmb_home: Path) -> str:\n \"\"\"Return cron jobs summary.\"\"\"\n jobs_file = bmb_home / \"cron\" / \"jobs.json\"\n if not jobs_file.exists():\n return \"0\"\n try:\n with open(jobs_file, encoding=\"utf-8\") as f:\n data = json.load(f)\n jobs = data.get(\"jobs\", [])\n active = sum(1 for j in jobs if j.get(\"enabled\", True))\n return f\"{active} active / {len(jobs)} total\"\n except Exception:\n return \"(error reading)\"\n\n\ndef _configured_platforms() -> list[str]:\n \"\"\"Return list of configured messaging platform names.\"\"\"\n checks = {\n \"telegram\": \"TELEGRAM_BOT_TOKEN\",\n \"discord\": \"DISCORD_BOT_TOKEN\",\n \"slack\": \"SLACK_BOT_TOKEN\",\n \"whatsapp\": \"WHATSAPP_ENABLED\",\n \"signal\": \"SIGNAL_HTTP_URL\",\n \"email\": \"EMAIL_ADDRESS\",\n \"sms\": \"TWILIO_ACCOUNT_SID\",\n \"matrix\": \"MATRIX_HOMESERVER_URL\",\n \"mattermost\": \"MATTERMOST_URL\",\n \"homeassistant\": \"HASS_TOKEN\",\n \"dingtalk\": \"DINGTALK_CLIENT_ID\",\n \"feishu\": \"FEISHU_APP_ID\",\n \"wecom\": \"WECOM_BOT_ID\",\n \"wecom_callback\": \"WECOM_CALLBACK_CORP_ID\",\n \"weixin\": \"WEIXIN_ACCOUNT_ID\",\n \"qqbot\": \"QQ_APP_ID\",\n }\n return [name for name, env in checks.items() if os.getenv(env)]\n\n\ndef _memory_provider(config: dict) -> str:\n \"\"\"Return the active memory provider name.\"\"\"\n mem = config.get(\"memory\", {})\n provider = mem.get(\"provider\", \"\")\n return provider if provider else \"built-in\"\n\n\ndef _get_model_and_provider(config: dict) -> tuple[str, str]:\n \"\"\"Extract model and provider from config.\"\"\"\n model_cfg = config.get(\"model\", \"\")\n if isinstance(model_cfg, dict):\n model = model_cfg.get(\"default\") or model_cfg.get(\"model\") or model_cfg.get(\"name\") or \"(not set)\"\n provider = model_cfg.get(\"provider\") or \"(auto)\"\n elif isinstance(model_cfg, str):\n model = model_cfg or \"(not set)\"\n provider = \"(auto)\"\n else:\n model = \"(not set)\"\n provider = \"(auto)\"\n return model, provider\n\n\ndef _config_overrides(config: dict) -> dict[str, str]:\n \"\"\"Find non-default config values worth reporting.\n \n Returns a flat dict of dotpath -> value for interesting overrides.\n \"\"\"\n from bmb_cli.config import DEFAULT_CONFIG\n\n overrides = {}\n\n # Sections with interesting user-facing overrides\n interesting_paths = [\n (\"agent\", \"max_turns\"),\n (\"agent\", \"gateway_timeout\"),\n (\"agent\", \"tool_use_enforcement\"),\n (\"terminal\", \"backend\"),\n (\"terminal\", \"docker_image\"),\n (\"terminal\", \"persistent_shell\"),\n (\"browser\", \"allow_private_urls\"),\n (\"compression\", \"enabled\"),\n (\"compression\", \"threshold\"),\n (\"display\", \"streaming\"),\n (\"display\", \"skin\"),\n (\"display\", \"show_reasoning\"),\n (\"privacy\", \"redact_pii\"),\n (\"tts\", \"provider\"),\n ]\n\n for section, key in interesting_paths:\n default_section = DEFAULT_CONFIG.get(section, {})\n user_section = config.get(section, {})\n if not isinstance(default_section, dict) or not isinstance(user_section, dict):\n continue\n default_val = default_section.get(key)\n user_val = user_section.get(key)\n if user_val is not None and user_val != default_val:\n overrides[f\"{section}.{key}\"] = str(user_val)\n\n # Toolsets (if different from default)\n default_toolsets = DEFAULT_CONFIG.get(\"toolsets\", [])\n user_toolsets = config.get(\"toolsets\", [])\n if user_toolsets != default_toolsets:\n overrides[\"toolsets\"] = str(user_toolsets)\n\n # Fallback providers\n fallbacks = config.get(\"fallback_providers\", [])\n if fallbacks:\n overrides[\"fallback_providers\"] = str(fallbacks)\n\n return overrides\n\n\ndef run_dump(args):\n \"\"\"Output a compact, copy-pasteable setup summary.\"\"\"\n show_keys = getattr(args, \"show_keys\", False)\n\n # Load env from .env file so key checks work\n from dotenv import load_dotenv\n env_path = get_env_path()\n if env_path.exists():\n try:\n load_dotenv(env_path, encoding=\"utf-8\")\n except UnicodeDecodeError:\n load_dotenv(env_path, encoding=\"latin-1\")\n # Also try project .env as dev fallback\n load_dotenv(get_project_root() / \".env\", override=False, encoding=\"utf-8\")\n\n project_root = get_project_root()\n bmb_home = get_bmb_home()\n\n try:\n from bmb_cli import __version__, __release_date__\n except ImportError:\n __version__ = \"(unknown)\"\n __release_date__ = \"\"\n\n commit = _get_git_commit(project_root)\n\n try:\n config = load_config()\n except Exception:\n config = {}\n\n model, provider = _get_model_and_provider(config)\n\n # Profile\n try:\n from bmb_cli.profiles import get_active_profile_name\n profile = get_active_profile_name() or \"(default)\"\n except Exception:\n profile = \"(default)\"\n\n # Terminal backend\n terminal_cfg = config.get(\"terminal\", {})\n backend = terminal_cfg.get(\"backend\", \"local\")\n\n # OpenAI SDK version\n try:\n import openai\n openai_ver = openai.__version__\n except ImportError:\n openai_ver = \"not installed\"\n\n # OS info\n os_info = f\"{platform.system()} {platform.release()} {platform.machine()}\"\n\n lines = []\n lines.append(\"--- bmb dump ---\")\n ver_str = f\"{__version__}\"\n if __release_date__:\n ver_str += f\" ({__release_date__})\"\n ver_str += f\" [{commit}]\"\n lines.append(f\"version: {ver_str}\")\n lines.append(f\"os: {os_info}\")\n lines.append(f\"python: {sys.version.split()[0]}\")\n lines.append(f\"openai_sdk: {openai_ver}\")\n lines.append(f\"profile: {profile}\")\n lines.append(f\"bmb_home: {display_bmb_home()}\")\n lines.append(f\"model: {model}\")\n lines.append(f\"provider: {provider}\")\n lines.append(f\"terminal: {backend}\")\n\n # API keys\n lines.append(\"\")\n lines.append(\"api_keys:\")\n api_keys = [\n (\"OPENROUTER_API_KEY\", \"openrouter\"),\n (\"OPENAI_API_KEY\", \"openai\"),\n (\"ANTHROPIC_API_KEY\", \"anthropic\"),\n (\"ANTHROPIC_TOKEN\", \"anthropic_token\"),\n (\"NOUS_API_KEY\", \"nous\"),\n (\"GOOGLE_API_KEY\", \"google/gemini\"),\n (\"GEMINI_API_KEY\", \"gemini\"),\n (\"GLM_API_KEY\", \"glm/zai\"),\n (\"ZAI_API_KEY\", \"zai\"),\n (\"KIMI_API_KEY\", \"kimi\"),\n (\"MINIMAX_API_KEY\", \"minimax\"),\n (\"DEEPSEEK_API_KEY\", \"deepseek\"),\n (\"DASHSCOPE_API_KEY\", \"dashscope\"),\n (\"HF_TOKEN\", \"huggingface\"),\n (\"NVIDIA_API_KEY\", \"nvidia\"),\n (\"AI_GATEWAY_API_KEY\", \"ai_gateway\"),\n (\"OPENCODE_ZEN_API_KEY\", \"opencode_zen\"),\n (\"OPENCODE_GO_API_KEY\", \"opencode_go\"),\n (\"KILOCODE_API_KEY\", \"kilocode\"),\n (\"FIRECRAWL_API_KEY\", \"firecrawl\"),\n (\"TAVILY_API_KEY\", \"tavily\"),\n (\"BROWSERBASE_API_KEY\", \"browserbase\"),\n (\"FAL_KEY\", \"fal\"),\n (\"ELEVENLABS_API_KEY\", \"elevenlabs\"),\n (\"GITHUB_TOKEN\", \"github\"),\n ]\n\n for env_var, label in api_keys:\n val = os.getenv(env_var, \"\")\n if show_keys and val:\n display = _redact(val)\n else:\n display = \"set\" if val else \"not set\"\n lines.append(f\" {label:<20} {display}\")\n\n # Features summary\n lines.append(\"\")\n lines.append(\"features:\")\n\n toolsets = config.get(\"toolsets\", [\"bmb-cli\"])\n lines.append(f\" toolsets: {', '.join(toolsets) if toolsets else '(default)'}\")\n lines.append(f\" mcp_servers: {_count_mcp_servers(config)}\")\n lines.append(f\" memory_provider: {_memory_provider(config)}\")\n lines.append(f\" gateway: {_gateway_status()}\")\n\n platforms = _configured_platforms()\n lines.append(f\" platforms: {', '.join(platforms) if platforms else 'none'}\")\n lines.append(f\" cron_jobs: {_cron_summary(bmb_home)}\")\n lines.append(f\" skills: {_count_skills(bmb_home)}\")\n\n # Config overrides (non-default values)\n overrides = _config_overrides(config)\n if overrides:\n lines.append(\"\")\n lines.append(\"config_overrides:\")\n for key, val in overrides.items():\n lines.append(f\" {key}: {val}\")\n\n lines.append(\"--- end dump ---\")\n\n output = \"\\n\".join(lines)\n print(output)\n"}, "998ca7f8007bed47fb54d80188580a99d9cc9d9c": {"t": 1785753013.2238905, "n": 10780, "h": "7b2380d2072d02b8", "c": "\"\"\"\nDump command for bmb CLI.\n\nOutputs a compact, plain-text summary of the user's BMB setup\nthat can be copy-pasted into Discord/GitHub/Telegram for support context.\nNo ANSI colors, no checkmarks — just data.\n\"\"\"\n\nimport json\nimport os\nimport platform\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nfrom bmb_cli.config import get_bmb_home, get_env_path, get_project_root, load_config\nfrom bmb_constants import display_bmb_home\n\n\ndef _get_git_commit(project_root: Path) -> str:\n \"\"\"Return short git commit hash, or '(unknown)'.\"\"\"\n try:\n result = subprocess.run(\n [\"git\", \"rev-parse\", \"--short=8\", \"HEAD\"],\n capture_output=True, text=True, timeout=5,\n cwd=str(project_root),\n )\n if result.returncode == 0:\n return result.stdout.strip()\n except Exception:\n pass\n return \"(unknown)\"\n\n\ndef _redact(value: str) -> str:\n \"\"\"Redact all but first 4 and last 4 chars.\n\n Thin wrapper over :func:`agent.redact.mask_secret`. Returns ``\"\"`` for\n an empty value (matches the historical behavior of this helper —\n ``bmb dump`` formats empty values as blank, not as ``\"(not set)\"``).\n \"\"\"\n from agent.redact import mask_secret\n return mask_secret(value)\n\n\ndef _gateway_status() -> str:\n \"\"\"Return a short gateway status string.\"\"\"\n try:\n from bmb_cli.gateway import get_gateway_runtime_snapshot\n\n snapshot = get_gateway_runtime_snapshot()\n if snapshot.running:\n mode = snapshot.manager\n if snapshot.has_process_service_mismatch:\n mode = \"manual\"\n return f\"running ({mode}, pid {snapshot.gateway_pids[0]})\"\n if snapshot.service_installed and not snapshot.service_running:\n return f\"stopped ({snapshot.manager})\"\n return f\"stopped ({snapshot.manager})\"\n except Exception:\n return \"unknown\" if sys.platform.startswith((\"linux\", \"darwin\")) else \"N/A\"\n\n\ndef _count_skills(bmb_home: Path) -> int:\n \"\"\"Count installed skills.\"\"\"\n skills_dir = bmb_home / \"skills\"\n if not skills_dir.is_dir():\n return 0\n count = 0\n for item in skills_dir.rglob(\"SKILL.md\"):\n count += 1\n return count\n\n\ndef _count_mcp_servers(config: dict) -> int:\n \"\"\"Count configured MCP servers.\"\"\"\n mcp = config.get(\"mcp\", {})\n servers = mcp.get(\"servers\", {})\n return len(servers)\n\n\ndef _cron_summary(bmb_home: Path) -> str:\n \"\"\"Return cron jobs summary.\"\"\"\n jobs_file = bmb_home / \"cron\" / \"jobs.json\"\n if not jobs_file.exists():\n return \"0\"\n try:\n with open(jobs_file, encoding=\"utf-8\") as f:\n data = json.load(f)\n jobs = data.get(\"jobs\", [])\n active = sum(1 for j in jobs if j.get(\"enabled\", True))\n return f\"{active} active / {len(jobs)} total\"\n except Exception:\n return \"(error reading)\"\n\n\ndef _configured_platforms() -> list[str]:\n \"\"\"Return list of configured messaging platform names.\"\"\"\n checks = {\n \"telegram\": \"TELEGRAM_BOT_TOKEN\",\n \"discord\": \"DISCORD_BOT_TOKEN\",\n \"slack\": \"SLACK_BOT_TOKEN\",\n \"whatsapp\": \"WHATSAPP_ENABLED\",\n \"signal\": \"SIGNAL_HTTP_URL\",\n \"email\": \"EMAIL_ADDRESS\",\n \"sms\": \"TWILIO_ACCOUNT_SID\",\n \"matrix\": \"MATRIX_HOMESERVER_URL\",\n \"mattermost\": \"MATTERMOST_URL\",\n \"homeassistant\": \"HASS_TOKEN\",\n \"dingtalk\": \"DINGTALK_CLIENT_ID\",\n \"feishu\": \"FEISHU_APP_ID\",\n \"wecom\": \"WECOM_BOT_ID\",\n \"wecom_callback\": \"WECOM_CALLBACK_CORP_ID\",\n \"weixin\": \"WEIXIN_ACCOUNT_ID\",\n \"qqbot\": \"QQ_APP_ID\",\n }\n return [name for name, env in checks.items() if os.getenv(env)]\n\n\ndef _memory_provider(config: dict) -> str:\n \"\"\"Return the active memory provider name.\"\"\"\n mem = config.get(\"memory\", {})\n provider = mem.get(\"provider\", \"\")\n return provider if provider else \"built-in\"\n\n\ndef _get_model_and_provider(config: dict) -> tuple[str, str]:\n \"\"\"Extract model and provider from config.\"\"\"\n model_cfg = config.get(\"model\", \"\")\n if isinstance(model_cfg, dict):\n model = model_cfg.get(\"default\") or model_cfg.get(\"model\") or model_cfg.get(\"name\") or \"(not set)\"\n provider = model_cfg.get(\"provider\") or \"(auto)\"\n elif isinstance(model_cfg, str):\n model = model_cfg or \"(not set)\"\n provider = \"(auto)\"\n else:\n model = \"(not set)\"\n provider = \"(auto)\"\n return model, provider\n\n\ndef _config_overrides(config: dict) -> dict[str, str]:\n \"\"\"Find non-default config values worth reporting.\n \n Returns a flat dict of dotpath -> value for interesting overrides.\n \"\"\"\n from bmb_cli.config import DEFAULT_CONFIG\n\n overrides = {}\n\n # Sections with interesting user-facing overrides\n interesting_paths = [\n (\"agent\", \"max_turns\"),\n (\"agent\", \"gateway_timeout\"),\n (\"agent\", \"tool_use_enforcement\"),\n (\"terminal\", \"backend\"),\n (\"terminal\", \"docker_image\"),\n (\"terminal\", \"persistent_shell\"),\n (\"browser\", \"allow_private_urls\"),\n (\"compression\", \"enabled\"),\n (\"compression\", \"threshold\"),\n (\"display\", \"streaming\"),\n (\"display\", \"skin\"),\n (\"display\", \"show_reasoning\"),\n (\"privacy\", \"redact_pii\"),\n (\"tts\", \"provider\"),\n ]\n\n for section, key in interesting_paths:\n default_section = DEFAULT_CONFIG.get(section, {})\n user_section = config.get(section, {})\n if not isinstance(default_section, dict) or not isinstance(user_section, dict):\n continue\n default_val = default_section.get(key)\n user_val = user_section.get(key)\n if user_val is not None and user_val != default_val:\n overrides[f\"{section}.{key}\"] = str(user_val)\n\n # Toolsets (if different from default)\n default_toolsets = DEFAULT_CONFIG.get(\"toolsets\", [])\n user_toolsets = config.get(\"toolsets\", [])\n if user_toolsets != default_toolsets:\n overrides[\"toolsets\"] = str(user_toolsets)\n\n # Fallback providers\n fallbacks = config.get(\"fallback_providers\", [])\n if fallbacks:\n overrides[\"fallback_providers\"] = str(fallbacks)\n\n return overrides\n\n\ndef run_dump(args):\n \"\"\"Output a compact, copy-pasteable setup summary.\"\"\"\n show_keys = getattr(args, \"show_keys\", False)\n\n # Load env from .env file so key checks work\n from dotenv import load_dotenv\n env_path = get_env_path()\n if env_path.exists():\n try:\n load_dotenv(env_path, encoding=\"utf-8\")\n except UnicodeDecodeError:\n load_dotenv(env_path, encoding=\"latin-1\")\n # Also try project .env as dev fallback\n load_dotenv(get_project_root() / \".env\", override=False, encoding=\"utf-8\")\n\n project_root = get_project_root()\n bmb_home = get_bmb_home()\n\n try:\n from bmb_cli import __version__, __release_date__\n except ImportError:\n __version__ = \"(unknown)\"\n __release_date__ = \"\"\n\n commit = _get_git_commit(project_root)\n\n try:\n config = load_config()\n except Exception:\n config = {}\n\n model, provider = _get_model_and_provider(config)\n\n # Profile\n try:\n from bmb_cli.profiles import get_active_profile_name\n profile = get_active_profile_name() or \"(default)\"\n except Exception:\n profile = \"(default)\"\n\n # Terminal backend\n terminal_cfg = config.get(\"terminal\", {})\n backend = terminal_cfg.get(\"backend\", \"local\")\n\n # OpenAI SDK version\n try:\n import openai\n openai_ver = openai.__version__\n except ImportError:\n openai_ver = \"not installed\"\n\n # OS info\n os_info = f\"{platform.system()} {platform.release()} {platform.machine()}\"\n\n lines = []\n lines.append(\"--- bmb dump ---\")\n ver_str = f\"{__version__}\"\n if __release_date__:\n ver_str += f\" ({__release_date__})\"\n ver_str += f\" [{commit}]\"\n lines.append(f\"version: {ver_str}\")\n lines.append(f\"os: {os_info}\")\n lines.append(f\"python: {sys.version.split()[0]}\")\n lines.append(f\"openai_sdk: {openai_ver}\")\n lines.append(f\"profile: {profile}\")\n lines.append(f\"bmb_home: {display_bmb_home()}\")\n lines.append(f\"model: {model}\")\n lines.append(f\"provider: {provider}\")\n lines.append(f\"terminal: {backend}\")\n\n # API keys\n lines.append(\"\")\n lines.append(\"api_keys:\")\n api_keys = [\n (\"OPENROUTER_API_KEY\", \"openrouter\"),\n (\"OPENAI_API_KEY\", \"openai\"),\n (\"ANTHROPIC_API_KEY\", \"anthropic\"),\n (\"ANTHROPIC_TOKEN\", \"anthropic_token\"),\n (\"NOUS_API_KEY\", \"nous\"),\n (\"GOOGLE_API_KEY\", \"google/gemini\"),\n (\"GEMINI_API_KEY\", \"gemini\"),\n (\"GLM_API_KEY\", \"glm/zai\"),\n (\"ZAI_API_KEY\", \"zai\"),\n (\"KIMI_API_KEY\", \"kimi\"),\n (\"MINIMAX_API_KEY\", \"minimax\"),\n (\"DEEPSEEK_API_KEY\", \"deepseek\"),\n (\"DASHSCOPE_API_KEY\", \"dashscope\"),\n (\"HF_TOKEN\", \"huggingface\"),\n (\"NVIDIA_API_KEY\", \"nvidia\"),\n (\"AI_GATEWAY_API_KEY\", \"ai_gateway\"),\n (\"OPENCODE_ZEN_API_KEY\", \"opencode_zen\"),\n (\"OPENCODE_GO_API_KEY\", \"opencode_go\"),\n (\"KILOCODE_API_KEY\", \"kilocode\"),\n (\"FIRECRAWL_API_KEY\", \"firecrawl\"),\n (\"TAVILY_API_KEY\", \"tavily\"),\n (\"BROWSERBASE_API_KEY\", \"browserbase\"),\n (\"FAL_KEY\", \"fal\"),\n (\"ELEVENLABS_API_KEY\", \"elevenlabs\"),\n (\"GITHUB_TOKEN\", \"github\"),\n ]\n\n for env_var, label in api_keys:\n val = os.getenv(env_var, \"\")\n if show_keys and val:\n display = _redact(val)\n else:\n display = \"set\" if val else \"not set\"\n lines.append(f\" {label:<20} {display}\")\n\n # Features summary\n lines.append(\"\")\n lines.append(\"features:\")\n\n toolsets = config.get(\"toolsets\", [\"bmb-cli\"])\n lines.append(f\" toolsets: {', '.join(toolsets) if toolsets else '(default)'}\")\n lines.append(f\" mcp_servers: {_count_mcp_servers(config)}\")\n lines.append(f\" memory_provider: {_memory_provider(config)}\")\n lines.append(f\" gateway: {_gateway_status()}\")\n\n platforms = _configured_platforms()\n lines.append(f\" platforms: {', '.join(platforms) if platforms else 'none'}\")\n lines.append(f\" cron_jobs: {_cron_summary(bmb_home)}\")\n lines.append(f\" skills: {_count_skills(bmb_home)}\")\n\n # Config overrides (non-default values)\n overrides = _config_overrides(config)\n if overrides:\n lines.append(\"\")\n lines.append(\"config_overrides:\")\n for key, val in overrides.items():\n lines.append(f\" {key}: {val}\")\n\n lines.append(\"--- end dump ---\")\n\n output = \"\\n\".join(lines)\n print(output)\n"}, "7ec16ce25d6f65eabe83309ba8c5314e56f69ba7": {"t": 1785753014.9401555, "n": 10786, "h": "c73ccea5cd02ed43", "c": "\"\"\"\nDump command for bmb CLI.\n\nOutputs a compact, plain-text summary of the user's Hermes setup\nthat can be copy-pasted into Discord/GitHub/Telegram for support context.\nNo ANSI colors, no checkmarks — just data.\n\"\"\"\n\nimport json\nimport os\nimport platform\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nfrom bmb_cli.config import get_bmb_home, get_env_path, get_project_root, load_config\nfrom bmb_constants import display_bmb_home\n\n\ndef _get_git_commit(project_root: Path) -> str:\n \"\"\"Return short git commit hash, or '(unknown)'.\"\"\"\n try:\n result = subprocess.run(\n [\"git\", \"rev-parse\", \"--short=8\", \"HEAD\"],\n capture_output=True, text=True, timeout=5,\n cwd=str(project_root),\n )\n if result.returncode == 0:\n return result.stdout.strip()\n except Exception:\n pass\n return \"(unknown)\"\n\n\ndef _redact(value: str) -> str:\n \"\"\"Redact all but first 4 and last 4 chars.\n\n Thin wrapper over :func:`agent.redact.mask_secret`. Returns ``\"\"`` for\n an empty value (matches the historical behavior of this helper —\n ``bmb dump`` formats empty values as blank, not as ``\"(not set)\"``).\n \"\"\"\n from agent.redact import mask_secret\n return mask_secret(value)\n\n\ndef _gateway_status() -> str:\n \"\"\"Return a short gateway status string.\"\"\"\n try:\n from bmb_cli.gateway import get_gateway_runtime_snapshot\n\n snapshot = get_gateway_runtime_snapshot()\n if snapshot.running:\n mode = snapshot.manager\n if snapshot.has_process_service_mismatch:\n mode = \"manual\"\n return f\"running ({mode}, pid {snapshot.gateway_pids[0]})\"\n if snapshot.service_installed and not snapshot.service_running:\n return f\"stopped ({snapshot.manager})\"\n return f\"stopped ({snapshot.manager})\"\n except Exception:\n return \"unknown\" if sys.platform.startswith((\"linux\", \"darwin\")) else \"N/A\"\n\n\ndef _count_skills(bmb_home: Path) -> int:\n \"\"\"Count installed skills.\"\"\"\n skills_dir = bmb_home / \"skills\"\n if not skills_dir.is_dir():\n return 0\n count = 0\n for item in skills_dir.rglob(\"SKILL.md\"):\n count += 1\n return count\n\n\ndef _count_mcp_servers(config: dict) -> int:\n \"\"\"Count configured MCP servers.\"\"\"\n mcp = config.get(\"mcp\", {})\n servers = mcp.get(\"servers\", {})\n return len(servers)\n\n\ndef _cron_summary(bmb_home: Path) -> str:\n \"\"\"Return cron jobs summary.\"\"\"\n jobs_file = bmb_home / \"cron\" / \"jobs.json\"\n if not jobs_file.exists():\n return \"0\"\n try:\n with open(jobs_file, encoding=\"utf-8\") as f:\n data = json.load(f)\n jobs = data.get(\"jobs\", [])\n active = sum(1 for j in jobs if j.get(\"enabled\", True))\n return f\"{active} active / {len(jobs)} total\"\n except Exception:\n return \"(error reading)\"\n\n\ndef _configured_platforms() -> list[str]:\n \"\"\"Return list of configured messaging platform names.\"\"\"\n checks = {\n \"telegram\": \"TELEGRAM_BOT_TOKEN\",\n \"discord\": \"DISCORD_BOT_TOKEN\",\n \"slack\": \"SLACK_BOT_TOKEN\",\n \"whatsapp\": \"WHATSAPP_ENABLED\",\n \"signal\": \"SIGNAL_HTTP_URL\",\n \"email\": \"EMAIL_ADDRESS\",\n \"sms\": \"TWILIO_ACCOUNT_SID\",\n \"matrix\": \"MATRIX_HOMESERVER_URL\",\n \"mattermost\": \"MATTERMOST_URL\",\n \"homeassistant\": \"HASS_TOKEN\",\n \"dingtalk\": \"DINGTALK_CLIENT_ID\",\n \"feishu\": \"FEISHU_APP_ID\",\n \"wecom\": \"WECOM_BOT_ID\",\n \"wecom_callback\": \"WECOM_CALLBACK_CORP_ID\",\n \"weixin\": \"WEIXIN_ACCOUNT_ID\",\n \"qqbot\": \"QQ_APP_ID\",\n }\n return [name for name, env in checks.items() if os.getenv(env)]\n\n\ndef _memory_provider(config: dict) -> str:\n \"\"\"Return the active memory provider name.\"\"\"\n mem = config.get(\"memory\", {})\n provider = mem.get(\"provider\", \"\")\n return provider if provider else \"built-in\"\n\n\ndef _get_model_and_provider(config: dict) -> tuple[str, str]:\n \"\"\"Extract model and provider from config.\"\"\"\n model_cfg = config.get(\"model\", \"\")\n if isinstance(model_cfg, dict):\n model = model_cfg.get(\"default\") or model_cfg.get(\"model\") or model_cfg.get(\"name\") or \"(not set)\"\n provider = model_cfg.get(\"provider\") or \"(auto)\"\n elif isinstance(model_cfg, str):\n model = model_cfg or \"(not set)\"\n provider = \"(auto)\"\n else:\n model = \"(not set)\"\n provider = \"(auto)\"\n return model, provider\n\n\ndef _config_overrides(config: dict) -> dict[str, str]:\n \"\"\"Find non-default config values worth reporting.\n \n Returns a flat dict of dotpath -> value for interesting overrides.\n \"\"\"\n from bmb_cli.config import DEFAULT_CONFIG\n\n overrides = {}\n\n # Sections with interesting user-facing overrides\n interesting_paths = [\n (\"agent\", \"max_turns\"),\n (\"agent\", \"gateway_timeout\"),\n (\"agent\", \"tool_use_enforcement\"),\n (\"terminal\", \"backend\"),\n (\"terminal\", \"docker_image\"),\n (\"terminal\", \"persistent_shell\"),\n (\"browser\", \"allow_private_urls\"),\n (\"compression\", \"enabled\"),\n (\"compression\", \"threshold\"),\n (\"display\", \"streaming\"),\n (\"display\", \"skin\"),\n (\"display\", \"show_reasoning\"),\n (\"privacy\", \"redact_pii\"),\n (\"tts\", \"provider\"),\n ]\n\n for section, key in interesting_paths:\n default_section = DEFAULT_CONFIG.get(section, {})\n user_section = config.get(section, {})\n if not isinstance(default_section, dict) or not isinstance(user_section, dict):\n continue\n default_val = default_section.get(key)\n user_val = user_section.get(key)\n if user_val is not None and user_val != default_val:\n overrides[f\"{section}.{key}\"] = str(user_val)\n\n # Toolsets (if different from default)\n default_toolsets = DEFAULT_CONFIG.get(\"toolsets\", [])\n user_toolsets = config.get(\"toolsets\", [])\n if user_toolsets != default_toolsets:\n overrides[\"toolsets\"] = str(user_toolsets)\n\n # Fallback providers\n fallbacks = config.get(\"fallback_providers\", [])\n if fallbacks:\n overrides[\"fallback_providers\"] = str(fallbacks)\n\n return overrides\n\n\ndef run_dump(args):\n \"\"\"Output a compact, copy-pasteable setup summary.\"\"\"\n show_keys = getattr(args, \"show_keys\", False)\n\n # Load env from .env file so key checks work\n from dotenv import load_dotenv\n env_path = get_env_path()\n if env_path.exists():\n try:\n load_dotenv(env_path, encoding=\"utf-8\")\n except UnicodeDecodeError:\n load_dotenv(env_path, encoding=\"latin-1\")\n # Also try project .env as dev fallback\n load_dotenv(get_project_root() / \".env\", override=False, encoding=\"utf-8\")\n\n project_root = get_project_root()\n bmb_home = get_bmb_home()\n\n try:\n from bmb_cli import __version__, __release_date__\n except ImportError:\n __version__ = \"(unknown)\"\n __release_date__ = \"\"\n\n commit = _get_git_commit(project_root)\n\n try:\n config = load_config()\n except Exception:\n config = {}\n\n model, provider = _get_model_and_provider(config)\n\n # Profile\n try:\n from bmb_cli.profiles import get_active_profile_name\n profile = get_active_profile_name() or \"(default)\"\n except Exception:\n profile = \"(default)\"\n\n # Terminal backend\n terminal_cfg = config.get(\"terminal\", {})\n backend = terminal_cfg.get(\"backend\", \"local\")\n\n # OpenAI SDK version\n try:\n import openai\n openai_ver = openai.__version__\n except ImportError:\n openai_ver = \"not installed\"\n\n # OS info\n os_info = f\"{platform.system()} {platform.release()} {platform.machine()}\"\n\n lines = []\n lines.append(\"--- bmb dump ---\")\n ver_str = f\"{__version__}\"\n if __release_date__:\n ver_str += f\" ({__release_date__})\"\n ver_str += f\" [{commit}]\"\n lines.append(f\"version: {ver_str}\")\n lines.append(f\"os: {os_info}\")\n lines.append(f\"python: {sys.version.split()[0]}\")\n lines.append(f\"openai_sdk: {openai_ver}\")\n lines.append(f\"profile: {profile}\")\n lines.append(f\"bmb_home: {display_bmb_home()}\")\n lines.append(f\"model: {model}\")\n lines.append(f\"provider: {provider}\")\n lines.append(f\"terminal: {backend}\")\n\n # API keys\n lines.append(\"\")\n lines.append(\"api_keys:\")\n api_keys = [\n (\"OPENROUTER_API_KEY\", \"openrouter\"),\n (\"OPENAI_API_KEY\", \"openai\"),\n (\"ANTHROPIC_API_KEY\", \"anthropic\"),\n (\"ANTHROPIC_TOKEN\", \"anthropic_token\"),\n (\"NOUS_API_KEY\", \"nous\"),\n (\"GOOGLE_API_KEY\", \"google/gemini\"),\n (\"GEMINI_API_KEY\", \"gemini\"),\n (\"GLM_API_KEY\", \"glm/zai\"),\n (\"ZAI_API_KEY\", \"zai\"),\n (\"KIMI_API_KEY\", \"kimi\"),\n (\"MINIMAX_API_KEY\", \"minimax\"),\n (\"DEEPSEEK_API_KEY\", \"deepseek\"),\n (\"DASHSCOPE_API_KEY\", \"dashscope\"),\n (\"HF_TOKEN\", \"huggingface\"),\n (\"NVIDIA_API_KEY\", \"nvidia\"),\n (\"AI_GATEWAY_API_KEY\", \"ai_gateway\"),\n (\"OPENCODE_ZEN_API_KEY\", \"opencode_zen\"),\n (\"OPENCODE_GO_API_KEY\", \"opencode_go\"),\n (\"KILOCODE_API_KEY\", \"kilocode\"),\n (\"FIRECRAWL_API_KEY\", \"firecrawl\"),\n (\"TAVILY_API_KEY\", \"tavily\"),\n (\"BROWSERBASE_API_KEY\", \"browserbase\"),\n (\"FAL_KEY\", \"fal\"),\n (\"ELEVENLABS_API_KEY\", \"elevenlabs\"),\n (\"GITHUB_TOKEN\", \"github\"),\n ]\n\n for env_var, label in api_keys:\n val = os.getenv(env_var, \"\")\n if show_keys and val:\n display = _redact(val)\n else:\n display = \"set\" if val else \"not set\"\n lines.append(f\" {label:<20} {display}\")\n\n # Features summary\n lines.append(\"\")\n lines.append(\"features:\")\n\n toolsets = config.get(\"toolsets\", [\"hermes-cli\"])\n lines.append(f\" toolsets: {', '.join(toolsets) if toolsets else '(default)'}\")\n lines.append(f\" mcp_servers: {_count_mcp_servers(config)}\")\n lines.append(f\" memory_provider: {_memory_provider(config)}\")\n lines.append(f\" gateway: {_gateway_status()}\")\n\n platforms = _configured_platforms()\n lines.append(f\" platforms: {', '.join(platforms) if platforms else 'none'}\")\n lines.append(f\" cron_jobs: {_cron_summary(bmb_home)}\")\n lines.append(f\" skills: {_count_skills(bmb_home)}\")\n\n # Config overrides (non-default values)\n overrides = _config_overrides(config)\n if overrides:\n lines.append(\"\")\n lines.append(\"config_overrides:\")\n for key, val in overrides.items():\n lines.append(f\" {key}: {val}\")\n\n lines.append(\"--- end dump ---\")\n\n output = \"\\n\".join(lines)\n print(output)\n"}}}}