@workclaw/cli 1.0.320 → 1.0.322

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 (40) hide show
  1. package/README.md +0 -1
  2. package/dist/box/installer/installer.d.ts.map +1 -1
  3. package/dist/box/src/box.d.ts +4 -0
  4. package/dist/box/src/box.d.ts.map +1 -1
  5. package/dist/box/types/index.d.ts +19 -10
  6. package/dist/box/types/index.d.ts.map +1 -1
  7. package/dist/{index-CzXh8eTR.js → index-ZySzMX4-.js} +269 -227
  8. package/dist/index.js +1 -1
  9. package/dist/local/apis/index.d.ts +8 -7
  10. package/dist/local/apis/index.d.ts.map +1 -1
  11. package/dist/local/installer/installer.d.ts +34 -0
  12. package/dist/local/installer/installer.d.ts.map +1 -1
  13. package/dist/local/src/local.d.ts +4 -0
  14. package/dist/local/src/local.d.ts.map +1 -1
  15. package/dist/local/types/index.d.ts +31 -18
  16. package/dist/local/types/index.d.ts.map +1 -1
  17. package/dist/local/utils/index.d.ts +2 -1
  18. package/dist/local/utils/index.d.ts.map +1 -1
  19. package/dist/local/utils/machine-id.d.ts +10 -0
  20. package/dist/local/utils/machine-id.d.ts.map +1 -0
  21. package/dist/local/utils/path.d.ts +0 -6
  22. package/dist/local/utils/path.d.ts.map +1 -1
  23. package/dist/shared/utils/debug.d.ts +11 -2
  24. package/dist/shared/utils/debug.d.ts.map +1 -1
  25. package/dist/shared/utils/env.d.ts +6 -0
  26. package/dist/shared/utils/env.d.ts.map +1 -0
  27. package/dist/shared/utils/index.d.ts +2 -1
  28. package/dist/shared/utils/index.d.ts.map +1 -1
  29. package/dist/shared/utils/path.d.ts.map +1 -1
  30. package/dist/shared/utils/validate.d.ts +80 -0
  31. package/dist/shared/utils/validate.d.ts.map +1 -1
  32. package/dist/utils/config.d.ts +12 -5
  33. package/dist/utils/config.d.ts.map +1 -1
  34. package/dist/utils/http.d.ts +4 -13
  35. package/dist/utils/http.d.ts.map +1 -1
  36. package/dist/utils/index.d.ts +0 -1
  37. package/dist/utils/index.d.ts.map +1 -1
  38. package/package.json +2 -1
  39. package/dist/utils/path.d.ts +0 -23
  40. package/dist/utils/path.d.ts.map +0 -1
@@ -3,47 +3,41 @@ import path, { resolve, dirname } from "node:path";
3
3
  import process$1 from "node:process";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Command } from "commander";
6
- import { exec, execSync } from "node:child_process";
7
6
  import boxen from "boxen";
8
7
  import chalk from "chalk";
9
8
  import inquirer from "inquirer";
9
+ import { Debug } from "@mingto/debug";
10
+ import { execSync, exec } from "node:child_process";
10
11
  import semver from "semver";
11
12
  import { z as z$1 } from "zod";
12
13
  import fs from "node:fs/promises";
13
- import tar from "tar";
14
14
  import ora from "ora";
15
+ import tar from "tar";
15
16
  import axios from "axios";
16
- let isDebug = false;
17
- function setDebug(debug) {
18
- isDebug = debug;
19
- }
20
- function debugLog(...args) {
21
- if (isDebug) {
22
- console.log(chalk.gray(`[DEBUG]`), ...args);
17
+ import os from "node:os";
18
+ import crypto from "node:crypto";
19
+ const debug = new Debug();
20
+ function setDebug(enabled) {
21
+ if (enabled) {
22
+ debug.enable();
23
+ } else {
24
+ debug.disable();
23
25
  }
24
26
  }
25
- function validateOpenclawPath(openclawPath) {
26
- debugLog(`[路径验证] 开始验证路径: ${openclawPath}`);
27
- if (!path.isAbsolute(openclawPath)) {
28
- debugLog(`[路径验证] 失败: 路径不是绝对路径`);
29
- throw new Error(`路径必须是绝对路径,当前输入: ${openclawPath}`);
30
- }
31
- const invalidChars = /[<>"|?*]/.test(openclawPath);
32
- if (invalidChars) {
33
- debugLog(`[路径验证] 失败: 路径包含特殊字符`);
34
- throw new Error('路径不能包含特殊字符: < > " | ? *');
35
- }
36
- debugLog(`[路径验证] 通过: ${openclawPath}`);
27
+ function debugLog(...args) {
37
28
  }
38
- const ipv4Schema = z$1.ipv4({
39
- message: "请输入正确的 IP 地址格式,例如: 192.168.1.100"
40
- });
41
29
  const ERROR_CODES$1 = {
42
- APP_KEY_REQUIRED: "APP_KEY_REQUIRED",
43
- APP_SECRET_REQUIRED: "APP_SECRET_REQUIRED",
30
+ PHONE_REQUIRED: "PHONE_REQUIRED",
31
+ USER_PASS_REQUIRED: "USER_PASS_REQUIRED",
32
+ LOGIN_FAILED: "LOGIN_FAILED",
33
+ GET_BOUND_CONFIG_FAILED: "GET_BOUND_CONFIG_FAILED",
44
34
  NODE_VERSION_LOW: "NODE_VERSION_LOW",
35
+ NODE_NOT_FOUND: "NODE_NOT_FOUND",
45
36
  NPM_NOT_FOUND: "NPM_NOT_FOUND",
46
37
  NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED",
38
+ CONFIG_WRITE_FAILED: "CONFIG_WRITE_FAILED",
39
+ HTTP_ERROR: "HTTP_ERROR",
40
+ NETWORK_ERROR: "NETWORK_ERROR",
47
41
  INVALID_OPENCLAW_PATH: "INVALID_OPENCLAW_PATH",
48
42
  INVALID_ARGUMENT: "INVALID_ARGUMENT"
49
43
  };
@@ -54,6 +48,64 @@ let AppError$1 = class AppError extends Error {
54
48
  this.name = "AppError";
55
49
  }
56
50
  };
51
+ function checkEnv() {
52
+ const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
53
+ if (!semver.gte(nodeVersion, "18.0.0")) {
54
+ throw new AppError$1(ERROR_CODES$1.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
55
+ }
56
+ try {
57
+ const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
58
+ debugLog(`[环境检查] npm 版本: ${npmVersion}`);
59
+ debugLog("[环境检查] npm 检测通过");
60
+ } catch {
61
+ throw new AppError$1(ERROR_CODES$1.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
62
+ }
63
+ }
64
+ const EnvironmentSchema = z$1.enum(["test", "prod", "custom"], {
65
+ message: "环境类型必须是 test、prod 或 custom"
66
+ });
67
+ const ipv4Schema = z$1.ipv4({
68
+ message: "请输入正确的 IP 地址格式,例如: 192.168.1.100"
69
+ });
70
+ const phoneSchema = z$1.string().regex(/^1[3-9]\d{9}$/, {
71
+ message: "请输入正确的手机号码"
72
+ });
73
+ const userPassSchema = z$1.string().min(1, {
74
+ message: "用户密码不能为空"
75
+ });
76
+ const appKeySchema = z$1.string().min(1, {
77
+ message: "AppKey 不能为空"
78
+ });
79
+ const appSecretSchema = z$1.string().min(1, {
80
+ message: "AppSecret 不能为空"
81
+ });
82
+ const absolutePathSchema = z$1.string().refine(
83
+ (value) => {
84
+ return path.isAbsolute(value);
85
+ },
86
+ {
87
+ message: "路径必须是绝对路径"
88
+ }
89
+ );
90
+ const windowsPathSchema = z$1.string().refine(
91
+ (value) => !/[<>"|?*]/.test(value),
92
+ {
93
+ message: '路径不能包含特殊字符: < > " | ? *'
94
+ }
95
+ );
96
+ const openclawPathSchema = absolutePathSchema.and(windowsPathSchema);
97
+ const pluginVersionSchema = z$1.string().optional();
98
+ const wsUrlSchema = z$1.string().url({
99
+ message: "请输入有效的 WebSocket URL"
100
+ }).optional();
101
+ z$1.boolean();
102
+ function validateOpenclawPath(openclawPath) {
103
+ const result = openclawPathSchema.safeParse(openclawPath);
104
+ if (!result.success) {
105
+ debugLog(`[路径验证] 失败: ${result.error.issues[0].message}`);
106
+ throw new Error(result.error.issues[0].message);
107
+ }
108
+ }
57
109
  const CONFIG = {
58
110
  PLUGIN_NAME: "openclaw-workclaw",
59
111
  DEFAULT_BASE_URL: "https://workbrain.cn/backend-api",
@@ -116,6 +168,20 @@ function getConfig(env, customIp) {
116
168
  }
117
169
  return CONFIG;
118
170
  }
171
+ const ERROR_CODES = {
172
+ APP_KEY_REQUIRED: "APP_KEY_REQUIRED",
173
+ APP_SECRET_REQUIRED: "APP_SECRET_REQUIRED",
174
+ NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED",
175
+ INVALID_OPENCLAW_PATH: "INVALID_OPENCLAW_PATH",
176
+ INVALID_ARGUMENT: "INVALID_ARGUMENT"
177
+ };
178
+ class AppError2 extends Error {
179
+ constructor(code, message) {
180
+ super(message);
181
+ this.code = code;
182
+ this.name = "AppError";
183
+ }
184
+ }
119
185
  function getHomeDir$1() {
120
186
  return process$1.env.HOME || process$1.env.USERPROFILE || "";
121
187
  }
@@ -134,12 +200,20 @@ function execAsync$1(command, options) {
134
200
  function deepMerge$1(target, source) {
135
201
  const result = { ...target };
136
202
  for (const key in source) {
137
- const sourceValue = source[key];
138
- const targetValue = target[key];
139
- if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
140
- result[key] = deepMerge$1(targetValue, sourceValue);
141
- } else if (sourceValue !== void 0) {
142
- result[key] = sourceValue;
203
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
204
+ const sourceValue = source[key];
205
+ const targetValue = target[key];
206
+ if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
207
+ const mergedArray = [.../* @__PURE__ */ new Set([...targetValue, ...sourceValue])];
208
+ result[key] = mergedArray;
209
+ } else if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
210
+ result[key] = deepMerge$1(
211
+ targetValue,
212
+ sourceValue
213
+ );
214
+ } else if (sourceValue !== void 0) {
215
+ result[key] = sourceValue;
216
+ }
143
217
  }
144
218
  }
145
219
  return result;
@@ -152,25 +226,21 @@ class BoxInstaller {
152
226
  spinner;
153
227
  prefixText = "";
154
228
  validateConfig() {
155
- debugLog("[验证配置] 检查 appKey 和 appSecret...");
156
229
  if (!this.config.appKey) {
157
- throw new AppError$1(ERROR_CODES$1.APP_KEY_REQUIRED, "AppKey 不能为空,请使用 --app-key 参数");
230
+ throw new AppError2(ERROR_CODES.APP_KEY_REQUIRED, "AppKey 不能为空,请使用 --app-key 参数");
158
231
  }
159
232
  if (!this.config.appSecret) {
160
- throw new AppError$1(ERROR_CODES$1.APP_SECRET_REQUIRED, "AppSecret 不能为空,请使用 --app-secret 参数");
233
+ throw new AppError2(ERROR_CODES.APP_SECRET_REQUIRED, "AppSecret 不能为空,请使用 --app-secret 参数");
161
234
  }
162
235
  if (this.config.env === "custom") {
163
- debugLog("[验证配置] 检查自定义环境 IP...");
164
236
  if (!this.config.customIp) {
165
- throw new AppError$1(ERROR_CODES$1.INVALID_ARGUMENT, "自定义环境必须提供 --customIp 参数");
237
+ throw new AppError2(ERROR_CODES.INVALID_ARGUMENT, "自定义环境必须提供 --customIp 参数");
166
238
  }
167
239
  const result = ipv4Schema.safeParse(this.config.customIp);
168
240
  if (!result.success) {
169
- throw new AppError$1(ERROR_CODES$1.INVALID_ARGUMENT, result.error.issues[0].message);
241
+ throw new AppError2(ERROR_CODES.INVALID_ARGUMENT, result.error.issues[0].message);
170
242
  }
171
- debugLog("[验证配置] 自定义环境 IP 验证通过");
172
243
  }
173
- debugLog("[验证配置] 配置检查通过");
174
244
  }
175
245
  getPaths() {
176
246
  const env = this.config.env || "test";
@@ -183,7 +253,7 @@ class BoxInstaller {
183
253
  validateOpenclawPath(this.config.openclawPath);
184
254
  debugLog("[路径验证] 通过");
185
255
  } catch (error) {
186
- throw new AppError$1(ERROR_CODES$1.INVALID_OPENCLAW_PATH, error.message);
256
+ throw new AppError2(ERROR_CODES.INVALID_OPENCLAW_PATH, error.message);
187
257
  }
188
258
  baseDir = this.config.openclawPath;
189
259
  } else {
@@ -207,7 +277,6 @@ class BoxInstaller {
207
277
  updateSpinner(text) {
208
278
  this.spinner.prefixText = this.prefixText;
209
279
  this.spinner.text = chalk.cyan(text);
210
- debugLog(`[Spinner] ${text}`);
211
280
  }
212
281
  getPrefixText() {
213
282
  return this.prefixText;
@@ -238,7 +307,6 @@ class BoxInstaller {
238
307
  }
239
308
  }
240
309
  async doCleanOldFiles(paths) {
241
- debugLog("[清理旧版本] 开始清理旧版本...");
242
310
  this.prefixText += chalk.green(` ✓ 开始清理旧版本
243
311
  `);
244
312
  this.updateSpinner("正在清理旧版本...");
@@ -250,7 +318,6 @@ class BoxInstaller {
250
318
  `);
251
319
  debugLog("[清理旧版本] 旧版本清理成功");
252
320
  } catch {
253
- debugLog("[清理旧版本] 无旧版本需要清理");
254
321
  this.prefixText += chalk.green(` ✓ 无旧版本需要清理
255
322
  `);
256
323
  }
@@ -258,30 +325,24 @@ class BoxInstaller {
258
325
  await fs.access(paths.temp);
259
326
  await fs.rm(paths.temp, { recursive: true, force: true });
260
327
  } catch {
261
- debugLog("[清理旧版本] 无临时目录需要清理");
262
328
  }
263
329
  }
264
330
  async doDownloadFromNpm() {
265
331
  const paths = this.getPaths();
266
332
  this.spinner.prefixText = this.prefixText;
267
333
  this.spinner.text = chalk.cyan("正在准备安装目录...");
268
- debugLog("[下载插件] 创建扩展目录...");
269
334
  await fs.mkdir(paths.extensions, { recursive: true });
270
335
  debugLog(`[下载插件] 扩展目录: ${paths.extensions}`);
271
336
  const tempDir = path.join(paths.temp, `install-${Date.now()}`);
272
- debugLog(`[下载插件] 创建临时目录: ${tempDir}`);
273
337
  await fs.mkdir(tempDir, { recursive: true });
274
338
  this.spinner.prefixText = this.prefixText;
275
339
  this.spinner.text = chalk.cyan("正在下载插件...");
276
- debugLog("[下载插件] 执行 npm pack 下载源码包");
277
- debugLog(`[下载插件] 工作目录: ${tempDir}`);
278
340
  const maxRetries = 3;
279
341
  let lastError = "";
280
342
  let tarballPath = "";
281
343
  for (let i = 1; i <= maxRetries; i++) {
282
344
  this.spinner.prefixText = this.prefixText;
283
345
  this.spinner.text = chalk.cyan(`正在下载 (${i}/${maxRetries})...`);
284
- debugLog(`[下载插件] 第 ${i} 次尝试下载 tarball...`);
285
346
  try {
286
347
  const packageName = this.getPackageName();
287
348
  debugLog(`[下载插件] 下载包名: ${packageName}`);
@@ -297,16 +358,14 @@ class BoxInstaller {
297
358
  break;
298
359
  } catch (error) {
299
360
  lastError = error.message || "未知错误";
300
- debugLog(`[下载插件] 第 ${i} 次尝试失败: ${lastError}`);
301
361
  if (i < maxRetries) {
302
362
  this.spinner.text = chalk.cyan(`下载失败,3秒后重试...`);
303
- debugLog("[下载插件] 等待 3 秒后重试...");
304
363
  await new Promise((resolve2) => setTimeout(resolve2, 3e3));
305
364
  }
306
365
  }
307
366
  }
308
367
  if (!tarballPath || !await fs.access(tarballPath).then(() => true).catch(() => false)) {
309
- throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
368
+ throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
310
369
  }
311
370
  this.spinner.prefixText = this.prefixText;
312
371
  this.spinner.text = chalk.cyan("正在解压插件...");
@@ -321,17 +380,15 @@ class BoxInstaller {
321
380
  });
322
381
  debugLog("[下载插件] tarball 解压成功");
323
382
  } catch (error) {
324
- throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
383
+ throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
325
384
  }
326
385
  const pluginPath = path.join(paths.target, "package.json");
327
386
  const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
328
387
  if (!pluginExists) {
329
- throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
388
+ throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
330
389
  }
331
- debugLog("[下载插件] 插件解压成功");
332
390
  this.spinner.prefixText = this.prefixText;
333
391
  this.spinner.text = chalk.cyan("正在安装依赖...");
334
- debugLog("[下载插件] 执行 npm install 安装生产环境依赖");
335
392
  try {
336
393
  await execAsync$1("npm install --omit=dev --ignore-scripts --quiet", {
337
394
  cwd: paths.target,
@@ -340,11 +397,7 @@ class BoxInstaller {
340
397
  debugLog("[下载插件] npm install 执行成功");
341
398
  } catch (error) {
342
399
  const errorMsg = error.message || "";
343
- if (errorMsg.includes("npm warn") || errorMsg.includes("deprecated")) {
344
- debugLog("[下载插件] npm install 有警告但可能成功,继续流程");
345
- } else {
346
- debugLog(`[下载插件] npm install 失败: ${errorMsg}`);
347
- }
400
+ if (errorMsg.includes("npm warn") || errorMsg.includes("deprecated")) ;
348
401
  }
349
402
  this.prefixText += chalk.green(` ✓ 插件安装完成
350
403
  `);
@@ -352,11 +405,9 @@ class BoxInstaller {
352
405
  await fs.rm(tempDir, { recursive: true, force: true });
353
406
  debugLog("[下载插件] 清理临时目录完成");
354
407
  } catch {
355
- debugLog("[下载插件] 清理临时目录失败(忽略)");
356
408
  }
357
409
  }
358
410
  async doUpdateConfig(paths) {
359
- debugLog("[更新配置] 开始更新配置...");
360
411
  this.prefixText += chalk.green(` ✓ 开始更新配置
361
412
  `);
362
413
  this.updateSpinner("正在生成配置...");
@@ -366,9 +417,8 @@ class BoxInstaller {
366
417
  originalConfig = JSON.parse(content);
367
418
  debugLog("[更新配置] 读取原有配置成功");
368
419
  } catch {
369
- debugLog("[更新配置] 无原有配置");
370
420
  }
371
- const config = getConfig(this.config.env);
421
+ const config = getConfig(this.config.env || "test", this.config.customIp);
372
422
  const newConfig = {
373
423
  // diagnostics: 诊断配置
374
424
  diagnostics: {
@@ -385,7 +435,7 @@ class BoxInstaller {
385
435
  // models: 模型配置
386
436
  models: {
387
437
  // 配置合并模式:'merge' 合并 | 'replace' 替换
388
- mode: "merge",
438
+ mode: "replace",
389
439
  providers: {
390
440
  "siliconflow-minimax": {
391
441
  // 模型 API 基础地址
@@ -503,8 +553,6 @@ class BoxInstaller {
503
553
  "openclaw-workclaw": {
504
554
  // 启用通道
505
555
  enabled: true,
506
- // 连接模式:'websocket' | 'http'
507
- connectionMode: "websocket",
508
556
  // 应用密钥
509
557
  appKey: this.config.appKey,
510
558
  // 应用密钥
@@ -589,6 +637,9 @@ class BoxInstaller {
589
637
  },
590
638
  // plugins: 插件配置
591
639
  plugins: {
640
+ load: {
641
+ paths: [paths.extensions]
642
+ },
592
643
  allow: [config.PLUGIN_NAME],
593
644
  installs: {
594
645
  [config.PLUGIN_NAME]: {
@@ -604,36 +655,16 @@ class BoxInstaller {
604
655
  };
605
656
  const finalConfig = deepMerge$1(originalConfig, newConfig);
606
657
  this.updateSpinner("正在写入配置...");
607
- debugLog("[更新配置] 写入配置文件...");
608
658
  await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
609
659
  this.prefixText += chalk.green(` ✓ 配置文件更新成功
610
660
  `);
611
661
  debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
612
662
  }
613
663
  }
614
- function checkEnv$1() {
615
- debugLog("[环境检查] 检测 Node.js 版本...");
616
- const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
617
- debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
618
- if (!semver.gte(nodeVersion, "18.0.0")) {
619
- throw new AppError$1(ERROR_CODES$1.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
620
- }
621
- debugLog("[环境检查] Node.js 版本检查通过");
622
- debugLog("[环境检查] 检测 npm...");
623
- try {
624
- const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
625
- debugLog(`[环境检查] npm 版本: ${npmVersion}`);
626
- debugLog("[环境检查] npm 检测通过");
627
- } catch {
628
- throw new AppError$1(ERROR_CODES$1.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
629
- }
630
- }
631
664
  async function createBoxCommand(options) {
632
665
  setDebug(!!options.debug);
633
- debugLog("[盒子安装] 开始处理...");
634
666
  debugLog(`[盒子安装] 参数: env=${options.env}, appKey=${options.appKey ? "***" : "未提供"}, customIp=${options.customIp}, debug=${options.debug}`);
635
- checkEnv$1();
636
- debugLog("[盒子安装] 环境检查通过");
667
+ checkEnv();
637
668
  try {
638
669
  let env = options.env;
639
670
  let appKey = options.appKey;
@@ -663,8 +694,9 @@ async function createBoxCommand(options) {
663
694
  name: "appKey",
664
695
  message: chalk.cyan("请输入 AppKey:"),
665
696
  validate: (value) => {
666
- if (!value || value.trim() === "") {
667
- return chalk.red("AppKey 不能为空");
697
+ const result = appKeySchema.safeParse(value);
698
+ if (!result.success) {
699
+ return chalk.red(result.error.issues[0].message);
668
700
  }
669
701
  return true;
670
702
  }
@@ -679,8 +711,9 @@ async function createBoxCommand(options) {
679
711
  name: "appSecret",
680
712
  message: chalk.cyan("请输入 AppSecret:"),
681
713
  validate: (value) => {
682
- if (!value || value.trim() === "") {
683
- return chalk.red("AppSecret 不能为空");
714
+ const result = appSecretSchema.safeParse(value);
715
+ if (!result.success) {
716
+ return chalk.red(result.error.issues[0].message);
684
717
  }
685
718
  return true;
686
719
  }
@@ -765,31 +798,10 @@ async function createBoxCommand(options) {
765
798
  function registerCommands$1(program2) {
766
799
  program2.command("box").description("盒子设备安装(无需登录)").option("-e, --env <env>", "环境 (test/prod/custom)").option("--app-key <appKey>", "App Key").option("--app-secret <appSecret>", "App Secret").option("--customIp <ip>", "自定义后端 IP(仅 custom 环境生效)").option("--ws-url <url>", "自定义 WebSocket URL(仅 custom 环境生效,默认自动生成)").option("--plugin-version <plugin-version>", "插件版本号(默认最新版)").option("--openclaw-path <path>", "OpenClaw 安装目录路径(默认 ~/.openclaw)").option("--debug", "开启调试日志").action(createBoxCommand);
767
800
  }
768
- const ERROR_CODES = {
769
- PHONE_REQUIRED: "PHONE_REQUIRED",
770
- USER_PASS_REQUIRED: "USER_PASS_REQUIRED",
771
- LOGIN_FAILED: "LOGIN_FAILED",
772
- GET_BOUND_CONFIG_FAILED: "GET_BOUND_CONFIG_FAILED",
773
- NODE_VERSION_LOW: "NODE_VERSION_LOW",
774
- NODE_NOT_FOUND: "NODE_NOT_FOUND",
775
- NPM_NOT_FOUND: "NPM_NOT_FOUND",
776
- NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED",
777
- CONFIG_WRITE_FAILED: "CONFIG_WRITE_FAILED",
778
- HTTP_ERROR: "HTTP_ERROR",
779
- NETWORK_ERROR: "NETWORK_ERROR",
780
- INVALID_OPENCLAW_PATH: "INVALID_OPENCLAW_PATH",
781
- INVALID_ARGUMENT: "INVALID_ARGUMENT"
782
- };
783
- class AppError2 extends Error {
784
- constructor(code, message) {
785
- super(message);
786
- this.code = code;
787
- this.name = "AppError";
788
- }
789
- }
790
- function createHttpClient() {
801
+ function createHttpClient(baseURL) {
791
802
  return axios.create({
792
- timeout: 1e4,
803
+ baseURL,
804
+ timeout: 3e4,
793
805
  headers: {
794
806
  "Content-Type": "application/json"
795
807
  }
@@ -797,7 +809,6 @@ function createHttpClient() {
797
809
  }
798
810
  async function httpPost(url, data, config) {
799
811
  const client = createHttpClient();
800
- debugLog(`[HTTP POST] 请求地址: ${url}`);
801
812
  debugLog(`[HTTP POST] 请求参数: ${JSON.stringify(data)}`);
802
813
  debugLog(`[HTTP POST] 请求头: ${JSON.stringify(config?.headers || {})}`);
803
814
  try {
@@ -816,28 +827,23 @@ async function httpPost(url, data, config) {
816
827
  debugLog(`[HTTP POST] 响应状态文本: ${axiosError.response.statusText}`);
817
828
  debugLog(`[HTTP POST] 响应数据: ${JSON.stringify(responseData)}`);
818
829
  if (typeof responseData === "string") {
819
- throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData}`);
830
+ throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData}`);
820
831
  } else if (responseData && typeof responseData === "object" && "message" in responseData) {
821
- throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData.message}`);
832
+ throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData.message}`);
822
833
  } else {
823
- throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${axiosError.response.statusText}`);
834
+ throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${axiosError.response.statusText}`);
824
835
  }
825
836
  } else if (axiosError.request) {
826
- debugLog(`[HTTP POST] 未收到响应`);
827
837
  debugLog(`[HTTP POST] 错误信息: ${axiosError.message}`);
828
- throw new AppError2(ERROR_CODES.NETWORK_ERROR, `网络错误: ${axiosError.message}`);
838
+ throw new AppError$1(ERROR_CODES$1.NETWORK_ERROR, `网络错误: ${axiosError.message}`);
829
839
  } else {
830
- debugLog(`[HTTP POST] 请求配置错误`);
831
840
  debugLog(`[HTTP POST] 错误信息: ${axiosError.message}`);
832
- throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求配置错误: ${axiosError.message}`);
841
+ throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求配置错误: ${axiosError.message}`);
833
842
  }
834
843
  }
835
844
  }
836
845
  async function login(phone, password, config) {
837
846
  const url = `${config.API.TUZAI_BASE_URL}/user/login/pass`;
838
- debugLog("[登录] 开始登录...");
839
- debugLog(`[登录] 请求地址: ${url}`);
840
- debugLog(`[登录] 请求参数: phone=${phone}, password=***`);
841
847
  try {
842
848
  const response = await httpPost(url, {
843
849
  phone,
@@ -850,25 +856,22 @@ async function login(phone, password, config) {
850
856
  return data.data.token;
851
857
  }
852
858
  debugLog("[登录] 登录失败");
853
- throw new AppError2(ERROR_CODES.LOGIN_FAILED, data.message || "登录失败");
859
+ throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, data.message || "登录失败");
854
860
  } catch (error) {
855
- if (error instanceof AppError2) {
861
+ if (error instanceof AppError$1) {
856
862
  throw error;
857
863
  }
858
864
  debugLog(`[登录] 发生错误: ${error.message}`);
859
- throw new AppError2(ERROR_CODES.LOGIN_FAILED, error.message);
865
+ throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, error.message);
860
866
  }
861
867
  }
862
- async function fetchBoundConfig(token, phone, config) {
868
+ async function fetchBoundConfig(token, phone, localCode, config) {
863
869
  const url = `${config.API.TUZAI_BASE_URL}/work-bot/local/bound/init`;
864
- debugLog("[获取绑定配置] 开始获取绑定配置...");
865
- debugLog(`[获取绑定配置] 请求地址: ${url}`);
866
- debugLog(`[获取绑定配置] 请求参数: phone=${phone}, localCode=001`);
867
870
  debugLog(`[获取绑定配置] 请求头: Authorization=${token.substring(0, 20)}...`);
868
871
  try {
869
872
  const response = await httpPost(url, {
870
873
  phone,
871
- localCode: "001"
874
+ localCode
872
875
  }, {
873
876
  headers: {
874
877
  Authorization: token
@@ -894,20 +897,48 @@ async function fetchBoundConfig(token, phone, config) {
894
897
  debugLog(`[获取绑定配置] modelApiBaseUrl: ${boundConfig.modelApiBaseUrl || "undefined"}`);
895
898
  if (!boundConfig.appKey || !boundConfig.appSecret || !boundConfig.agentId) {
896
899
  debugLog("[获取绑定配置] 缺少必要字段");
897
- throw new AppError2(ERROR_CODES.GET_BOUND_CONFIG_FAILED, "获取绑定配置失败:缺少必要字段");
900
+ throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, "获取绑定配置失败:缺少必要字段");
898
901
  }
899
902
  return boundConfig;
900
903
  }
901
904
  debugLog("[获取绑定配置] 获取绑定配置失败");
902
- throw new AppError2(ERROR_CODES.GET_BOUND_CONFIG_FAILED, data.message || data.msg || "获取绑定配置失败");
905
+ throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, data.message || data.msg || "获取绑定配置失败");
903
906
  } catch (error) {
904
- if (error instanceof AppError2) {
907
+ if (error instanceof AppError$1) {
905
908
  throw error;
906
909
  }
907
910
  debugLog(`[获取绑定配置] 发生错误: ${error.message}`);
908
- throw new AppError2(ERROR_CODES.GET_BOUND_CONFIG_FAILED, error.message);
911
+ throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, error.message);
909
912
  }
910
913
  }
914
+ const LocalInstallerConfigSchema = z$1.object({
915
+ env: EnvironmentSchema.optional(),
916
+ customIp: ipv4Schema.optional(),
917
+ wsUrl: wsUrlSchema,
918
+ phone: phoneSchema,
919
+ userPass: userPassSchema,
920
+ pluginVersion: pluginVersionSchema,
921
+ openclawPath: openclawPathSchema.optional()
922
+ }).refine(
923
+ (data) => {
924
+ if (data.env === "custom") {
925
+ return data.customIp !== void 0 && data.customIp !== "";
926
+ }
927
+ return true;
928
+ },
929
+ {
930
+ message: "自定义环境必须提供 customIp",
931
+ path: ["customIp"]
932
+ }
933
+ );
934
+ z$1.object({
935
+ appKey: z$1.string(),
936
+ appSecret: z$1.string(),
937
+ userId: z$1.string().optional(),
938
+ agentId: z$1.string(),
939
+ modelApiKey: z$1.string().optional(),
940
+ modelApiBaseUrl: z$1.string().optional()
941
+ });
911
942
  var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
912
943
  function int2char(n) {
913
944
  return BI_RM.charAt(n);
@@ -4798,6 +4829,29 @@ function rsaEncrypt(txt) {
4798
4829
  const result = encrypt.encrypt(txt);
4799
4830
  return result || null;
4800
4831
  }
4832
+ function generateMachineId() {
4833
+ const info = [
4834
+ os.hostname(),
4835
+ // 主机名
4836
+ os.platform(),
4837
+ // 平台
4838
+ os.arch(),
4839
+ // 架构
4840
+ os.totalmem(),
4841
+ // 总内存
4842
+ os.cpus().length,
4843
+ // CPU 核数
4844
+ os.cpus()[0]?.model || "",
4845
+ // CPU 型号
4846
+ os.release()
4847
+ // 系统版本
4848
+ ].join("|");
4849
+ const hash = crypto.createHash("sha256").update(info).digest("hex");
4850
+ return hash.substring(0, 16).toUpperCase();
4851
+ }
4852
+ function buildLocalCode(phone, machineId) {
4853
+ return `${phone}-${machineId}`;
4854
+ }
4801
4855
  function getHomeDir() {
4802
4856
  return process$1.env.HOME || process$1.env.USERPROFILE || "";
4803
4857
  }
@@ -4816,12 +4870,20 @@ function execAsync(command, options) {
4816
4870
  function deepMerge(target, source) {
4817
4871
  const result = { ...target };
4818
4872
  for (const key in source) {
4819
- const sourceValue = source[key];
4820
- const targetValue = target[key];
4821
- if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
4822
- result[key] = deepMerge(targetValue, sourceValue);
4823
- } else if (sourceValue !== void 0) {
4824
- result[key] = sourceValue;
4873
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
4874
+ const sourceValue = source[key];
4875
+ const targetValue = target[key];
4876
+ if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
4877
+ const mergedArray = [.../* @__PURE__ */ new Set([...targetValue, ...sourceValue])];
4878
+ result[key] = mergedArray;
4879
+ } else if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
4880
+ result[key] = deepMerge(
4881
+ targetValue,
4882
+ sourceValue
4883
+ );
4884
+ } else if (sourceValue !== void 0) {
4885
+ result[key] = sourceValue;
4886
+ }
4825
4887
  }
4826
4888
  }
4827
4889
  return result;
@@ -4831,38 +4893,32 @@ class LocalInstaller {
4831
4893
  prefixText = "";
4832
4894
  config;
4833
4895
  envConfig;
4896
+ /**
4897
+ * 构造函数
4898
+ */
4834
4899
  constructor(config) {
4835
4900
  this.config = config;
4836
4901
  const env = this.config.env || "test";
4837
4902
  this.envConfig = getConfig(env, this.config.customIp);
4838
4903
  this.spinner = ora({ color: "cyan" }).start();
4839
4904
  }
4905
+ /**
4906
+ * 验证安装配置
4907
+ */
4840
4908
  validateConfig() {
4841
- debugLog("[验证配置] 检查手机号码和密码...");
4842
- if (!this.config.phone) {
4843
- throw new AppError2(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4844
- }
4845
- if (!this.config.userPass) {
4846
- throw new AppError2(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
4847
- }
4848
- if (this.config.env === "custom") {
4849
- debugLog("[验证配置] 检查自定义环境 IP...");
4850
- if (!this.config.customIp) {
4851
- throw new AppError2(ERROR_CODES.INVALID_ARGUMENT, "自定义环境必须提供 --custom-ip 参数");
4852
- }
4853
- const result = ipv4Schema.safeParse(this.config.customIp);
4854
- if (!result.success) {
4855
- throw new AppError2(ERROR_CODES.INVALID_ARGUMENT, result.error.issues[0].message);
4856
- }
4857
- debugLog("[验证配置] 自定义环境 IP 验证通过");
4909
+ const result = LocalInstallerConfigSchema.safeParse(this.config);
4910
+ if (!result.success) {
4911
+ debugLog(`[验证配置] 验证失败: ${result.error.issues[0].message}`);
4912
+ throw new AppError$1(ERROR_CODES$1.INVALID_ARGUMENT, result.error.issues[0].message);
4858
4913
  }
4859
- debugLog("[验证配置] 配置验证通过");
4860
4914
  }
4915
+ /**
4916
+ * 执行安装流程
4917
+ */
4861
4918
  async install() {
4862
4919
  try {
4863
4920
  debugLog("[安装开始]");
4864
4921
  this.validateConfig();
4865
- const env = this.config.env || "test";
4866
4922
  let baseDir;
4867
4923
  if (this.config.openclawPath) {
4868
4924
  debugLog(`[路径验证] 验证自定义路径: ${this.config.openclawPath}`);
@@ -4870,7 +4926,7 @@ class LocalInstaller {
4870
4926
  validateOpenclawPath(this.config.openclawPath);
4871
4927
  debugLog("[路径验证] 通过");
4872
4928
  } catch (error) {
4873
- throw new AppError2(ERROR_CODES.INVALID_OPENCLAW_PATH, error.message);
4929
+ throw new AppError$1(ERROR_CODES$1.INVALID_OPENCLAW_PATH, error.message);
4874
4930
  }
4875
4931
  baseDir = this.config.openclawPath;
4876
4932
  } else {
@@ -4890,8 +4946,12 @@ class LocalInstaller {
4890
4946
  debugLog(`[路径配置] target=${paths.target}`);
4891
4947
  debugLog(`[路径配置] config=${paths.config}`);
4892
4948
  debugLog(`[路径配置] workspace=${paths.workspace}`);
4949
+ const machineId = generateMachineId();
4950
+ const localCode = buildLocalCode(this.config.phone, machineId);
4951
+ debugLog(`[机器码] machineId=${machineId}`);
4952
+ debugLog(`[机器码] localCode=${localCode}`);
4893
4953
  const token = await this.doLogin();
4894
- const boundConfig = await this.doFetchBoundConfig(token);
4954
+ const boundConfig = await this.doFetchBoundConfig(token, localCode);
4895
4955
  await this.doCleanOldFiles(paths.target);
4896
4956
  await this.doDownloadFromNpm(paths);
4897
4957
  await this.doUpdateConfig(paths, boundConfig);
@@ -4907,47 +4967,55 @@ class LocalInstaller {
4907
4967
  throw error;
4908
4968
  }
4909
4969
  }
4970
+ /**
4971
+ * 获取前缀文本(用于显示安装进度)
4972
+ */
4910
4973
  getPrefixText() {
4911
4974
  return this.prefixText;
4912
4975
  }
4976
+ /**
4977
+ * 获取完整的 npm 包名(包含版本号)
4978
+ */
4913
4979
  getPackageName() {
4914
4980
  if (this.config.pluginVersion) {
4915
4981
  return `${PLUGIN_PACKAGE_NAME}@${this.config.pluginVersion}`;
4916
4982
  }
4917
4983
  return PLUGIN_PACKAGE_NAME;
4918
4984
  }
4985
+ /**
4986
+ * 执行用户登录
4987
+ */
4919
4988
  async doLogin() {
4920
4989
  this.spinner.prefixText = this.prefixText;
4921
4990
  this.spinner.text = chalk.cyan("正在验证账号...");
4922
4991
  debugLog(`[用户登录] 手机号: ${this.config.phone}`);
4923
- debugLog("[用户登录] RSA 加密密码...");
4924
4992
  const encryptedPassword = rsaEncrypt(this.config.userPass);
4925
4993
  if (!encryptedPassword) {
4926
- throw new AppError2(ERROR_CODES.LOGIN_FAILED, "密码加密失败");
4994
+ throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, "密码加密失败");
4927
4995
  }
4928
- debugLog("[用户登录] 密码加密成功");
4929
- debugLog("[用户登录] 调用登录接口...");
4930
4996
  const token = await login(this.config.phone, encryptedPassword, this.envConfig);
4931
4997
  this.prefixText += chalk.green(` ✓ 账号验证成功
4932
4998
  `);
4933
- debugLog("[用户登录] 登录成功");
4934
4999
  return token;
4935
5000
  }
4936
- async doFetchBoundConfig(token) {
5001
+ /**
5002
+ * 获取绑定配置
5003
+ */
5004
+ async doFetchBoundConfig(token, localCode) {
4937
5005
  this.spinner.prefixText = this.prefixText;
4938
5006
  this.spinner.text = chalk.cyan("正在获取绑定信息...");
4939
- debugLog("[获取配置] 调用绑定配置接口...");
4940
- const boundConfig = await fetchBoundConfig(token, this.config.phone, this.envConfig);
5007
+ const boundConfig = await fetchBoundConfig(token, this.config.phone, localCode, this.envConfig);
4941
5008
  debugLog(`[获取配置] agentId=${boundConfig.agentId}, appKey=${boundConfig.appKey?.slice(0, 8)}...`);
4942
5009
  this.prefixText += chalk.green(` ✓ 绑定信息获取成功
4943
5010
  `);
4944
- debugLog("[获取配置] 获取成功");
4945
5011
  return boundConfig;
4946
5012
  }
5013
+ /**
5014
+ * 清理旧版本文件
5015
+ */
4947
5016
  async doCleanOldFiles(targetPath) {
4948
5017
  this.spinner.prefixText = this.prefixText;
4949
5018
  this.spinner.text = chalk.cyan("正在检查已安装版本...");
4950
- debugLog(`[清理旧版本] 检查目录: ${targetPath}`);
4951
5019
  try {
4952
5020
  await fs.access(targetPath);
4953
5021
  this.spinner.text = chalk.cyan("正在清理旧文件...");
@@ -4959,29 +5027,26 @@ class LocalInstaller {
4959
5027
  } catch {
4960
5028
  this.prefixText += chalk.green(` ✓ 无旧版本需要清理
4961
5029
  `);
4962
- debugLog("[清理旧版本] 未发现旧版本");
4963
5030
  }
4964
5031
  }
5032
+ /**
5033
+ * 从 npm 下载并安装插件
5034
+ */
4965
5035
  async doDownloadFromNpm(paths) {
4966
5036
  this.spinner.prefixText = this.prefixText;
4967
5037
  this.spinner.text = chalk.cyan("正在准备安装目录...");
4968
- debugLog("[下载插件] 创建扩展目录...");
4969
5038
  await fs.mkdir(paths.extensions, { recursive: true });
4970
5039
  debugLog(`[下载插件] 扩展目录: ${paths.extensions}`);
4971
5040
  const tempDir = path.join(paths.temp, `install-${Date.now()}`);
4972
- debugLog(`[下载插件] 创建临时目录: ${tempDir}`);
4973
5041
  await fs.mkdir(tempDir, { recursive: true });
4974
5042
  this.spinner.prefixText = this.prefixText;
4975
5043
  this.spinner.text = chalk.cyan("正在下载插件...");
4976
- debugLog("[下载插件] 执行 npm pack 下载源码包");
4977
- debugLog(`[下载插件] 工作目录: ${tempDir}`);
4978
5044
  const maxRetries = 3;
4979
5045
  let lastError = "";
4980
5046
  let tarballPath = "";
4981
5047
  for (let i = 1; i <= maxRetries; i++) {
4982
5048
  this.spinner.prefixText = this.prefixText;
4983
5049
  this.spinner.text = chalk.cyan(`正在下载 (${i}/${maxRetries})...`);
4984
- debugLog(`[下载插件] 第 ${i} 次尝试下载 tarball...`);
4985
5050
  try {
4986
5051
  const packageName = this.getPackageName();
4987
5052
  debugLog(`[下载插件] 下载包名: ${packageName}`);
@@ -4997,16 +5062,14 @@ class LocalInstaller {
4997
5062
  break;
4998
5063
  } catch (error) {
4999
5064
  lastError = error.message || "未知错误";
5000
- debugLog(`[下载插件] 第 ${i} 次尝试失败: ${lastError}`);
5001
5065
  if (i < maxRetries) {
5002
5066
  this.spinner.text = chalk.cyan(`下载失败,3秒后重试...`);
5003
- debugLog("[下载插件] 等待 3 秒后重试...");
5004
5067
  await new Promise((resolve2) => setTimeout(resolve2, 3e3));
5005
5068
  }
5006
5069
  }
5007
5070
  }
5008
5071
  if (!tarballPath || !await fs.access(tarballPath).then(() => true).catch(() => false)) {
5009
- throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
5072
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
5010
5073
  }
5011
5074
  this.spinner.prefixText = this.prefixText;
5012
5075
  this.spinner.text = chalk.cyan("正在解压插件...");
@@ -5021,17 +5084,15 @@ class LocalInstaller {
5021
5084
  });
5022
5085
  debugLog("[下载插件] tarball 解压成功");
5023
5086
  } catch (error) {
5024
- throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
5087
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
5025
5088
  }
5026
5089
  const pluginPath = path.join(paths.target, "package.json");
5027
5090
  const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
5028
5091
  if (!pluginExists) {
5029
- throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
5092
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
5030
5093
  }
5031
- debugLog("[下载插件] 插件解压成功");
5032
5094
  this.spinner.prefixText = this.prefixText;
5033
5095
  this.spinner.text = chalk.cyan("正在安装依赖...");
5034
- debugLog("[下载插件] 执行 npm install 安装生产环境依赖");
5035
5096
  try {
5036
5097
  await execAsync("npm install --omit=dev --ignore-scripts --quiet", {
5037
5098
  cwd: paths.target,
@@ -5039,11 +5100,10 @@ class LocalInstaller {
5039
5100
  });
5040
5101
  debugLog("[下载插件] npm install 执行成功");
5041
5102
  } catch (error) {
5042
- const errorMsg = error.message || "";
5043
- if (errorMsg.includes("npm warn") || errorMsg.includes("deprecated")) {
5044
- debugLog("[下载插件] npm install 有警告但可能成功,继续流程");
5045
- } else {
5046
- debugLog(`[下载插件] npm install 失败: ${errorMsg}`);
5103
+ const errorMsg = error instanceof Error ? error.message : String(error);
5104
+ if (errorMsg.includes("npm warn") || errorMsg.includes("deprecated")) ;
5105
+ else {
5106
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `依赖安装失败: ${errorMsg}`);
5047
5107
  }
5048
5108
  }
5049
5109
  this.prefixText += chalk.green(` ✓ 插件安装完成
@@ -5052,24 +5112,22 @@ class LocalInstaller {
5052
5112
  await fs.rm(tempDir, { recursive: true, force: true });
5053
5113
  debugLog("[下载插件] 清理临时下载目录完成");
5054
5114
  } catch {
5055
- debugLog("[下载插件] 清理临时下载目录失败(忽略)");
5056
5115
  }
5057
5116
  }
5117
+ /**
5118
+ * 更新配置文件
5119
+ */
5058
5120
  async doUpdateConfig(paths, boundConfig) {
5059
5121
  this.spinner.prefixText = this.prefixText;
5060
5122
  this.spinner.text = chalk.cyan("正在生成配置...");
5061
- debugLog("[更新配置] 创建目录...");
5062
5123
  await fs.mkdir(paths.home, { recursive: true });
5063
5124
  await fs.mkdir(paths.workspace, { recursive: true });
5064
- debugLog("[更新配置] 目录创建完成");
5065
- debugLog("[更新配置] 读取原有配置...");
5066
5125
  let originalConfig = {};
5067
5126
  try {
5068
5127
  const content = await fs.readFile(paths.config, "utf-8");
5069
5128
  originalConfig = JSON.parse(content);
5070
5129
  debugLog("[更新配置] 读取原有配置成功");
5071
5130
  } catch {
5072
- debugLog("[更新配置] 无原有配置");
5073
5131
  }
5074
5132
  const config = this.envConfig;
5075
5133
  const newConfig = {
@@ -5084,7 +5142,7 @@ class LocalInstaller {
5084
5142
  },
5085
5143
  // models: 模型配置
5086
5144
  models: {
5087
- mode: "merge",
5145
+ mode: "replace",
5088
5146
  providers: {
5089
5147
  "siliconflow-minimax": {
5090
5148
  baseUrl: boundConfig.modelApiBaseUrl || config.MODEL_BASE_URL,
@@ -5164,7 +5222,6 @@ class LocalInstaller {
5164
5222
  channels: {
5165
5223
  "openclaw-workclaw": {
5166
5224
  enabled: true,
5167
- connectionMode: "websocket",
5168
5225
  appKey: boundConfig.appKey,
5169
5226
  appSecret: boundConfig.appSecret,
5170
5227
  baseUrl: this.config.customIp ? `http://${this.config.customIp}` : config.DEFAULT_BASE_URL,
@@ -5220,6 +5277,9 @@ class LocalInstaller {
5220
5277
  },
5221
5278
  // plugins: 插件配置
5222
5279
  plugins: {
5280
+ load: {
5281
+ paths: [paths.extensions]
5282
+ },
5223
5283
  allow: [config.PLUGIN_NAME],
5224
5284
  installs: {
5225
5285
  [config.PLUGIN_NAME]: {
@@ -5236,36 +5296,16 @@ class LocalInstaller {
5236
5296
  const finalConfig = deepMerge(originalConfig, newConfig);
5237
5297
  this.spinner.prefixText = this.prefixText;
5238
5298
  this.spinner.text = chalk.cyan("正在写入配置...");
5239
- debugLog("[更新配置] 写入配置文件...");
5240
5299
  await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
5241
5300
  this.prefixText += chalk.green(` ✓ 配置更新完成
5242
5301
  `);
5243
5302
  debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
5244
5303
  }
5245
5304
  }
5246
- function checkEnv() {
5247
- debugLog("[环境检查] 检测 Node.js 版本...");
5248
- const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
5249
- debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
5250
- if (!semver.gte(nodeVersion, "18.0.0")) {
5251
- throw new AppError2(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
5252
- }
5253
- debugLog("[环境检查] Node.js 版本检查通过");
5254
- debugLog("[环境检查] 检测 npm...");
5255
- try {
5256
- const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
5257
- debugLog(`[环境检查] npm 版本: ${npmVersion}`);
5258
- debugLog("[环境检查] npm 检测通过");
5259
- } catch {
5260
- throw new AppError2(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
5261
- }
5262
- }
5263
5305
  async function createLocalCommand(options) {
5264
5306
  setDebug(!!options.debug);
5265
- debugLog("[初始化] 开始处理...");
5266
5307
  debugLog(`[初始化] 参数: env=${options.env}, phone=${options.phone}, customIp=${options.customIp}, debug=${options.debug}`);
5267
5308
  checkEnv();
5268
- debugLog("[初始化] 环境检查通过");
5269
5309
  try {
5270
5310
  let env = options.env;
5271
5311
  let phone = options.phone;
@@ -5298,8 +5338,9 @@ async function createLocalCommand(options) {
5298
5338
  if (!value || value.trim() === "") {
5299
5339
  return chalk.red("手机号码不能为空");
5300
5340
  }
5301
- if (!/^1[3-9]\d{9}$/.test(value)) {
5302
- return chalk.yellow("请输入正确的手机号码");
5341
+ const result = phoneSchema.safeParse(value);
5342
+ if (!result.success) {
5343
+ return chalk.yellow(result.error.issues[0].message);
5303
5344
  }
5304
5345
  return true;
5305
5346
  }
@@ -5314,8 +5355,9 @@ async function createLocalCommand(options) {
5314
5355
  name: "userPass",
5315
5356
  message: chalk.cyan("请输入用户密码:"),
5316
5357
  validate: (value) => {
5317
- if (!value || value.trim() === "") {
5318
- return chalk.red("用户密码不能为空");
5358
+ const result = userPassSchema.safeParse(value);
5359
+ if (!result.success) {
5360
+ return chalk.red(result.error.issues[0].message);
5319
5361
  }
5320
5362
  return true;
5321
5363
  }