feat: publish workphone SDK deployment and API docs

This commit is contained in:
Manus AI
2026-07-14 18:10:52 +08:00
commit 021d633cc1
534 changed files with 122391 additions and 0 deletions

150
sdk/scripts/build_agent_config.py Executable file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""生成 Termux Agent config.json — 连接顺序 ① NAS 局域网 ② 本机 Docker ③ NAS 外网 frp。
与 agent.py BIND-03 / workphone.mdc §零.十 一致。
用法:
python3 build_agent_config.py --device-id xgfe65eimrrofyws --out /tmp/config.json
python3 build_agent_config.py --print-primary # 只输出当前可达主服 ws 基址
"""
from __future__ import annotations
import argparse
import json
import socket
import sys
import urllib.request
from typing import List, Optional, Tuple
SDK_PORT = 8899
NAS_LAN_HOSTS = ("192.168.110.101", "192.168.1.201")
NAS_FRP_HOST = "open.quwanzhi.com"
HEARTBEAT = 10
PROJECT_ID = "cunkebao"
def _tcp_ok(host: str, port: int, timeout: float = 2.5) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
def _health_ok(host: str, port: int = SDK_PORT, timeout: float = 3.0) -> bool:
try:
url = f"http://{host}:{port}/health"
with urllib.request.urlopen(url, timeout=timeout) as resp:
return resp.status == 200
except Exception:
return False
def pick_mac_lan_ip() -> str:
import subprocess
try:
out = subprocess.check_output(["ifconfig"], text=True, stderr=subprocess.DEVNULL)
except Exception:
return "127.0.0.1"
ips: List[str] = []
for line in out.splitlines():
parts = line.strip().split()
if len(parts) >= 2 and parts[0] == "inet":
ip = parts[1]
if ip.startswith("127.") or ip.startswith("198.18."):
continue
ips.append(ip)
for ip in ips:
if ip.startswith("192.168.110."):
return ip
for ip in ips:
if ip.startswith("192.168.") or ip.startswith("10."):
return ip
return ips[0] if ips else "127.0.0.1"
def resolve_primary(for_phone: bool = False) -> Tuple[str, str, List[str]]:
"""返回 (primary_ws_base, tier, ordered_ws_bases)。
手机/本机统一顺序BIND-03 · workphone.mdc §零.十):
① NAS 局域网 → ② 本机 Mac Docker 局域网 → ③ NAS 外网 frp
Agent 运行时 TCP 探测,连不上自动切下一候选。
"""
mac_ip = pick_mac_lan_ip()
nas_bases = [(f"ws://{h}:{SDK_PORT}/ws/device", "nas_lan") for h in NAS_LAN_HOSTS]
frp_base = (f"ws://{NAS_FRP_HOST}:{SDK_PORT}/ws/device", "nas_frp")
mac_base = (f"ws://{mac_ip}:{SDK_PORT}/ws/device", "mac_docker") if mac_ip != "127.0.0.1" else None
ordered: List[Tuple[str, str]] = list(nas_bases)
if mac_base:
ordered.append(mac_base)
ordered.append(frp_base)
if for_phone:
# 手机 OTA固定 NAS 为主连接写入 configpublic_servers 按序探测(局域网优先于公网)
primary_ws, tier = nas_bases[0][0], "nas_lan"
bases = [b for b, _ in ordered]
seen = set()
ordered_bases: List[str] = []
for b in bases:
if b not in seen:
seen.add(b)
ordered_bases.append(b)
return primary_ws, tier, ordered_bases
primary_ws, tier = ordered[0][0], ordered[0][1]
for ws_base, t in ordered:
host = ws_base.split("//")[1].split(":")[0]
if _health_ok(host, SDK_PORT):
primary_ws, tier = ws_base, t
break
bases: List[str] = []
seen = set()
for ws_base, _ in ordered:
if ws_base not in seen:
seen.add(ws_base)
bases.append(ws_base)
bases = [primary_ws] + [b for b in bases if b != primary_ws]
return primary_ws, tier, bases
def build_config(device_id: str, for_phone: bool = False) -> dict:
primary_ws, tier, bases = resolve_primary(for_phone=for_phone)
return {
"device_id": device_id,
"server_url": primary_ws,
"public_servers": [b for b in bases if b != primary_ws],
"heartbeat_interval": HEARTBEAT,
"project_id": PROJECT_ID,
"connection_priority": ["nas_lan", "mac_docker", "nas_frp"],
"_meta": {"primary_tier": tier, "generator": "build_agent_config.py"},
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--device-id", default="xgfe65eimrrofyws")
ap.add_argument("--out", default="")
ap.add_argument("--print-primary", action="store_true")
ap.add_argument("--for-phone", action="store_true", help="手机 OTANAS→frp→Mac")
args = ap.parse_args()
primary_ws, tier, _ = resolve_primary(for_phone=args.for_phone)
if args.print_primary:
print(primary_ws)
print(tier, file=sys.stderr)
return 0
cfg = build_config(args.device_id, for_phone=args.for_phone)
text = json.dumps(cfg, ensure_ascii=False, indent=2)
if args.out:
with open(args.out, "w", encoding="utf-8") as f:
f.write(text)
else:
print(text)
return 0
if __name__ == "__main__":
raise SystemExit(main())