mirror of
https://github.com/This-weiyuqing/mi-fitness-health.git
synced 2026-10-17 18:55:37 +08:00
194 lines
6.5 KiB
Python
194 lines
6.5 KiB
Python
"""CLI for Mi Fitness MCP."""
|
||
|
||
import argparse
|
||
import asyncio
|
||
import sys
|
||
|
||
from mi_fitness_mcp.adapters.mi_fitness_cloud import MiFitnessCloudAdapter
|
||
from mi_fitness_mcp.auth import load_mi_fitness_token, save_mi_fitness_token
|
||
from mi_fitness_mcp.config import Config, get_config_path, load_config, save_config
|
||
from mi_fitness_mcp.server import main as server_main
|
||
from mi_fitness_mcp.services.sync_service import SyncService
|
||
from mi_fitness_mcp.storage import Database
|
||
|
||
PROGRAM_NAME = "mi-fitness-mcp"
|
||
|
||
|
||
async def _check_adapter_health(adapter) -> tuple[bool, list[str], str | None]:
|
||
connected = await adapter.connect()
|
||
region = getattr(adapter, "region", None)
|
||
data_types = adapter.get_available_data_types() if connected else []
|
||
if hasattr(adapter, "close"):
|
||
await adapter.close()
|
||
return connected, data_types, region
|
||
|
||
|
||
def cmd_setup(args):
|
||
if args.mode == "mi_fitness_cloud" and args.user_id and args.pass_token:
|
||
save_mi_fitness_token(args.user_id, args.pass_token)
|
||
config = Config(mode="mi_fitness_cloud", region=args.region or "cn")
|
||
save_config(config)
|
||
print("✅ 配置已保存(mi_fitness_cloud)")
|
||
print(f" User ID: {args.user_id}")
|
||
print(f" Region: {config.region}")
|
||
return
|
||
|
||
print(f"{PROGRAM_NAME} - 配置向导")
|
||
print("=" * 50)
|
||
print()
|
||
user_id = input("Mi Fitness user_id: ").strip()
|
||
pass_token = input("Mi Fitness passToken: ").strip()
|
||
region = input("区域 [cn]: ").strip() or "cn"
|
||
if not user_id or not pass_token:
|
||
print("❌ 必须提供 user_id 和 passToken")
|
||
sys.exit(1)
|
||
save_mi_fitness_token(user_id, pass_token)
|
||
config = Config(mode="mi_fitness_cloud", region=region)
|
||
save_config(config)
|
||
print("✅ Mi Fitness 配置已保存!")
|
||
|
||
|
||
def cmd_doctor(args):
|
||
print(f"{PROGRAM_NAME} - 诊断")
|
||
print("=" * 50)
|
||
print()
|
||
config_path = get_config_path()
|
||
print(f"配置文件: {config_path}")
|
||
|
||
if not config_path.exists():
|
||
print("❌ 未找到配置文件")
|
||
print(f" 请运行: {PROGRAM_NAME} setup")
|
||
sys.exit(1)
|
||
|
||
try:
|
||
config = load_config()
|
||
print("✅ 配置已加载")
|
||
print(f" 模式: {config.mode}")
|
||
user_id, pass_token = load_mi_fitness_token()
|
||
if user_id and pass_token:
|
||
print("✅ 已找到 Mi Fitness 凭据")
|
||
print(f" User ID: {user_id}")
|
||
print(f" Region: {config.region}")
|
||
adapter = MiFitnessCloudAdapter(
|
||
user_id=user_id, pass_token=pass_token, region=config.region
|
||
)
|
||
connected, data_types, region = asyncio.run(_check_adapter_health(adapter))
|
||
print(f" 连接状态: {'✅' if connected else '❌'}")
|
||
if connected:
|
||
print(f" 识别到的区域: {region}")
|
||
print(f" 数据类型: {', '.join(data_types)}")
|
||
else:
|
||
print("❌ 未找到 Mi Fitness 凭据")
|
||
print(f" 请运行: {PROGRAM_NAME} setup")
|
||
|
||
print()
|
||
print(f"数据库: {config.database_path}")
|
||
if config.database_path.exists():
|
||
Database(config.database_path)
|
||
print("✅ 数据库可用")
|
||
else:
|
||
print("ℹ️ 数据库将在首次运行时创建")
|
||
except Exception as e:
|
||
print(f"❌ 加载配置失败: {e}")
|
||
sys.exit(1)
|
||
|
||
|
||
async def cmd_sync_async(args):
|
||
print(f"{PROGRAM_NAME} - 同步")
|
||
print("=" * 50)
|
||
print()
|
||
|
||
try:
|
||
config = load_config()
|
||
except Exception as e:
|
||
print(f"❌ 加载配置失败: {e}")
|
||
sys.exit(1)
|
||
|
||
if config.mode == "not_configured":
|
||
print("❌ 服务尚未配置")
|
||
print(f" 请运行: {PROGRAM_NAME} setup")
|
||
sys.exit(1)
|
||
|
||
user_id, pass_token = load_mi_fitness_token()
|
||
if not user_id or not pass_token:
|
||
print("❌ 未找到 Mi Fitness 凭据")
|
||
print(f" 请运行: {PROGRAM_NAME} setup")
|
||
sys.exit(1)
|
||
|
||
db = Database(config.database_path)
|
||
adapter = MiFitnessCloudAdapter(user_id=user_id, pass_token=pass_token, region=config.region)
|
||
if not await adapter.connect():
|
||
print("❌ 无法连接到 Mi Fitness API")
|
||
sys.exit(1)
|
||
|
||
sync_service = SyncService(adapter, db)
|
||
data_types = [args.type] if args.type else adapter.get_available_data_types()
|
||
print(f"正在同步 {len(data_types)} 种数据类型...")
|
||
print()
|
||
for data_type in data_types:
|
||
try:
|
||
result = await sync_service.sync_data_type(
|
||
data_type=data_type,
|
||
start_date=args.start_date,
|
||
end_date=args.end_date,
|
||
)
|
||
print(f"✅ {data_type}: 新增 {result['added']} 条,更新 {result['updated']} 条")
|
||
except Exception as e:
|
||
print(f"❌ {data_type}: {e}")
|
||
|
||
print()
|
||
print("同步完成!")
|
||
|
||
|
||
def cmd_sync(args):
|
||
asyncio.run(cmd_sync_async(args))
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(prog=PROGRAM_NAME, description="小米运动健康数据 MCP Server")
|
||
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
||
subparsers.add_parser("serve", help="运行 MCP Server")
|
||
|
||
setup_parser = subparsers.add_parser("setup", help="配置服务")
|
||
setup_parser.add_argument("--mode", choices=["mi_fitness_cloud"], help="配置模式")
|
||
setup_parser.add_argument("--user-id", help="Mi Fitness user ID")
|
||
setup_parser.add_argument("--pass-token", help="Mi Fitness passToken")
|
||
setup_parser.add_argument("--region", help="云端区域")
|
||
|
||
subparsers.add_parser("doctor", help="检查配置并诊断问题")
|
||
|
||
sync_parser = subparsers.add_parser("sync", help="从数据源同步数据")
|
||
sync_parser.add_argument(
|
||
"--type",
|
||
choices=[
|
||
"daily_activity",
|
||
"body_measurements",
|
||
"heart_rate",
|
||
"sleep",
|
||
"workouts",
|
||
"spo2",
|
||
"stress",
|
||
"abnormal_heart_beat",
|
||
],
|
||
help="要同步的数据类型",
|
||
)
|
||
sync_parser.add_argument("--start-date", help="开始日期(YYYY-MM-DD)")
|
||
sync_parser.add_argument("--end-date", help="结束日期(YYYY-MM-DD)")
|
||
|
||
args = parser.parse_args()
|
||
if args.command == "serve" or args.command is None:
|
||
asyncio.run(server_main())
|
||
elif args.command == "setup":
|
||
cmd_setup(args)
|
||
elif args.command == "doctor":
|
||
cmd_doctor(args)
|
||
elif args.command == "sync":
|
||
cmd_sync(args)
|
||
else:
|
||
parser.print_help()
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|