@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
@@ -12,11 +12,11 @@ import os from 'node:os';
12
12
  export function deriveSdkPaths(devecoSdkHome) {
13
13
  const home = devecoSdkHome.trim().replace(/[\\/]+$/, '');
14
14
  if (!home) {
15
- return { DEVECO_SDK_HOME: '', DEVECO_PATH: '', OHOS_SDK_PATH: '', HMS_SDK_PATH: '' };
15
+ return { DEVECO_SDK_HOME: '', DEVECO_HOME: '', OHOS_SDK_PATH: '', HMS_SDK_PATH: '' };
16
16
  }
17
17
  return {
18
18
  DEVECO_SDK_HOME: home,
19
- DEVECO_PATH: path.dirname(home),
19
+ DEVECO_HOME: path.dirname(home),
20
20
  OHOS_SDK_PATH: path.join(home, 'default', 'openharmony', 'ets'),
21
21
  HMS_SDK_PATH: path.join(home, 'default', 'hms', 'ets'),
22
22
  };
@@ -139,12 +139,39 @@ async function fileExists(p) {
139
139
  export function defaultEnv() {
140
140
  return {
141
141
  DEVECO_SDK_HOME: '',
142
- DEVECO_PATH: '',
142
+ DEVECO_HOME: '',
143
143
  OHOS_SDK_PATH: '',
144
144
  HMS_SDK_PATH: '',
145
145
  TEST_API_KEY: '',
146
146
  GLM_API_KEY: '',
147
- TOOL_PATH: '',
147
+ HOMETRANS_TOOL_PATH: '',
148
+ };
149
+ }
150
+ /** AutoTest 自测默认配置(单层模式)。`ht init` 的「测试配置」环节会填充 api_key。 */
151
+ export function defaultAutotest() {
152
+ return {
153
+ unified_model: {
154
+ name: '',
155
+ base_url: '',
156
+ api_key: '',
157
+ provider: '',
158
+ temperature: 0.1,
159
+ top_p: 0.9,
160
+ frequency_penalty: 0.5,
161
+ },
162
+ device: {
163
+ device_sn: null,
164
+ ip: '127.0.0.1',
165
+ port: 8710,
166
+ device_log_level: 'debug',
167
+ },
168
+ agent: {
169
+ max_steps: 50,
170
+ verbose: true,
171
+ lang: 'zh',
172
+ enable_preprocess: false,
173
+ debug_mode: null,
174
+ },
148
175
  };
149
176
  }
150
177
  export async function loadHomeTransConfig() {
@@ -153,6 +180,7 @@ export async function loadHomeTransConfig() {
153
180
  const config = {
154
181
  editors: defaultEditors(),
155
182
  env: defaultEnv(),
183
+ autotest: defaultAutotest(),
156
184
  };
157
185
  await fs.mkdir(getConfigDir(), { recursive: true });
158
186
  await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
@@ -173,7 +201,7 @@ export async function loadHomeTransConfig() {
173
201
  const anyParsed = parsed;
174
202
  // Migrate legacy fields into `env`:
175
203
  // - `sdkPaths` / `params` held OHOS_SDK_PATH / HMS_SDK_PATH / TEST_API_KEY.
176
- // - `tool_path` held the tools dir, now env.TOOL_PATH.
204
+ // - `tool_path` held the tools dir, now env.HOMETRANS_TOOL_PATH.
177
205
  const legacyParams = (anyParsed.params ?? anyParsed.sdkPaths);
178
206
  const legacyToolPath = typeof anyParsed.tool_path === 'string' ? anyParsed.tool_path : undefined;
179
207
  config.env = {
@@ -181,8 +209,8 @@ export async function loadHomeTransConfig() {
181
209
  ...(legacyParams ?? {}),
182
210
  ...(config.env ?? {}),
183
211
  };
184
- if (legacyToolPath && !config.env.TOOL_PATH) {
185
- config.env.TOOL_PATH = legacyToolPath;
212
+ if (legacyToolPath && !config.env.HOMETRANS_TOOL_PATH) {
213
+ config.env.HOMETRANS_TOOL_PATH = legacyToolPath;
186
214
  }
187
215
  delete anyParsed.sdkPaths;
188
216
  delete anyParsed.params;
@@ -190,6 +218,34 @@ export async function loadHomeTransConfig() {
190
218
  // SDK 路径统一迁移:老配置只有 OHOS_SDK_PATH/HMS_SDK_PATH 时反推
191
219
  // DEVECO_SDK_HOME,再用它补齐缺失的派生路径(不覆盖已有非空值)。
192
220
  let envDirty = false;
221
+ const envRecord = config.env;
222
+ // 旧字段重命名:env.DEVECO_PATH(DevEco 安装根目录)-> env.DEVECO_HOME。
223
+ // 保留旧值(DEVECO_HOME 为空时),并删除遗留键。
224
+ if ('DEVECO_PATH' in envRecord) {
225
+ const legacy = envRecord.DEVECO_PATH;
226
+ if (!config.env.DEVECO_HOME && typeof legacy === 'string') {
227
+ config.env.DEVECO_HOME = legacy;
228
+ }
229
+ delete envRecord.DEVECO_PATH;
230
+ envDirty = true;
231
+ }
232
+ // 移除已废弃的派生字段:JAVA_HOME / NODE_HOME 不再写入 env(构建时由
233
+ // build agent 自行从 DEVECO_HOME 推导),清理老配置里残留的同名键。
234
+ for (const legacyKey of ['JAVA_HOME', 'NODE_HOME']) {
235
+ if (legacyKey in envRecord) {
236
+ delete envRecord[legacyKey];
237
+ envDirty = true;
238
+ }
239
+ }
240
+ // 旧字段重命名:env.TOOL_PATH(HomeTrans 工具目录)-> env.HOMETRANS_TOOL_PATH。
241
+ if ('TOOL_PATH' in envRecord) {
242
+ const legacy = envRecord.TOOL_PATH;
243
+ if (!config.env.HOMETRANS_TOOL_PATH && typeof legacy === 'string') {
244
+ config.env.HOMETRANS_TOOL_PATH = legacy;
245
+ }
246
+ delete envRecord.TOOL_PATH;
247
+ envDirty = true;
248
+ }
193
249
  if (!config.env.DEVECO_SDK_HOME) {
194
250
  const migrated = sdkHomeFromLegacyPaths(config.env);
195
251
  if (migrated) {
@@ -199,7 +255,7 @@ export async function loadHomeTransConfig() {
199
255
  }
200
256
  if (config.env.DEVECO_SDK_HOME) {
201
257
  const derived = deriveSdkPaths(config.env.DEVECO_SDK_HOME);
202
- for (const key of ['DEVECO_PATH', 'OHOS_SDK_PATH', 'HMS_SDK_PATH']) {
258
+ for (const key of ['DEVECO_HOME', 'OHOS_SDK_PATH', 'HMS_SDK_PATH']) {
203
259
  if (!config.env[key]) {
204
260
  config.env[key] = derived[key];
205
261
  envDirty = true;
@@ -209,6 +265,26 @@ export async function loadHomeTransConfig() {
209
265
  if (envDirty) {
210
266
  await saveHomeTransConfig(config);
211
267
  }
268
+ // Seed the `autotest` block for configs created before it existed (it
269
+ // replaced test-tools/autotest/config.yaml). Only added when absent, so
270
+ // manual edits to an existing block are preserved.
271
+ if (!config.autotest || typeof config.autotest !== 'object') {
272
+ config.autotest = defaultAutotest();
273
+ await saveHomeTransConfig(config);
274
+ }
275
+ // Migrate a legacy env.TEST_API_KEY into the autotest block. The self-test
276
+ // api_key now lives in autotest.unified_model.api_key (configured by the Test
277
+ // Configuration step) and is no longer a machine env var. Carry the old value
278
+ // over (when the block still holds the placeholder) and clear it from env.
279
+ const legacyTestKey = (config.env.TEST_API_KEY || '').trim();
280
+ if (legacyTestKey) {
281
+ const um = config.autotest.unified_model;
282
+ if (um && (!um.api_key || um.api_key === 'YOUR_API_KEY_HERE')) {
283
+ um.api_key = legacyTestKey;
284
+ }
285
+ config.env.TEST_API_KEY = '';
286
+ await saveHomeTransConfig(config);
287
+ }
212
288
  // Append any default editors that this (older) config predates, matched by
213
289
  // name. Existing entries are never modified, so manual edits are preserved;
214
290
  // we only add editors the user's file is missing (e.g. CodeBuddy shipped
@@ -20,12 +20,12 @@ export async function configCommand() {
20
20
  const mask = (key) => key ? key.slice(0, 4) + '***' + key.slice(-4) : '(not set)';
21
21
  console.log(' Parameters:');
22
22
  console.log(` DEVECO_SDK_HOME : ${config.env.DEVECO_SDK_HOME || '(not set)'}`);
23
- console.log(` DEVECO_PATH : ${config.env.DEVECO_PATH || '(not set)'} (derived)`);
23
+ console.log(` DEVECO_HOME : ${config.env.DEVECO_HOME || '(not set)'} (derived)`);
24
24
  console.log(` OHOS_SDK_PATH : ${config.env.OHOS_SDK_PATH || '(not set)'} (derived)`);
25
25
  console.log(` HMS_SDK_PATH : ${config.env.HMS_SDK_PATH || '(not set)'} (derived)`);
26
- console.log(` TEST_API_KEY : ${mask(config.env.TEST_API_KEY)}`);
26
+ console.log(` autotest api_key: ${mask(config.autotest?.unified_model?.api_key ?? '')} (config.json → autotest)`);
27
27
  console.log(` GLM_API_KEY : ${mask(config.env.GLM_API_KEY)}`);
28
- console.log(` TOOL_PATH : ${config.env.TOOL_PATH || '(not set)'}`);
28
+ console.log(` HOMETRANS_TOOL_PATH : ${config.env.HOMETRANS_TOOL_PATH || '(not set)'}`);
29
29
  console.log('');
30
30
  // Full config content
31
31
  console.log(' Full Content:');
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Persist HomeTrans config values as real OS environment variables, so tools,
3
+ * shells and skills can read them straight from the environment (not only from
4
+ * ~/.hometrans/config.json).
5
+ *
6
+ * Cross-platform:
7
+ * - Windows: `setx NAME "VALUE"` — writes to the per-user environment (HKCU);
8
+ * visible in newly opened terminals.
9
+ * - macOS / Linux: a marked, idempotent block of `export NAME="VALUE"` lines
10
+ * appended to the user's shell rc file (~/.zshrc / ~/.bashrc / ~/.profile).
11
+ */
12
+ import { execFileSync } from 'node:child_process';
13
+ import fs from 'node:fs/promises';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ const BLOCK_BEGIN = '# >>> hometrans env >>>';
17
+ const BLOCK_END = '# <<< hometrans env <<<';
18
+ /** Pick the shell rc file to write exports into, based on $SHELL. */
19
+ export function unixProfilePath(homeDir, shell) {
20
+ if (shell.includes('zsh'))
21
+ return path.join(homeDir, '.zshrc');
22
+ if (shell.includes('bash'))
23
+ return path.join(homeDir, '.bashrc');
24
+ return path.join(homeDir, '.profile');
25
+ }
26
+ function escapeRegExp(s) {
27
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
28
+ }
29
+ /** Build the managed export block for POSIX shells. */
30
+ export function buildUnixBlock(vars) {
31
+ const lines = vars.map(([name, value]) => `export ${name}="${value.replace(/(["\\$`])/g, '\\$1')}"`);
32
+ return [BLOCK_BEGIN, ...lines, BLOCK_END].join('\n');
33
+ }
34
+ /** Insert or replace the marked block in `content`, returning the new content. */
35
+ export function upsertBlock(content, block) {
36
+ const re = new RegExp(`${escapeRegExp(BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(BLOCK_END)}`);
37
+ if (re.test(content))
38
+ return content.replace(re, block);
39
+ const trimmed = content.replace(/\s*$/, '');
40
+ return (trimmed ? `${trimmed}\n\n` : '') + `${block}\n`;
41
+ }
42
+ /**
43
+ * Strip the managed hometrans block (including surrounding blank lines) from
44
+ * `content`. Returns the new content and whether anything was removed.
45
+ */
46
+ export function stripBlock(content) {
47
+ const re = new RegExp(`\\n*${escapeRegExp(BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(BLOCK_END)}\\n*`);
48
+ if (!re.test(content))
49
+ return { content, changed: false };
50
+ const next = content.replace(re, '\n').replace(/\n{3,}/g, '\n\n');
51
+ return { content: next, changed: true };
52
+ }
53
+ async function persistUnix(vars) {
54
+ const profile = unixProfilePath(os.homedir(), process.env.SHELL ?? '');
55
+ let content = '';
56
+ try {
57
+ content = await fs.readFile(profile, 'utf-8');
58
+ }
59
+ catch {
60
+ content = '';
61
+ }
62
+ await fs.writeFile(profile, upsertBlock(content, buildUnixBlock(vars)), 'utf-8');
63
+ return profile;
64
+ }
65
+ function persistWindows(vars) {
66
+ for (const [name, value] of vars) {
67
+ // No shell: each value is passed as a single argv, so spaces are safe.
68
+ execFileSync('setx', [name, value], { stdio: ['ignore', 'ignore', 'ignore'] });
69
+ }
70
+ }
71
+ /**
72
+ * Persist `vars` to the machine's per-user environment. Returns a short note
73
+ * describing where they were written and how to activate them.
74
+ */
75
+ export async function persistEnvVars(vars) {
76
+ if (vars.length === 0)
77
+ return 'no values to set';
78
+ if (process.platform === 'win32') {
79
+ persistWindows(vars);
80
+ return 'written to the Windows user environment (setx) — open a new terminal to use them';
81
+ }
82
+ const profile = await persistUnix(vars);
83
+ return `written to ${profile} — run \`source ${profile}\` or open a new terminal to use them`;
84
+ }
85
+ function removeWindows(names) {
86
+ let removed = 0;
87
+ for (const name of names) {
88
+ try {
89
+ // Delete the value from the per-user environment (HKCU\Environment),
90
+ // mirroring how `setx` writes it. Missing values throw — ignore those.
91
+ execFileSync('reg', ['delete', 'HKCU\\Environment', '/v', name, '/f'], {
92
+ stdio: ['ignore', 'ignore', 'ignore'],
93
+ });
94
+ removed++;
95
+ }
96
+ catch {
97
+ // value wasn't present — nothing to remove
98
+ }
99
+ }
100
+ return removed;
101
+ }
102
+ /**
103
+ * Remove previously persisted HomeTrans env vars from the machine.
104
+ * - Windows: `reg delete HKCU\Environment /v NAME /f` for each name.
105
+ * - macOS / Linux: strip the marked export block from the shell rc file
106
+ * (the `names` are ignored — the whole managed block is removed).
107
+ * Returns a short note describing what happened.
108
+ */
109
+ export async function removeEnvVars(names) {
110
+ if (process.platform === 'win32') {
111
+ const removed = removeWindows(names);
112
+ return removed > 0
113
+ ? `removed ${removed} variable(s) from the Windows user environment — open a new terminal to refresh`
114
+ : 'no HomeTrans variables found in the Windows user environment';
115
+ }
116
+ const profile = unixProfilePath(os.homedir(), process.env.SHELL ?? '');
117
+ let content;
118
+ try {
119
+ content = await fs.readFile(profile, 'utf-8');
120
+ }
121
+ catch {
122
+ return 'no shell profile found — nothing to remove';
123
+ }
124
+ const { content: next, changed } = stripBlock(content);
125
+ if (!changed)
126
+ return `no HomeTrans export block found in ${profile}`;
127
+ await fs.writeFile(profile, next, 'utf-8');
128
+ return `removed the HomeTrans export block from ${profile} — open a new terminal to refresh`;
129
+ }