@buaa_smat/hometrans 0.1.13 → 0.1.14

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 (86) hide show
  1. package/README.md +164 -112
  2. package/agents/build-fixer.md +384 -394
  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 +393 -392
  9. package/agents/spec-generator.md +540 -540
  10. package/dist/cli/config-store.js +84 -8
  11. package/dist/cli/config.js +3 -3
  12. package/dist/cli/env-vars.js +129 -0
  13. package/dist/cli/init.js +272 -272
  14. package/dist/cli/uninstall.js +152 -17
  15. package/dist/context/index.js +10 -197
  16. package/env-requirements.json +3 -3
  17. package/package.json +1 -1
  18. package/resource/choose_editor.png +0 -0
  19. package/resource/common_config.png +0 -0
  20. package/resource/integration_test_config.png +0 -0
  21. package/resource/set_env.png +0 -0
  22. package/resource/ui_align_config.png +0 -0
  23. package/skills/hmos-batch-ui-align/SKILL.md +108 -98
  24. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +180 -180
  25. package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -2533
  26. package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -555
  27. package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -117
  28. package/skills/hmos-batch-ui-align/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -648
  29. package/skills/hmos-batch-ui-align/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2088 -2088
  30. package/skills/hmos-batch-ui-align/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -1033
  31. package/skills/hmos-batch-ui-align/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -1183
  32. package/skills/hmos-batch-ui-align/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -576
  33. package/skills/hmos-batch-ui-align/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -297
  34. package/skills/hmos-batch-ui-align/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -395
  35. package/skills/hmos-batch-ui-align/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +902 -902
  36. package/skills/hmos-batch-ui-align/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -106
  37. package/skills/hmos-batch-ui-align/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -1178
  38. package/skills/hmos-batch-ui-align/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -911
  39. package/skills/hmos-batch-ui-align/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +354 -354
  40. package/skills/hmos-batch-ui-align/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -11
  41. package/skills/hmos-convert-pipeline/SKILL.md +429 -415
  42. package/skills/hmos-fix-build-errors/SKILL.md +272 -273
  43. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +219 -219
  44. package/skills/hmos-fix-build-errors/references/known-patterns.md +157 -157
  45. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +131 -131
  46. package/skills/hmos-incremental-ui-align/SKILL.md +219 -200
  47. package/skills/hmos-incremental-ui-align/diff_analysis.md +52 -52
  48. package/skills/hmos-incremental-ui-align/page_align.md +62 -62
  49. package/skills/hmos-incremental-ui-align/readme.md +237 -230
  50. package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +2 -2
  51. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -648
  52. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2088 -2088
  53. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -1033
  54. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -1183
  55. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -576
  56. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -297
  57. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -395
  58. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +902 -902
  59. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -106
  60. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -1178
  61. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -911
  62. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +354 -354
  63. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -11
  64. package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +3 -3
  65. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -2533
  66. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -555
  67. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -117
  68. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
  69. package/skills/hmos-integration-test/SKILL.md +380 -369
  70. package/skills/hmos-integration-test/readme.md +309 -309
  71. package/skills/hmos-resources-convert/SKILL.md +623 -623
  72. package/skills/hmos-resources-convert/references/conversion-rules.md +663 -663
  73. package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -388
  74. package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -457
  75. package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -513
  76. package/skills/hmos-spec-generate/SKILL.md +331 -331
  77. package/skills/hmos-spec-generate/references/android-platform-tokens.md +105 -105
  78. package/skills/hmos-spec-generate/references/spec-sample-1.md +78 -78
  79. package/skills/hmos-spec-generate/references/spec-sample-2.md +58 -58
  80. package/skills/hmos-spec-generate/references/spec-sample-3.md +116 -116
  81. package/skills/hmos-spec-generate/references/step4-report-template.md +33 -33
  82. package/tools/test-tools/autotest/README.md +33 -17
  83. package/tools/test-tools/autotest/self_test_runner.py +109 -15
  84. package/resource/hometrans_config.png +0 -0
  85. package/skills/hmos-incremental-ui-align/config-example.json +0 -11
  86. package/tools/test-tools/autotest/config.yaml.example +0 -58
package/dist/cli/init.js CHANGED
@@ -12,9 +12,9 @@ import { fileURLToPath } from 'node:url';
12
12
  import chalk from 'chalk';
13
13
  import figlet from 'figlet';
14
14
  import inquirer from 'inquirer';
15
- import { parseTree, modify, applyEdits } from 'jsonc-parser';
16
15
  import { setupMcpForAllEditors } from './mcp-setup.js';
17
16
  import { deriveSdkPaths, expandHome, getConfigDir, getConfigPath, getToolsDir, loadHomeTransConfig, saveHomeTransConfig, } from './config-store.js';
17
+ import { persistEnvVars } from './env-vars.js';
18
18
  function ensureChalkColor() {
19
19
  if (process.stdout.isTTY && chalk.level === 0) {
20
20
  chalk.level = 1;
@@ -70,7 +70,7 @@ function resolveToolsRoot() {
70
70
  }
71
71
  /**
72
72
  * Copy the bundled `tools/` folder into ~/.hometrans/tools and record the
73
- * destination in config.env.TOOL_PATH. Returns the destination path, or null if
73
+ * destination in config.env.HOMETRANS_TOOL_PATH. Returns the destination path, or null if
74
74
  * the package ships no tools/ folder.
75
75
  */
76
76
  async function installTools(toolsRoot, config) {
@@ -78,239 +78,187 @@ async function installTools(toolsRoot, config) {
78
78
  return null;
79
79
  const toolsDest = getToolsDir();
80
80
  await copyDirRecursive(toolsRoot, toolsDest);
81
- config.env.TOOL_PATH = toolsDest;
81
+ config.env.HOMETRANS_TOOL_PATH = toolsDest;
82
82
  await saveHomeTransConfig(config);
83
83
  return toolsDest;
84
84
  }
85
- /**
86
- * The AutoTest tools live under the installed HomeTrans tools dir
87
- * (~/.hometrans/tools/test-tools/autotest) once `installTools` has run.
88
- * This is the same location the self-tester agent resolves via config.env.TOOL_PATH.
89
- */
90
- function resolveAutotestDir(toolPath) {
91
- return path.join(toolPath, 'test-tools', 'autotest');
85
+ /** Mask an api_key for display; placeholders/empty shown as-is. */
86
+ function maskKey(key) {
87
+ if (!key || key === 'YOUR_API_KEY_HERE')
88
+ return key || '';
89
+ return key.length <= 8 ? '***' : `${key.slice(0, 4)}***${key.slice(-4)}`;
92
90
  }
93
- async function readFileIfExists(p) {
94
- try {
95
- return await fs.readFile(p, 'utf-8');
96
- }
97
- catch {
98
- return null;
99
- }
91
+ /** Inline notes for each autotest field, shown in the table's Description column. */
92
+ const AUTOTEST_FIELD_NOTES = {
93
+ name: '多模态(视觉)模型的名字',
94
+ base_url: '模型API服务的基础地址',
95
+ api_key: '用于模型API请求认证的令牌',
96
+ provider: '模型API服务的提供方标识,如openai',
97
+ temperature: '采样温度',
98
+ top_p: '核采样阈值',
99
+ frequency_penalty: '重复惩罚',
100
+ device_sn: '设备序列号;null 表示自动选用当前连接的设备',
101
+ ip: '设备连接 IP(hdc)',
102
+ port: '设备连接端口(hdc)',
103
+ device_log_level: '设备日志级别:debug/info/...',
104
+ max_steps: '单条用例最大执行步数',
105
+ verbose: '是否输出详细日志',
106
+ lang: '输出语言:zh/en',
107
+ enable_preprocess: '执行前是否先用 unified_model 把用例拆成步骤序列(多一次 LLM 调用,默认关)',
108
+ debug_mode: '调试会话:null 关闭 / memory 内存 / file 存盘并跑完后起调试服务(:5000)',
109
+ };
110
+ /** Visual width of a string in a monospace terminal (CJK / fullwidth glyphs = 2). */
111
+ function displayWidth(s) {
112
+ let w = 0;
113
+ for (const ch of s) {
114
+ const cp = ch.codePointAt(0) ?? 0;
115
+ const wide = (cp >= 0x1100 && cp <= 0x115f) ||
116
+ (cp >= 0x2e80 && cp <= 0xa4cf) ||
117
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
118
+ (cp >= 0xf900 && cp <= 0xfaff) ||
119
+ (cp >= 0xfe30 && cp <= 0xfe4f) ||
120
+ (cp >= 0xff00 && cp <= 0xff60) ||
121
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
122
+ (cp >= 0x20000 && cp <= 0x3fffd);
123
+ w += wide ? 2 : 1;
124
+ }
125
+ return w;
100
126
  }
101
- /** Template drift check; line-ending differences alone don't count. */
102
- export function templatesDiffer(a, b) {
103
- if (a === null || b === null)
104
- return false;
105
- const norm = (s) => s.replace(/\r\n/g, '\n');
106
- return norm(a) !== norm(b);
127
+ /** padEnd by visual width so columns with CJK content still line up. */
128
+ function padEndDisplay(s, width) {
129
+ return s + ' '.repeat(Math.max(0, width - displayWidth(s)));
107
130
  }
108
- /** e.g. 20260610-153012 — used to name config backups. */
109
- function timestampSuffix() {
110
- const d = new Date();
111
- const p = (n) => String(n).padStart(2, '0');
112
- return (`${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}` +
113
- `-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`);
131
+ /**
132
+ * Render the current/default autotest block as a table so the user sees every
133
+ * field, its current/default value, and a short description. Section label shows
134
+ * only on the first row of each group for readability.
135
+ */
136
+ function printAutotestTable(at) {
137
+ const fmt = (v) => (v === null ? 'null' : String(v));
138
+ const um = at.unified_model;
139
+ const data = [
140
+ ['unified_model', 'name', um.name],
141
+ ['unified_model', 'base_url', um.base_url],
142
+ ['unified_model', 'provider', um.provider],
143
+ ['unified_model', 'api_key', maskKey(um.api_key)],
144
+ ['unified_model', 'temperature', fmt(um.temperature)],
145
+ ['unified_model', 'top_p', fmt(um.top_p)],
146
+ ['unified_model', 'frequency_penalty', fmt(um.frequency_penalty)],
147
+ ['device', 'device_sn', fmt(at.device.device_sn)],
148
+ ['device', 'ip', at.device.ip],
149
+ ['device', 'port', fmt(at.device.port)],
150
+ ['device', 'device_log_level', at.device.device_log_level],
151
+ ['agent', 'max_steps', fmt(at.agent.max_steps)],
152
+ ['agent', 'verbose', fmt(at.agent.verbose)],
153
+ ['agent', 'lang', at.agent.lang],
154
+ ['agent', 'enable_preprocess', fmt(at.agent.enable_preprocess)],
155
+ ['agent', 'debug_mode', fmt(at.agent.debug_mode)],
156
+ ];
157
+ const header = ['Section', 'Field', 'Current/Default', 'Description'];
158
+ // Section label only on the first row of each group.
159
+ const rows = [];
160
+ let prevSection = '';
161
+ for (const [s, f, v] of data) {
162
+ rows.push([s === prevSection ? '' : s, f, v, AUTOTEST_FIELD_NOTES[f] ?? '']);
163
+ prevSection = s;
164
+ }
165
+ const cols = header.map((h) => displayWidth(h));
166
+ for (const r of rows) {
167
+ for (let i = 0; i < cols.length; i++) {
168
+ cols[i] = Math.max(cols[i], displayWidth(r[i]));
169
+ }
170
+ }
171
+ const border = (l, m, r) => ` ${l}` + cols.map((w) => '─'.repeat(w + 2)).join(m) + r;
172
+ const row = (cells) => ' │ ' + cells.map((c, i) => padEndDisplay(c, cols[i])).join(' │ ') + ' │';
173
+ console.log(chalk.gray(border('┌', '┬', '┐')));
174
+ console.log(chalk.gray(row(header)));
175
+ console.log(chalk.gray(border('├', '┼', '┤')));
176
+ for (const r of rows)
177
+ console.log(chalk.gray(row(r)));
178
+ console.log(chalk.gray(border('└', '┴', '┘')));
114
179
  }
115
180
  /**
116
- * The bundled template changed since the local config was created — ask
117
- * whether to regenerate. Y = back up the current file with a timestamp and
118
- * recreate from the new template; N (default, and the non-interactive
119
- * fallback) = keep the current file and only refresh the keys in place.
120
- * Ctrl-C aborts init.
181
+ * Test Configuration step configures the `autotest` block in
182
+ * ~/.hometrans/config.json (used by the on-device self-test agent). Shows the
183
+ * default content, then prompts for the model essentials. The api_key (formerly
184
+ * TEST_API_KEY) is stored here and NOT written to the system environment;
185
+ * self_test_runner.py reads it from config.json at run time.
186
+ *
187
+ * device / agent stay at their defaults (shown above) — edit config.json to tune.
121
188
  */
122
- async function confirmRegenerateFromTemplate(label) {
123
- console.log(chalk.yellow(` ! ${label}: the bundled template changed since this file was created.`));
189
+ async function promptTestConfig(config) {
190
+ const at = config.autotest;
191
+ const um = at.unified_model;
192
+ console.log('');
193
+ console.log(chalk.blue(' Test Configuration'));
194
+ printAutotestTable(at);
195
+ console.log(chalk.gray(' This step configures only the parameters prompted below; configure all other'));
196
+ console.log(chalk.gray(` parameters in config.json: ${getConfigPath()}`));
197
+ console.log('');
124
198
  try {
125
199
  const answers = await inquirer.prompt([
126
200
  {
127
- type: 'confirm',
128
- name: 'regenerate',
129
- message: `Regenerate ${label} from the new template? (Y = back up current file with a timestamp, then recreate; N = keep current file, only refresh keys)`,
130
- default: false,
201
+ type: 'input',
202
+ name: 'api_key',
203
+ message: 'autotest api_key (LLM key the self-test agent uses to run test cases):',
204
+ default: um.api_key && um.api_key !== 'YOUR_API_KEY_HERE' ? um.api_key : undefined,
205
+ },
206
+ { type: 'input', name: 'name', message: 'model name:', default: um.name },
207
+ { type: 'input', name: 'base_url', message: 'base_url:', default: um.base_url },
208
+ {
209
+ type: 'input',
210
+ name: 'provider',
211
+ message: 'provider (openai / azure / ...):',
212
+ default: um.provider,
131
213
  },
132
214
  ]);
133
- return answers.regenerate;
215
+ if (answers.api_key.trim())
216
+ um.api_key = answers.api_key.trim();
217
+ if (answers.name.trim())
218
+ um.name = answers.name.trim();
219
+ if (answers.base_url.trim())
220
+ um.base_url = answers.base_url.trim();
221
+ if (answers.provider.trim())
222
+ um.provider = answers.provider.trim();
223
+ await saveHomeTransConfig(config);
224
+ const note = um.api_key && um.api_key !== 'YOUR_API_KEY_HERE'
225
+ ? `api_key ${maskKey(um.api_key)}`
226
+ : 'api_key left as placeholder — self-test will not run until it is set';
227
+ console.log(chalk.green(` + autotest config saved (${note})`));
134
228
  }
135
229
  catch (err) {
136
230
  if (isPromptAbort(err))
137
231
  abortInit();
138
- console.log(chalk.yellow(' Non-interactive: keeping current file (keys refreshed only).'));
139
- return false;
232
+ console.log(chalk.yellow(' Test configuration skipped (non-interactive mode).'));
140
233
  }
141
234
  }
142
235
  /**
143
- * Initialize / refresh `agents/test-tools/autotest/config.yaml`:
144
- * - If config.yaml does not exist, seed it from config.yaml.example.
145
- * - Replace every `api_key: "..."` line with the supplied apiKey.
146
- *
147
- * Returns a status string for the result summary, or null if nothing was done
148
- * (e.g., the autotest folder isn't present in this package).
236
+ * UI Migration Configuration step — configures the GLM key used by the
237
+ * incremental UI-alignment skill's phone-agent. GLM_API_KEY is read from the OS
238
+ * environment at run time (app_feature_verify.py), so it stays in config.env and
239
+ * is persisted as a machine env var by maybePersistMachineEnv.
149
240
  */
150
- async function refreshAutotestConfig(autotestDir, apiKey, prevExampleContent = null) {
151
- const examplePath = path.join(autotestDir, 'config.yaml.example');
152
- const configPath = path.join(autotestDir, 'config.yaml');
153
- const hasExample = await fs
154
- .access(examplePath)
155
- .then(() => true)
156
- .catch(() => false);
157
- if (!hasExample)
158
- return null;
159
- let seeded = false;
160
- let backupName = null;
161
- let templateBackupName = null;
162
- const hasConfig = await fs
163
- .access(configPath)
164
- .then(() => true)
165
- .catch(() => false);
166
- if (!hasConfig) {
167
- await fs.copyFile(examplePath, configPath);
168
- seeded = true;
169
- }
170
- else {
171
- // Template drift: the bundled example changed since the local config was
172
- // created (prevExampleContent = local example snapshot taken before the
173
- // tools copy). Y regenerates from the new template after a timestamped
174
- // backup; N keeps the local file (key-only refresh below) — but saves the
175
- // old template alongside, since the new one just overwrote it on disk
176
- // and the local config is still based on the old one.
177
- const newExample = await fs.readFile(examplePath, 'utf-8');
178
- if (templatesDiffer(prevExampleContent, newExample)) {
179
- if (await confirmRegenerateFromTemplate('autotest config.yaml')) {
180
- backupName = `config.yaml.${timestampSuffix()}.bak`;
181
- await fs.copyFile(configPath, path.join(autotestDir, backupName));
182
- await fs.copyFile(examplePath, configPath);
183
- }
184
- else {
185
- templateBackupName = `config.yaml.example.${timestampSuffix()}.bak`;
186
- await fs.writeFile(path.join(autotestDir, templateBackupName), prevExampleContent, 'utf-8');
187
- }
188
- }
189
- }
190
- if (!apiKey) {
191
- if (backupName) {
192
- return `autotest config.yaml regenerated from new template (backup: ${backupName}; api_key left as placeholder)`;
193
- }
194
- if (templateBackupName) {
195
- return `autotest config.yaml kept (old template backed up: ${templateBackupName})`;
196
- }
197
- return seeded
198
- ? `autotest config.yaml seeded (api_key left as placeholder)`
199
- : null;
200
- }
201
- const original = await fs.readFile(configPath, 'utf-8');
202
- // Match `api_key: <value>` with optional quoting, preserve indent + key.
203
- const updated = original.replace(/^(\s*api_key\s*:\s*)(?:"[^"]*"|'[^']*'|\S+)\s*$/gm, (_m, prefix) => `${prefix}"${apiKey}"`);
204
- if (updated !== original) {
205
- await fs.writeFile(configPath, updated, 'utf-8');
206
- }
207
- if (backupName) {
208
- return `autotest config.yaml regenerated from new template + api_key filled (backup: ${backupName})`;
209
- }
210
- if (templateBackupName) {
211
- return `autotest config.yaml api_key refreshed (kept local file; old template backed up: ${templateBackupName})`;
212
- }
213
- return seeded
214
- ? `autotest config.yaml seeded + api_key filled`
215
- : `autotest config.yaml api_key refreshed`;
216
- }
217
- const UI_ALIGN_SKILL = 'hmos-incremental-ui-align';
218
- /**
219
- * Initialize / refresh `<skillsDir>/hmos-incremental-ui-align/config.json`
220
- * in an editor's installed skills dir — same semantics as the autotest
221
- * config.yaml handling:
222
- * - If config.json does not exist, seed it from config-example.json.
223
- * - If it exists, surgically overwrite ONLY the `glm_api_key` and
224
- * `hmos_sdk_dir` values (via jsonc edits), preserving every other
225
- * field the user has set. `hmos_sdk_dir` = `<DEVECO_SDK_HOME>/default`.
226
- *
227
- * Returns a status string for the result summary, or null if nothing was
228
- * done (skill not installed there, or no values to write into an existing file).
229
- */
230
- export async function refreshUiAlignConfig(skillsDir, glmApiKey, hmosSdkDir, prevExampleContent = null) {
231
- const skillDir = path.join(skillsDir, UI_ALIGN_SKILL);
232
- const examplePath = path.join(skillDir, 'config-example.json');
233
- const configPath = path.join(skillDir, 'config.json');
234
- const hasExample = await fs
235
- .access(examplePath)
236
- .then(() => true)
237
- .catch(() => false);
238
- if (!hasExample)
239
- return null;
240
- let seeded = false;
241
- let backupName = null;
242
- let templateBackupName = null;
243
- const hasConfig = await fs
244
- .access(configPath)
245
- .then(() => true)
246
- .catch(() => false);
247
- if (!hasConfig) {
248
- await fs.copyFile(examplePath, configPath);
249
- seeded = true;
250
- }
251
- else {
252
- // Template drift: bundled config-example.json changed since the local
253
- // config.json was created (prevExampleContent = snapshot taken before the
254
- // skills copy). Y regenerates from the new template after a timestamped
255
- // backup; N keeps the local file (key-only update below) — but saves the
256
- // old template alongside, since the new one just overwrote it on disk
257
- // and the local config is still based on the old one.
258
- const newExample = await fs.readFile(examplePath, 'utf-8');
259
- if (templatesDiffer(prevExampleContent, newExample)) {
260
- if (await confirmRegenerateFromTemplate(`${UI_ALIGN_SKILL}/config.json`)) {
261
- backupName = `config.json.${timestampSuffix()}.bak`;
262
- await fs.copyFile(configPath, path.join(skillDir, backupName));
263
- await fs.copyFile(examplePath, configPath);
264
- }
265
- else {
266
- templateBackupName = `config-example.json.${timestampSuffix()}.bak`;
267
- await fs.writeFile(path.join(skillDir, templateBackupName), prevExampleContent, 'utf-8');
268
- }
269
- }
270
- }
271
- const updates = [];
272
- if (glmApiKey)
273
- updates.push(['glm_api_key', glmApiKey]);
274
- if (hmosSdkDir)
275
- updates.push(['hmos_sdk_dir', hmosSdkDir]);
276
- if (updates.length === 0) {
277
- if (backupName) {
278
- return `${UI_ALIGN_SKILL}/config.json regenerated from new template (backup: ${backupName}; glm_api_key / hmos_sdk_dir left empty)`;
279
- }
280
- if (templateBackupName) {
281
- return `${UI_ALIGN_SKILL}/config.json kept (old template backed up: ${templateBackupName})`;
282
- }
283
- return seeded
284
- ? `${UI_ALIGN_SKILL}/config.json seeded (glm_api_key / hmos_sdk_dir left empty)`
285
- : null;
286
- }
287
- let raw = await fs.readFile(configPath, 'utf-8');
288
- const parseErrors = [];
289
- const tree = parseTree(raw, parseErrors);
290
- if (!tree || tree.type !== 'object' || parseErrors.length > 0) {
291
- // Corrupt config — never regenerate over user content; just report.
292
- return `${UI_ALIGN_SKILL}/config.json NOT updated (file is not valid JSON — fix it manually)`;
293
- }
294
- const original = raw;
295
- for (const [key, value] of updates) {
296
- const edits = modify(raw, [key], value, {
297
- formattingOptions: { tabSize: 2, insertSpaces: true },
298
- });
299
- raw = applyEdits(raw, edits);
300
- }
301
- if (raw !== original) {
302
- await fs.writeFile(configPath, raw, 'utf-8');
303
- }
304
- const what = updates.map(([k]) => k).join(' + ');
305
- if (backupName) {
306
- return `${UI_ALIGN_SKILL}/config.json regenerated from new template + ${what} filled (backup: ${backupName})`;
241
+ async function promptUiAlignConfig(config) {
242
+ console.log('');
243
+ console.log(chalk.blue(' UI Migration Configuration'));
244
+ console.log(chalk.gray(' GLM_API_KEY: Zhipu GLM key for the UI-alignment phone-agent (read from the OS env at run time).'));
245
+ try {
246
+ const answers = await inquirer.prompt([
247
+ {
248
+ type: 'input',
249
+ name: 'GLM_API_KEY',
250
+ message: 'GLM_API_KEY (LLM API key for the GLM phone-agent used in UI alignment):',
251
+ default: config.env.GLM_API_KEY || undefined,
252
+ },
253
+ ]);
254
+ config.env.GLM_API_KEY = answers.GLM_API_KEY.trim();
255
+ await saveHomeTransConfig(config);
307
256
  }
308
- if (templateBackupName) {
309
- return `${UI_ALIGN_SKILL}/config.json ${what} refreshed (kept local file; old template backed up: ${templateBackupName})`;
257
+ catch (err) {
258
+ if (isPromptAbort(err))
259
+ abortInit();
260
+ console.log(chalk.yellow(' UI migration configuration skipped (non-interactive mode).'));
310
261
  }
311
- return seeded
312
- ? `${UI_ALIGN_SKILL}/config.json seeded + ${what} filled`
313
- : `${UI_ALIGN_SKILL}/config.json ${what} refreshed`;
314
262
  }
315
263
  async function installSkillsTo(skillsRoot, targetDir, skipNames = new Set()) {
316
264
  let entries;
@@ -438,7 +386,7 @@ async function confirmOverwrite(editorName, skillConflicts, agentConflicts) {
438
386
  return true;
439
387
  }
440
388
  }
441
- async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosSdkDir, result) {
389
+ async function installForEditor(editor, skillsRoot, agentsRoot, result) {
442
390
  const marker = expandHome(editor.markerDir);
443
391
  if (marker && !(await dirExists(marker))) {
444
392
  result.skipped.push(`${editor.name} (not installed)`);
@@ -461,9 +409,6 @@ async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosS
461
409
  if (skillConflicts.length > 0 || agentConflicts.length > 0) {
462
410
  overwrite = await confirmOverwrite(editor.name, skillConflicts, agentConflicts);
463
411
  }
464
- // Snapshot the installed ui-align template BEFORE the skills copy updates
465
- // it — refreshUiAlignConfig uses it to detect template drift.
466
- const prevUiAlignExample = await readFileIfExists(path.join(skillsDir, UI_ALIGN_SKILL, 'config-example.json'));
467
412
  // N = incremental install: keep the conflicting items untouched, still copy
468
413
  // everything that doesn't exist in the target dirs yet.
469
414
  const skipSkills = overwrite ? new Set() : new Set(skillConflicts);
@@ -478,19 +423,7 @@ async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosS
478
423
  result.errors.push(`${editor.name} skills: ${err.message}`);
479
424
  }
480
425
  if (!overwrite) {
481
- result.skipped.push(`${editor.name} existing kept: ${skipSkills.size} skills, ${skipAgents.size} agents (new items installed, API-key configs refreshed)`);
482
- }
483
- // Seed / refresh the incremental-ui-align per-skill config.json with the
484
- // GLM key + SDK dir from ~/.hometrans/config.json (existing files: key-only
485
- // update). Runs regardless of the overwrite decision above.
486
- try {
487
- const status = await refreshUiAlignConfig(skillsDir, glmApiKey, hmosSdkDir, prevUiAlignExample);
488
- if (status) {
489
- result.configured.push(`${editor.name} ${status}`);
490
- }
491
- }
492
- catch (err) {
493
- result.errors.push(`${editor.name} ${UI_ALIGN_SKILL}/config.json: ${err.message}`);
426
+ result.skipped.push(`${editor.name} existing kept: ${skipSkills.size} skills, ${skipAgents.size} agents (new items installed)`);
494
427
  }
495
428
  try {
496
429
  const agents = await installAgentsTo(agentsRoot, agentsDir, skipAgents);
@@ -654,8 +587,8 @@ async function detectEnvironment(spec, env) {
654
587
  const isWin = process.platform === 'win32';
655
588
  const baseDir = (key) => key === 'DEVECO_SDK_HOME'
656
589
  ? env.DEVECO_SDK_HOME
657
- : key === 'DEVECO_PATH'
658
- ? env.DEVECO_PATH
590
+ : key === 'DEVECO_HOME'
591
+ ? env.DEVECO_HOME
659
592
  : '';
660
593
  for (const [id, tool] of Object.entries(spec.tools)) {
661
594
  if (tool.source === 'env-dir') {
@@ -815,6 +748,82 @@ async function detectInstalledEditors(editors) {
815
748
  }
816
749
  return status;
817
750
  }
751
+ /**
752
+ * Config env keys that are useful as real OS environment variables (the SDK
753
+ * paths + tool dir + GLM key). PATH entries for node/java/hdc are not included
754
+ * here — those are documented as manual PATH setup.
755
+ *
756
+ * TEST_API_KEY is intentionally absent: the self-test api_key now lives in the
757
+ * `autotest` block of config.json (configured by the Test Configuration step)
758
+ * and is read from there by self_test_runner.py — never from the system env.
759
+ * GLM_API_KEY stays, because the UI-alignment phone-agent reads it from the OS
760
+ * environment at run time.
761
+ */
762
+ export const MACHINE_ENV_KEYS = [
763
+ 'DEVECO_SDK_HOME',
764
+ 'DEVECO_HOME',
765
+ 'OHOS_SDK_PATH',
766
+ 'HMS_SDK_PATH',
767
+ 'HOMETRANS_TOOL_PATH',
768
+ 'GLM_API_KEY',
769
+ ];
770
+ /** Mask API-key values for display; show paths in full. */
771
+ function maskEnvValue(name, value) {
772
+ if (/_API_KEY$/.test(name) && value.length > 0) {
773
+ return value.length <= 8 ? '***' : `${value.slice(0, 4)}***${value.slice(-4)}`;
774
+ }
775
+ return value;
776
+ }
777
+ /**
778
+ * Show the resolved config values that would become machine environment
779
+ * variables, then ask Y/N before writing them (Windows: setx; macOS/Linux:
780
+ * shell rc block). Non-interactive shells skip without changing anything.
781
+ * Re-throws ExitPromptError so the caller can abort init on Ctrl-C.
782
+ */
783
+ async function maybePersistMachineEnv(env) {
784
+ const vars = MACHINE_ENV_KEYS.map((k) => [k, (env[k] ?? '').trim()]).filter(([, value]) => value.length > 0);
785
+ if (vars.length === 0)
786
+ return;
787
+ console.log('');
788
+ console.log(chalk.blue(' Add these to your machine environment variables?'));
789
+ console.log(chalk.gray(process.platform === 'win32'
790
+ ? ' (Windows: persisted via setx to your user environment)'
791
+ : ' (macOS/Linux: written as an export block to your shell profile)'));
792
+ for (const [name, value] of vars) {
793
+ console.log(` ${chalk.cyan(name)} = ${maskEnvValue(name, value)}`);
794
+ }
795
+ console.log('');
796
+ console.log(chalk.yellow(' Recommended: skills and MCP tools read these from the OS environment at runtime.'));
797
+ console.log(chalk.gray(' If you choose No, they are NOT set as system variables — skills/tools that rely on\n' +
798
+ ' them (self-test, UI alignment, commit-context analysis) may fail until you set them\n' +
799
+ ' yourself or re-run `ht init`.'));
800
+ let confirmed;
801
+ try {
802
+ const answers = await inquirer.prompt([
803
+ {
804
+ type: 'confirm',
805
+ name: 'persist',
806
+ message: 'Write these environment variables to this machine?',
807
+ default: true,
808
+ },
809
+ ]);
810
+ confirmed = answers.persist;
811
+ }
812
+ catch (err) {
813
+ if (isPromptAbort(err))
814
+ throw err;
815
+ console.log(chalk.yellow(' Non-interactive: skipped (set them manually if needed).'));
816
+ return;
817
+ }
818
+ if (!confirmed) {
819
+ console.log(chalk.yellow(' Skipped — no system environment variables were set.'));
820
+ console.log(chalk.gray(' Skills/MCP tools that need these (e.g. SDK paths, API keys) will not find them\n' +
821
+ ' automatically. Set them manually or re-run `ht init` to enable those features.'));
822
+ return;
823
+ }
824
+ const note = await persistEnvVars(vars);
825
+ console.log(chalk.green(` + Environment variables ${note}`));
826
+ }
818
827
  export async function initCommand(options = {}) {
819
828
  ensureChalkColor();
820
829
  const banner = figlet.textSync('HomeTrans', { font: 'Standard' });
@@ -833,7 +842,16 @@ export async function initCommand(options = {}) {
833
842
  process.exitCode = 1;
834
843
  return;
835
844
  }
845
+ // Generate an initial ~/.hometrans/config.json up front (first-use scenario):
846
+ // loadHomeTransConfig writes a complete default file (editors + env + autotest)
847
+ // when none exists, and saveHomeTransConfig flushes it again to be certain it is
848
+ // on disk before any later step (e.g. Test Configuration) points the user at it.
849
+ const configPath = getConfigPath();
850
+ const configExisted = await pathExists(configPath);
836
851
  const config = await loadHomeTransConfig();
852
+ await saveHomeTransConfig(config);
853
+ console.log(chalk.green(` ${configExisted ? 'Using' : 'Initialized'} config: ${configPath}`));
854
+ console.log('');
837
855
  const { editors } = config;
838
856
  const installedStatus = await detectInstalledEditors(editors);
839
857
  let selectedEditors;
@@ -889,7 +907,7 @@ export async function initCommand(options = {}) {
889
907
  console.log(chalk.yellow('\n No editors selected. Nothing to do.\n'));
890
908
  return;
891
909
  }
892
- // --- User parameters (SDK paths, test API key) ---
910
+ // --- User parameters (SDK paths) ---
893
911
  console.log('');
894
912
  console.log(chalk.blue(' Parameter Configuration'));
895
913
  console.log(chalk.gray(' (press Enter to keep current value or leave empty)\n'));
@@ -901,33 +919,19 @@ export async function initCommand(options = {}) {
901
919
  message: 'DEVECO_SDK_HOME (DevEco Studio SDK dir):',
902
920
  default: config.env.DEVECO_SDK_HOME || undefined,
903
921
  },
904
- {
905
- type: 'input',
906
- name: 'TEST_API_KEY',
907
- message: 'TEST_API_KEY (LLM API key used by the integration-test agent to run test cases):',
908
- default: config.env.TEST_API_KEY || undefined,
909
- },
910
- {
911
- type: 'input',
912
- name: 'GLM_API_KEY',
913
- message: 'GLM_API_KEY (LLM API key for the GLM phone-agent used in UI alignment):',
914
- default: config.env.GLM_API_KEY || undefined,
915
- },
916
922
  ]);
917
923
  const sdk = deriveSdkPaths(answers.DEVECO_SDK_HOME);
918
924
  config.env.DEVECO_SDK_HOME = sdk.DEVECO_SDK_HOME;
919
- config.env.DEVECO_PATH = sdk.DEVECO_PATH;
925
+ config.env.DEVECO_HOME = sdk.DEVECO_HOME;
920
926
  config.env.OHOS_SDK_PATH = sdk.OHOS_SDK_PATH;
921
927
  config.env.HMS_SDK_PATH = sdk.HMS_SDK_PATH;
922
- config.env.TEST_API_KEY = answers.TEST_API_KEY.trim();
923
- config.env.GLM_API_KEY = answers.GLM_API_KEY.trim();
924
928
  await saveHomeTransConfig(config);
925
929
  // Echo the derived paths and validate each exists on disk; missing ones are
926
930
  // a strong signal of a typo'd DEVECO_SDK_HOME or a broken DevEco install.
927
931
  if (sdk.DEVECO_SDK_HOME) {
928
932
  const checks = [
929
933
  ['DEVECO_SDK_HOME', sdk.DEVECO_SDK_HOME],
930
- ['DEVECO_PATH', sdk.DEVECO_PATH],
934
+ ['DEVECO_HOME', sdk.DEVECO_HOME],
931
935
  ['OHOS_SDK_PATH', sdk.OHOS_SDK_PATH],
932
936
  ['HMS_SDK_PATH', sdk.HMS_SDK_PATH],
933
937
  ];
@@ -955,6 +959,13 @@ export async function initCommand(options = {}) {
955
959
  abortInit();
956
960
  console.log(chalk.yellow(' Parameter prompts skipped (non-interactive mode).'));
957
961
  }
962
+ // UI Migration Configuration — configure the GLM key for the UI-alignment
963
+ // phone-agent. Stays in config.env (read from the OS env at run time).
964
+ await promptUiAlignConfig(config);
965
+ // Test Configuration — configure the autotest block (self-test api_key etc.)
966
+ // in config.json. Replaces the old TEST_API_KEY env prompt; never written to
967
+ // the system environment.
968
+ await promptTestConfig(config);
958
969
  // Detect external tools (adb / hdc / python / uv / java / gitnexus + DevEco)
959
970
  // and report which skills/agents are impacted by anything missing.
960
971
  try {
@@ -963,19 +974,15 @@ export async function initCommand(options = {}) {
963
974
  catch (err) {
964
975
  console.log(chalk.yellow(` ! environment check skipped: ${err.message}`));
965
976
  }
966
- // Snapshot the currently installed autotest template BEFORE the tools copy
967
- // overwrites it refreshAutotestConfig compares it with the new template
968
- // to detect drift and offer a config.yaml regeneration.
969
- const prevAutotestExample = await readFileIfExists(path.join(resolveAutotestDir(config.env.TOOL_PATH || getToolsDir()), 'config.yaml.example'));
970
- // Copy bundled tools/ into ~/.hometrans/tools and record env.TOOL_PATH.
971
- // Must run before the autotest config step below, which seeds config.yaml
972
- // into the installed tools dir (the location agents read via env.TOOL_PATH).
977
+ // Copy bundled tools/ into ~/.hometrans/tools and record env.HOMETRANS_TOOL_PATH.
978
+ // self_test_runner.py (under the installed tools dir) reads the autotest
979
+ // config from ~/.hometrans/config.json at run time see promptTestConfig.
973
980
  let installedToolPath = null;
974
981
  try {
975
982
  installedToolPath = await installTools(toolsRoot, config);
976
983
  if (installedToolPath) {
977
984
  console.log(chalk.green(` + tools copied -> ${prettyHome(installedToolPath)}`));
978
- console.log(chalk.gray(` TOOL_PATH set in ${prettyHome(getConfigPath())}`));
985
+ console.log(chalk.gray(` HOMETRANS_TOOL_PATH set in ${prettyHome(getConfigPath())}`));
979
986
  }
980
987
  }
981
988
  catch (err) {
@@ -992,32 +999,25 @@ export async function initCommand(options = {}) {
992
999
  catch (err) {
993
1000
  console.log(chalk.red(` ! env-requirements.json copy: ${err.message}`));
994
1001
  }
995
- // Initialize / refresh autotest config.yaml from the example template,
996
- // inside the installed tools dir (<TOOL_PATH>/test-tools/autotest).
1002
+ // (The autotest block in config.json was already configured by the Test
1003
+ // Configuration step above; self_test_runner.py reads it at run time.)
1004
+ // Offer to persist the resolved config values as real OS environment
1005
+ // variables (Windows: setx; macOS/Linux: shell rc block). Shows what will be
1006
+ // written and asks Y/N first; runs after HOMETRANS_TOOL_PATH is finalized above.
997
1007
  try {
998
- const toolPath = installedToolPath ?? config.env.TOOL_PATH;
999
- if (toolPath) {
1000
- const autotestDir = resolveAutotestDir(toolPath);
1001
- const status = await refreshAutotestConfig(autotestDir, config.env.TEST_API_KEY, prevAutotestExample);
1002
- if (status) {
1003
- console.log(chalk.green(` + ${status}`));
1004
- console.log(chalk.gray(` ${path.join(autotestDir, 'config.yaml')}`));
1005
- }
1006
- }
1008
+ await maybePersistMachineEnv(config.env);
1007
1009
  }
1008
1010
  catch (err) {
1009
- console.log(chalk.red(` ! autotest config.yaml: ${err.message}`));
1011
+ if (isPromptAbort(err))
1012
+ abortInit();
1013
+ console.log(chalk.red(` ! environment variables: ${err.message}`));
1010
1014
  }
1011
1015
  console.log('');
1012
1016
  const editorsToSetup = editors.filter((e) => selectedEditors.includes(e.name));
1013
1017
  const result = { configured: [], skipped: [], errors: [] };
1014
- // hmos_sdk_dir consumed by incremental-ui-align = <DEVECO_SDK_HOME>/default.
1015
- const hmosSdkDir = config.env.DEVECO_SDK_HOME
1016
- ? path.join(config.env.DEVECO_SDK_HOME, 'default')
1017
- : '';
1018
1018
  for (const editor of editorsToSetup) {
1019
1019
  console.log(chalk.blue(` Configuring ${editor.name}...`));
1020
- await installForEditor(editor, skillsRoot, agentsRoot, config.env.GLM_API_KEY, hmosSdkDir, result);
1020
+ await installForEditor(editor, skillsRoot, agentsRoot, result);
1021
1021
  }
1022
1022
  await setupMcpForAllEditors(editorsToSetup, result);
1023
1023
  console.log('');