@buaa_smat/hometrans 0.1.13 → 0.1.15

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 (88) hide show
  1. package/README.md +194 -136
  2. package/agents/build-fixer.md +38 -48
  3. package/agents/code-reviewer.md +20 -20
  4. package/agents/logic-coder.md +8 -8
  5. package/agents/logic-context-builder.md +5 -5
  6. package/agents/review-fixer.md +16 -16
  7. package/agents/self-test-fixer.md +15 -15
  8. package/agents/self-tester.md +56 -55
  9. package/agents/spec-generator.md +16 -16
  10. package/dist/cli/config-store.js +120 -9
  11. package/dist/cli/config.js +4 -4
  12. package/dist/cli/env-vars.js +129 -0
  13. package/dist/cli/init.js +315 -276
  14. package/dist/cli/uninstall.js +152 -17
  15. package/dist/context/index.js +10 -197
  16. package/env-requirements.json +181 -181
  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/migration_process.svg +94 -0
  22. package/resource/migration_process_transparent.svg +93 -0
  23. package/resource/set_env.png +0 -0
  24. package/resource/ui_align_config.png +0 -0
  25. package/skills/hmos-batch-ui-align/SKILL.md +10 -0
  26. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +180 -180
  27. package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -2533
  28. package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -555
  29. package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -117
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. package/skills/hmos-batch-ui-align/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -911
  41. 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
  42. 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
  43. package/skills/hmos-convert-pipeline/SKILL.md +63 -49
  44. package/skills/hmos-fix-build-errors/SKILL.md +5 -6
  45. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +219 -219
  46. package/skills/hmos-fix-build-errors/references/known-patterns.md +157 -157
  47. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +131 -131
  48. package/skills/hmos-incremental-ui-align/{readme.md → README.md} +28 -21
  49. package/skills/hmos-incremental-ui-align/SKILL.md +46 -27
  50. package/skills/hmos-incremental-ui-align/diff_analysis.md +52 -52
  51. package/skills/hmos-incremental-ui-align/page_align.md +62 -62
  52. package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +2 -2
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +3 -3
  67. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -2533
  68. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -555
  69. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -117
  70. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
  71. package/skills/hmos-integration-test/{readme.md → README.md} +38 -38
  72. package/skills/hmos-integration-test/SKILL.md +63 -52
  73. package/skills/hmos-resources-convert/SKILL.md +5 -5
  74. package/skills/hmos-resources-convert/references/conversion-rules.md +663 -663
  75. package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -388
  76. package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -457
  77. package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -513
  78. package/skills/hmos-spec-generate/SKILL.md +19 -19
  79. package/skills/hmos-spec-generate/references/android-platform-tokens.md +105 -105
  80. package/skills/hmos-spec-generate/references/spec-sample-1.md +78 -78
  81. package/skills/hmos-spec-generate/references/spec-sample-2.md +58 -58
  82. package/skills/hmos-spec-generate/references/spec-sample-3.md +116 -116
  83. package/skills/hmos-spec-generate/references/step4-report-template.md +33 -33
  84. package/tools/test-tools/autotest/README.md +33 -17
  85. package/tools/test-tools/autotest/self_test_runner.py +109 -15
  86. package/resource/hometrans_config.png +0 -0
  87. package/skills/hmos-incremental-ui-align/config-example.json +0 -11
  88. 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;
@@ -339,7 +287,46 @@ async function installSkillsTo(skillsRoot, targetDir, skipNames = new Set()) {
339
287
  }
340
288
  return installed;
341
289
  }
342
- async function installAgentsTo(agentsRoot, targetDir, skipNames = new Set()) {
290
+ /**
291
+ * Strip the `color:` line from a markdown agent's YAML frontmatter. DevEco Code's
292
+ * config schema rejects named colors (only hex `#RRGGBB` or a theme token is
293
+ * allowed) and aborts startup, so for that editor we drop the field entirely —
294
+ * agents work fine without it. Only the leading frontmatter block is touched.
295
+ */
296
+ export function stripAgentMarkdownColor(content) {
297
+ const fm = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
298
+ if (!fm)
299
+ return content;
300
+ const block = fm[0];
301
+ const stripped = block.replace(/^color:[ \t]*\S.*\r?\n/m, '');
302
+ return stripped === block ? content : content.replace(block, stripped);
303
+ }
304
+ /** Copy one agent file, stripping the frontmatter color from `.md` when requested. */
305
+ async function copyAgentFile(src, dest, stripColor) {
306
+ if (stripColor && src.endsWith('.md')) {
307
+ const raw = await fs.readFile(src, 'utf-8');
308
+ await fs.writeFile(dest, stripAgentMarkdownColor(raw), 'utf-8');
309
+ }
310
+ else {
311
+ await fs.copyFile(src, dest);
312
+ }
313
+ }
314
+ /** Recursively copy an agent subdirectory, stripping colors from `.md` files. */
315
+ async function copyAgentDirRecursive(src, dest, stripColor) {
316
+ await fs.mkdir(dest, { recursive: true });
317
+ const entries = await fs.readdir(src, { withFileTypes: true });
318
+ for (const entry of entries) {
319
+ const srcPath = path.join(src, entry.name);
320
+ const destPath = path.join(dest, entry.name);
321
+ if (entry.isDirectory()) {
322
+ await copyAgentDirRecursive(srcPath, destPath, stripColor);
323
+ }
324
+ else {
325
+ await copyAgentFile(srcPath, destPath, stripColor);
326
+ }
327
+ }
328
+ }
329
+ async function installAgentsTo(agentsRoot, targetDir, skipNames = new Set(), stripColor = false) {
343
330
  let entries;
344
331
  try {
345
332
  entries = await fs.readdir(agentsRoot, { withFileTypes: true });
@@ -355,10 +342,10 @@ async function installAgentsTo(agentsRoot, targetDir, skipNames = new Set()) {
355
342
  const srcPath = path.join(agentsRoot, entry.name);
356
343
  const destPath = path.join(targetDir, entry.name);
357
344
  if (entry.isDirectory()) {
358
- await copyDirRecursive(srcPath, destPath);
345
+ await copyAgentDirRecursive(srcPath, destPath, stripColor);
359
346
  }
360
347
  else if (entry.isFile()) {
361
- await fs.copyFile(srcPath, destPath);
348
+ await copyAgentFile(srcPath, destPath, stripColor);
362
349
  if (entry.name.endsWith('.md')) {
363
350
  installed.push(entry.name.slice(0, -3));
364
351
  }
@@ -438,7 +425,7 @@ async function confirmOverwrite(editorName, skillConflicts, agentConflicts) {
438
425
  return true;
439
426
  }
440
427
  }
441
- async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosSdkDir, result) {
428
+ async function installForEditor(editor, skillsRoot, agentsRoot, result) {
442
429
  const marker = expandHome(editor.markerDir);
443
430
  if (marker && !(await dirExists(marker))) {
444
431
  result.skipped.push(`${editor.name} (not installed)`);
@@ -461,9 +448,6 @@ async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosS
461
448
  if (skillConflicts.length > 0 || agentConflicts.length > 0) {
462
449
  overwrite = await confirmOverwrite(editor.name, skillConflicts, agentConflicts);
463
450
  }
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
451
  // N = incremental install: keep the conflicting items untouched, still copy
468
452
  // everything that doesn't exist in the target dirs yet.
469
453
  const skipSkills = overwrite ? new Set() : new Set(skillConflicts);
@@ -478,22 +462,10 @@ async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosS
478
462
  result.errors.push(`${editor.name} skills: ${err.message}`);
479
463
  }
480
464
  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}`);
465
+ result.skipped.push(`${editor.name} existing kept: ${skipSkills.size} skills, ${skipAgents.size} agents (new items installed)`);
494
466
  }
495
467
  try {
496
- const agents = await installAgentsTo(agentsRoot, agentsDir, skipAgents);
468
+ const agents = await installAgentsTo(agentsRoot, agentsDir, skipAgents, editor.stripAgentColors ?? false);
497
469
  if (agents.length > 0) {
498
470
  result.configured.push(`${editor.name} agents (${agents.length} -> ${prettyHome(agentsDir)})`);
499
471
  }
@@ -654,8 +626,8 @@ async function detectEnvironment(spec, env) {
654
626
  const isWin = process.platform === 'win32';
655
627
  const baseDir = (key) => key === 'DEVECO_SDK_HOME'
656
628
  ? env.DEVECO_SDK_HOME
657
- : key === 'DEVECO_PATH'
658
- ? env.DEVECO_PATH
629
+ : key === 'DEVECO_HOME'
630
+ ? env.DEVECO_HOME
659
631
  : '';
660
632
  for (const [id, tool] of Object.entries(spec.tools)) {
661
633
  if (tool.source === 'env-dir') {
@@ -815,6 +787,82 @@ async function detectInstalledEditors(editors) {
815
787
  }
816
788
  return status;
817
789
  }
790
+ /**
791
+ * Config env keys that are useful as real OS environment variables (the SDK
792
+ * paths + tool dir + GLM key). PATH entries for node/java/hdc are not included
793
+ * here — those are documented as manual PATH setup.
794
+ *
795
+ * TEST_API_KEY is intentionally absent: the self-test api_key now lives in the
796
+ * `autotest` block of config.json (configured by the Test Configuration step)
797
+ * and is read from there by self_test_runner.py — never from the system env.
798
+ * GLM_API_KEY stays, because the UI-alignment phone-agent reads it from the OS
799
+ * environment at run time.
800
+ */
801
+ export const MACHINE_ENV_KEYS = [
802
+ 'DEVECO_SDK_HOME',
803
+ 'DEVECO_HOME',
804
+ 'OHOS_SDK_PATH',
805
+ 'HMS_SDK_PATH',
806
+ 'HOMETRANS_TOOL_PATH',
807
+ 'GLM_API_KEY',
808
+ ];
809
+ /** Mask API-key values for display; show paths in full. */
810
+ function maskEnvValue(name, value) {
811
+ if (/_API_KEY$/.test(name) && value.length > 0) {
812
+ return value.length <= 8 ? '***' : `${value.slice(0, 4)}***${value.slice(-4)}`;
813
+ }
814
+ return value;
815
+ }
816
+ /**
817
+ * Show the resolved config values that would become machine environment
818
+ * variables, then ask Y/N before writing them (Windows: setx; macOS/Linux:
819
+ * shell rc block). Non-interactive shells skip without changing anything.
820
+ * Re-throws ExitPromptError so the caller can abort init on Ctrl-C.
821
+ */
822
+ async function maybePersistMachineEnv(env) {
823
+ const vars = MACHINE_ENV_KEYS.map((k) => [k, (env[k] ?? '').trim()]).filter(([, value]) => value.length > 0);
824
+ if (vars.length === 0)
825
+ return;
826
+ console.log('');
827
+ console.log(chalk.blue(' Add these to your machine environment variables?'));
828
+ console.log(chalk.gray(process.platform === 'win32'
829
+ ? ' (Windows: persisted via setx to your user environment)'
830
+ : ' (macOS/Linux: written as an export block to your shell profile)'));
831
+ for (const [name, value] of vars) {
832
+ console.log(` ${chalk.cyan(name)} = ${maskEnvValue(name, value)}`);
833
+ }
834
+ console.log('');
835
+ console.log(chalk.yellow(' Recommended: skills and MCP tools read these from the OS environment at runtime.'));
836
+ console.log(chalk.gray(' If you choose No, they are NOT set as system variables — skills/tools that rely on\n' +
837
+ ' them (self-test, UI alignment, commit-context analysis) may fail until you set them\n' +
838
+ ' yourself or re-run `ht init`.'));
839
+ let confirmed;
840
+ try {
841
+ const answers = await inquirer.prompt([
842
+ {
843
+ type: 'confirm',
844
+ name: 'persist',
845
+ message: 'Write these environment variables to this machine?',
846
+ default: true,
847
+ },
848
+ ]);
849
+ confirmed = answers.persist;
850
+ }
851
+ catch (err) {
852
+ if (isPromptAbort(err))
853
+ throw err;
854
+ console.log(chalk.yellow(' Non-interactive: skipped (set them manually if needed).'));
855
+ return;
856
+ }
857
+ if (!confirmed) {
858
+ console.log(chalk.yellow(' Skipped — no system environment variables were set.'));
859
+ console.log(chalk.gray(' Skills/MCP tools that need these (e.g. SDK paths, API keys) will not find them\n' +
860
+ ' automatically. Set them manually or re-run `ht init` to enable those features.'));
861
+ return;
862
+ }
863
+ const note = await persistEnvVars(vars);
864
+ console.log(chalk.green(` + Environment variables ${note}`));
865
+ }
818
866
  export async function initCommand(options = {}) {
819
867
  ensureChalkColor();
820
868
  const banner = figlet.textSync('HomeTrans', { font: 'Standard' });
@@ -833,7 +881,16 @@ export async function initCommand(options = {}) {
833
881
  process.exitCode = 1;
834
882
  return;
835
883
  }
884
+ // Generate an initial ~/.hometrans/config.json up front (first-use scenario):
885
+ // loadHomeTransConfig writes a complete default file (editors + env + autotest)
886
+ // when none exists, and saveHomeTransConfig flushes it again to be certain it is
887
+ // on disk before any later step (e.g. Test Configuration) points the user at it.
888
+ const configPath = getConfigPath();
889
+ const configExisted = await pathExists(configPath);
836
890
  const config = await loadHomeTransConfig();
891
+ await saveHomeTransConfig(config);
892
+ console.log(chalk.green(` ${configExisted ? 'Using' : 'Initialized'} config: ${configPath}`));
893
+ console.log('');
837
894
  const { editors } = config;
838
895
  const installedStatus = await detectInstalledEditors(editors);
839
896
  let selectedEditors;
@@ -889,7 +946,7 @@ export async function initCommand(options = {}) {
889
946
  console.log(chalk.yellow('\n No editors selected. Nothing to do.\n'));
890
947
  return;
891
948
  }
892
- // --- User parameters (SDK paths, test API key) ---
949
+ // --- User parameters (SDK paths) ---
893
950
  console.log('');
894
951
  console.log(chalk.blue(' Parameter Configuration'));
895
952
  console.log(chalk.gray(' (press Enter to keep current value or leave empty)\n'));
@@ -901,33 +958,19 @@ export async function initCommand(options = {}) {
901
958
  message: 'DEVECO_SDK_HOME (DevEco Studio SDK dir):',
902
959
  default: config.env.DEVECO_SDK_HOME || undefined,
903
960
  },
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
961
  ]);
917
962
  const sdk = deriveSdkPaths(answers.DEVECO_SDK_HOME);
918
963
  config.env.DEVECO_SDK_HOME = sdk.DEVECO_SDK_HOME;
919
- config.env.DEVECO_PATH = sdk.DEVECO_PATH;
964
+ config.env.DEVECO_HOME = sdk.DEVECO_HOME;
920
965
  config.env.OHOS_SDK_PATH = sdk.OHOS_SDK_PATH;
921
966
  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
967
  await saveHomeTransConfig(config);
925
968
  // Echo the derived paths and validate each exists on disk; missing ones are
926
969
  // a strong signal of a typo'd DEVECO_SDK_HOME or a broken DevEco install.
927
970
  if (sdk.DEVECO_SDK_HOME) {
928
971
  const checks = [
929
972
  ['DEVECO_SDK_HOME', sdk.DEVECO_SDK_HOME],
930
- ['DEVECO_PATH', sdk.DEVECO_PATH],
973
+ ['DEVECO_HOME', sdk.DEVECO_HOME],
931
974
  ['OHOS_SDK_PATH', sdk.OHOS_SDK_PATH],
932
975
  ['HMS_SDK_PATH', sdk.HMS_SDK_PATH],
933
976
  ];
@@ -955,6 +998,13 @@ export async function initCommand(options = {}) {
955
998
  abortInit();
956
999
  console.log(chalk.yellow(' Parameter prompts skipped (non-interactive mode).'));
957
1000
  }
1001
+ // UI Migration Configuration — configure the GLM key for the UI-alignment
1002
+ // phone-agent. Stays in config.env (read from the OS env at run time).
1003
+ await promptUiAlignConfig(config);
1004
+ // Test Configuration — configure the autotest block (self-test api_key etc.)
1005
+ // in config.json. Replaces the old TEST_API_KEY env prompt; never written to
1006
+ // the system environment.
1007
+ await promptTestConfig(config);
958
1008
  // Detect external tools (adb / hdc / python / uv / java / gitnexus + DevEco)
959
1009
  // and report which skills/agents are impacted by anything missing.
960
1010
  try {
@@ -963,19 +1013,15 @@ export async function initCommand(options = {}) {
963
1013
  catch (err) {
964
1014
  console.log(chalk.yellow(` ! environment check skipped: ${err.message}`));
965
1015
  }
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).
1016
+ // Copy bundled tools/ into ~/.hometrans/tools and record env.HOMETRANS_TOOL_PATH.
1017
+ // self_test_runner.py (under the installed tools dir) reads the autotest
1018
+ // config from ~/.hometrans/config.json at run time see promptTestConfig.
973
1019
  let installedToolPath = null;
974
1020
  try {
975
1021
  installedToolPath = await installTools(toolsRoot, config);
976
1022
  if (installedToolPath) {
977
1023
  console.log(chalk.green(` + tools copied -> ${prettyHome(installedToolPath)}`));
978
- console.log(chalk.gray(` TOOL_PATH set in ${prettyHome(getConfigPath())}`));
1024
+ console.log(chalk.gray(` HOMETRANS_TOOL_PATH set in ${prettyHome(getConfigPath())}`));
979
1025
  }
980
1026
  }
981
1027
  catch (err) {
@@ -992,32 +1038,25 @@ export async function initCommand(options = {}) {
992
1038
  catch (err) {
993
1039
  console.log(chalk.red(` ! env-requirements.json copy: ${err.message}`));
994
1040
  }
995
- // Initialize / refresh autotest config.yaml from the example template,
996
- // inside the installed tools dir (<TOOL_PATH>/test-tools/autotest).
1041
+ // (The autotest block in config.json was already configured by the Test
1042
+ // Configuration step above; self_test_runner.py reads it at run time.)
1043
+ // Offer to persist the resolved config values as real OS environment
1044
+ // variables (Windows: setx; macOS/Linux: shell rc block). Shows what will be
1045
+ // written and asks Y/N first; runs after HOMETRANS_TOOL_PATH is finalized above.
997
1046
  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
- }
1047
+ await maybePersistMachineEnv(config.env);
1007
1048
  }
1008
1049
  catch (err) {
1009
- console.log(chalk.red(` ! autotest config.yaml: ${err.message}`));
1050
+ if (isPromptAbort(err))
1051
+ abortInit();
1052
+ console.log(chalk.red(` ! environment variables: ${err.message}`));
1010
1053
  }
1011
1054
  console.log('');
1012
1055
  const editorsToSetup = editors.filter((e) => selectedEditors.includes(e.name));
1013
1056
  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
1057
  for (const editor of editorsToSetup) {
1019
1058
  console.log(chalk.blue(` Configuring ${editor.name}...`));
1020
- await installForEditor(editor, skillsRoot, agentsRoot, config.env.GLM_API_KEY, hmosSdkDir, result);
1059
+ await installForEditor(editor, skillsRoot, agentsRoot, result);
1021
1060
  }
1022
1061
  await setupMcpForAllEditors(editorsToSetup, result);
1023
1062
  console.log('');