sophhub 0.4.2 → 0.4.4
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.
- package/agents/ai-cs-admin/.config.json +6 -1
- package/agents/ai-cs-qa/.config.json +9 -1
- package/agents/ai-cs-qa/AGENTS.md +43 -15
- package/agents/ai-cs-qa/scripts/setup_links.sh +39 -0
- package/agents/vip-admin/.config.json +51 -0
- package/agents/vip-admin/AGENTS.md +331 -0
- package/agents/vip-admin/BOOTSTRAP.md +21 -0
- package/agents/vip-admin/HEARTBEAT.md +19 -0
- package/agents/vip-admin/IDENTITY.md +6 -0
- package/agents/vip-admin/MEMORY.md +29 -0
- package/agents/vip-admin/SOUL.md +25 -0
- package/agents/vip-admin/TOOLS.md +102 -0
- package/agents/vip-admin/USER.md +17 -0
- package/agents/vip-qa/.config.json +58 -0
- package/agents/vip-qa/AGENTS.md +312 -0
- package/agents/vip-qa/BOOTSTRAP.md +74 -0
- package/agents/vip-qa/HEARTBEAT.md +23 -0
- package/agents/vip-qa/IDENTITY.md +6 -0
- package/agents/vip-qa/MEMORY.md +23 -0
- package/agents/vip-qa/SOUL.md +34 -0
- package/agents/vip-qa/TOOLS.md +41 -0
- package/agents/vip-qa/USER.md +16 -0
- package/agents/vip-qa/scripts/setup_links.sh +39 -0
- package/package.json +1 -1
- package/skills/agent-install/skill.json +27 -0
- package/skills/agent-install/src/SKILL.md +238 -0
- package/skills/agent-install/src/pyproject.toml +6 -0
- package/skills/agent-install/src/scripts/backup_agent.py +120 -0
- package/skills/agent-install/src/scripts/check_installed.py +479 -0
- package/skills/agent-install/src/scripts/common.py +487 -0
- package/skills/agent-install/src/scripts/copy_agent_files.py +59 -0
- package/skills/agent-install/src/scripts/list_agents.py +285 -0
- package/skills/agent-install/src/scripts/resolve_install_params.py +90 -0
- package/skills/agent-install/src/scripts/update_agent_md.py +76 -0
- package/skills/agent-install/src/scripts/update_openclaw.py +183 -0
- package/skills/agent-install/src/scripts/verify_download.py +148 -0
- package/skills/bot-api-status/skill.json +36 -0
- package/skills/bot-api-status/src/SKILL.md +89 -0
- package/skills/bot-api-status/src/pyproject.toml +5 -0
- package/skills/bot-api-status/src/scripts/secret.py +481 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import secrets
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
DEFAULT_CONFIG_PATH = Path(os.path.expanduser(os.environ.get("OPENCLAW_CONFIG_PATH", "~/.openclaw/openclaw.json"))).resolve()
|
|
14
|
+
DEFAULT_BASE_URL_PATH = Path("/home/node/.openclaw/.base.json")
|
|
15
|
+
BASEURL_FETCH_FAILED_MESSAGE = "获取BASEURL失败,刷新页面或者重新登录后重试。"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_json(path: Path) -> dict[str, Any]:
|
|
19
|
+
if not path.exists():
|
|
20
|
+
raise FileNotFoundError(f"Config file not found: {path}")
|
|
21
|
+
try:
|
|
22
|
+
with path.open("r", encoding="utf-8") as f:
|
|
23
|
+
return json.load(f)
|
|
24
|
+
except json.JSONDecodeError as exc:
|
|
25
|
+
raise ValueError(f"Invalid JSON in config file: {path}") from exc
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def dump_json(path: Path, data: dict[str, Any]) -> None:
|
|
29
|
+
with path.open("w", encoding="utf-8") as f:
|
|
30
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
31
|
+
f.write("\n")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def normalize_base_url(base_url: str | None) -> str | None:
|
|
35
|
+
if not isinstance(base_url, str):
|
|
36
|
+
return None
|
|
37
|
+
normalized = base_url.strip().rstrip("/")
|
|
38
|
+
return normalized or None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def read_base_url(base_url_path: Path = DEFAULT_BASE_URL_PATH) -> str | None:
|
|
42
|
+
try:
|
|
43
|
+
data = load_json(base_url_path)
|
|
44
|
+
except (FileNotFoundError, OSError, ValueError):
|
|
45
|
+
return None
|
|
46
|
+
return normalize_base_url(data.get("base_url"))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def require_base_url(base_url_path: Path = DEFAULT_BASE_URL_PATH) -> str:
|
|
50
|
+
base_url = read_base_url(base_url_path)
|
|
51
|
+
if not base_url:
|
|
52
|
+
raise ValueError(BASEURL_FETCH_FAILED_MESSAGE)
|
|
53
|
+
return base_url
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_bot_api_url(account_id: str | None, path_suffix: str, base_url: str | None = None) -> str | None:
|
|
57
|
+
if not isinstance(account_id, str) or not account_id.strip():
|
|
58
|
+
return None
|
|
59
|
+
segment = account_id.strip()
|
|
60
|
+
path = f"/bot-api/v2/{segment}/{path_suffix}"
|
|
61
|
+
normalized = normalize_base_url(base_url)
|
|
62
|
+
if normalized:
|
|
63
|
+
return f"{normalized}{path}"
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def openclaw_root(config_path: Path) -> Path:
|
|
68
|
+
return config_path.expanduser().resolve().parent
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def find_nearest_agent_config(start_dir: Path | None = None) -> Path | None:
|
|
72
|
+
current = (start_dir or Path.cwd()).resolve()
|
|
73
|
+
for candidate in [current, *current.parents]:
|
|
74
|
+
config_path = candidate / ".config.json"
|
|
75
|
+
if config_path.is_file():
|
|
76
|
+
return config_path
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def read_agent_definition(config_path: Path | None) -> dict[str, Any] | None:
|
|
81
|
+
if config_path is None or not config_path.is_file():
|
|
82
|
+
return None
|
|
83
|
+
data = load_json(config_path)
|
|
84
|
+
agent_id = data.get("agent_id")
|
|
85
|
+
if not isinstance(agent_id, str) or not agent_id.strip():
|
|
86
|
+
return None
|
|
87
|
+
return data
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def read_install_state_source_agent_id(config_path: Path, openclaw_id: str) -> str | None:
|
|
91
|
+
state_path = openclaw_root(config_path) / "agents" / openclaw_id / "install-state.json"
|
|
92
|
+
if not state_path.is_file():
|
|
93
|
+
return None
|
|
94
|
+
try:
|
|
95
|
+
data = load_json(state_path)
|
|
96
|
+
except (OSError, ValueError):
|
|
97
|
+
return None
|
|
98
|
+
source_agent_id = data.get("agent_id")
|
|
99
|
+
return source_agent_id if isinstance(source_agent_id, str) else None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def read_agent_dir_source_agent_id(agent_entry: dict[str, Any]) -> str | None:
|
|
103
|
+
agent_dir = agent_entry.get("agentDir")
|
|
104
|
+
if not isinstance(agent_dir, str) or not agent_dir.strip():
|
|
105
|
+
return None
|
|
106
|
+
config_path = Path(agent_dir).expanduser().resolve() / ".config.json"
|
|
107
|
+
if not config_path.is_file():
|
|
108
|
+
return None
|
|
109
|
+
try:
|
|
110
|
+
data = load_json(config_path)
|
|
111
|
+
except (OSError, ValueError):
|
|
112
|
+
return None
|
|
113
|
+
source_agent_id = data.get("agent_id")
|
|
114
|
+
return source_agent_id if isinstance(source_agent_id, str) else None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def entry_matches_source_agent(config_path: Path, agent_entry: dict[str, Any], source_agent_id: str) -> bool:
|
|
118
|
+
entry_id = agent_entry.get("id")
|
|
119
|
+
if entry_id == source_agent_id:
|
|
120
|
+
return True
|
|
121
|
+
if isinstance(entry_id, str) and read_install_state_source_agent_id(config_path, entry_id) == source_agent_id:
|
|
122
|
+
return True
|
|
123
|
+
return read_agent_dir_source_agent_id(agent_entry) == source_agent_id
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_path_within(path_value: Path, parent_value: Path) -> bool:
|
|
127
|
+
try:
|
|
128
|
+
path_value.relative_to(parent_value)
|
|
129
|
+
return True
|
|
130
|
+
except ValueError:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def find_agent_entry_by_workspace(config: dict[str, Any], cwd: Path) -> dict[str, Any] | None:
|
|
135
|
+
agents = config.get("agents", {}).get("list", [])
|
|
136
|
+
best_match: dict[str, Any] | None = None
|
|
137
|
+
best_len = -1
|
|
138
|
+
for agent_entry in agents:
|
|
139
|
+
if not isinstance(agent_entry, dict):
|
|
140
|
+
continue
|
|
141
|
+
workspace = agent_entry.get("workspace")
|
|
142
|
+
if not isinstance(workspace, str) or not workspace.strip():
|
|
143
|
+
continue
|
|
144
|
+
workspace_path = Path(workspace).expanduser().resolve()
|
|
145
|
+
if not is_path_within(cwd, workspace_path):
|
|
146
|
+
continue
|
|
147
|
+
workspace_len = len(str(workspace_path))
|
|
148
|
+
if workspace_len > best_len:
|
|
149
|
+
best_match = agent_entry
|
|
150
|
+
best_len = workspace_len
|
|
151
|
+
return best_match
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def resolve_current_agent(config_path: Path, cwd: Path | None = None) -> dict[str, Any]:
|
|
155
|
+
current_dir = (cwd or Path.cwd()).resolve()
|
|
156
|
+
local_config_path = find_nearest_agent_config(current_dir)
|
|
157
|
+
agent_def = read_agent_definition(local_config_path)
|
|
158
|
+
source_agent_id = agent_def.get("agent_id") if agent_def else None
|
|
159
|
+
config = load_json(config_path)
|
|
160
|
+
agents = config.get("agents", {}).get("list", [])
|
|
161
|
+
|
|
162
|
+
entry: dict[str, Any] | None = None
|
|
163
|
+
if isinstance(source_agent_id, str) and source_agent_id:
|
|
164
|
+
for agent_entry in agents:
|
|
165
|
+
if isinstance(agent_entry, dict) and entry_matches_source_agent(config_path, agent_entry, source_agent_id):
|
|
166
|
+
entry = agent_entry
|
|
167
|
+
break
|
|
168
|
+
if entry is None:
|
|
169
|
+
entry = find_agent_entry_by_workspace(config, current_dir)
|
|
170
|
+
|
|
171
|
+
if entry is None:
|
|
172
|
+
raise ValueError("无法识别当前 Agent,请在已安装 Agent 的 workspace 中执行。")
|
|
173
|
+
|
|
174
|
+
openclaw_id = entry.get("id")
|
|
175
|
+
if not isinstance(openclaw_id, str) or not openclaw_id.strip():
|
|
176
|
+
raise ValueError("当前 Agent 在 openclaw.json 中缺少 id。")
|
|
177
|
+
|
|
178
|
+
workspace = entry.get("workspace")
|
|
179
|
+
if not isinstance(workspace, str) or not workspace.strip():
|
|
180
|
+
raise ValueError("当前 Agent 在 openclaw.json 中缺少 workspace。")
|
|
181
|
+
|
|
182
|
+
account_ids = find_account_ids_by_agent_id(config, openclaw_id)
|
|
183
|
+
display_name = (
|
|
184
|
+
entry.get("name")
|
|
185
|
+
or entry.get("identity", {}).get("name")
|
|
186
|
+
or (agent_def or {}).get("description")
|
|
187
|
+
or openclaw_id
|
|
188
|
+
)
|
|
189
|
+
if not isinstance(display_name, str) or not display_name.strip():
|
|
190
|
+
display_name = openclaw_id
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"config": config,
|
|
194
|
+
"config_path": config_path,
|
|
195
|
+
"local_config_path": local_config_path,
|
|
196
|
+
"agent_definition": agent_def,
|
|
197
|
+
"source_agent_id": source_agent_id or openclaw_id,
|
|
198
|
+
"openclaw_id": openclaw_id,
|
|
199
|
+
"workspace": workspace,
|
|
200
|
+
"display_name": display_name.strip(),
|
|
201
|
+
"account_ids": account_ids,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def find_account_ids_by_agent_id(config: dict[str, Any], agent_id: str) -> list[str]:
|
|
206
|
+
accounts = config.get("channels", {}).get("bot-api", {}).get("accounts", {})
|
|
207
|
+
result: list[str] = []
|
|
208
|
+
for account_id, account in accounts.items():
|
|
209
|
+
if isinstance(account, dict) and account.get("agentId") == agent_id:
|
|
210
|
+
result.append(account_id)
|
|
211
|
+
return sorted(result)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_primary_account(config: dict[str, Any], account_ids: list[str]) -> tuple[str | None, dict[str, Any] | None]:
|
|
215
|
+
accounts = config.get("channels", {}).get("bot-api", {}).get("accounts", {})
|
|
216
|
+
for account_id in account_ids:
|
|
217
|
+
account = accounts.get(account_id)
|
|
218
|
+
if isinstance(account, dict):
|
|
219
|
+
return account_id, account
|
|
220
|
+
return None, None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_plugin_enabled(config: dict[str, Any]) -> bool:
|
|
224
|
+
allow = config.get("plugins", {}).get("allow", [])
|
|
225
|
+
entries = config.get("plugins", {}).get("entries", {})
|
|
226
|
+
allow_enabled = isinstance(allow, list) and "bot-api" in allow
|
|
227
|
+
entry_enabled = isinstance(entries, dict) and isinstance(entries.get("bot-api"), dict) and entries["bot-api"].get("enabled") is True
|
|
228
|
+
return allow_enabled and entry_enabled
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def set_local_bot_api_enabled(local_config_path: Path | None, enabled: bool) -> bool:
|
|
232
|
+
if local_config_path is None or not local_config_path.is_file():
|
|
233
|
+
return False
|
|
234
|
+
data = load_json(local_config_path)
|
|
235
|
+
data["bot_api_enabled"] = enabled
|
|
236
|
+
dump_json(local_config_path, data)
|
|
237
|
+
return True
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def build_status(context: dict[str, Any]) -> dict[str, Any]:
|
|
241
|
+
config = context["config"]
|
|
242
|
+
account_id, account = get_primary_account(config, context["account_ids"])
|
|
243
|
+
local_agent_def = context.get("agent_definition") or {}
|
|
244
|
+
base_url = context.get("base_url")
|
|
245
|
+
account_enabled = bool(account.get("enabled")) if isinstance(account, dict) else False
|
|
246
|
+
plugin_enabled = get_plugin_enabled(config)
|
|
247
|
+
bot_api_url = build_bot_api_url(account_id, "chat", base_url)
|
|
248
|
+
bot_api_stream_url = build_bot_api_url(account_id, "chat-stream", base_url)
|
|
249
|
+
return {
|
|
250
|
+
"source_agent_id": context["source_agent_id"],
|
|
251
|
+
"openclaw_id": context["openclaw_id"],
|
|
252
|
+
"workspace": context["workspace"],
|
|
253
|
+
"display_name": context["display_name"],
|
|
254
|
+
"config_bot_api_enabled": bool(local_agent_def.get("bot_api_enabled")),
|
|
255
|
+
"plugin_enabled": plugin_enabled,
|
|
256
|
+
"account_exists": bool(account),
|
|
257
|
+
"account_id": account_id,
|
|
258
|
+
"bot_api_url": bot_api_url,
|
|
259
|
+
"bot_api_stream_url": bot_api_stream_url,
|
|
260
|
+
"enabled": bool(account) and account_enabled and plugin_enabled,
|
|
261
|
+
"account_enabled": account_enabled,
|
|
262
|
+
"api_secret": account.get("apiSecret") if isinstance(account, dict) else None,
|
|
263
|
+
"security_notice": "🔒 请勿在公开群聊、工单或截图中泄漏 api_secret。",
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def ensure_bot_api(context: dict[str, Any]) -> dict[str, Any]:
|
|
268
|
+
config = context["config"]
|
|
269
|
+
config.setdefault("plugins", {})
|
|
270
|
+
allow = config["plugins"].setdefault("allow", [])
|
|
271
|
+
if "bot-api" not in allow:
|
|
272
|
+
allow.append("bot-api")
|
|
273
|
+
entries = config["plugins"].setdefault("entries", {})
|
|
274
|
+
entries.setdefault("bot-api", {})["enabled"] = True
|
|
275
|
+
|
|
276
|
+
channels = config.setdefault("channels", {})
|
|
277
|
+
bot_api = channels.setdefault("bot-api", {})
|
|
278
|
+
accounts = bot_api.setdefault("accounts", {})
|
|
279
|
+
|
|
280
|
+
account_id, existing_account = get_primary_account(config, context["account_ids"])
|
|
281
|
+
if account_id is None:
|
|
282
|
+
account_id = context["source_agent_id"]
|
|
283
|
+
|
|
284
|
+
api_secret = None
|
|
285
|
+
if isinstance(existing_account, dict):
|
|
286
|
+
api_secret = existing_account.get("apiSecret")
|
|
287
|
+
if not isinstance(api_secret, str) or not api_secret:
|
|
288
|
+
api_secret = secrets.token_hex(32)
|
|
289
|
+
|
|
290
|
+
accounts[account_id] = {
|
|
291
|
+
"agentId": context["openclaw_id"],
|
|
292
|
+
"name": context["display_name"],
|
|
293
|
+
"apiSecret": api_secret,
|
|
294
|
+
"enabled": True,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
dump_json(context["config_path"], config)
|
|
298
|
+
set_local_bot_api_enabled(context["local_config_path"], True)
|
|
299
|
+
if isinstance(context.get("agent_definition"), dict):
|
|
300
|
+
context["agent_definition"]["bot_api_enabled"] = True
|
|
301
|
+
context["account_ids"] = find_account_ids_by_agent_id(config, context["openclaw_id"])
|
|
302
|
+
return {
|
|
303
|
+
"action": "created" if existing_account is None else "updated",
|
|
304
|
+
**build_status(context),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def delete_bot_api(context: dict[str, Any]) -> dict[str, Any]:
|
|
309
|
+
config = context["config"]
|
|
310
|
+
accounts = config.get("channels", {}).get("bot-api", {}).get("accounts", {})
|
|
311
|
+
removed_account_ids: list[str] = []
|
|
312
|
+
for account_id in list(context["account_ids"]):
|
|
313
|
+
if account_id in accounts:
|
|
314
|
+
removed_account_ids.append(account_id)
|
|
315
|
+
del accounts[account_id]
|
|
316
|
+
|
|
317
|
+
remaining_accounts = config.get("channels", {}).get("bot-api", {}).get("accounts", {})
|
|
318
|
+
if not remaining_accounts:
|
|
319
|
+
config.setdefault("plugins", {})
|
|
320
|
+
allow = config.get("plugins", {}).get("allow", [])
|
|
321
|
+
if isinstance(allow, list):
|
|
322
|
+
config["plugins"]["allow"] = [item for item in allow if item != "bot-api"]
|
|
323
|
+
bot_api_entry = config.get("plugins", {}).get("entries", {}).get("bot-api")
|
|
324
|
+
if isinstance(bot_api_entry, dict):
|
|
325
|
+
bot_api_entry["enabled"] = False
|
|
326
|
+
|
|
327
|
+
dump_json(context["config_path"], config)
|
|
328
|
+
set_local_bot_api_enabled(context["local_config_path"], False)
|
|
329
|
+
if isinstance(context.get("agent_definition"), dict):
|
|
330
|
+
context["agent_definition"]["bot_api_enabled"] = False
|
|
331
|
+
context["account_ids"] = []
|
|
332
|
+
return {
|
|
333
|
+
"action": "deleted",
|
|
334
|
+
"removed_account_ids": removed_account_ids,
|
|
335
|
+
**build_status(context),
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def reset_bot_api_secret(context: dict[str, Any]) -> dict[str, Any]:
|
|
340
|
+
config = context["config"]
|
|
341
|
+
account_id, account = get_primary_account(config, context["account_ids"])
|
|
342
|
+
if account_id is None or not isinstance(account, dict):
|
|
343
|
+
raise ValueError("当前 Agent 尚未创建 bot-api,无法重置密钥。")
|
|
344
|
+
|
|
345
|
+
new_secret = secrets.token_hex(32)
|
|
346
|
+
account["apiSecret"] = new_secret
|
|
347
|
+
config["channels"]["bot-api"]["accounts"][account_id] = account
|
|
348
|
+
dump_json(context["config_path"], config)
|
|
349
|
+
return {
|
|
350
|
+
"action": "reset",
|
|
351
|
+
"new_secret": new_secret,
|
|
352
|
+
**build_status(context),
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _fmt_secret(value: object) -> str:
|
|
357
|
+
if isinstance(value, str) and value.strip():
|
|
358
|
+
return value.strip()
|
|
359
|
+
return "📭 暂无"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _fmt_url(value: object) -> str:
|
|
363
|
+
if isinstance(value, str) and value.strip():
|
|
364
|
+
return value.strip()
|
|
365
|
+
return "📭 暂无"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _print_link_block(url: str, stream_url: str, baseurl_note: str) -> None:
|
|
369
|
+
if url != "📭 暂无":
|
|
370
|
+
print(f"🔗 非流式链接:{url}")
|
|
371
|
+
if stream_url != "📭 暂无":
|
|
372
|
+
print(f"🔗 流式链接:{stream_url}")
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def print_human_response(command: str, payload: dict[str, Any]) -> int:
|
|
376
|
+
"""非 --json 时输出,风格与 SKILL 模板一致。"""
|
|
377
|
+
action = payload.get("action")
|
|
378
|
+
enabled = payload.get("enabled")
|
|
379
|
+
url = _fmt_url(payload.get("bot_api_url"))
|
|
380
|
+
stream_url = _fmt_url(payload.get("bot_api_stream_url"))
|
|
381
|
+
secret = _fmt_secret(payload.get("api_secret") or payload.get("new_secret"))
|
|
382
|
+
|
|
383
|
+
if command in {"get", "status"}:
|
|
384
|
+
state = "🟢 开启" if enabled else "⚪ 关闭"
|
|
385
|
+
print(f"📋 🤖 bot-api 状态:{state}")
|
|
386
|
+
print()
|
|
387
|
+
_print_link_block(url, stream_url, "")
|
|
388
|
+
print(f"🔑 密钥:{secret}")
|
|
389
|
+
return 0
|
|
390
|
+
|
|
391
|
+
if command == "create":
|
|
392
|
+
title = "已更新" if action == "updated" else "已创建"
|
|
393
|
+
prefix = "📝" if action == "updated" else "✨"
|
|
394
|
+
print(f"{prefix} 🤖 bot-api {title}")
|
|
395
|
+
print()
|
|
396
|
+
_print_link_block(url, stream_url, "")
|
|
397
|
+
print(f"🔑 密钥:{secret}")
|
|
398
|
+
print()
|
|
399
|
+
print("🔒 请妥善保管,勿对外转发或截图。")
|
|
400
|
+
return 0
|
|
401
|
+
|
|
402
|
+
if command == "delete":
|
|
403
|
+
print("🛑 🤖 bot-api 已关闭")
|
|
404
|
+
print()
|
|
405
|
+
print("📴 当前 Agent 不再通过 bot-api 对外提供服务。")
|
|
406
|
+
return 0
|
|
407
|
+
|
|
408
|
+
if command == "reset":
|
|
409
|
+
if not isinstance(payload.get("new_secret"), str) or not payload["new_secret"].strip():
|
|
410
|
+
print("⚠️ 当前 Agent 尚未创建 bot-api,无法重置密钥。", file=sys.stderr)
|
|
411
|
+
return 1
|
|
412
|
+
print("🔄 🤖 bot-api 密钥已更新")
|
|
413
|
+
print()
|
|
414
|
+
print(f"🔑 新密钥:{secret}")
|
|
415
|
+
print()
|
|
416
|
+
_print_link_block(url, stream_url, "")
|
|
417
|
+
print()
|
|
418
|
+
print("🔒 请妥善保管,勿对外转发或截图。")
|
|
419
|
+
return 0
|
|
420
|
+
|
|
421
|
+
print(f"❓ 未知命令:{command}", file=sys.stderr)
|
|
422
|
+
return 1
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
426
|
+
parser = argparse.ArgumentParser(description="管理当前 Agent 的 bot-api 与 API Secret")
|
|
427
|
+
parser.add_argument(
|
|
428
|
+
"--config",
|
|
429
|
+
default=str(DEFAULT_CONFIG_PATH),
|
|
430
|
+
help="openclaw.json 路径",
|
|
431
|
+
)
|
|
432
|
+
parser.add_argument(
|
|
433
|
+
"--json",
|
|
434
|
+
action="store_true",
|
|
435
|
+
help="输出 JSON",
|
|
436
|
+
)
|
|
437
|
+
parser.add_argument(
|
|
438
|
+
"command",
|
|
439
|
+
choices=["status", "create", "delete", "reset", "get"],
|
|
440
|
+
help="执行的动作:status/create/delete/reset,get 为 status 的兼容别名",
|
|
441
|
+
)
|
|
442
|
+
return parser
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def main(argv: list[str] | None = None) -> int:
|
|
446
|
+
parser = create_parser()
|
|
447
|
+
args = parser.parse_args(argv)
|
|
448
|
+
config_path = Path(args.config).expanduser().resolve()
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
context = resolve_current_agent(config_path)
|
|
452
|
+
if args.command in {"status", "get", "create", "reset"}:
|
|
453
|
+
context["base_url"] = require_base_url()
|
|
454
|
+
if args.command in {"status", "get"}:
|
|
455
|
+
payload = {"action": "status", **build_status(context)}
|
|
456
|
+
elif args.command == "create":
|
|
457
|
+
payload = ensure_bot_api(context)
|
|
458
|
+
elif args.command == "delete":
|
|
459
|
+
payload = delete_bot_api(context)
|
|
460
|
+
elif args.command == "reset":
|
|
461
|
+
payload = reset_bot_api_secret(context)
|
|
462
|
+
else:
|
|
463
|
+
raise ValueError(f"Unsupported command: {args.command}")
|
|
464
|
+
except Exception as exc: # pragma: no cover - CLI fallback
|
|
465
|
+
if args.json:
|
|
466
|
+
json.dump({"ok": False, "error": str(exc)}, sys.stdout, ensure_ascii=False, indent=2)
|
|
467
|
+
sys.stdout.write("\n")
|
|
468
|
+
else:
|
|
469
|
+
print(f"⚠️ {exc}", file=sys.stderr)
|
|
470
|
+
return 1
|
|
471
|
+
|
|
472
|
+
if args.json:
|
|
473
|
+
json.dump({"ok": True, **payload}, sys.stdout, ensure_ascii=False, indent=2)
|
|
474
|
+
sys.stdout.write("\n")
|
|
475
|
+
return 0
|
|
476
|
+
|
|
477
|
+
return print_human_response(args.command, payload)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if __name__ == "__main__":
|
|
481
|
+
raise SystemExit(main())
|