@buaa_smat/hometrans 0.1.15 → 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 (35) hide show
  1. package/README.md +54 -89
  2. package/agents/build-fixer.md +15 -14
  3. package/agents/code-reviewer.md +3 -3
  4. package/agents/logic-coder.md +1 -1
  5. package/agents/logic-context-builder.md +1 -1
  6. package/agents/self-tester.md +16 -13
  7. package/dist/cli/config-store.js +127 -29
  8. package/dist/cli/config.js +9 -2
  9. package/dist/cli/init.js +367 -248
  10. package/dist/cli/uninstall.js +12 -7
  11. package/dist/context/index.js +19 -2
  12. package/env-requirements.json +19 -23
  13. package/package.json +1 -1
  14. package/resource/choose_editor.png +0 -0
  15. package/resource/set_environment.png +0 -0
  16. package/resource/set_multimodel.png +0 -0
  17. package/resource/set_sdk.png +0 -0
  18. package/skills/hmos-batch-ui-align/SKILL.md +6 -6
  19. package/skills/hmos-convert-pipeline/SKILL.md +7 -7
  20. package/skills/hmos-fix-build-errors/SKILL.md +4 -3
  21. package/skills/hmos-incremental-ui-align/README.md +9 -12
  22. package/skills/hmos-incremental-ui-align/SKILL.md +9 -9
  23. package/skills/hmos-incremental-ui-align/scripts/__pycache__/app_feature_verify.cpython-314.pyc +0 -0
  24. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.py +128 -29
  25. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
  26. package/skills/hmos-incremental-ui-align/scripts/page_capture.py +7 -2
  27. package/skills/hmos-integration-test/README.md +3 -3
  28. package/skills/hmos-integration-test/SKILL.md +7 -7
  29. package/skills/hmos-spec-generate/SKILL.md +45 -52
  30. package/tools/test-tools/autotest/README.md +10 -11
  31. package/tools/test-tools/autotest/self_test_runner.py +40 -12
  32. package/resource/common_config.png +0 -0
  33. package/resource/integration_test_config.png +0 -0
  34. package/resource/set_env.png +0 -0
  35. package/resource/ui_align_config.png +0 -0
@@ -22,9 +22,13 @@ import { removeEnvVars } from './env-vars.js';
22
22
  const execFileAsync = promisify(execFile);
23
23
  const __filename = fileURLToPath(import.meta.url);
24
24
  const __dirname = path.dirname(__filename);
25
- /** Names + readable count of env vars that have a non-empty value in config. */
26
- function plannedEnvVarNames(env) {
27
- return MACHINE_ENV_KEYS.filter((k) => (env[k] ?? '').trim().length > 0);
25
+ /**
26
+ * Machine env var names that `ht init` may have set and that have a non-empty
27
+ * value in config.env: the SDK paths / tool dir plus the mirrored unified model
28
+ * fields (HOMETRANS_MODEL_*, kept in sync with autotest.unified_model on load).
29
+ */
30
+ function plannedEnvVarNames(config) {
31
+ return MACHINE_ENV_KEYS.filter((k) => (config.env[k] ?? '').trim().length > 0);
28
32
  }
29
33
  /* ------------------------------------------------------------------ */
30
34
  /* Bundled content discovery */
@@ -398,7 +402,8 @@ export async function uninstallCommand() {
398
402
  console.log(chalk.gray(' Reinstall hometrans or manually remove skill/agent directories from your editors.\n'));
399
403
  return;
400
404
  }
401
- const { editors, env } = await loadHomeTransConfig();
405
+ const config = await loadHomeTransConfig();
406
+ const { editors } = config;
402
407
  const plan = {
403
408
  deletions: [],
404
409
  modifications: [],
@@ -409,9 +414,9 @@ export async function uninstallCommand() {
409
414
  for (const editor of editors) {
410
415
  await buildPlanForEditor(editor, skillNames, agentEntries, plan);
411
416
  }
412
- // Env vars to remove come from config.json `env`; the ~/.hometrans dir
413
- // (which holds config.json) is deleted last.
414
- plan.envVars = plannedEnvVarNames(env);
417
+ // Env vars to remove come from config.json (`env` + the unified model
418
+ // fields); the ~/.hometrans dir (which holds config.json) is deleted last.
419
+ plan.envVars = plannedEnvVarNames(config);
415
420
  const configDir = getConfigDir();
416
421
  if (await dirExists(configDir)) {
417
422
  plan.configDir = configDir;
@@ -10,8 +10,25 @@ import {
10
10
  } from "arkanalyzer";
11
11
 
12
12
  // src/cli/config-store.ts
13
+ import { readFileSync } from "node:fs";
13
14
  import path from "node:path";
14
15
  import os from "node:os";
16
+ function getConfigDir() {
17
+ return path.join(os.homedir(), ".hometrans");
18
+ }
19
+ function getConfigPath() {
20
+ return path.join(getConfigDir(), "config.json");
21
+ }
22
+ function readConfigEnvFieldSync(key) {
23
+ try {
24
+ const raw = readFileSync(getConfigPath(), "utf-8");
25
+ const parsed = JSON.parse(raw);
26
+ const v = parsed?.env?.[key];
27
+ return typeof v === "string" ? v : "";
28
+ } catch {
29
+ return "";
30
+ }
31
+ }
15
32
  function expandHome(p) {
16
33
  if (!p) return p;
17
34
  if (p === "~") return os.homedir();
@@ -594,10 +611,10 @@ async function extractCommitContext(input) {
594
611
  const { projectPath, commitId } = input;
595
612
  const mode = input.mode ?? "default";
596
613
  const ohosSdkPath = expandHome(
597
- input.ohosSdkPath ?? process.env.OHOS_SDK_PATH ?? ""
614
+ input.ohosSdkPath || process.env.OHOS_SDK_PATH || readConfigEnvFieldSync("OHOS_SDK_PATH")
598
615
  );
599
616
  const hmsSdkPath = expandHome(
600
- input.hmsSdkPath ?? process.env.HMS_SDK_PATH ?? ""
617
+ input.hmsSdkPath || process.env.HMS_SDK_PATH || readConfigEnvFieldSync("HMS_SDK_PATH")
601
618
  );
602
619
  if (!projectPath) {
603
620
  throw new Error("extractCommitContext: projectPath is required");
@@ -27,6 +27,7 @@
27
27
 
28
28
  "tools": {
29
29
  "deveco": {
30
+ "label": "harmony sdk",
30
31
  "source": "env-dir",
31
32
  "envVar": "DEVECO_SDK_HOME",
32
33
  "hint": "set DEVECO_SDK_HOME via `ht init` (DevEco Studio install)"
@@ -46,20 +47,12 @@
46
47
  "fallback": { "base": "DEVECO_SDK_HOME", "segments": ["default", "openharmony", "toolchains"], "exe": "hdc" },
47
48
  "hint": "ships with DevEco: <sdk>/default/openharmony/toolchains"
48
49
  },
49
- "python": {
50
- "source": "path",
51
- "aliases": ["python", "python3", "py"],
52
- "verify": ["--version"],
53
- "versionRegex": "(\\d+\\.\\d+(?:\\.\\d+)?)",
54
- "version": { "min": "3.10.0", "max": null },
55
- "hint": "install Python >= 3.10 and add to PATH (UI-align capture/parse scripts)"
56
- },
57
50
  "uv": {
58
51
  "source": "path",
59
52
  "aliases": ["uv"],
60
53
  "verify": ["--version"],
61
54
  "versionRegex": "(\\d+\\.\\d+(?:\\.\\d+)?)",
62
- "hint": "https://docs.astral.sh/uv (AutoTest runs under uv)"
55
+ "hint": "https://docs.astral.sh/uv — runs every Python script (AutoTest, UI-align, logic) via `uv run` and provides Python ≥ 3.10 on demand (no standalone Python needed)"
63
56
  },
64
57
  "java": {
65
58
  "source": "path",
@@ -70,12 +63,12 @@
70
63
  "fallback": { "base": "DEVECO_HOME", "segments": ["jbr", "bin"], "exe": "java" },
71
64
  "hint": "any JDK 17+, or reuse DevEco jbr: <deveco>/jbr/bin"
72
65
  },
73
- "gitnexus": {
66
+ "homegraph": {
74
67
  "source": "path",
75
- "aliases": ["gitnexus"],
68
+ "aliases": ["homegraph"],
76
69
  "verify": ["--version"],
77
70
  "versionRegex": "(\\d+\\.\\d+(?:\\.\\d+)?)",
78
- "hint": "npm i -g gitnexus && gitnexus setup"
71
+ "hint": "npm install -g homegraph arkanalyzer (code-graph backend for spec generation)"
79
72
  }
80
73
  },
81
74
 
@@ -83,7 +76,7 @@
83
76
  {
84
77
  "name": "hmos-spec-generate",
85
78
  "kind": "skill",
86
- "dependencies": [{ "tool": "gitnexus", "level": "required" }]
79
+ "dependencies": [{ "tool": "homegraph", "level": "required" }]
87
80
  },
88
81
  {
89
82
  "name": "hmos-resources-convert",
@@ -95,20 +88,21 @@
95
88
  "name": "hmos-batch-ui-align",
96
89
  "kind": "skill",
97
90
  "dependencies": [
98
- { "tool": "python", "level": "required", "version": { "min": "3.10.0", "max": null } },
91
+ { "tool": "uv", "level": "required" },
99
92
  { "tool": "adb", "level": "optional" }
100
93
  ],
101
- "note": "adb only for page exploration"
94
+ "note": "android_parse_fast.py runs via `uv run` (uv provides Python); adb only for page exploration"
102
95
  },
103
96
  {
104
97
  "name": "hmos-incremental-ui-align",
105
98
  "kind": "skill",
106
99
  "dependencies": [
107
100
  { "tool": "deveco", "level": "required" },
108
- { "tool": "python", "level": "required", "version": { "min": "3.10.0", "max": null } },
101
+ { "tool": "uv", "level": "required" },
109
102
  { "tool": "hdc", "level": "required" },
110
103
  { "tool": "adb", "level": "required" }
111
- ]
104
+ ],
105
+ "note": "app_feature_verify.py / page_capture.py run via `uv run` (PEP 723 inline deps; uv provides Python)"
112
106
  },
113
107
  {
114
108
  "name": "hmos-integration-test",
@@ -117,7 +111,8 @@
117
111
  { "tool": "deveco", "level": "required" },
118
112
  { "tool": "uv", "level": "required" },
119
113
  { "tool": "hdc", "level": "required" }
120
- ]
114
+ ],
115
+ "note": "self_test_runner.py runs via `uv run --no-project python` (uv provides Python) and then `uv sync`s the AutoTest venv"
121
116
  },
122
117
  {
123
118
  "name": "hmos-fix-build-errors",
@@ -132,22 +127,22 @@
132
127
  { "tool": "uv", "level": "optional" },
133
128
  { "tool": "hdc", "level": "optional" }
134
129
  ],
135
- "note": "test stage skippable via skip_test"
130
+ "note": "test stage skippable via skip_test; when run it needs uv + hdc"
136
131
  },
137
132
  {
138
133
  "name": "logic-context-builder",
139
134
  "kind": "agent",
140
- "dependencies": [{ "tool": "python", "level": "optional" }]
135
+ "dependencies": [{ "tool": "uv", "level": "optional" }]
141
136
  },
142
137
  {
143
138
  "name": "logic-coder",
144
139
  "kind": "agent",
145
- "dependencies": [{ "tool": "python", "level": "optional" }]
140
+ "dependencies": [{ "tool": "uv", "level": "optional" }]
146
141
  },
147
142
  {
148
143
  "name": "spec-generator",
149
144
  "kind": "agent",
150
- "dependencies": [{ "tool": "gitnexus", "level": "required" }]
145
+ "dependencies": [{ "tool": "homegraph", "level": "required" }]
151
146
  },
152
147
  {
153
148
  "name": "build-fixer",
@@ -170,7 +165,8 @@
170
165
  "dependencies": [
171
166
  { "tool": "uv", "level": "required" },
172
167
  { "tool": "hdc", "level": "required" }
173
- ]
168
+ ],
169
+ "note": "invokes `uv run --no-project python self_test_runner.py run/status/kill` (uv provides Python; --no-project keeps the interpreter out of the .venv it rebuilds)"
174
170
  },
175
171
  {
176
172
  "name": "self-test-fixer",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buaa_smat/hometrans",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "HomeTrans (Android-to-HarmonyOS) skill + agent installer. Run `ht init` to distribute conversion skills and subagents into AI editors.",
5
5
  "license": "MIT",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
@@ -9,13 +9,13 @@ Batch-convert a set of Android page snapshots (each snapshot is a `page_NNNN_Act
9
9
 
10
10
  ## Step 0 — Environment Variables Check (run first)
11
11
 
12
- The final build (Step 6, via `hmos_fix_build_errors`) reads the DevEco location **directly from the OS environment variables** (`echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
12
+ The final build (Step 6, via `hmos_fix_build_errors`) resolves the DevEco location along the standard chain — **OS environment variable first, then `~/.hometrans/config.json`, then ask the user** (read env vars with `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
13
13
 
14
- | Variable | Needed for | Valid when |
15
- |---|---|---|
16
- | `DEVECO_HOME` *(or `DEVECO_SDK_HOME`)* | Step 6 unified build | resolves to a valid DevEco install |
14
+ | Variable | config.json fallback | Needed for | Valid when |
15
+ |---|---|---|---|
16
+ | `DEVECO_HOME` *(or `DEVECO_SDK_HOME`)* | `env.DEVECO_HOME` / `env.DEVECO_SDK_HOME` | Step 6 unified build | resolves to a valid DevEco install |
17
17
 
18
- If neither `DEVECO_HOME` nor `DEVECO_SDK_HOME` is set, **ask the user** for the DevEco Studio install path before proceeding (suggest running `ht init` to persist it as a machine environment variable).
18
+ If neither the env var nor `~/.hometrans/config.json` yields a valid DevEco install, **ask the user** for the DevEco Studio install path before proceeding (suggest running `ht init` to persist it as a machine environment variable).
19
19
 
20
20
  ## Step 1 — Parse User Request
21
21
 
@@ -50,7 +50,7 @@ This skill batch-converts Android resources (strings, colors, drawables, images,
50
50
  2. Otherwise, determine the target App package name (`package`). If the user didn't provide it, extract the `package` attribute from `AndroidManifest.xml` in `android_project_dir`; if still unavailable, ask the user.
51
51
  3. Execute with Bash:
52
52
  ```
53
- python ./scripts/android_parse_fast.py --package <package_name> --output <ui_info_root_absolute_path>
53
+ uv run --no-project python ./scripts/android_parse_fast.py --package <package_name> --output <ui_info_root_absolute_path>
54
54
  ```
55
55
  This script connects to the Android emulator via ADB, BFS-traverses all reachable pages in the App, and for each page saves `screenshot.png`, `view.xml`, `meta.json` into `page_NNNN_ActivityName/` subdirectories. It also generates `index.json` and `report.html` under `ui_info_root`.
56
56
  4. After execution, confirm that `index.json` has been generated under `ui_info_root`. If not, report an error and abort.
@@ -47,15 +47,15 @@ Define shorthand variables for the instructions below:
47
47
 
48
48
  ## Environment Variables Check (run before Stage 1)
49
49
 
50
- The pipeline's sub-agents read their settings **directly from the OS environment variables**. Verify upfront so the long pipeline fails fast (read each from the OS environment: `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
50
+ The pipeline's sub-agents resolve each setting along the standard chain — **OS environment variable first, then `~/.hometrans/config.json`, then ask the user**. Verify upfront so the long pipeline fails fast (read env vars with `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
51
51
 
52
- | Variable | Needed for | Valid when |
53
- |---|---|---|
54
- | `DEVECO_HOME` *(or `DEVECO_SDK_HOME`)* | Stage 2 build + Stage 3a/4b rebuilds | resolves to a valid DevEco install |
55
- | `TEST_API_KEY` | Stage 4 self-test (only when `SKIP_TEST=false`) | non-empty |
56
- | `HOMETRANS_TOOL_PATH` | Stage 4 self-test AutoTest dir; defaults to `~/.hometrans/tools` | path exists on disk |
52
+ | Variable | config.json fallback | Needed for | Valid when |
53
+ |---|---|---|---|
54
+ | `DEVECO_HOME` *(or `DEVECO_SDK_HOME`)* | `env.DEVECO_HOME` / `env.DEVECO_SDK_HOME` | Stage 2 build + Stage 3a/4b rebuilds | resolves to a valid DevEco install |
55
+ | `HOMETRANS_MODEL_API_KEY` *(legacy `TEST_API_KEY` still honored as a fallback)* | `autotest.unified_model.api_key` | Stage 4 self-test (only when `SKIP_TEST=false`) | non-empty / not the placeholder |
56
+ | `HOMETRANS_TOOL_PATH` | `env.HOMETRANS_TOOL_PATH` (defaults to `~/.hometrans/tools`) | Stage 4 self-test AutoTest dir | path exists on disk |
57
57
 
58
- If `DEVECO_HOME`/`DEVECO_SDK_HOME` is missing, **ask the user** for the DevEco Studio install path before starting. When `SKIP_TEST=false`, also require `TEST_API_KEY` if missing, **ask the user** for it (or have them pass `skip_test=true`). Suggest running `ht init` to persist these as machine environment variables.
58
+ For each value, if the env var is unset, fall back to the listed `~/.hometrans/config.json` field. If neither yields a value: for the DevEco path / model api_key, **ask the user** (for the api_key they may instead pass `skip_test=true`). Suggest running `ht init` to persist these as machine environment variables.
59
59
 
60
60
  ---
61
61
 
@@ -21,10 +21,11 @@ Automatically build a HarmonyOS NEXT project from the command line, parse compil
21
21
 
22
22
  1. **Verify project exists** — Check that `$ARGUMENTS[0]` contains a valid HarmonyOS project (look for `build-profile.json5`, `entry/src` directory, `oh-package.json5`).
23
23
 
24
- 2. **Resolve `<deveco-path>`** — read **directly from the OS environment**; stop at the first source that yields a valid path:
24
+ 2. **Resolve `<deveco-path>`** — follow the standard config chain, stopping at the first source that yields a valid path:
25
25
  1. `$ARGUMENTS[1]` if provided (and not `--signed`).
26
- 2. Environment variables (`echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows): `DEVECO_HOME` → parent of `DEVECO_SDK_HOME` → strip the trailing `/sdk/default/openharmony/ets` from `OHOS_SDK_PATH` (legacy).
27
- 3. **Ask the user** for the DevEco Studio install path (suggest running `ht init` to persist it as an environment variable).
26
+ 2. **OS environment variables** (`echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows): `DEVECO_HOME` → parent of `DEVECO_SDK_HOME` → strip the trailing `/sdk/default/openharmony/ets` from `OHOS_SDK_PATH` (legacy).
27
+ 3. **`~/.hometrans/config.json`** read `env.DEVECO_HOME`, else parent of `env.DEVECO_SDK_HOME` (written by `ht init`).
28
+ 4. **Ask the user** for the DevEco Studio install path (suggest running `ht init` to persist it as an environment variable).
28
29
 
29
30
  **Verify the resolved `<deveco-path>`** contains:
30
31
  - `tools/node/node.exe`
@@ -13,7 +13,7 @@ HarmonyOS-Android UI 自动对齐流水线。
13
13
  每多一个页面/弹窗/tab,上述步骤都要重复一遍,非常费时。
14
14
 
15
15
  本 skill 做了三件事:
16
- - 用智谱 GLM 驱动的 phone-agent 自动**寻路**(根据自然语言点到指定页面)
16
+ - phone-agent 自动**寻路**(根据自然语言点到指定页面,模型复用 `~/.hometrans/config.json` 统一配置)
17
17
  - 自动**采集** view tree + 截图(安卓走 adb、鸿蒙走 hdc)
18
18
  - 自动**对比 + 改码**,并按 MVVM 模式把 mock 数据放到 Model 层
19
19
 
@@ -31,12 +31,10 @@ HarmonyOS-Android UI 自动对齐流水线。
31
31
  - 两台设备建议都保持亮屏解锁
32
32
 
33
33
  2. **Python 依赖**
34
- ```bash
35
- pip install openpyxl phone-agent
36
- ```
34
+ 无需手动安装。脚本通过 `uv run` 调用,`openpyxl` / `phone-agent` 已在 `app_feature_verify.py` 顶部的 PEP 723 内联元数据里声明,uv 首次运行时自动解析安装(走清华镜像)。只需确保已安装 `uv`(`uv --version`)。
37
35
 
38
- 3. **智谱 GLM API Key**(phone-agent 调用的模型)
39
- https://open.bigmodel.cn/ 申请,有免费额度。设为 OS 环境变量 `GLM_API_KEY`(`ht init` 可帮你写入机器环境变量)。
36
+ 3. **模型配置**(phone-agent 导航用)
37
+ 与自测共用同一个多模态模型:`ht init` 把模型四要素(api_key / name / base_url / provider)写入 `~/.hometrans/config.json` 的 `autotest.unified_model`,并导出为 `HOMETRANS_MODEL_*` 环境变量。脚本优先读环境变量,缺失时回落 config.json;都没有时兼容老版本的 `GLM_API_KEY`(走智谱 GLM 端点)。无需单独设置 API Key。
40
38
 
41
39
  4. **鸿蒙 SDK 路径**(给改码阶段查 API 用)
42
40
  取自 OS 环境变量 `OHOS_SDK_PATH` / `HMS_SDK_PATH`(为空时由 `DEVECO_SDK_HOME` 派生),由 `ht init` 写入机器环境变量;例如 DevEco Studio 自带的 `E:\DevEco Studio\sdk\default\openharmony\ets`。
@@ -55,9 +53,9 @@ HarmonyOS-Android UI 自动对齐流水线。
55
53
  | `harmony_project_dir` | 是 | 鸿蒙工程根目录(会被直接修改) |
56
54
  | `capture_output_dir` | 否 | 采集产物输出目录(截图/view tree/分析报告)。默认 `<harmony_project_dir>/.hometrans/capture_output` |
57
55
 
58
- **② OS 环境变量直接读取的全局配置**(由 `ht init` 写入机器环境变量,无需每次传):
56
+ **② 全局配置**(统一链路「环境变量 `~/.hometrans/config.json` → 询问用户」,由 `ht init` 同时写入机器环境变量与 config.json,无需每次传):
59
57
 
60
- - GLM key 环境变量 `GLM_API_KEY`
58
+ - 模型配置`HOMETRANS_MODEL_*` 环境变量(缺失时回落 `~/.hometrans/config.json``autotest.unified_model`;与自测共用同一个模型)
61
59
  - 鸿蒙 SDK ETS API 目录 ← 环境变量 `OHOS_SDK_PATH` / `HMS_SDK_PATH`(为空时由 `DEVECO_SDK_HOME` 派生为 `<DEVECO_SDK_HOME>/default/openharmony/ets`、`.../hms/ets`)
62
60
 
63
61
  > **Step 0.0** 会在执行前检查这些环境变量是否存在;缺失时会要求用户输入。
@@ -120,7 +118,7 @@ HarmonyOS-Android UI 自动对齐流水线。
120
118
  skill 会严格按 `SKILL.md` 里的 5 步流水线跑:
121
119
 
122
120
  ### Step 0 · 解析入参
123
- 读取调用入参 `android_project_dir` / `harmony_project_dir` / `capture_output_dir`,并从 OS 环境变量直接读取 `GLM_API_KEY` `OHOS_SDK_PATH` / `HMS_SDK_PATH`(**Step 0.0** 会先做存在性检查,缺失则要求用户输入)。随后 **Step 0.1** 按两个工程目录自动解析两端的 App 显示名与包名(无需手动配置)。
121
+ 读取调用入参 `android_project_dir` / `harmony_project_dir` / `capture_output_dir`,并按统一链路「环境变量 config.json → 询问」解析 `HOMETRANS_MODEL_*`(统一模型,回落 `~/.hometrans/config.json` `autotest.unified_model`)与 `OHOS_SDK_PATH` / `HMS_SDK_PATH`(回落 config.json 的 `env.*`;**Step 0.0** 会先做存在性检查,两层都缺失才要求用户输入)。随后 **Step 0.1** 按两个工程目录自动解析两端的 App 显示名与包名(无需手动配置)。
124
122
 
125
123
  ### Step 1 · 双端页面采集
126
124
  1.1 解析你的需求,拆成一组「基础页」,每个基础页有 `android_nav_path` 和 `hmos_nav_path`
@@ -193,16 +191,15 @@ Agents/hmos-ui-align/
193
191
  ```powershell
194
192
  # 安卓寻路
195
193
  $env:PYTHONIOENCODING="utf-8"
196
- python Agents/hmos-ui-align/scripts/app_feature_verify.py `
194
+ uv run Agents/hmos-ui-align/scripts/app_feature_verify.py `
197
195
  --device adb `
198
196
  --app "Salt Player" `
199
197
  --package "com.salt.music" `
200
198
  --prompt "进入首页-点击AI智能填报-点击专业筛选按钮" `
201
- --api-key "$env:GLM_API_KEY" `
202
199
  --max-steps 15
203
200
 
204
201
  # 安卓采集
205
- python Agents/hmos-ui-align/scripts/page_capture.py --device adb -o ./tmp/android_page_1_xxx
202
+ uv run Agents/hmos-ui-align/scripts/page_capture.py --device adb -o ./tmp/android_page_1_xxx
206
203
 
207
204
  # 鸿蒙同理,把 --device adb 换成 --device hdc
208
205
  ```
@@ -18,7 +18,7 @@ You are writing ArkTS codes.
18
18
 
19
19
  ## Step 0: Resolve Inputs
20
20
 
21
- This skill takes three path inputs from the invocation and reads its other settings **directly from the OS environment variables**.
21
+ This skill takes three path inputs from the invocation and resolves its other settings along the standard chain — **OS environment variable → `~/.hometrans/config.json` → ask the user** (see Step 0.0).
22
22
 
23
23
  ### Skill input arguments (provided by the user when invoking the skill)
24
24
 
@@ -32,17 +32,17 @@ The user may pass them as named args (e.g. `android_project_dir: D:\path\to\andr
32
32
 
33
33
  ### Step 0.0: Environment Variables Check (run first)
34
34
 
35
- Read each variable below **directly from the OS environment** (`echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell / `echo %VAR%` in cmd on Windows).
35
+ Resolve each value along the standard chain — **OS environment variable first, then `~/.hometrans/config.json`, then ask the user** (read env vars with `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell / `echo %VAR%` in cmd on Windows).
36
36
 
37
- | Variable | Purpose | Valid when |
38
- |---|---|---|
39
- | `GLM_API_KEY` | Zhipu GLM key for the phone-agent (Step 1 navigation) | non-empty |
40
- | `OHOS_SDK_PATH` | HarmonyOS SDK ETS API reference (Step 3) | path exists on disk |
41
- | `HMS_SDK_PATH` | HMS SDK ETS API reference (Step 3) | path exists on disk |
37
+ | Variable | config.json fallback | Purpose | Valid when |
38
+ |---|---|---|---|
39
+ | `HOMETRANS_MODEL_*` (`HOMETRANS_MODEL_API_KEY` / `HOMETRANS_MODEL_NAME` / `HOMETRANS_MODEL_BASE_URL`) | `autotest.unified_model.{api_key,name,base_url}`(provider 默认 openai,不导出环境变量;再兜底老版本 `GLM_API_KEY` → 智谱 GLM 端点) | 统一多模态模型(UI 对齐 + 自测共用),由 `ht init` 导出 | `HOMETRANS_MODEL_API_KEY` 非空(或 config.json 已配置,或老版本 `GLM_API_KEY` 已设置) |
40
+ | `OHOS_SDK_PATH` | `env.OHOS_SDK_PATH` | HarmonyOS SDK ETS API reference (Step 3) | path exists on disk |
41
+ | `HMS_SDK_PATH` | `env.HMS_SDK_PATH` | HMS SDK ETS API reference (Step 3) | path exists on disk |
42
42
 
43
- For `OHOS_SDK_PATH` / `HMS_SDK_PATH`, if unset but `DEVECO_SDK_HOME` is set, derive them as `<DEVECO_SDK_HOME>/default/openharmony/ets` and `<DEVECO_SDK_HOME>/default/hms/ets`.
43
+ The model config is resolved automatically by `scripts/app_feature_verify.py` (env → config.json), so you don't read it by hand — just confirm `HOMETRANS_MODEL_API_KEY` or `autotest.unified_model.api_key` is set. For `OHOS_SDK_PATH` / `HMS_SDK_PATH`, if both the env var and `~/.hometrans/config.json` are empty but `DEVECO_SDK_HOME` is set (env or `env.DEVECO_SDK_HOME` in config.json), derive them as `<DEVECO_SDK_HOME>/default/openharmony/ets` and `<DEVECO_SDK_HOME>/default/hms/ets`.
44
44
 
45
- For each variable that is still missing/empty (or a path that does not exist), **stop and ask the user to provide a value**, then use what they give for this run. Suggest running `ht init` to persist them as machine environment variables for next time.
45
+ For each value that is still missing/empty (or a path that does not exist) after both the env var and `~/.hometrans/config.json`, **stop and ask the user to provide a value**, then use what they give for this run. Suggest running `ht init` to persist them as machine environment variables for next time.
46
46
 
47
47
  ### Step 0.1: Auto-derive App Names & Package Names
48
48
 
@@ -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})")