@buaa_smat/hometrans 0.1.14 → 0.1.16

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.
Files changed (41) hide show
  1. package/README.md +110 -139
  2. package/agents/build-fixer.md +385 -384
  3. package/agents/code-reviewer.md +240 -240
  4. package/agents/logic-coder.md +199 -199
  5. package/agents/logic-context-builder.md +194 -194
  6. package/agents/review-fixer.md +405 -405
  7. package/agents/self-test-fixer.md +296 -296
  8. package/agents/self-tester.md +396 -393
  9. package/agents/spec-generator.md +540 -540
  10. package/dist/cli/config-store.js +150 -17
  11. package/dist/cli/config.js +10 -3
  12. package/dist/cli/init.js +391 -233
  13. package/dist/cli/uninstall.js +12 -7
  14. package/dist/context/index.js +19 -2
  15. package/env-requirements.json +177 -181
  16. package/package.json +1 -1
  17. package/resource/choose_editor.png +0 -0
  18. package/resource/migration_process.svg +94 -0
  19. package/resource/migration_process_transparent.svg +93 -0
  20. package/resource/set_environment.png +0 -0
  21. package/resource/set_multimodel.png +0 -0
  22. package/resource/set_sdk.png +0 -0
  23. package/skills/hmos-batch-ui-align/SKILL.md +108 -108
  24. package/skills/hmos-convert-pipeline/SKILL.md +429 -429
  25. package/skills/hmos-fix-build-errors/SKILL.md +273 -272
  26. package/skills/hmos-incremental-ui-align/{readme.md → README.md} +234 -237
  27. package/skills/hmos-incremental-ui-align/SKILL.md +218 -218
  28. package/skills/hmos-incremental-ui-align/scripts/__pycache__/app_feature_verify.cpython-314.pyc +0 -0
  29. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.py +128 -29
  30. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
  31. package/skills/hmos-incremental-ui-align/scripts/page_capture.py +7 -2
  32. package/skills/hmos-integration-test/{readme.md → README.md} +309 -309
  33. package/skills/hmos-integration-test/SKILL.md +380 -380
  34. package/skills/hmos-resources-convert/SKILL.md +623 -623
  35. package/skills/hmos-spec-generate/SKILL.md +324 -331
  36. package/tools/test-tools/autotest/README.md +10 -11
  37. package/tools/test-tools/autotest/self_test_runner.py +40 -12
  38. package/resource/common_config.png +0 -0
  39. package/resource/integration_test_config.png +0 -0
  40. package/resource/set_env.png +0 -0
  41. package/resource/ui_align_config.png +0 -0
@@ -1,22 +1,32 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # "openpyxl",
7
+ # "phone-agent",
8
+ # ]
9
+ # [[tool.uv.index]]
10
+ # url = "https://pypi.tuna.tsinghua.edu.cn/simple"
11
+ # ///
3
12
  """
4
13
  应用页面导航工具 —— 使用 PhoneAgent 按指定路径操作设备,导航到目标页面。
5
14
 
6
15
  支持 Android (ADB) 和 HarmonyOS (HDC) 两种设备类型。
7
16
 
8
- 依赖: pip install openpyxl phone-agent
17
+ 依赖: openpyxlphone-agent —— 已在文件顶部 PEP 723 元数据声明,`uv run` 自动安装(无需手动 pip install)
9
18
  运行前需确保: 设备已连接 (HDC/ADB)、模型 API 可用
10
19
 
11
20
  用法:
12
21
  # 自由 prompt 导航
13
- python app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
22
+ uv run app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
14
23
 
15
24
  # 按功能路径导航
16
- python app_feature_verify.py --app 简单图库 --package com.simplemobiletools.gallery.pro --task "L1相册详情L2更多L3筛选媒体文件"
25
+ uv run app_feature_verify.py --app 简单图库 --package com.simplemobiletools.gallery.pro --task "L1相册详情L2更多L3筛选媒体文件"
17
26
  """
18
27
 
19
28
  import argparse
29
+ import json
20
30
  import re
21
31
  import sys
22
32
  import os
@@ -267,25 +277,62 @@ def _apply_phone_agent_patches():
267
277
  _swipe._patched = True
268
278
  _hdc.swipe = _swipe
269
279
 
270
- # Patch 2: parse_actionregex fallback when do() contains unescaped quotes
280
+ # Patch 2: _parse_responsestrip <think>/<answer> tags before splitting by do()/finish()
281
+ import phone_agent.model.client as _client
282
+ if not getattr(_client.ModelClient._parse_response, '_patched', False):
283
+ _orig_parse_response = _client.ModelClient._parse_response
284
+
285
+ def _patched_parse_response(self, content: str):
286
+ # Strip <think>...</think> and <answer>...</answer> wrappers first
287
+ cleaned = re.sub(r'<think>.*?</think>\s*', '', content, flags=re.DOTALL)
288
+ m = re.search(r'<answer>\s*(.*?)\s*</answer>', cleaned, re.DOTALL)
289
+ if m:
290
+ cleaned = m.group(1)
291
+ # Also strip trailing </answer> if only opening was consumed by rule 2
292
+ cleaned = cleaned.replace('</answer>', '').strip()
293
+ if cleaned:
294
+ return _orig_parse_response(self, cleaned)
295
+ return _orig_parse_response(self, content)
296
+
297
+ _patched_parse_response._patched = True
298
+ _client.ModelClient._parse_response = _patched_parse_response
299
+
300
+ # Patch 3: parse_action — strip thinking tags + regex fallback for malformed do()
271
301
  import phone_agent.actions.handler as _handler
272
302
  if not getattr(_handler.parse_action, '_patched', False):
273
303
  _orig_parse = _handler.parse_action
304
+ _THINK_RE = re.compile(r'<think>.*?</think>\s*', re.DOTALL)
305
+ _ANSWER_RE = re.compile(r'<answer>\s*(.*?)\s*</answer>', re.DOTALL)
306
+
307
+ def _strip_llm_tags(text: str) -> str:
308
+ text = _THINK_RE.sub('', text)
309
+ m = _ANSWER_RE.search(text)
310
+ if m:
311
+ text = m.group(1)
312
+ return text.strip()
313
+
274
314
  def _parse_action(response):
315
+ cleaned = _strip_llm_tags(response)
275
316
  try:
276
- return _orig_parse(response)
277
- except ValueError as e:
278
- if 'Failed to parse do() action' in str(e):
279
- m = re.search(
280
- r'do\s*\(\s*action\s*=\s*"([^"]*)"\s*,\s*message\s*="(.*)"\s*\)\s*$',
281
- response.strip(), re.DOTALL,
282
- )
283
- if m:
284
- msg = m.group(2)
285
- msg = msg.replace("\\n", "\x0a").replace("\\t", "\x09").replace("\\r", "\x0d")
286
- msg = msg.replace('\\"', '"')
287
- return {"_metadata": "do", "action": m.group(1), "message": msg}
288
- raise
317
+ return _orig_parse(cleaned)
318
+ except ValueError:
319
+ pass
320
+ # regex fallback for do() with unescaped quotes
321
+ for text in (cleaned, response.strip()):
322
+ m = re.search(
323
+ r'do\s*\(\s*action\s*=\s*"([^"]*)"\s*,\s*message\s*="(.*)"\s*\)\s*$',
324
+ text, re.DOTALL,
325
+ )
326
+ if m:
327
+ msg = m.group(2)
328
+ msg = msg.replace("\\n", "\x0a").replace("\\t", "\x09").replace("\\r", "\x0d")
329
+ msg = msg.replace('\\"', '"')
330
+ return {"_metadata": "do", "action": m.group(1), "message": msg}
331
+ # fallback: extract bare do() from anywhere in the text
332
+ m = re.search(r'do\s*\(\s*action\s*=\s*"([^"]*)".*?\)', cleaned)
333
+ if m:
334
+ return _orig_parse(m.group(0))
335
+ return _orig_parse(response)
289
336
  _parse_action._patched = True
290
337
  _handler.parse_action = _parse_action
291
338
 
@@ -374,6 +421,63 @@ def navigate(
374
421
  agent.reset()
375
422
 
376
423
 
424
+ # ---------------------------------------------------------------------------
425
+ # 模型配置加载:UI 对齐与自测共用同一个多模态模型。
426
+ # 优先级:CLI --api-key > HOMETRANS_MODEL_* 环境变量 > ~/.hometrans/config.json
427
+ # 的 autotest.unified_model(由 `ht init` 写入,两端共用)> 老版本 GLM_API_KEY(兼容)。
428
+ # ---------------------------------------------------------------------------
429
+
430
+ def _load_model_config(cli_api_key: str = ""):
431
+ """加载统一多模态模型配置(UI 对齐 + 自测共用)。
432
+
433
+ `ht init` 把模型四要素(api_key / name / base_url / provider)写入
434
+ `autotest.unified_model`,并导出为 HOMETRANS_MODEL_* 环境变量。这里优先读
435
+ 环境变量,缺字段时回落到 config.json,单个字段可被 CLI/环境变量覆盖;
436
+ 都没有时再兼容老版本的 GLM_API_KEY(走智谱 GLM 端点)。
437
+ """
438
+ from phone_agent.model import ModelConfig
439
+
440
+ # 1) 环境变量(ht init 导出)+ CLI 覆盖
441
+ api_key = (cli_api_key or os.environ.get("HOMETRANS_MODEL_API_KEY", "")).strip()
442
+ base_url = os.environ.get("HOMETRANS_MODEL_BASE_URL", "").strip()
443
+ model_name = os.environ.get("HOMETRANS_MODEL_NAME", "").strip()
444
+ temperature = 0.1
445
+
446
+ # 2) 回落 ~/.hometrans/config.json 的 autotest.unified_model(补齐缺失字段)
447
+ config_path = Path.home() / ".hometrans" / "config.json"
448
+ if config_path.exists():
449
+ try:
450
+ cfg = json.loads(config_path.read_text(encoding="utf-8"))
451
+ um = cfg.get("autotest", {}).get("unified_model", {})
452
+ api_key = api_key or str(um.get("api_key", "")).strip()
453
+ base_url = base_url or str(um.get("base_url", "")).strip()
454
+ model_name = model_name or str(um.get("name", "")).strip()
455
+ if um.get("temperature") is not None:
456
+ temperature = um.get("temperature")
457
+ except (json.JSONDecodeError, KeyError):
458
+ pass
459
+
460
+ if api_key:
461
+ return ModelConfig(
462
+ base_url=base_url or "http://localhost:8000/v1",
463
+ api_key=api_key,
464
+ model_name=model_name,
465
+ temperature=temperature,
466
+ )
467
+
468
+ # 3) 向后兼容老版本:仅设置了 GLM_API_KEY 时,回落到智谱 GLM 端点。
469
+ glm_key = os.environ.get("GLM_API_KEY", "").strip()
470
+ if glm_key:
471
+ return ModelConfig(
472
+ base_url="https://open.bigmodel.cn/api/paas/v4",
473
+ api_key=glm_key,
474
+ model_name="autoglm-phone",
475
+ temperature=0.1,
476
+ )
477
+
478
+ return ModelConfig(temperature=temperature)
479
+
480
+
377
481
  # ---------------------------------------------------------------------------
378
482
  # CLI 入口
379
483
  # ---------------------------------------------------------------------------
@@ -395,7 +499,7 @@ def main():
395
499
  parser.add_argument("--limit", type=int, default=None, help="仅导航前 N 条")
396
500
  parser.add_argument("--offset", type=int, default=1, help="从第几条开始(1-based)")
397
501
  parser.add_argument("--sheet", type=int, default=0, help="Excel 工作表索引")
398
- parser.add_argument("--api-key", type=str, default="", help="智谱 API Key")
502
+ parser.add_argument("--api-key", type=str, default="", help="API Key(可选,不传则读取 HOMETRANS_MODEL_* 环境变量 / ~/.hometrans/config.json)")
399
503
  parser.add_argument("--max-steps", type=int, default=30, help="Agent 最大步数")
400
504
  parser.add_argument("--app-hints", type=str, default=None, help="应用上下文提示")
401
505
  parser.add_argument("-q", "--quiet", action="store_true", help="减少输出")
@@ -404,19 +508,14 @@ def main():
404
508
  if not args.prompt and not args.task and not args.feature:
405
509
  parser.error("必须指定 --feature、--task 或 --prompt 之一")
406
510
 
407
- api_key = args.api_key or os.environ.get("GLM_API_KEY", "")
408
- if not api_key and not args.quiet:
409
- print("提示: 未设置 API Key,请通过 --api-key 或环境变量 GLM_API_KEY 配置")
410
- print("继续运行可能因鉴权失败而报错\n")
411
-
412
511
  from phone_agent.model import ModelConfig
413
512
 
414
- model_config = ModelConfig(
415
- base_url="https://open.bigmodel.cn/api/paas/v4",
416
- api_key=api_key,
417
- model_name="autoglm-phone",
418
- temperature=0.1,
419
- )
513
+ model_config = _load_model_config(args.api_key)
514
+ if not model_config.api_key or model_config.api_key == "EMPTY":
515
+ if not args.quiet:
516
+ print("提示: 未找到可用的模型配置。请运行 `ht init` 配置统一模型(UI 对齐 + 自测共用),")
517
+ print("它会写入 ~/.hometrans/config.json 的 autotest.unified_model 并导出 HOMETRANS_MODEL_* 环境变量;")
518
+ print("或通过 --api-key 传入 API Key。\n")
420
519
 
421
520
  device_label = "ADB (Android)" if args.device == "adb" else "HDC (HarmonyOS)"
422
521
  print(f"设备: {device_label}, 应用: {args.app} ({args.package})")
@@ -1,37 +1,37 @@
1
- Create a timestamped output directory under `capture_output_dir`:
2
- ```
3
- task_dir = capture_output_dir / "task_{timestamp}"
4
- android_capture_dir = task_dir / "android_page_{i}_{name}"
5
- hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
- ```
7
- Each paired hmos-android page-pair should have the same `i`
8
-
9
- ## Navigation prompt rules
10
-
11
- Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
12
- The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
-
14
- ## Navigate to Android page and Capture (If navigated Success)
15
- ```bash
16
- PYTHONIOENCODING=utf-8 python ./app_feature_verify.py \
17
- --device adb \
18
- --app "{android.app_name}" \
19
- --package "{android.package}" \
20
- --prompt "{pages[i].android_nav_path}" \
21
- --api-key "{glm_api_key}" \
22
- --max-steps 15
23
- ```
24
-
25
- **Capture Android page** (if navigation succeeded)
26
- ```bash
27
- PYTHONIOENCODING=utf-8 python ./page_capture.py --device adb -o "{android_capture_dir}"
28
- ```
29
-
30
- ## Navigate & Capture HarmonyOS Pages
31
-
32
- Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
33
- ```
34
- hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
35
- ```
36
- For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
37
-
1
+ Create a timestamped output directory under `capture_output_dir`:
2
+ ```
3
+ task_dir = capture_output_dir / "task_{timestamp}"
4
+ android_capture_dir = task_dir / "android_page_{i}_{name}"
5
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
+ ```
7
+ Each paired hmos-android page-pair should have the same `i`
8
+
9
+ ## Navigation prompt rules
10
+
11
+ Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
12
+ The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
+
14
+ ## Navigate to Android page and Capture (If navigated Success)
15
+ ```bash
16
+ PYTHONIOENCODING=utf-8 uv run ./app_feature_verify.py \
17
+ --device adb \
18
+ --app "{android.app_name}" \
19
+ --package "{android.package}" \
20
+ --prompt "{pages[i].android_nav_path}" \
21
+ \
22
+ --max-steps 15
23
+ ```
24
+
25
+ **Capture Android page** (if navigation succeeded)
26
+ ```bash
27
+ PYTHONIOENCODING=utf-8 uv run ./page_capture.py --device adb -o "{android_capture_dir}"
28
+ ```
29
+
30
+ ## Navigate & Capture HarmonyOS Pages
31
+
32
+ Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
33
+ ```
34
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
35
+ ```
36
+ For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
37
+
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ # /// script
4
+ # requires-python = ">=3.10"
5
+ # [[tool.uv.index]]
6
+ # url = "https://pypi.tuna.tsinghua.edu.cn/simple"
7
+ # ///
3
8
  """
4
9
  统一页面采集工具 —— 单次采集当前设备页面的 View Tree (XML)、截图,支持滚动采集。
5
10
 
@@ -7,10 +12,10 @@
7
12
 
8
13
  用法:
9
14
  # HarmonyOS
10
- python page_capture.py --device hdc -o ./output
15
+ uv run page_capture.py --device hdc -o ./output
11
16
 
12
17
  # Android
13
- python page_capture.py --device adb -o ./output --serial emulator-5554
18
+ uv run page_capture.py --device adb -o ./output --serial emulator-5554
14
19
  """
15
20
 
16
21
  import argparse