@buaa_smat/hometrans 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -139
- package/agents/build-fixer.md +385 -384
- package/agents/code-reviewer.md +240 -240
- package/agents/logic-coder.md +199 -199
- package/agents/logic-context-builder.md +194 -194
- package/agents/review-fixer.md +405 -405
- package/agents/self-test-fixer.md +296 -296
- package/agents/self-tester.md +396 -393
- package/agents/spec-generator.md +540 -540
- package/dist/cli/config-store.js +150 -17
- package/dist/cli/config.js +10 -3
- package/dist/cli/init.js +391 -233
- package/dist/cli/uninstall.js +12 -7
- package/dist/context/index.js +19 -2
- package/env-requirements.json +177 -181
- package/package.json +1 -1
- package/resource/choose_editor.png +0 -0
- package/resource/migration_process.svg +94 -0
- package/resource/migration_process_transparent.svg +93 -0
- package/resource/set_environment.png +0 -0
- package/resource/set_multimodel.png +0 -0
- package/resource/set_sdk.png +0 -0
- package/skills/hmos-batch-ui-align/SKILL.md +108 -108
- package/skills/hmos-convert-pipeline/SKILL.md +429 -429
- package/skills/hmos-fix-build-errors/SKILL.md +273 -272
- package/skills/hmos-incremental-ui-align/{readme.md → README.md} +234 -237
- package/skills/hmos-incremental-ui-align/SKILL.md +218 -218
- package/skills/hmos-incremental-ui-align/scripts/__pycache__/app_feature_verify.cpython-314.pyc +0 -0
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.py +128 -29
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
- package/skills/hmos-incremental-ui-align/scripts/page_capture.py +7 -2
- package/skills/hmos-integration-test/{readme.md → README.md} +309 -309
- package/skills/hmos-integration-test/SKILL.md +380 -380
- package/skills/hmos-resources-convert/SKILL.md +623 -623
- package/skills/hmos-spec-generate/SKILL.md +324 -331
- package/tools/test-tools/autotest/README.md +10 -11
- package/tools/test-tools/autotest/self_test_runner.py +40 -12
- package/resource/common_config.png +0 -0
- package/resource/integration_test_config.png +0 -0
- package/resource/set_env.png +0 -0
- package/resource/ui_align_config.png +0 -0
package/dist/cli/init.js
CHANGED
|
@@ -13,7 +13,7 @@ import chalk from 'chalk';
|
|
|
13
13
|
import figlet from 'figlet';
|
|
14
14
|
import inquirer from 'inquirer';
|
|
15
15
|
import { setupMcpForAllEditors } from './mcp-setup.js';
|
|
16
|
-
import { deriveSdkPaths, expandHome, getConfigDir, getConfigPath, getToolsDir, loadHomeTransConfig, saveHomeTransConfig, } from './config-store.js';
|
|
16
|
+
import { deriveSdkPaths, expandHome, getConfigDir, getConfigPath, getToolsDir, isPlaceholderApiKey, loadHomeTransConfig, saveHomeTransConfig, syncModelEnvIntoConfig, } from './config-store.js';
|
|
17
17
|
import { persistEnvVars } from './env-vars.js';
|
|
18
18
|
function ensureChalkColor() {
|
|
19
19
|
if (process.stdout.isTTY && chalk.level === 0) {
|
|
@@ -88,176 +88,51 @@ function maskKey(key) {
|
|
|
88
88
|
return key || '';
|
|
89
89
|
return key.length <= 8 ? '***' : `${key.slice(0, 4)}***${key.slice(-4)}`;
|
|
90
90
|
}
|
|
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;
|
|
126
|
-
}
|
|
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)));
|
|
130
|
-
}
|
|
131
91
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
92
|
+
* Model Configuration step — configures the single multimodal (vision) LLM
|
|
93
|
+
* shared by BOTH the on-device self-test agent AND the UI-alignment phone-agent.
|
|
94
|
+
* Stored once in the `autotest.unified_model` block of ~/.hometrans/config.json
|
|
95
|
+
* and additionally exported as the HOMETRANS_MODEL_* machine environment
|
|
96
|
+
* variables (see maybePersistMachineEnv), so each runtime reads the same config:
|
|
97
|
+
* - self_test_runner.py reads autotest.unified_model from config.json;
|
|
98
|
+
* - app_feature_verify.py reads the HOMETRANS_MODEL_* env vars (falling back
|
|
99
|
+
* to config.json).
|
|
100
|
+
* Prompts for the four essentials (api_key / name / base_url / provider);
|
|
101
|
+
* all other parameters (device / agent, temperature, …) keep their defaults —
|
|
102
|
+
* edit config.json to tune them.
|
|
135
103
|
*/
|
|
136
|
-
function
|
|
137
|
-
const
|
|
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('└', '┴', '┘')));
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
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.
|
|
188
|
-
*/
|
|
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()}`));
|
|
104
|
+
async function promptModelConfig(config) {
|
|
105
|
+
const um = config.autotest.unified_model;
|
|
197
106
|
console.log('');
|
|
107
|
+
console.log(chalk.blue(' MultiModel LLM Configuration'));
|
|
198
108
|
try {
|
|
199
109
|
const answers = await inquirer.prompt([
|
|
200
110
|
{
|
|
201
111
|
type: 'input',
|
|
202
|
-
name: '
|
|
203
|
-
message: '
|
|
204
|
-
default: um.api_key
|
|
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,
|
|
112
|
+
name: 'model_api_key',
|
|
113
|
+
message: 'model_api_key (multimodal LLM key for ui alignment + integration test):',
|
|
114
|
+
default: isPlaceholderApiKey(um.api_key) ? undefined : um.api_key,
|
|
213
115
|
},
|
|
116
|
+
{ type: 'input', name: 'model_name', message: 'model_name:', default: um.name },
|
|
117
|
+
{ type: 'input', name: 'model_base_url', message: 'model_base_url:', default: um.base_url },
|
|
214
118
|
]);
|
|
215
|
-
if (answers.
|
|
216
|
-
um.api_key = answers.
|
|
217
|
-
if (answers.
|
|
218
|
-
um.name = answers.
|
|
219
|
-
if (answers.
|
|
220
|
-
um.base_url = answers.
|
|
221
|
-
|
|
222
|
-
|
|
119
|
+
if (answers.model_api_key.trim())
|
|
120
|
+
um.api_key = answers.model_api_key.trim();
|
|
121
|
+
if (answers.model_name.trim())
|
|
122
|
+
um.name = answers.model_name.trim();
|
|
123
|
+
if (answers.model_base_url.trim())
|
|
124
|
+
um.base_url = answers.model_base_url.trim();
|
|
125
|
+
// provider is no longer prompted — default to openai when unset.
|
|
126
|
+
if (!um.provider || !um.provider.trim())
|
|
127
|
+
um.provider = 'openai';
|
|
128
|
+
// Mirror into config.env.HOMETRANS_MODEL_* so config.json stays consistent.
|
|
129
|
+
syncModelEnvIntoConfig(config);
|
|
223
130
|
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})`));
|
|
228
131
|
}
|
|
229
132
|
catch (err) {
|
|
230
133
|
if (isPromptAbort(err))
|
|
231
134
|
abortInit();
|
|
232
|
-
console.log(chalk.yellow('
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
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.
|
|
240
|
-
*/
|
|
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);
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
if (isPromptAbort(err))
|
|
259
|
-
abortInit();
|
|
260
|
-
console.log(chalk.yellow(' UI migration configuration skipped (non-interactive mode).'));
|
|
135
|
+
console.log(chalk.yellow(' Model configuration skipped (non-interactive mode).'));
|
|
261
136
|
}
|
|
262
137
|
}
|
|
263
138
|
async function installSkillsTo(skillsRoot, targetDir, skipNames = new Set()) {
|
|
@@ -287,7 +162,78 @@ async function installSkillsTo(skillsRoot, targetDir, skipNames = new Set()) {
|
|
|
287
162
|
}
|
|
288
163
|
return installed;
|
|
289
164
|
}
|
|
290
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Named ANSI/agent colors → `#RRGGBB`. DevEco Code and opencode reject the named
|
|
167
|
+
* form (`cyan`, `blue`, …) in agent frontmatter `color:` and only accept hex, so
|
|
168
|
+
* for those editors we rewrite the named value to its hex equivalent instead of
|
|
169
|
+
* dropping it — the agent keeps its color.
|
|
170
|
+
*/
|
|
171
|
+
const AGENT_COLOR_HEX = {
|
|
172
|
+
black: '#000000',
|
|
173
|
+
red: '#ff0000',
|
|
174
|
+
green: '#008000',
|
|
175
|
+
yellow: '#ffff00',
|
|
176
|
+
blue: '#0000ff',
|
|
177
|
+
magenta: '#ff00ff',
|
|
178
|
+
purple: '#800080',
|
|
179
|
+
cyan: '#00ffff',
|
|
180
|
+
white: '#ffffff',
|
|
181
|
+
gray: '#808080',
|
|
182
|
+
grey: '#808080',
|
|
183
|
+
orange: '#ffa500',
|
|
184
|
+
pink: '#ffc0cb',
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Map a named `color:` in a markdown agent's YAML frontmatter to its `#RRGGBB`
|
|
188
|
+
* hex form. Only the leading frontmatter block is touched; an already-hex or
|
|
189
|
+
* unknown color is left untouched, as is a file without frontmatter.
|
|
190
|
+
*/
|
|
191
|
+
export function mapAgentMarkdownColorToHex(content) {
|
|
192
|
+
const fm = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
193
|
+
if (!fm)
|
|
194
|
+
return content;
|
|
195
|
+
const block = fm[0];
|
|
196
|
+
const mapped = block.replace(/^(color:[ \t]*)(\S+)([ \t]*\r?\n)/m, (whole, prefix, value, suffix) => {
|
|
197
|
+
const hex = AGENT_COLOR_HEX[value.trim().toLowerCase()];
|
|
198
|
+
return hex ? `${prefix}"${hex}"${suffix}` : whole;
|
|
199
|
+
});
|
|
200
|
+
return mapped === block ? content : content.replace(block, mapped);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* opencode-style editors (DevEco Code, OpenCode) only accept hex `color:` in
|
|
204
|
+
* agent frontmatter — they're exactly the ones using the OpenCode `{type,
|
|
205
|
+
* command:[]}` MCP format. Identify them by that format so no per-editor config
|
|
206
|
+
* flag is needed.
|
|
207
|
+
*/
|
|
208
|
+
function needsHexAgentColor(editor) {
|
|
209
|
+
return editor.mcp?.format === 'jsonc-command-array';
|
|
210
|
+
}
|
|
211
|
+
/** Copy one agent file, mapping the frontmatter color to hex for `.md` when requested. */
|
|
212
|
+
async function copyAgentFile(src, dest, mapColorToHex) {
|
|
213
|
+
if (mapColorToHex && src.endsWith('.md')) {
|
|
214
|
+
const raw = await fs.readFile(src, 'utf-8');
|
|
215
|
+
await fs.writeFile(dest, mapAgentMarkdownColorToHex(raw), 'utf-8');
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
await fs.copyFile(src, dest);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Recursively copy an agent subdirectory, mapping colors to hex in `.md` files. */
|
|
222
|
+
async function copyAgentDirRecursive(src, dest, mapColorToHex) {
|
|
223
|
+
await fs.mkdir(dest, { recursive: true });
|
|
224
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const srcPath = path.join(src, entry.name);
|
|
227
|
+
const destPath = path.join(dest, entry.name);
|
|
228
|
+
if (entry.isDirectory()) {
|
|
229
|
+
await copyAgentDirRecursive(srcPath, destPath, mapColorToHex);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
await copyAgentFile(srcPath, destPath, mapColorToHex);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function installAgentsTo(agentsRoot, targetDir, skipNames = new Set(), mapColorToHex = false) {
|
|
291
237
|
let entries;
|
|
292
238
|
try {
|
|
293
239
|
entries = await fs.readdir(agentsRoot, { withFileTypes: true });
|
|
@@ -303,10 +249,10 @@ async function installAgentsTo(agentsRoot, targetDir, skipNames = new Set()) {
|
|
|
303
249
|
const srcPath = path.join(agentsRoot, entry.name);
|
|
304
250
|
const destPath = path.join(targetDir, entry.name);
|
|
305
251
|
if (entry.isDirectory()) {
|
|
306
|
-
await
|
|
252
|
+
await copyAgentDirRecursive(srcPath, destPath, mapColorToHex);
|
|
307
253
|
}
|
|
308
254
|
else if (entry.isFile()) {
|
|
309
|
-
await
|
|
255
|
+
await copyAgentFile(srcPath, destPath, mapColorToHex);
|
|
310
256
|
if (entry.name.endsWith('.md')) {
|
|
311
257
|
installed.push(entry.name.slice(0, -3));
|
|
312
258
|
}
|
|
@@ -426,7 +372,7 @@ async function installForEditor(editor, skillsRoot, agentsRoot, result) {
|
|
|
426
372
|
result.skipped.push(`${editor.name} existing kept: ${skipSkills.size} skills, ${skipAgents.size} agents (new items installed)`);
|
|
427
373
|
}
|
|
428
374
|
try {
|
|
429
|
-
const agents = await installAgentsTo(agentsRoot, agentsDir, skipAgents);
|
|
375
|
+
const agents = await installAgentsTo(agentsRoot, agentsDir, skipAgents, needsHexAgentColor(editor));
|
|
430
376
|
if (agents.length > 0) {
|
|
431
377
|
result.configured.push(`${editor.name} agents (${agents.length} -> ${prettyHome(agentsDir)})`);
|
|
432
378
|
}
|
|
@@ -666,7 +612,7 @@ function evaluateDependency(dep, spec, tools) {
|
|
|
666
612
|
/** Print detected tools + the per-skill/agent impact table. */
|
|
667
613
|
export async function runEnvironmentCheck(env) {
|
|
668
614
|
console.log('');
|
|
669
|
-
console.log(chalk.
|
|
615
|
+
console.log(chalk.cyan(' Environment Check'));
|
|
670
616
|
const spec = await loadEnvRequirements();
|
|
671
617
|
if (!spec)
|
|
672
618
|
return;
|
|
@@ -675,20 +621,21 @@ export async function runEnvironmentCheck(env) {
|
|
|
675
621
|
for (const name of order) {
|
|
676
622
|
const st = tools.get(name);
|
|
677
623
|
const tool = spec.tools[name];
|
|
624
|
+
const label = (tool.label ?? name).padEnd(12);
|
|
678
625
|
if (st.found) {
|
|
679
626
|
let note = st.note ?? '';
|
|
680
627
|
if (st.version && tool.version && !versionInRange(st.version, tool.version)) {
|
|
681
628
|
note += ` (requires ${describeRange(tool.version)})`;
|
|
682
629
|
}
|
|
683
630
|
const noteStr = note ? chalk.gray(` (${note})`) : '';
|
|
684
|
-
console.log(` ${chalk.green('+')} ${
|
|
631
|
+
console.log(` ${chalk.green('+')} ${label} ${st.path ?? ''}${noteStr}`);
|
|
685
632
|
}
|
|
686
633
|
else {
|
|
687
|
-
console.log(` ${chalk.yellow('!')} ${
|
|
634
|
+
console.log(` ${chalk.yellow('!')} ${label} ${chalk.yellow('not found')} ${chalk.gray(tool.hint ?? '')}`);
|
|
688
635
|
}
|
|
689
636
|
}
|
|
690
637
|
console.log('');
|
|
691
|
-
console.log(chalk.
|
|
638
|
+
console.log(chalk.cyan(' Impact on skills/agents:'));
|
|
692
639
|
const nameW = 28;
|
|
693
640
|
console.log(chalk.gray(` ${'Name'.padEnd(nameW)} ${'Kind'.padEnd(6)} ${'Status'.padEnd(8)} Missing`));
|
|
694
641
|
let allOk = true;
|
|
@@ -727,11 +674,8 @@ export async function runEnvironmentCheck(env) {
|
|
|
727
674
|
const noteStr = parts.length > 0 && row.note ? chalk.gray(` — ${row.note}`) : '';
|
|
728
675
|
console.log(` ${row.name.padEnd(nameW)} ${row.kind.padEnd(6)} ${color(plain.padEnd(8))} ${parts.join(', ')}${noteStr}`);
|
|
729
676
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
console.log(chalk.green(' All environment dependencies found — every skill/agent is fully usable.'));
|
|
733
|
-
}
|
|
734
|
-
else {
|
|
677
|
+
if (!allOk) {
|
|
678
|
+
console.log('');
|
|
735
679
|
console.log(chalk.gray(' Install the missing tools above and re-run `ht init` to clear the impact list.'));
|
|
736
680
|
}
|
|
737
681
|
}
|
|
@@ -750,14 +694,14 @@ async function detectInstalledEditors(editors) {
|
|
|
750
694
|
}
|
|
751
695
|
/**
|
|
752
696
|
* Config env keys that are useful as real OS environment variables (the SDK
|
|
753
|
-
* paths + tool dir
|
|
754
|
-
*
|
|
697
|
+
* paths + tool dir). PATH entries for node/java/hdc are not included here —
|
|
698
|
+
* those are documented as manual PATH setup.
|
|
755
699
|
*
|
|
756
|
-
*
|
|
757
|
-
* `autotest` block
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
700
|
+
* The multimodal model config (HOMETRANS_MODEL_*) is included too: its canonical
|
|
701
|
+
* store is the `autotest.unified_model` block, but those four fields are mirrored
|
|
702
|
+
* into config.env by syncModelEnvIntoConfig so this one list is the single source
|
|
703
|
+
* for both config.json's `env` block and the machine env vars. Shared by UI
|
|
704
|
+
* alignment and self-test.
|
|
761
705
|
*/
|
|
762
706
|
export const MACHINE_ENV_KEYS = [
|
|
763
707
|
'DEVECO_SDK_HOME',
|
|
@@ -765,7 +709,9 @@ export const MACHINE_ENV_KEYS = [
|
|
|
765
709
|
'OHOS_SDK_PATH',
|
|
766
710
|
'HMS_SDK_PATH',
|
|
767
711
|
'HOMETRANS_TOOL_PATH',
|
|
768
|
-
'
|
|
712
|
+
'HOMETRANS_MODEL_API_KEY',
|
|
713
|
+
'HOMETRANS_MODEL_NAME',
|
|
714
|
+
'HOMETRANS_MODEL_BASE_URL',
|
|
769
715
|
];
|
|
770
716
|
/** Mask API-key values for display; show paths in full. */
|
|
771
717
|
function maskEnvValue(name, value) {
|
|
@@ -777,26 +723,43 @@ function maskEnvValue(name, value) {
|
|
|
777
723
|
/**
|
|
778
724
|
* Show the resolved config values that would become machine environment
|
|
779
725
|
* variables, then ask Y/N before writing them (Windows: setx; macOS/Linux:
|
|
780
|
-
* shell rc block).
|
|
781
|
-
*
|
|
726
|
+
* shell rc block). Variables already present in the OS environment with the
|
|
727
|
+
* exact same value are skipped — they are neither shown nor re-written, and if
|
|
728
|
+
* nothing needs updating the prompt is skipped entirely. Non-interactive shells
|
|
729
|
+
* skip without changing anything. Re-throws ExitPromptError so the caller can
|
|
730
|
+
* abort init on Ctrl-C.
|
|
782
731
|
*/
|
|
783
|
-
async function maybePersistMachineEnv(
|
|
784
|
-
|
|
785
|
-
|
|
732
|
+
async function maybePersistMachineEnv(config) {
|
|
733
|
+
// Refresh the mirrored HOMETRANS_MODEL_* in config.env from autotest.unified_model
|
|
734
|
+
// (the Model Configuration step above just edited the latter), so config.env is
|
|
735
|
+
// the single source for both config.json and the machine env vars below.
|
|
736
|
+
syncModelEnvIntoConfig(config);
|
|
737
|
+
const env = config.env;
|
|
738
|
+
const allVars = MACHINE_ENV_KEYS.map((k) => [k, (env[k] ?? '').trim()]).filter(([, value]) => value.length > 0);
|
|
739
|
+
if (allVars.length === 0)
|
|
786
740
|
return;
|
|
741
|
+
// Skip variables already set in the OS environment with the same value — no
|
|
742
|
+
// need to re-write them or re-ask. process.env reflects the environment this
|
|
743
|
+
// process launched with, so a prior `ht init` (seen from a fresh terminal)
|
|
744
|
+
// already shows up here.
|
|
745
|
+
const sameValue = ([name, value]) => (process.env[name] ?? '').trim() === value;
|
|
746
|
+
const alreadySet = allVars.filter(sameValue);
|
|
747
|
+
const vars = allVars.filter((v) => !sameValue(v));
|
|
748
|
+
if (vars.length === 0) {
|
|
749
|
+
console.log('');
|
|
750
|
+
console.log(chalk.green(' + Machine environment variables already set with the current values — nothing to add.'));
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
787
753
|
console.log('');
|
|
788
|
-
console.log(chalk.blue(' Add these to your machine environment variables?'));
|
|
789
|
-
console.log(chalk.gray(process.platform === 'win32'
|
|
790
|
-
? ' (Windows: persisted via setx to your user environment)'
|
|
791
|
-
: ' (macOS/Linux: written as an export block to your shell profile)'));
|
|
754
|
+
console.log(chalk.blue(' Add these to your machine environment variables? Skills and MCP tools read these from the OS environment at runtime.'));
|
|
792
755
|
for (const [name, value] of vars) {
|
|
793
756
|
console.log(` ${chalk.cyan(name)} = ${maskEnvValue(name, value)}`);
|
|
794
757
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
758
|
+
if (alreadySet.length > 0) {
|
|
759
|
+
console.log(chalk.gray(` (${alreadySet.length} already set with the same value — skipped: ${alreadySet
|
|
760
|
+
.map(([name]) => name)
|
|
761
|
+
.join(', ')})`));
|
|
762
|
+
}
|
|
800
763
|
let confirmed;
|
|
801
764
|
try {
|
|
802
765
|
const answers = await inquirer.prompt([
|
|
@@ -824,6 +787,199 @@ async function maybePersistMachineEnv(env) {
|
|
|
824
787
|
const note = await persistEnvVars(vars);
|
|
825
788
|
console.log(chalk.green(` + Environment variables ${note}`));
|
|
826
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Run `homegraph --version`. Returns the trimmed first output line on success
|
|
792
|
+
* (an empty string still counts as success — exit 0), or null when the command
|
|
793
|
+
* is missing or exits non-zero. On Windows the global npm bin is a `.cmd` shim,
|
|
794
|
+
* so the command is run through the shell there.
|
|
795
|
+
*/
|
|
796
|
+
function homegraphVersion() {
|
|
797
|
+
try {
|
|
798
|
+
const out = execFileSync('homegraph', ['--version'], {
|
|
799
|
+
encoding: 'utf-8',
|
|
800
|
+
timeout: 15000,
|
|
801
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
802
|
+
shell: process.platform === 'win32',
|
|
803
|
+
});
|
|
804
|
+
return (out ?? '').split('\n').map((l) => l.trim()).filter(Boolean)[0] ?? '';
|
|
805
|
+
}
|
|
806
|
+
catch {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Run `uv --version`. Returns the trimmed first output line on success (exit 0),
|
|
812
|
+
* or null when uv is missing / exits non-zero. Probes `uv` on PATH first, then
|
|
813
|
+
* the standalone installer's default location (`~/.local/bin/uv[.exe]`) — that
|
|
814
|
+
* binary exists right after a fresh install, before the current process PATH is
|
|
815
|
+
* refreshed, so verification works within the same `ht init` run.
|
|
816
|
+
*/
|
|
817
|
+
function uvVersion() {
|
|
818
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
819
|
+
const isWin = process.platform === 'win32';
|
|
820
|
+
const candidates = ['uv'];
|
|
821
|
+
if (home)
|
|
822
|
+
candidates.push(path.join(home, '.local', 'bin', isWin ? 'uv.exe' : 'uv'));
|
|
823
|
+
for (const cmd of candidates) {
|
|
824
|
+
try {
|
|
825
|
+
const out = execFileSync(cmd, ['--version'], {
|
|
826
|
+
encoding: 'utf-8',
|
|
827
|
+
timeout: 15000,
|
|
828
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
829
|
+
});
|
|
830
|
+
return (out ?? '').split('\n').map((l) => l.trim()).filter(Boolean)[0] ?? '';
|
|
831
|
+
}
|
|
832
|
+
catch {
|
|
833
|
+
// try next candidate
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Node-CLI part of the Dependency Installation step — installs the global Node
|
|
840
|
+
* CLIs the spec-generation skill needs (step 2「生成需求规格」): `homegraph` plus
|
|
841
|
+
* `arkanalyzer` (arkanalyzer parses ArkTS `.ets` and must be installed globally
|
|
842
|
+
* alongside homegraph). Verifies the result with `homegraph --version`.
|
|
843
|
+
* Idempotent: if homegraph already reports a version, the install is skipped.
|
|
844
|
+
* Never throws — failures are reported and `ht init` continues. The
|
|
845
|
+
* "Dependency Installation" section header is printed once by the caller.
|
|
846
|
+
*/
|
|
847
|
+
async function installNodeDependencies() {
|
|
848
|
+
console.log(chalk.gray(' homegraph + arkanalyzer'));
|
|
849
|
+
// Idempotent: a working `homegraph --version` means the deps are already there.
|
|
850
|
+
const existing = homegraphVersion();
|
|
851
|
+
if (existing !== null) {
|
|
852
|
+
console.log(chalk.green(` + homegraph already installed (${existing || 'version unknown'}) — skipping.`));
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (!whichSync('npm')) {
|
|
856
|
+
console.log(chalk.yellow(' ! npm not found on PATH — cannot install homegraph / arkanalyzer.'));
|
|
857
|
+
console.log(chalk.gray(' Install Node.js (bundles npm), then run: npm install -g homegraph arkanalyzer'));
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
let proceed = true;
|
|
861
|
+
try {
|
|
862
|
+
const answers = await inquirer.prompt([
|
|
863
|
+
{
|
|
864
|
+
type: 'confirm',
|
|
865
|
+
name: 'install',
|
|
866
|
+
message: 'Install homegraph + arkanalyzer globally now (npm install -g homegraph arkanalyzer)?',
|
|
867
|
+
default: true,
|
|
868
|
+
},
|
|
869
|
+
]);
|
|
870
|
+
proceed = answers.install;
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
if (isPromptAbort(err))
|
|
874
|
+
abortInit();
|
|
875
|
+
console.log(chalk.yellow(' Non-interactive: proceeding with install.'));
|
|
876
|
+
}
|
|
877
|
+
if (!proceed) {
|
|
878
|
+
console.log(chalk.yellow(' Skipped — install later with:'));
|
|
879
|
+
console.log(chalk.gray(' npm install -g homegraph arkanalyzer'));
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
console.log(chalk.gray(' Running: npm install -g homegraph arkanalyzer ...'));
|
|
883
|
+
try {
|
|
884
|
+
// Use the bare command name (not a resolved path) so the Windows shell can
|
|
885
|
+
// resolve the `npm.cmd` shim; shell:true is required on Windows for that.
|
|
886
|
+
execFileSync('npm', ['install', '-g', 'homegraph', 'arkanalyzer'], {
|
|
887
|
+
stdio: 'inherit',
|
|
888
|
+
timeout: 5 * 60 * 1000,
|
|
889
|
+
shell: process.platform === 'win32',
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
console.log(chalk.red(` ! npm install failed: ${err.message}`));
|
|
894
|
+
console.log(chalk.gray(' Re-run manually: npm install -g homegraph arkanalyzer'));
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
// Verify install: `homegraph --version` must return normally (exit 0).
|
|
898
|
+
const version = homegraphVersion();
|
|
899
|
+
if (version !== null) {
|
|
900
|
+
console.log(chalk.green(` + homegraph installed & verified (${version || 'version unknown'}).`));
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
console.log(chalk.yellow(' ! Installed, but `homegraph --version` did not return successfully.'));
|
|
904
|
+
console.log(chalk.gray(' Open a new terminal (PATH refresh), then verify: homegraph --version'));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* uv part of the Dependency Installation step — uv is the only Python toolchain
|
|
909
|
+
* HomeTrans needs: every Python script runs via `uv run`, and uv provides the
|
|
910
|
+
* Python interpreter on demand (no standalone Python install required). Uses the
|
|
911
|
+
* official standalone installer (https://docs.astral.sh/uv/getting-started/installation/):
|
|
912
|
+
* - Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
913
|
+
* - macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh (wget fallback)
|
|
914
|
+
* Idempotent: a working `uv --version` skips the install. Never throws —
|
|
915
|
+
* failures are reported and `ht init` continues. The "Dependency Installation"
|
|
916
|
+
* section header is printed once by the caller.
|
|
917
|
+
*/
|
|
918
|
+
async function installUv() {
|
|
919
|
+
console.log(chalk.gray(' uv'));
|
|
920
|
+
// Idempotent: a working `uv --version` means uv is already there.
|
|
921
|
+
const existing = uvVersion();
|
|
922
|
+
if (existing !== null) {
|
|
923
|
+
console.log(chalk.green(` + uv already installed (${existing || 'version unknown'}) — skipping.`));
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const isWin = process.platform === 'win32';
|
|
927
|
+
const installCmdText = isWin
|
|
928
|
+
? 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"'
|
|
929
|
+
: 'curl -LsSf https://astral.sh/uv/install.sh | sh';
|
|
930
|
+
const docs = 'https://docs.astral.sh/uv/getting-started/installation/';
|
|
931
|
+
let proceed = true;
|
|
932
|
+
try {
|
|
933
|
+
const answers = await inquirer.prompt([
|
|
934
|
+
{
|
|
935
|
+
type: 'confirm',
|
|
936
|
+
name: 'install',
|
|
937
|
+
message: `uv not found. Install it now via the official installer?\n ${installCmdText}`,
|
|
938
|
+
default: true,
|
|
939
|
+
},
|
|
940
|
+
]);
|
|
941
|
+
proceed = answers.install;
|
|
942
|
+
}
|
|
943
|
+
catch (err) {
|
|
944
|
+
if (isPromptAbort(err))
|
|
945
|
+
abortInit();
|
|
946
|
+
console.log(chalk.yellow(' Non-interactive: proceeding with install.'));
|
|
947
|
+
}
|
|
948
|
+
if (!proceed) {
|
|
949
|
+
console.log(chalk.yellow(' Skipped — install later with:'));
|
|
950
|
+
console.log(chalk.gray(` ${installCmdText}`));
|
|
951
|
+
console.log(chalk.gray(` Docs: ${docs}`));
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
console.log(chalk.gray(` Running: ${installCmdText} ...`));
|
|
955
|
+
try {
|
|
956
|
+
if (isWin) {
|
|
957
|
+
execFileSync('powershell', ['-ExecutionPolicy', 'ByPass', '-c', 'irm https://astral.sh/uv/install.ps1 | iex'], { stdio: 'inherit', timeout: 5 * 60 * 1000 });
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
// `curl | sh` needs a shell to pipe; prefer curl, fall back to wget so the
|
|
961
|
+
// installer also works on minimal systems that ship only one of them.
|
|
962
|
+
const script = 'if command -v curl >/dev/null 2>&1; then curl -LsSf https://astral.sh/uv/install.sh | sh; ' +
|
|
963
|
+
'else wget -qO- https://astral.sh/uv/install.sh | sh; fi';
|
|
964
|
+
execFileSync('sh', ['-c', script], { stdio: 'inherit', timeout: 5 * 60 * 1000 });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
console.log(chalk.red(` ! uv install failed: ${err.message}`));
|
|
969
|
+
console.log(chalk.gray(` Re-run manually: ${installCmdText}`));
|
|
970
|
+
console.log(chalk.gray(` Docs: ${docs}`));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
// Verify install: `uv --version` must return (PATH or standalone-install path).
|
|
974
|
+
const version = uvVersion();
|
|
975
|
+
if (version !== null) {
|
|
976
|
+
console.log(chalk.green(` + uv installed & verified (${version || 'version unknown'}).`));
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
console.log(chalk.yellow(' ! Installed, but `uv --version` did not return successfully.'));
|
|
980
|
+
console.log(chalk.gray(' Open a new terminal (PATH refresh), then verify: uv --version'));
|
|
981
|
+
}
|
|
982
|
+
}
|
|
827
983
|
export async function initCommand(options = {}) {
|
|
828
984
|
ensureChalkColor();
|
|
829
985
|
const banner = figlet.textSync('HomeTrans', { font: 'Standard' });
|
|
@@ -845,7 +1001,7 @@ export async function initCommand(options = {}) {
|
|
|
845
1001
|
// Generate an initial ~/.hometrans/config.json up front (first-use scenario):
|
|
846
1002
|
// loadHomeTransConfig writes a complete default file (editors + env + autotest)
|
|
847
1003
|
// when none exists, and saveHomeTransConfig flushes it again to be certain it is
|
|
848
|
-
// on disk before any later step (e.g.
|
|
1004
|
+
// on disk before any later step (e.g. Model Configuration) points the user at it.
|
|
849
1005
|
const configPath = getConfigPath();
|
|
850
1006
|
const configExisted = await pathExists(configPath);
|
|
851
1007
|
const config = await loadHomeTransConfig();
|
|
@@ -909,8 +1065,7 @@ export async function initCommand(options = {}) {
|
|
|
909
1065
|
}
|
|
910
1066
|
// --- User parameters (SDK paths) ---
|
|
911
1067
|
console.log('');
|
|
912
|
-
console.log(chalk.blue('
|
|
913
|
-
console.log(chalk.gray(' (press Enter to keep current value or leave empty)\n'));
|
|
1068
|
+
console.log(chalk.blue(' Harmony SDK Configuration'));
|
|
914
1069
|
try {
|
|
915
1070
|
const answers = await inquirer.prompt([
|
|
916
1071
|
{
|
|
@@ -935,16 +1090,12 @@ export async function initCommand(options = {}) {
|
|
|
935
1090
|
['OHOS_SDK_PATH', sdk.OHOS_SDK_PATH],
|
|
936
1091
|
['HMS_SDK_PATH', sdk.HMS_SDK_PATH],
|
|
937
1092
|
];
|
|
938
|
-
|
|
939
|
-
|
|
1093
|
+
// Validate the derived paths on disk silently — the listing itself is not
|
|
1094
|
+
// printed; only a warning surfaces when one or more paths fail to resolve.
|
|
940
1095
|
let missingCount = 0;
|
|
941
|
-
for (const [
|
|
942
|
-
|
|
943
|
-
if (!exists)
|
|
1096
|
+
for (const [, p] of checks) {
|
|
1097
|
+
if (!(await dirExists(p)))
|
|
944
1098
|
missingCount++;
|
|
945
|
-
const mark = exists ? chalk.green('+') : chalk.yellow('!');
|
|
946
|
-
const note = exists ? '' : chalk.yellow(' (not found on disk)');
|
|
947
|
-
console.log(` ${mark} ${name.padEnd(15)} : ${p}${note}`);
|
|
948
1099
|
}
|
|
949
1100
|
if (missingCount > 0) {
|
|
950
1101
|
console.log('');
|
|
@@ -959,15 +1110,37 @@ export async function initCommand(options = {}) {
|
|
|
959
1110
|
abortInit();
|
|
960
1111
|
console.log(chalk.yellow(' Parameter prompts skipped (non-interactive mode).'));
|
|
961
1112
|
}
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
//
|
|
967
|
-
// the
|
|
968
|
-
|
|
969
|
-
//
|
|
1113
|
+
// Model Configuration — configure the single multimodal LLM shared by UI
|
|
1114
|
+
// alignment and self-test. Stored in autotest.unified_model (config.json) and
|
|
1115
|
+
// exported as HOMETRANS_MODEL_* env vars by maybePersistMachineEnv below.
|
|
1116
|
+
await promptModelConfig(config);
|
|
1117
|
+
// Dependency Installation — auto-provisionable deps: the global npm CLIs the
|
|
1118
|
+
// spec-generation skill needs (homegraph + arkanalyzer) and uv (the Python
|
|
1119
|
+
// toolchain every script runs under). The section header is printed once here;
|
|
1120
|
+
// each sub-install prints its own gray sub-line. Runs before the Environment
|
|
1121
|
+
// Check so the check reflects anything freshly installed.
|
|
1122
|
+
console.log('');
|
|
1123
|
+
console.log(chalk.cyan(' Dependency Installation'));
|
|
1124
|
+
try {
|
|
1125
|
+
await installNodeDependencies();
|
|
1126
|
+
}
|
|
1127
|
+
catch (err) {
|
|
1128
|
+
if (isPromptAbort(err))
|
|
1129
|
+
abortInit();
|
|
1130
|
+
console.log(chalk.yellow(` ! homegraph / arkanalyzer install skipped: ${err.message}`));
|
|
1131
|
+
}
|
|
1132
|
+
try {
|
|
1133
|
+
await installUv();
|
|
1134
|
+
}
|
|
1135
|
+
catch (err) {
|
|
1136
|
+
if (isPromptAbort(err))
|
|
1137
|
+
abortInit();
|
|
1138
|
+
console.log(chalk.yellow(` ! uv install skipped: ${err.message}`));
|
|
1139
|
+
}
|
|
1140
|
+
// Detect external tools (adb / hdc / uv / java / homegraph + DevEco)
|
|
970
1141
|
// and report which skills/agents are impacted by anything missing.
|
|
1142
|
+
// (Python is no longer detected separately — every script runs via `uv run`,
|
|
1143
|
+
// and uv provides the interpreter on demand.)
|
|
971
1144
|
try {
|
|
972
1145
|
await runEnvironmentCheck(config.env);
|
|
973
1146
|
}
|
|
@@ -976,36 +1149,21 @@ export async function initCommand(options = {}) {
|
|
|
976
1149
|
}
|
|
977
1150
|
// Copy bundled tools/ into ~/.hometrans/tools and record env.HOMETRANS_TOOL_PATH.
|
|
978
1151
|
// self_test_runner.py (under the installed tools dir) reads the autotest
|
|
979
|
-
// config from ~/.hometrans/config.json at run time — see
|
|
980
|
-
let installedToolPath = null;
|
|
1152
|
+
// config from ~/.hometrans/config.json at run time — see promptModelConfig.
|
|
981
1153
|
try {
|
|
982
|
-
|
|
983
|
-
if (installedToolPath) {
|
|
984
|
-
console.log(chalk.green(` + tools copied -> ${prettyHome(installedToolPath)}`));
|
|
985
|
-
console.log(chalk.gray(` HOMETRANS_TOOL_PATH set in ${prettyHome(getConfigPath())}`));
|
|
986
|
-
}
|
|
1154
|
+
await installTools(toolsRoot, config);
|
|
987
1155
|
}
|
|
988
1156
|
catch (err) {
|
|
989
1157
|
console.log(chalk.red(` ! tools copy: ${err.message}`));
|
|
990
1158
|
}
|
|
991
|
-
//
|
|
992
|
-
//
|
|
993
|
-
|
|
994
|
-
const envReqDest = await installEnvRequirements();
|
|
995
|
-
if (envReqDest) {
|
|
996
|
-
console.log(chalk.green(` + env-requirements.json copied -> ${prettyHome(envReqDest)}`));
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
catch (err) {
|
|
1000
|
-
console.log(chalk.red(` ! env-requirements.json copy: ${err.message}`));
|
|
1001
|
-
}
|
|
1002
|
-
// (The autotest block in config.json was already configured by the Test
|
|
1003
|
-
// Configuration step above; self_test_runner.py reads it at run time.)
|
|
1159
|
+
// (The autotest.unified_model block in config.json was already configured by
|
|
1160
|
+
// the Model Configuration step above; self_test_runner.py reads it at run time
|
|
1161
|
+
// and the UI-alignment phone-agent reads the exported HOMETRANS_MODEL_* vars.)
|
|
1004
1162
|
// Offer to persist the resolved config values as real OS environment
|
|
1005
1163
|
// variables (Windows: setx; macOS/Linux: shell rc block). Shows what will be
|
|
1006
1164
|
// written and asks Y/N first; runs after HOMETRANS_TOOL_PATH is finalized above.
|
|
1007
1165
|
try {
|
|
1008
|
-
await maybePersistMachineEnv(config
|
|
1166
|
+
await maybePersistMachineEnv(config);
|
|
1009
1167
|
}
|
|
1010
1168
|
catch (err) {
|
|
1011
1169
|
if (isPromptAbort(err))
|
|
@@ -1016,7 +1174,7 @@ export async function initCommand(options = {}) {
|
|
|
1016
1174
|
const editorsToSetup = editors.filter((e) => selectedEditors.includes(e.name));
|
|
1017
1175
|
const result = { configured: [], skipped: [], errors: [] };
|
|
1018
1176
|
for (const editor of editorsToSetup) {
|
|
1019
|
-
console.log(chalk.
|
|
1177
|
+
console.log(chalk.cyan(` Configuring ${editor.name}...`));
|
|
1020
1178
|
await installForEditor(editor, skillsRoot, agentsRoot, result);
|
|
1021
1179
|
}
|
|
1022
1180
|
await setupMcpForAllEditors(editorsToSetup, result);
|