64 lines
3.8 KiB
Python
64 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
sys.path.insert(0, str(ROOT))
|
||
from autotrain_next import device, ledger, profiles, recorder, runner, sampling, web
|
||
from autotrain_next.paths import CONFIG, WORK
|
||
from autotrain_next.platform import scrcpy_binary
|
||
|
||
|
||
def config() -> dict:
|
||
return json.loads(CONFIG.read_text(encoding="utf-8")) if CONFIG.exists() else {"planner": {"enabled": False}, "safety": {"max_loop_steps": 30}}
|
||
|
||
|
||
def print_json(data: dict) -> None:
|
||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(description="Autotrain Next 原子工具编排入口")
|
||
subs = parser.add_subparsers(dest="command", required=True)
|
||
subs.add_parser("device-status")
|
||
roster = subs.add_parser("roster-import"); roster.add_argument("source", type=Path)
|
||
sample = subs.add_parser("sample-import"); sample.add_argument("recording", type=Path); sample.add_argument("--label", default=""); sample.add_argument("--serial", default="")
|
||
verify = subs.add_parser("profile-verify"); verify.add_argument("--serial", default="")
|
||
record = subs.add_parser("sample-record"); record.add_argument("--output", default=""); record.add_argument("--serial", default="")
|
||
task = subs.add_parser("task-create"); task.add_argument("--serial", default=""); task.add_argument("--roster", default=""); task.add_argument("--qr-image", default=""); task.add_argument("--success-action", choices=("download", "email"), default="download"); task.add_argument("--allow-submit", action="store_true")
|
||
run_task = subs.add_parser("task-run"); run_task.add_argument("task_id")
|
||
ui = subs.add_parser("web"); ui.add_argument("--port", type=int, default=8766)
|
||
args = parser.parse_args()
|
||
if args.command == "device-status":
|
||
target = device.selected_device(); current = device.fingerprint(target.serial); print_json({"device": current, "profile": profiles.compatibility(profiles.load(target.serial), current)}); return 0
|
||
if args.command == "sample-record":
|
||
# scrcpy 仅供人工控制;录制器明确绑定目标 serial,不记录输入内容到设备档案。
|
||
target = device.selected_device(args.serial); output = args.output or str(WORK / "replay" / time.strftime("%Y%m%d-%H%M%S"))
|
||
subprocess.Popen([scrcpy_binary(), "-s", target.serial, "--window-title", "Autotrain Next 手机屏幕控制"])
|
||
recorder.record(Path(output), target.serial)
|
||
return 0
|
||
if args.command == "roster-import": print_json({"roster": str(ledger.import_roster(args.source))}); return 0
|
||
if args.command == "sample-import":
|
||
target = device.selected_device(args.serial); print_json(sampling.import_recording(args.recording, device.fingerprint(target.serial), args.label)); return 0
|
||
if args.command == "profile-verify":
|
||
target = device.selected_device(args.serial); profile = profiles.load(target.serial)
|
||
if not profile: raise RuntimeError("该手机尚未导入采样")
|
||
samples = profile.get("sampling", {}).get("samples", {})
|
||
missing = sorted(profiles.REQUIRED_SAMPLE_PAGES - set(samples))
|
||
if missing: raise RuntimeError(f"采样不完整,缺少页面:{', '.join(missing)}")
|
||
profile["status"] = "verified"; profiles.save(profile); print_json(profile); return 0
|
||
if args.command == "task-create": print_json(runner.create(args.serial, allow_submit=args.allow_submit, roster_path=args.roster, success_action=args.success_action, qr_image=args.qr_image)); return 0
|
||
if args.command == "task-run": print_json(runner.run(runner.load(args.task_id), config())); return 0
|
||
if args.command == "web": web.serve(args.port); return 0
|
||
return 2
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|