@buaa_smat/hometrans 0.1.4 → 0.1.6

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 (80) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +17 -18
  3. package/agents/build-fixer.md +6 -5
  4. package/agents/{logic-coding.md → logic-coder.md} +2 -2
  5. package/agents/logic-context-builder.md +1 -1
  6. package/agents/self-tester.md +19 -8
  7. package/package.json +1 -1
  8. package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/SKILL.md +3 -3
  9. package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/references/conversion-procedure.md +2 -2
  10. package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +3 -5
  11. package/skills/{convert_pipeline → hmos-convert-pipeline}/SKILL.md +4 -4
  12. package/skills/hmos-fix-build-errors/SKILL.md +265 -0
  13. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +219 -0
  14. package/skills/hmos-fix-build-errors/references/known-patterns.md +157 -0
  15. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +131 -0
  16. package/skills/{hmos-ui-align → hmos-incremental-ui-align}/SKILL.md +4 -2
  17. package/skills/{hmos-ui-align → hmos-incremental-ui-align}/readme.md +5 -6
  18. package/skills/{hmos-ui-align → hmos-incremental-ui-align}/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +3 -5
  19. package/skills/{self-test → hmos-integration-test}/SKILL.md +1 -1
  20. package/skills/{self-test → hmos-integration-test}/readme.md +3 -3
  21. package/skills/{spec-generator-skill → hmos-spec-generate}/SKILL.md +1 -1
  22. package/agents/code-review-fix.md +0 -356
  23. package/dist/cli/config-store.js +0 -143
  24. package/dist/cli/config.js +0 -40
  25. package/dist/cli/index.js +0 -43
  26. package/dist/cli/init.js +0 -366
  27. package/dist/cli/mcp-setup.js +0 -262
  28. package/dist/cli/mcp.js +0 -94
  29. package/dist/cli/uninstall.js +0 -310
  30. package/dist/context/index.js +0 -788
  31. package/skills/code-dev-review-fix/SKILL.md +0 -279
  32. package/skills/code-dev-review-fix-workspace/evals/evals.json +0 -56
  33. package/skills/code-dev-review-fix-workspace/iteration-1/routing-results.md +0 -23
  34. package/skills/hmos-ui-align/config.json +0 -11
  35. /package/agents/{logic-coding → logic-coder}/scripts/platform_context_query.py +0 -0
  36. /package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  37. /package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  38. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  39. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  40. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  41. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  42. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  43. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  44. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  45. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  46. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  47. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  48. /package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
  49. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  50. /package/skills/{hmos-ui-align-batch → 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" +0 -0
  51. /package/skills/{hmos-ui-align-batch → hmos-batch-ui-align}/scripts/android_parse_fast.py +0 -0
  52. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/config-example.json +0 -0
  53. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/diff_analysis.md +0 -0
  54. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/page_align.md +0 -0
  55. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/references/Comparison_Template.md +0 -0
  56. /package/skills/{hmos-ui-align → 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" +0 -0
  57. /package/skills/{hmos-ui-align → 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" +0 -0
  58. /package/skills/{hmos-ui-align → 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" +0 -0
  59. /package/skills/{hmos-ui-align → 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" +0 -0
  60. /package/skills/{hmos-ui-align → 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" +0 -0
  61. /package/skills/{hmos-ui-align → 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" +0 -0
  62. /package/skills/{hmos-ui-align → 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" +0 -0
  63. /package/skills/{hmos-ui-align → 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" +0 -0
  64. /package/skills/{hmos-ui-align → 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" +0 -0
  65. /package/skills/{hmos-ui-align → 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" +0 -0
  66. /package/skills/{hmos-ui-align → 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" +0 -0
  67. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
  68. /package/skills/{hmos-ui-align → 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" +0 -0
  69. /package/skills/{hmos-ui-align → 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" +0 -0
  70. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/references/UI_Analysis_Template.md +0 -0
  71. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  72. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/references/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  73. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/scripts/app_feature_verify.py +0 -0
  74. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/scripts/navigation-capure.md +0 -0
  75. /package/skills/{hmos-ui-align → hmos-incremental-ui-align}/scripts/page_capture.py +0 -0
  76. /package/skills/{spec-generator-skill → hmos-spec-generate}/references/android-platform-tokens.md +0 -0
  77. /package/skills/{spec-generator-skill → hmos-spec-generate}/references/spec-sample-1.md +0 -0
  78. /package/skills/{spec-generator-skill → hmos-spec-generate}/references/spec-sample-2.md +0 -0
  79. /package/skills/{spec-generator-skill → hmos-spec-generate}/references/spec-sample-3.md +0 -0
  80. /package/skills/{spec-generator-skill → hmos-spec-generate}/references/step4-report-template.md +0 -0
package/dist/cli/init.js DELETED
@@ -1,366 +0,0 @@
1
- /**
2
- * `ht init` — interactive initialization for HomeTrans.
3
- *
4
- * Displays the HomeTrans banner, lets the user select which editors to
5
- * configure, then copies bundled skills/agents and registers MCP servers
6
- * for the chosen editors.
7
- */
8
- import fs from 'node:fs/promises';
9
- import path from 'node:path';
10
- import os from 'node:os';
11
- import { fileURLToPath } from 'node:url';
12
- import chalk from 'chalk';
13
- import figlet from 'figlet';
14
- import inquirer from 'inquirer';
15
- import { setupMcpForAllEditors } from './mcp-setup.js';
16
- import { expandHome, getConfigPath, getToolsDir, loadHomeTransConfig, saveHomeTransConfig, } from './config-store.js';
17
- function ensureChalkColor() {
18
- if (process.stdout.isTTY && chalk.level === 0) {
19
- chalk.level = 1;
20
- }
21
- }
22
- const __filename = fileURLToPath(import.meta.url);
23
- const __dirname = path.dirname(__filename);
24
- export async function dirExists(dirPath) {
25
- try {
26
- const stat = await fs.stat(dirPath);
27
- return stat.isDirectory();
28
- }
29
- catch {
30
- return false;
31
- }
32
- }
33
- async function copyDirRecursive(src, dest) {
34
- await fs.mkdir(dest, { recursive: true });
35
- const entries = await fs.readdir(src, { withFileTypes: true });
36
- for (const entry of entries) {
37
- const srcPath = path.join(src, entry.name);
38
- const destPath = path.join(dest, entry.name);
39
- if (entry.isDirectory()) {
40
- await copyDirRecursive(srcPath, destPath);
41
- }
42
- else {
43
- await fs.copyFile(srcPath, destPath);
44
- }
45
- }
46
- }
47
- function resolveSkillsRoot() {
48
- return path.resolve(__dirname, '..', '..', 'skills');
49
- }
50
- function resolveAgentsRoot() {
51
- return path.resolve(__dirname, '..', '..', 'agents');
52
- }
53
- function resolveToolsRoot() {
54
- return path.resolve(__dirname, '..', '..', 'tools');
55
- }
56
- /**
57
- * Copy the bundled `tools/` folder into ~/.hometrans/tools and record the
58
- * destination in config.tool_path. Returns the destination path, or null if
59
- * the package ships no tools/ folder.
60
- */
61
- async function installTools(toolsRoot, config) {
62
- if (!(await dirExists(toolsRoot)))
63
- return null;
64
- const toolsDest = getToolsDir();
65
- await copyDirRecursive(toolsRoot, toolsDest);
66
- config.tool_path = toolsDest;
67
- await saveHomeTransConfig(config);
68
- return toolsDest;
69
- }
70
- function resolveAutotestDir(agentsRoot) {
71
- return path.join(agentsRoot, 'test-tools', 'autotest');
72
- }
73
- /**
74
- * Initialize / refresh `agents/test-tools/autotest/config.yaml`:
75
- * - If config.yaml does not exist, seed it from config.yaml.example.
76
- * - Replace every `api_key: "..."` line with the supplied apiKey.
77
- *
78
- * Returns a status string for the result summary, or null if nothing was done
79
- * (e.g., the autotest folder isn't present in this package).
80
- */
81
- async function refreshAutotestConfig(autotestDir, apiKey) {
82
- const examplePath = path.join(autotestDir, 'config.yaml.example');
83
- const configPath = path.join(autotestDir, 'config.yaml');
84
- const hasExample = await fs
85
- .access(examplePath)
86
- .then(() => true)
87
- .catch(() => false);
88
- if (!hasExample)
89
- return null;
90
- let seeded = false;
91
- const hasConfig = await fs
92
- .access(configPath)
93
- .then(() => true)
94
- .catch(() => false);
95
- if (!hasConfig) {
96
- await fs.copyFile(examplePath, configPath);
97
- seeded = true;
98
- }
99
- if (!apiKey) {
100
- return seeded
101
- ? `autotest config.yaml seeded (api_key left as placeholder)`
102
- : null;
103
- }
104
- const original = await fs.readFile(configPath, 'utf-8');
105
- // Match `api_key: <value>` with optional quoting, preserve indent + key.
106
- const updated = original.replace(/^(\s*api_key\s*:\s*)(?:"[^"]*"|'[^']*'|\S+)\s*$/gm, (_m, prefix) => `${prefix}"${apiKey}"`);
107
- if (updated !== original) {
108
- await fs.writeFile(configPath, updated, 'utf-8');
109
- }
110
- return seeded
111
- ? `autotest config.yaml seeded + api_key filled`
112
- : `autotest config.yaml api_key refreshed`;
113
- }
114
- async function installSkillsTo(skillsRoot, targetDir) {
115
- let entries;
116
- try {
117
- entries = await fs.readdir(skillsRoot, { withFileTypes: true });
118
- }
119
- catch {
120
- return [];
121
- }
122
- const installed = [];
123
- for (const entry of entries) {
124
- if (!entry.isDirectory())
125
- continue;
126
- const skillSrc = path.join(skillsRoot, entry.name);
127
- const hasSkillFile = await fs
128
- .access(path.join(skillSrc, 'SKILL.md'))
129
- .then(() => true)
130
- .catch(() => false);
131
- if (!hasSkillFile)
132
- continue;
133
- const skillDest = path.join(targetDir, entry.name);
134
- await copyDirRecursive(skillSrc, skillDest);
135
- installed.push(entry.name);
136
- }
137
- return installed;
138
- }
139
- async function installAgentsTo(agentsRoot, targetDir) {
140
- let entries;
141
- try {
142
- entries = await fs.readdir(agentsRoot, { withFileTypes: true });
143
- }
144
- catch {
145
- return [];
146
- }
147
- await fs.mkdir(targetDir, { recursive: true });
148
- const installed = [];
149
- for (const entry of entries) {
150
- const srcPath = path.join(agentsRoot, entry.name);
151
- const destPath = path.join(targetDir, entry.name);
152
- if (entry.isDirectory()) {
153
- await copyDirRecursive(srcPath, destPath);
154
- }
155
- else if (entry.isFile()) {
156
- await fs.copyFile(srcPath, destPath);
157
- if (entry.name.endsWith('.md')) {
158
- installed.push(entry.name.slice(0, -3));
159
- }
160
- }
161
- }
162
- return installed;
163
- }
164
- async function installForEditor(editor, skillsRoot, agentsRoot, result) {
165
- const marker = expandHome(editor.markerDir);
166
- if (marker && !(await dirExists(marker))) {
167
- result.skipped.push(`${editor.name} (not installed)`);
168
- return;
169
- }
170
- const skillsDir = expandHome(editor.skillsDir);
171
- const agentsDir = expandHome(editor.agentsDir);
172
- try {
173
- const skills = await installSkillsTo(skillsRoot, skillsDir);
174
- if (skills.length > 0) {
175
- result.configured.push(`${editor.name} skills (${skills.length} -> ${prettyHome(skillsDir)})`);
176
- }
177
- }
178
- catch (err) {
179
- result.errors.push(`${editor.name} skills: ${err.message}`);
180
- }
181
- try {
182
- const agents = await installAgentsTo(agentsRoot, agentsDir);
183
- if (agents.length > 0) {
184
- result.configured.push(`${editor.name} agents (${agents.length} -> ${prettyHome(agentsDir)})`);
185
- }
186
- }
187
- catch (err) {
188
- result.errors.push(`${editor.name} agents: ${err.message}`);
189
- }
190
- }
191
- export function prettyHome(p) {
192
- const home = os.homedir();
193
- if (p.startsWith(home)) {
194
- return '~' + p.slice(home.length).replace(/\\/g, '/');
195
- }
196
- return p.replace(/\\/g, '/');
197
- }
198
- async function detectInstalledEditors(editors) {
199
- const status = new Map();
200
- for (const editor of editors) {
201
- const marker = expandHome(editor.markerDir);
202
- if (!marker) {
203
- status.set(editor.name, true);
204
- }
205
- else {
206
- status.set(editor.name, await dirExists(marker));
207
- }
208
- }
209
- return status;
210
- }
211
- export async function initCommand(options = {}) {
212
- ensureChalkColor();
213
- const banner = figlet.textSync('HomeTrans', { font: 'Standard' });
214
- console.log(chalk.cyan(`\n${banner.trimEnd()}`));
215
- console.log(chalk.gray('\n Android-to-HarmonyOS skill & agent installer for AI editors\n'));
216
- const skillsRoot = resolveSkillsRoot();
217
- const agentsRoot = resolveAgentsRoot();
218
- const toolsRoot = resolveToolsRoot();
219
- const hasSkills = await dirExists(skillsRoot);
220
- const hasAgents = await dirExists(agentsRoot);
221
- if (!hasSkills && !hasAgents) {
222
- console.error(chalk.red(` ! Neither skills/ nor agents/ found at package root.`));
223
- console.error(chalk.gray(` Looked in: ${skillsRoot}`));
224
- console.error(chalk.gray(` ${agentsRoot}`));
225
- console.error(chalk.gray(' Reinstall hometrans or run `npm run build` from the package root.'));
226
- process.exitCode = 1;
227
- return;
228
- }
229
- const config = await loadHomeTransConfig();
230
- const { editors } = config;
231
- const installedStatus = await detectInstalledEditors(editors);
232
- let selectedEditors;
233
- if (options.all) {
234
- selectedEditors = editors.map((e) => e.name);
235
- console.log(chalk.blue(' --all: selecting all editors.\n'));
236
- }
237
- else {
238
- const choices = editors.map((editor) => {
239
- const installed = installedStatus.get(editor.name);
240
- const label = installed
241
- ? `${editor.name} ${chalk.green('(detected)')}`
242
- : `${editor.name} ${chalk.gray('(not detected)')}`;
243
- return {
244
- name: label,
245
- value: editor.name,
246
- checked: installed,
247
- };
248
- });
249
- try {
250
- const answers = await inquirer.prompt([
251
- {
252
- type: 'checkbox',
253
- name: 'selectedEditors',
254
- message: 'Select editors to configure (arrow keys + space to toggle, enter to confirm):',
255
- choices,
256
- },
257
- ]);
258
- selectedEditors = answers.selectedEditors;
259
- }
260
- catch {
261
- console.log(chalk.yellow('\n Interactive selection failed. Auto-selecting all detected editors.'));
262
- console.log(chalk.gray(' Tip: use --all to skip interactive selection.\n'));
263
- selectedEditors = editors
264
- .filter((e) => installedStatus.get(e.name))
265
- .map((e) => e.name);
266
- }
267
- }
268
- if (selectedEditors.length === 0) {
269
- console.log(chalk.yellow('\n No editors selected. Nothing to do.\n'));
270
- return;
271
- }
272
- // --- User parameters (SDK paths, test API key) ---
273
- console.log('');
274
- console.log(chalk.blue(' Parameter Configuration'));
275
- console.log(chalk.gray(' (press Enter to keep current value or leave empty)\n'));
276
- try {
277
- const answers = await inquirer.prompt([
278
- {
279
- type: 'input',
280
- name: 'OHOS_SDK_PATH',
281
- message: 'OHOS_SDK_PATH:',
282
- default: config.params.OHOS_SDK_PATH || undefined,
283
- },
284
- {
285
- type: 'input',
286
- name: 'HMS_SDK_PATH',
287
- message: 'HMS_SDK_PATH:',
288
- default: config.params.HMS_SDK_PATH || undefined,
289
- },
290
- {
291
- type: 'input',
292
- name: 'TEST_API_KEY',
293
- message: 'TEST_API_KEY (for autotest config.yaml):',
294
- default: config.params.TEST_API_KEY || undefined,
295
- },
296
- ]);
297
- config.params.OHOS_SDK_PATH = answers.OHOS_SDK_PATH.trim();
298
- config.params.HMS_SDK_PATH = answers.HMS_SDK_PATH.trim();
299
- config.params.TEST_API_KEY = answers.TEST_API_KEY.trim();
300
- await saveHomeTransConfig(config);
301
- }
302
- catch {
303
- console.log(chalk.yellow(' Parameter prompts skipped (non-interactive mode).'));
304
- }
305
- // Initialize / refresh autotest config.yaml from the example template.
306
- try {
307
- const autotestDir = resolveAutotestDir(agentsRoot);
308
- const status = await refreshAutotestConfig(autotestDir, config.params.TEST_API_KEY);
309
- if (status) {
310
- console.log(chalk.green(` + ${status}`));
311
- console.log(chalk.gray(` ${path.join(autotestDir, 'config.yaml')}`));
312
- }
313
- }
314
- catch (err) {
315
- console.log(chalk.red(` ! autotest config.yaml: ${err.message}`));
316
- }
317
- // Copy bundled tools/ into ~/.hometrans/tools and record tool_path.
318
- try {
319
- const toolsDest = await installTools(toolsRoot, config);
320
- if (toolsDest) {
321
- console.log(chalk.green(` + tools copied -> ${prettyHome(toolsDest)}`));
322
- console.log(chalk.gray(` tool_path set in ${prettyHome(getConfigPath())}`));
323
- }
324
- }
325
- catch (err) {
326
- console.log(chalk.red(` ! tools copy: ${err.message}`));
327
- }
328
- console.log('');
329
- const editorsToSetup = editors.filter((e) => selectedEditors.includes(e.name));
330
- const result = { configured: [], skipped: [], errors: [] };
331
- for (const editor of editorsToSetup) {
332
- console.log(chalk.blue(` Configuring ${editor.name}...`));
333
- await installForEditor(editor, skillsRoot, agentsRoot, result);
334
- }
335
- await setupMcpForAllEditors(editorsToSetup, result);
336
- console.log('');
337
- if (result.configured.length > 0) {
338
- console.log(chalk.green(' Configured:'));
339
- for (const name of result.configured)
340
- console.log(chalk.green(` + ${name}`));
341
- }
342
- if (result.skipped.length > 0) {
343
- console.log('');
344
- console.log(chalk.yellow(' Skipped:'));
345
- for (const name of result.skipped)
346
- console.log(chalk.yellow(` - ${name}`));
347
- }
348
- if (result.errors.length > 0) {
349
- console.log('');
350
- console.log(chalk.red(' Errors:'));
351
- for (const err of result.errors)
352
- console.log(chalk.red(` ! ${err}`));
353
- }
354
- console.log('');
355
- console.log(chalk.gray(` Source skills: ${skillsRoot}`));
356
- console.log(chalk.gray(` Source agents: ${agentsRoot}`));
357
- console.log('');
358
- if (result.configured.length === 0 && result.errors.length === 0) {
359
- console.log(chalk.yellow(' No editors were configured. Make sure the selected editors are installed,'));
360
- console.log(chalk.yellow(' then re-run `ht init`.'));
361
- }
362
- else {
363
- console.log(chalk.green(' Done. Re-open your editor for the new skills/agents to be picked up.'));
364
- }
365
- console.log('');
366
- }
@@ -1,262 +0,0 @@
1
- /**
2
- * `ht init` 阶段:按 editors.json 中每个 editor 的 mcp.format 把 hometrans MCP
3
- * server 注册到对应的 editor 配置文件。
4
- *
5
- * 支持的 mcp.format:
6
- * jsonc-object Cursor / Claude Code 风格:jsonc 文件,key 路径写对象
7
- * jsonc-command-array OpenCode 风格:jsonc 文件,value 是 {type, command:[]}
8
- * codex-cli Codex:优先 `codex mcp add`,失败回退追加 TOML section
9
- * toml-section 纯 TOML:追加 [<section>] 段
10
- * none 跳过 MCP 写入
11
- */
12
- import fs from 'node:fs/promises';
13
- import path from 'node:path';
14
- import { execFile, execFileSync } from 'node:child_process';
15
- import { createRequire } from 'node:module';
16
- import { promisify } from 'node:util';
17
- import { parseTree, modify, applyEdits, } from 'jsonc-parser';
18
- import { dirExists } from './init.js';
19
- import { expandHome } from './config-store.js';
20
- const execFileAsync = promisify(execFile);
21
- const _require = createRequire(import.meta.url);
22
- const _pkg = _require('../../package.json');
23
- if (typeof _pkg.version !== 'string' || !_pkg.version) {
24
- throw new Error('hometrans package.json#version is missing — cannot generate MCP fallback config.');
25
- }
26
- const PKG_NAME = typeof _pkg.name === 'string' && _pkg.name ? _pkg.name : 'hometrans';
27
- const NPX_REF = `${PKG_NAME}@${_pkg.version}`;
28
- /** Locate the globally-installed `hometrans` (or `ht`) binary. */
29
- function resolveHometransBin() {
30
- const isWin = process.platform === 'win32';
31
- const cmd = isWin ? 'where' : 'which';
32
- for (const candidate of ['hometrans', 'ht']) {
33
- try {
34
- const output = execFileSync(cmd, [candidate], {
35
- encoding: 'utf-8',
36
- timeout: 5000,
37
- stdio: ['ignore', 'pipe', 'ignore'],
38
- });
39
- const lines = output
40
- .split('\n')
41
- .map(l => l.trim())
42
- .filter(Boolean);
43
- if (lines.length === 0)
44
- continue;
45
- if (isWin) {
46
- const cmdLine = lines.find(l => /\.(cmd|bat)$/i.test(l));
47
- return cmdLine || lines[0];
48
- }
49
- return lines[0];
50
- }
51
- catch {
52
- // try next candidate
53
- }
54
- }
55
- return null;
56
- }
57
- /**
58
- * MCP server entry written to editor config.
59
- *
60
- * Prefers global binary (fast startup) over `npx -y hometrans@<version> mcp`
61
- * (cold-start may exceed Claude Code's 30s MCP connect timeout, esp. with
62
- * arkanalyzer install). Falls back to npx when binary not on PATH.
63
- */
64
- function getMcpEntry() {
65
- const bin = resolveHometransBin();
66
- if (bin) {
67
- return { command: bin, args: ['mcp'] };
68
- }
69
- if (process.platform === 'win32') {
70
- return { command: 'cmd', args: ['/c', 'npx', '-y', NPX_REF, 'mcp'] };
71
- }
72
- return { command: 'npx', args: ['-y', NPX_REF, 'mcp'] };
73
- }
74
- /** OpenCode uses a flat command-array format. */
75
- function getOpenCodeMcpEntry() {
76
- const bin = resolveHometransBin();
77
- if (bin) {
78
- return { type: 'local', command: [bin, 'mcp'] };
79
- }
80
- if (process.platform === 'win32') {
81
- return {
82
- type: 'local',
83
- command: ['cmd', '/c', 'npx', '-y', NPX_REF, 'mcp'],
84
- };
85
- }
86
- return { type: 'local', command: ['npx', '-y', NPX_REF, 'mcp'] };
87
- }
88
- function detectIndentation(raw) {
89
- const firstIndented = raw.match(/^( +|\t)/m);
90
- if (!firstIndented)
91
- return { tabSize: 2, insertSpaces: true };
92
- if (firstIndented[1] === '\t')
93
- return { tabSize: 1, insertSpaces: false };
94
- return { tabSize: firstIndented[1].length, insertSpaces: true };
95
- }
96
- /**
97
- * Merge a key/value pair into a JSONC config file, preserving comments and formatting.
98
- * Returns false if the file is genuinely corrupt (leaves it untouched).
99
- */
100
- async function mergeJsoncFile(filePath, keyPath, value) {
101
- let raw;
102
- try {
103
- raw = await fs.readFile(filePath, 'utf-8');
104
- }
105
- catch {
106
- raw = '';
107
- }
108
- if (raw.trim().length === 0) {
109
- await fs.mkdir(path.dirname(filePath), { recursive: true });
110
- const formattingOptions = { tabSize: 2, insertSpaces: true };
111
- const edits = modify('{}', keyPath, value, { formattingOptions });
112
- const result = applyEdits('{}', edits);
113
- await fs.writeFile(filePath, result, 'utf-8');
114
- return true;
115
- }
116
- const parseErrors = [];
117
- const tree = parseTree(raw, parseErrors);
118
- if (tree && tree.type === 'object' && parseErrors.length === 0) {
119
- const formattingOptions = detectIndentation(raw);
120
- const edits = modify(raw, keyPath, value, { formattingOptions });
121
- const result = applyEdits(raw, edits);
122
- await fs.writeFile(filePath, result, 'utf-8');
123
- return true;
124
- }
125
- return false;
126
- }
127
- function getTomlSection(sectionHeader) {
128
- const entry = getMcpEntry();
129
- const command = JSON.stringify(entry.command);
130
- const args = `[${entry.args.map(arg => JSON.stringify(arg)).join(', ')}]`;
131
- return `[${sectionHeader}]\ncommand = ${command}\nargs = ${args}\n`;
132
- }
133
- async function upsertTomlSection(configPath, sectionHeader) {
134
- let existing = '';
135
- try {
136
- existing = await fs.readFile(configPath, 'utf-8');
137
- }
138
- catch {
139
- existing = '';
140
- }
141
- if (existing.includes(`[${sectionHeader}]`)) {
142
- return;
143
- }
144
- const section = getTomlSection(sectionHeader);
145
- const nextContent = existing.trim().length > 0 ? `${existing.trimEnd()}\n\n${section}` : section;
146
- await fs.mkdir(path.dirname(configPath), { recursive: true });
147
- await fs.writeFile(configPath, `${nextContent.trimEnd()}\n`, 'utf-8');
148
- }
149
- // ─── Per-format writers ────────────────────────────────────────────
150
- async function writeJsoncObject(editor, result) {
151
- const mcp = editor.mcp;
152
- if (!mcp.path || !mcp.keyPath) {
153
- result.errors.push(`${editor.name} MCP: jsonc-object format requires path + keyPath`);
154
- return;
155
- }
156
- const configPath = expandHome(mcp.path);
157
- try {
158
- const ok = await mergeJsoncFile(configPath, mcp.keyPath, getMcpEntry());
159
- if (ok) {
160
- result.configured.push(`${editor.name} MCP (${prettyConfigPath(configPath)})`);
161
- }
162
- else {
163
- result.errors.push(`${editor.name} MCP: ${configPath} is corrupt — skipping to preserve existing content`);
164
- }
165
- }
166
- catch (err) {
167
- result.errors.push(`${editor.name} MCP: ${err.message}`);
168
- }
169
- }
170
- async function writeJsoncCommandArray(editor, result) {
171
- const mcp = editor.mcp;
172
- if (!mcp.path || !mcp.keyPath) {
173
- result.errors.push(`${editor.name} MCP: jsonc-command-array format requires path + keyPath`);
174
- return;
175
- }
176
- const configPath = expandHome(mcp.path);
177
- try {
178
- const ok = await mergeJsoncFile(configPath, mcp.keyPath, getOpenCodeMcpEntry());
179
- if (ok) {
180
- result.configured.push(`${editor.name} MCP (${prettyConfigPath(configPath)})`);
181
- }
182
- else {
183
- result.errors.push(`${editor.name} MCP: ${configPath} is corrupt — skipping to preserve existing content`);
184
- }
185
- }
186
- catch (err) {
187
- result.errors.push(`${editor.name} MCP: ${err.message}`);
188
- }
189
- }
190
- async function writeCodexCli(editor, result) {
191
- try {
192
- const entry = getMcpEntry();
193
- await execFileAsync('codex', ['mcp', 'add', 'hometrans', '--', entry.command, ...entry.args], { shell: process.platform === 'win32' });
194
- result.configured.push(`${editor.name} MCP (via codex mcp add)`);
195
- return;
196
- }
197
- catch {
198
- // Fall through to TOML write.
199
- }
200
- const mcp = editor.mcp;
201
- if (!mcp.path || !mcp.section) {
202
- result.errors.push(`${editor.name} MCP: codex-cli fallback requires path + section`);
203
- return;
204
- }
205
- try {
206
- const configPath = expandHome(mcp.path);
207
- await upsertTomlSection(configPath, mcp.section);
208
- result.configured.push(`${editor.name} MCP (${prettyConfigPath(configPath)})`);
209
- }
210
- catch (err) {
211
- result.errors.push(`${editor.name} MCP: ${err.message}`);
212
- }
213
- }
214
- async function writeTomlSection(editor, result) {
215
- const mcp = editor.mcp;
216
- if (!mcp.path || !mcp.section) {
217
- result.errors.push(`${editor.name} MCP: toml-section format requires path + section`);
218
- return;
219
- }
220
- try {
221
- const configPath = expandHome(mcp.path);
222
- await upsertTomlSection(configPath, mcp.section);
223
- result.configured.push(`${editor.name} MCP (${prettyConfigPath(configPath)})`);
224
- }
225
- catch (err) {
226
- result.errors.push(`${editor.name} MCP: ${err.message}`);
227
- }
228
- }
229
- function prettyConfigPath(p) {
230
- // 与 init.ts 中 prettyHome 行为一致,但避免循环引用:就地实现。
231
- const home = process.env.HOME || process.env.USERPROFILE || '';
232
- if (home && p.startsWith(home)) {
233
- return '~' + p.slice(home.length).replace(/\\/g, '/');
234
- }
235
- return p.replace(/\\/g, '/');
236
- }
237
- async function setupOneEditor(editor, result) {
238
- const marker = expandHome(editor.markerDir);
239
- if (marker && !(await dirExists(marker))) {
240
- result.skipped.push(`${editor.name} MCP (not installed)`);
241
- return;
242
- }
243
- switch (editor.mcp.format) {
244
- case 'jsonc-object':
245
- return writeJsoncObject(editor, result);
246
- case 'jsonc-command-array':
247
- return writeJsoncCommandArray(editor, result);
248
- case 'codex-cli':
249
- return writeCodexCli(editor, result);
250
- case 'toml-section':
251
- return writeTomlSection(editor, result);
252
- case 'none':
253
- return;
254
- default:
255
- result.errors.push(`${editor.name} MCP: unknown mcp.format "${editor.mcp.format}"`);
256
- }
257
- }
258
- export async function setupMcpForAllEditors(editors, result) {
259
- for (const editor of editors) {
260
- await setupOneEditor(editor, result);
261
- }
262
- }