@workclaw/cli 1.0.27 → 1.0.29

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 (91) hide show
  1. package/README.md +119 -161
  2. package/dist/bin/cli.d.ts +3 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/box/error/index.d.ts +30 -0
  5. package/dist/box/error/index.d.ts.map +1 -0
  6. package/dist/box/index.d.ts +3 -0
  7. package/dist/box/index.d.ts.map +1 -0
  8. package/dist/box/installer/index.d.ts +2 -0
  9. package/dist/box/installer/index.d.ts.map +1 -0
  10. package/dist/box/installer/installer.d.ts +16 -0
  11. package/dist/box/installer/installer.d.ts.map +1 -0
  12. package/dist/box/src/box.d.ts +3 -0
  13. package/dist/box/src/box.d.ts.map +1 -0
  14. package/dist/box/types/index.d.ts +30 -0
  15. package/dist/box/types/index.d.ts.map +1 -0
  16. package/dist/box/utils/index.d.ts +2 -0
  17. package/dist/box/utils/index.d.ts.map +1 -0
  18. package/dist/box/utils/path.d.ts +2 -0
  19. package/dist/box/utils/path.d.ts.map +1 -0
  20. package/dist/command/index.d.ts +3 -0
  21. package/dist/command/index.d.ts.map +1 -0
  22. package/dist/command/src/base-command.d.ts +12 -0
  23. package/dist/command/src/base-command.d.ts.map +1 -0
  24. package/dist/command/types/index.d.ts +14 -0
  25. package/dist/command/types/index.d.ts.map +1 -0
  26. package/dist/{index-BWLa_Wav.js → index-0P3M9kKr.js} +949 -1230
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -1
  29. package/dist/init/index.d.ts +6 -0
  30. package/dist/init/index.d.ts.map +1 -0
  31. package/dist/init/src/init.d.ts +39 -0
  32. package/dist/init/src/init.d.ts.map +1 -0
  33. package/dist/init/src/installer/base-installer.d.ts +36 -0
  34. package/dist/init/src/installer/base-installer.d.ts.map +1 -0
  35. package/dist/init/src/installer/box-installer.d.ts +39 -0
  36. package/dist/init/src/installer/box-installer.d.ts.map +1 -0
  37. package/dist/init/src/installer/index.d.ts +4 -0
  38. package/dist/init/src/installer/index.d.ts.map +1 -0
  39. package/dist/init/src/installer/local-installer.d.ts +27 -0
  40. package/dist/init/src/installer/local-installer.d.ts.map +1 -0
  41. package/dist/init/src/types/index.d.ts +36 -0
  42. package/dist/init/src/types/index.d.ts.map +1 -0
  43. package/dist/lib/command/base-command.d.ts +16 -0
  44. package/dist/lib/command/base-command.d.ts.map +1 -0
  45. package/dist/lib/index.d.ts +2 -0
  46. package/dist/lib/index.d.ts.map +1 -0
  47. package/dist/local/apis/index.d.ts +11 -0
  48. package/dist/local/apis/index.d.ts.map +1 -0
  49. package/dist/local/error/index.d.ts +33 -0
  50. package/dist/local/error/index.d.ts.map +1 -0
  51. package/dist/local/index.d.ts +3 -0
  52. package/dist/local/index.d.ts.map +1 -0
  53. package/dist/local/installer/index.d.ts +2 -0
  54. package/dist/local/installer/index.d.ts.map +1 -0
  55. package/dist/local/installer/installer.d.ts +16 -0
  56. package/dist/local/installer/installer.d.ts.map +1 -0
  57. package/dist/local/src/local.d.ts +3 -0
  58. package/dist/local/src/local.d.ts.map +1 -0
  59. package/dist/local/types/index.d.ts +41 -0
  60. package/dist/local/types/index.d.ts.map +1 -0
  61. package/dist/local/utils/crypto.d.ts +2 -0
  62. package/dist/local/utils/crypto.d.ts.map +1 -0
  63. package/dist/local/utils/index.d.ts +4 -0
  64. package/dist/local/utils/index.d.ts.map +1 -0
  65. package/dist/local/utils/path.d.ts +9 -0
  66. package/dist/local/utils/path.d.ts.map +1 -0
  67. package/dist/shared/config/index.d.ts +48 -0
  68. package/dist/shared/config/index.d.ts.map +1 -0
  69. package/dist/shared/utils/debug.d.ts +4 -0
  70. package/dist/shared/utils/debug.d.ts.map +1 -0
  71. package/dist/shared/utils/index.d.ts +4 -0
  72. package/dist/shared/utils/index.d.ts.map +1 -0
  73. package/dist/shared/utils/logger.d.ts +14 -0
  74. package/dist/shared/utils/logger.d.ts.map +1 -0
  75. package/dist/utils/config-default.d.ts +28 -0
  76. package/dist/utils/config-default.d.ts.map +1 -0
  77. package/dist/utils/config.d.ts +21 -0
  78. package/dist/utils/config.d.ts.map +1 -0
  79. package/dist/utils/crypto.d.ts +13 -0
  80. package/dist/utils/crypto.d.ts.map +1 -0
  81. package/dist/utils/error.d.ts +34 -0
  82. package/dist/utils/error.d.ts.map +1 -0
  83. package/dist/utils/http.d.ts +27 -0
  84. package/dist/utils/http.d.ts.map +1 -0
  85. package/dist/utils/index.d.ts +8 -0
  86. package/dist/utils/index.d.ts.map +1 -0
  87. package/dist/utils/logger.d.ts +16 -0
  88. package/dist/utils/logger.d.ts.map +1 -0
  89. package/dist/utils/path.d.ts +23 -0
  90. package/dist/utils/path.d.ts.map +1 -0
  91. package/package.json +2 -4
@@ -3,209 +3,92 @@ 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 { execSync } from "node:child_process";
7
- import boxen from "boxen";
8
- import chalk from "chalk";
9
- import inquirer from "inquirer";
10
- import * as nodeEmoji from "node-emoji";
11
- import semver from "semver";
12
6
  import fs from "node:fs/promises";
7
+ import chalk from "chalk";
13
8
  import ora from "ora";
14
- import * as tar from "tar";
15
9
  import axios from "axios";
16
- import "node:os";
17
- let isDebug = false;
18
- function setDebug(debug) {
19
- isDebug = debug;
20
- }
21
- function debugLog(...args) {
22
- if (isDebug) {
23
- console.log(chalk.gray(`[DEBUG]`), ...args);
24
- }
25
- }
26
- const ERROR_CODES$1 = {
27
- PHONE_REQUIRED: "PHONE_REQUIRED",
28
- USER_PASS_REQUIRED: "USER_PASS_REQUIRED",
29
- LOGIN_FAILED: "LOGIN_FAILED",
30
- GET_BOUND_CONFIG_FAILED: "GET_BOUND_CONFIG_FAILED",
31
- NODE_VERSION_LOW: "NODE_VERSION_LOW",
32
- NODE_NOT_FOUND: "NODE_NOT_FOUND",
33
- NPM_NOT_FOUND: "NPM_NOT_FOUND",
34
- NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED",
35
- CONFIG_WRITE_FAILED: "CONFIG_WRITE_FAILED",
36
- HTTP_ERROR: "HTTP_ERROR",
37
- NETWORK_ERROR: "NETWORK_ERROR"
38
- };
39
- let AppError$1 = class AppError extends Error {
40
- constructor(code, message) {
41
- super(message);
42
- this.code = code;
43
- this.name = "AppError";
44
- }
45
- };
46
- const CONFIG = {
47
- PLUGIN_NAME: "openclaw-workclaw",
48
- DEFAULT_BASE_URL: "https://workbrain.cn/backend-api",
49
- DEFAULT_WS_URL: "wss://workbrain.cn/backend-api/open-apis/connect",
50
- MODEL_BASE_URL: "https://maas-api.workbrain.cn/v1/",
51
- DIRS: {
52
- OPENCLAW: ".openclaw",
53
- EXTENSIONS: "extensions",
54
- CONFIG_FILE: "openclaw.json",
55
- WORKSPACE: "workspace",
56
- TEMP: ".temp"
57
- },
58
- IGNORE_FILES: ["node_modules", ".git", ".trae", ".vscode"],
59
- API: {
60
- TUZAI_BASE_URL: "https://workbrain.cn/backend-api/tuzai",
61
- BASE_URL: "https://workbrain.cn/backend-api/open-apis",
62
- TIMEOUT: 1e4
10
+ import os from "node:os";
11
+ import { execSync } from "node:child_process";
12
+ class BaseCommand {
13
+ program;
14
+ options = [];
15
+ constructor(program2) {
16
+ this.program = program2;
63
17
  }
64
- };
65
- const TEST_CONFIG = {
66
- PLUGIN_NAME: "openclaw-workclaw",
67
- DEFAULT_BASE_URL: "http://172.168.80.30:32005",
68
- DEFAULT_WS_URL: "ws://172.168.80.30:32005/open-apis/connect",
69
- MODEL_BASE_URL: "http://172.168.80.30:30005/v1",
70
- DIRS: {
71
- OPENCLAW: ".openclaw",
72
- EXTENSIONS: "extensions",
73
- CONFIG_FILE: "openclaw.json",
74
- WORKSPACE: "workspace",
75
- TEMP: ".temp"
76
- },
77
- IGNORE_FILES: ["node_modules", ".git", ".trae", ".vscode"],
78
- API: {
79
- TUZAI_BASE_URL: "http://172.168.80.30:32005/tuzai",
80
- BASE_URL: "http://172.168.80.30:32005/open-apis",
81
- TIMEOUT: 1e4
18
+ init() {
19
+ const cmd = this.program.command(this.command);
20
+ cmd.description(this.description);
21
+ this.options.forEach(({ name, description }) => {
22
+ cmd.option(name, description);
23
+ });
24
+ cmd.action(this.action.bind(this));
82
25
  }
83
- };
84
- function getConfig(env) {
85
- return env === "test" ? TEST_CONFIG : CONFIG;
86
26
  }
87
- function createHttpClient() {
88
- return axios.create({
89
- timeout: 1e4,
90
- headers: {
91
- "Content-Type": "application/json"
27
+ function createLogger() {
28
+ return {
29
+ info(message) {
30
+ console.log(chalk.blue("•"), message);
31
+ },
32
+ success(message) {
33
+ console.log(chalk.green("✓"), message);
34
+ },
35
+ warn(message) {
36
+ console.log(chalk.yellow("!"), message);
37
+ },
38
+ error(message) {
39
+ console.log(chalk.red("✗"), message);
40
+ },
41
+ step(message) {
42
+ console.log(chalk.cyan("›"), chalk.cyan(message));
43
+ },
44
+ start(message) {
45
+ return ora({
46
+ text: message,
47
+ color: "cyan"
48
+ }).start();
92
49
  }
93
- });
50
+ };
94
51
  }
95
- async function httpPost(url, data, config) {
96
- const client = createHttpClient();
97
- debugLog(`[HTTP POST] 请求地址: ${url}`);
98
- debugLog(`[HTTP POST] 请求参数: ${JSON.stringify(data)}`);
99
- debugLog(`[HTTP POST] 请求头: ${JSON.stringify(config?.headers || {})}`);
52
+ const logger = createLogger();
53
+ async function readConfig(configPath) {
100
54
  try {
101
- const response = await client.post(url, data, config);
102
- debugLog(`[HTTP POST] 响应状态: ${response.status}`);
103
- debugLog(`[HTTP POST] 响应内容: ${JSON.stringify(response.data)}`);
104
- return {
105
- status: response.status,
106
- data: response.data
107
- };
108
- } catch (error) {
109
- const axiosError = error;
110
- if (axiosError.response) {
111
- const responseData = axiosError.response.data;
112
- debugLog(`[HTTP POST] 响应状态: ${axiosError.response.status}`);
113
- debugLog(`[HTTP POST] 响应状态文本: ${axiosError.response.statusText}`);
114
- debugLog(`[HTTP POST] 响应数据: ${JSON.stringify(responseData)}`);
115
- if (typeof responseData === "string") {
116
- throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData}`);
117
- } else if (responseData && typeof responseData === "object" && "message" in responseData) {
118
- throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData.message}`);
119
- } else {
120
- throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${axiosError.response.statusText}`);
121
- }
122
- } else if (axiosError.request) {
123
- debugLog(`[HTTP POST] 未收到响应`);
124
- debugLog(`[HTTP POST] 错误信息: ${axiosError.message}`);
125
- throw new AppError$1(ERROR_CODES$1.NETWORK_ERROR, `网络错误: ${axiosError.message}`);
126
- } else {
127
- debugLog(`[HTTP POST] 请求配置错误`);
128
- debugLog(`[HTTP POST] 错误信息: ${axiosError.message}`);
129
- throw new AppError$1(ERROR_CODES$1.HTTP_ERROR, `请求配置错误: ${axiosError.message}`);
130
- }
55
+ const content = await fs.readFile(configPath, "utf-8");
56
+ return JSON.parse(content);
57
+ } catch {
58
+ logger.warn(`配置文件不存在或读取失败: ${configPath}`);
59
+ return null;
131
60
  }
132
61
  }
133
- async function login(phone, password, env) {
134
- const config = getConfig(env);
135
- const url = `${config.API.TUZAI_BASE_URL}/user/login/pass`;
136
- debugLog("[登录] 开始登录...");
137
- debugLog(`[登录] 请求地址: ${url}`);
138
- debugLog(`[登录] 请求参数: phone=${phone}, password=***`);
139
- try {
140
- const response = await httpPost(url, {
141
- phone,
142
- userPass: password
143
- });
144
- const data = response.data;
145
- debugLog(`[登录] 响应数据: ${JSON.stringify(data)}`);
146
- if (data.code === 200 && data.data?.token) {
147
- debugLog("[登录] 登录成功,获取到 token");
148
- return data.data.token;
149
- }
150
- debugLog("[登录] 登录失败");
151
- throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, data.message || "登录失败");
152
- } catch (error) {
153
- if (error instanceof AppError$1) {
154
- throw error;
155
- }
156
- debugLog(`[登录] 发生错误: ${error.message}`);
157
- throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, error.message);
158
- }
62
+ async function writeConfig(configPath, config) {
63
+ const dir = path.dirname(configPath);
64
+ await fs.mkdir(dir, { recursive: true });
65
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
159
66
  }
160
- async function fetchBoundConfig(token, phone, env) {
161
- const config = getConfig(env);
162
- const url = `${config.API.TUZAI_BASE_URL}/work-bot/local/bound/init`;
163
- debugLog("[获取绑定配置] 开始获取绑定配置...");
164
- debugLog(`[获取绑定配置] 请求地址: ${url}`);
165
- debugLog(`[获取绑定配置] 请求参数: phone=${phone}, localCode=001`);
166
- debugLog(`[获取绑定配置] 请求头: Authorization=${token.substring(0, 20)}...`);
167
- try {
168
- const response = await httpPost(url, {
169
- phone,
170
- localCode: "001"
171
- }, {
172
- headers: {
173
- Authorization: token
174
- }
175
- });
176
- const data = response.data;
177
- debugLog(`[获取绑定配置] 响应数据: ${JSON.stringify(data)}`);
178
- if (data.code === 200 && data.data) {
179
- const boundConfig = {
180
- appKey: data.data.app_key,
181
- appSecret: data.data.app_secret,
182
- userId: data.data.user_id,
183
- agentId: data.data.agent_id || data.data.agents?.[0]?.id,
184
- modelApiKey: data.data.model_api_key,
185
- modelApiBaseUrl: data.data.model_api_base_url
186
- };
187
- debugLog("[获取绑定配置] 获取绑定配置成功");
188
- debugLog(`[获取绑定配置] appKey: ${boundConfig.appKey}`);
189
- debugLog(`[获取绑定配置] appSecret: ${boundConfig.appSecret ? "***" : "undefined"}`);
190
- debugLog(`[获取绑定配置] userId: ${boundConfig.userId}`);
191
- debugLog(`[获取绑定配置] agentId: ${boundConfig.agentId}`);
192
- debugLog(`[获取绑定配置] modelApiKey: ${boundConfig.modelApiKey || "undefined"}`);
193
- debugLog(`[获取绑定配置] modelApiBaseUrl: ${boundConfig.modelApiBaseUrl || "undefined"}`);
194
- if (!boundConfig.appKey || !boundConfig.appSecret || !boundConfig.agentId) {
195
- debugLog("[获取绑定配置] 缺少必要字段");
196
- throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, "获取绑定配置失败:缺少必要字段");
197
- }
198
- return boundConfig;
199
- }
200
- debugLog("[获取绑定配置] 获取绑定配置失败");
201
- throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, data.message || data.msg || "获取绑定配置失败");
202
- } catch (error) {
203
- if (error instanceof AppError$1) {
204
- throw error;
205
- }
206
- debugLog(`[获取绑定配置] 发生错误: ${error.message}`);
207
- throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, error.message);
67
+ const defaultConfig = {
68
+ modelUrl: {
69
+ test: "http://172.168.80.30:30005/v1",
70
+ prod: "https://maas-api.workbrain.cn/v1/"
208
71
  }
72
+ };
73
+ const openApisUrl = {
74
+ test: "http://172.168.80.30:32005/open-apis",
75
+ prod: "https://workbrain.cn/backend-api/open-apis"
76
+ };
77
+ const apiBaseUrl = {
78
+ test: "https://test-api.workbrain.cn/backend-api/",
79
+ prod: "https://workbrain.cn/backend-api/"
80
+ };
81
+ function getApiBaseUrl(env) {
82
+ return env === "test" ? apiBaseUrl.test : apiBaseUrl.prod;
83
+ }
84
+ function getWsUrl(env) {
85
+ return env === "test" ? openApisUrl.test : openApisUrl.prod;
86
+ }
87
+ function getOpenApisUrl(env) {
88
+ return env === "test" ? openApisUrl.test : openApisUrl.prod;
89
+ }
90
+ function getModelUrl(env) {
91
+ return env === "test" ? defaultConfig.modelUrl.test : defaultConfig.modelUrl.prod;
209
92
  }
210
93
  var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
211
94
  function int2char(n) {
@@ -4092,1037 +3975,818 @@ var JSEncrypt = (
4092
3975
  );
4093
3976
  const PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBSYtNb6neLrrwsuPBsIFjpZBrffdkA8bpp2S35o2TCdhfdxS0nc4pkv9cJLkUvFa+gdQ5nLifnK9B1XoVbIQwY212QAftTDbl77bcHu7GAbv2TZr9pelSeUm1SrtMK5HDr/LzxTutGr4DovVHiDgEn45GQ1X5U+zC0Jp4Awn6ZwIDAQAB";
4094
3977
  function rsaEncrypt(txt) {
4095
- const encrypt = new JSEncrypt();
4096
- encrypt.setPublicKey(PUBLIC_KEY);
4097
- const result = encrypt.encrypt(txt);
4098
- return result || null;
3978
+ try {
3979
+ const encrypt = new JSEncrypt();
3980
+ encrypt.setPublicKey(PUBLIC_KEY);
3981
+ const encrypted = encrypt.encrypt(txt);
3982
+ if (!encrypted) {
3983
+ return null;
3984
+ }
3985
+ return encrypted;
3986
+ } catch (error) {
3987
+ console.error("RSA 加密失败:", error);
3988
+ return null;
3989
+ }
4099
3990
  }
4100
- function getHomeDir$1() {
4101
- return process$1.env.HOME || process$1.env.USERPROFILE || "";
3991
+ const ERROR_CODES = {
3992
+ // 环境检查错误 (1000-1999)
3993
+ NODE_VERSION_LOW: 1001,
3994
+ NODE_NOT_FOUND: 1002,
3995
+ NPM_NOT_FOUND: 1003,
3996
+ // 配置错误 (2000-2999)
3997
+ PHONE_REQUIRED: 2001,
3998
+ USER_PASS_REQUIRED: 2002,
3999
+ MISSING_AGENT_ID: 2003,
4000
+ MISSING_APP_KEY: 2004,
4001
+ MISSING_APP_SECRET: 2005,
4002
+ // 网络错误 (3000-3999)
4003
+ LOGIN_FAILED: 3001,
4004
+ GET_BOUND_CONFIG_FAILED: 3002,
4005
+ FETCH_CONFIG_FAILED: 3003,
4006
+ // 文件系统错误 (4000-4999)
4007
+ CONFIG_WRITE_FAILED: 4001,
4008
+ FILE_COPY_FAILED: 4002,
4009
+ NPM_INSTALL_FAILED: 4003,
4010
+ // 加密错误 (5000-5999)
4011
+ PASSWORD_ENCRYPT_FAILED: 5001
4012
+ };
4013
+ class AppError extends Error {
4014
+ code;
4015
+ details;
4016
+ constructor(code, message, details) {
4017
+ super(message);
4018
+ this.name = "AppError";
4019
+ this.code = code;
4020
+ this.details = details;
4021
+ }
4022
+ toJSON() {
4023
+ return {
4024
+ code: this.code,
4025
+ message: this.message,
4026
+ details: this.details
4027
+ };
4028
+ }
4029
+ toString() {
4030
+ return `[${this.code}] ${this.message}`;
4031
+ }
4102
4032
  }
4103
- const PLUGIN_PACKAGE_NAME$1 = "@workclaw/openclaw-workclaw";
4104
- function deepMerge$1(target, source) {
4105
- const result = { ...target };
4106
- for (const key in source) {
4107
- const sourceValue = source[key];
4108
- const targetValue = target[key];
4109
- if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
4110
- result[key] = deepMerge$1(targetValue, sourceValue);
4111
- } else if (sourceValue !== void 0) {
4112
- result[key] = sourceValue;
4033
+ function formatError(error) {
4034
+ if (error instanceof AppError) {
4035
+ return error.toString();
4036
+ }
4037
+ if (error instanceof Error) {
4038
+ return error.message;
4039
+ }
4040
+ return String(error);
4041
+ }
4042
+ function createHttpClient(baseURL) {
4043
+ return axios.create({
4044
+ baseURL,
4045
+ timeout: 3e4,
4046
+ headers: {
4047
+ "Content-Type": "application/json"
4113
4048
  }
4049
+ });
4050
+ }
4051
+ function printRequestLog(log) {
4052
+ console.log();
4053
+ console.log(chalk.cyan("=".repeat(50)));
4054
+ console.log(chalk.bold(`HTTP ${log.method} 请求`));
4055
+ console.log(chalk.cyan("=".repeat(50)));
4056
+ console.log(chalk.blue("[请求地址]"), log.url);
4057
+ if (log.headers) {
4058
+ const headersStr = JSON.stringify(log.headers, null, 2);
4059
+ console.log(chalk.blue("[请求头]"), headersStr);
4114
4060
  }
4115
- return result;
4061
+ if (log.data) {
4062
+ const dataStr = JSON.stringify(log.data, null, 2);
4063
+ console.log(chalk.blue("[请求参数]"), dataStr);
4064
+ }
4065
+ if (log.duration) {
4066
+ console.log(chalk.yellow("[耗时]"), `${log.duration}ms`);
4067
+ }
4068
+ if (log.error) {
4069
+ console.log(chalk.red("[错误]"), log.error);
4070
+ }
4071
+ console.log(chalk.cyan("=".repeat(50)));
4072
+ console.log();
4116
4073
  }
4117
- class LocalInstaller {
4118
- constructor(config) {
4074
+ function printResponseLog(log) {
4075
+ if (log.response) {
4076
+ console.log();
4077
+ console.log(chalk.green("=".repeat(50)));
4078
+ console.log(chalk.bold(`HTTP ${log.method} 响应`));
4079
+ console.log(chalk.green("=".repeat(50)));
4080
+ console.log(chalk.green("[状态码]"), log.response.status);
4081
+ const responseStr = JSON.stringify(log.response.data, null, 2);
4082
+ console.log(chalk.green("[响应数据]"), responseStr);
4083
+ console.log(chalk.green("=".repeat(50)));
4084
+ console.log();
4085
+ }
4086
+ }
4087
+ async function httpGet(url, config) {
4088
+ const startTime = Date.now();
4089
+ printRequestLog({
4090
+ method: "GET",
4091
+ url,
4092
+ headers: config?.headers
4093
+ });
4094
+ try {
4095
+ const client = createHttpClient();
4096
+ const response = await client.get(url, config);
4097
+ const duration = Date.now() - startTime;
4098
+ const result = {
4099
+ status: response.status,
4100
+ data: response.data
4101
+ };
4102
+ printResponseLog({
4103
+ method: "GET",
4104
+ url,
4105
+ headers: config?.headers,
4106
+ response: result,
4107
+ duration
4108
+ });
4109
+ return result;
4110
+ } catch (error) {
4111
+ const duration = Date.now() - startTime;
4112
+ const errorMessage = error instanceof Error ? error.message : String(error);
4113
+ printRequestLog({
4114
+ method: "GET",
4115
+ url,
4116
+ headers: config?.headers,
4117
+ error: errorMessage,
4118
+ duration
4119
+ });
4120
+ throw error;
4121
+ }
4122
+ }
4123
+ async function httpPost(url, data, config) {
4124
+ const startTime = Date.now();
4125
+ printRequestLog({
4126
+ method: "POST",
4127
+ url,
4128
+ headers: config?.headers,
4129
+ data
4130
+ });
4131
+ try {
4132
+ const client = createHttpClient();
4133
+ const response = await client.post(url, data, config);
4134
+ const duration = Date.now() - startTime;
4135
+ const result = {
4136
+ status: response.status,
4137
+ data: response.data
4138
+ };
4139
+ printResponseLog({
4140
+ method: "POST",
4141
+ url,
4142
+ headers: config?.headers,
4143
+ data,
4144
+ response: result,
4145
+ duration
4146
+ });
4147
+ return result;
4148
+ } catch (error) {
4149
+ const duration = Date.now() - startTime;
4150
+ const errorMessage = error instanceof Error ? error.message : String(error);
4151
+ printRequestLog({
4152
+ method: "POST",
4153
+ url,
4154
+ headers: config?.headers,
4155
+ data,
4156
+ error: errorMessage,
4157
+ duration
4158
+ });
4159
+ throw error;
4160
+ }
4161
+ }
4162
+ function getPathInfo(_scenario) {
4163
+ const homeDir = os.homedir();
4164
+ const configDir = path.join(homeDir, ".openclaw");
4165
+ return {
4166
+ configDir,
4167
+ configFile: path.join(configDir, "openclaw.json"),
4168
+ pluginDir: path.join(configDir, "plugins")
4169
+ };
4170
+ }
4171
+ function detectOS() {
4172
+ const platform = os.platform();
4173
+ switch (platform) {
4174
+ case "win32":
4175
+ return "windows-local";
4176
+ case "darwin":
4177
+ return "mac-local";
4178
+ case "linux":
4179
+ return "linux-local";
4180
+ default:
4181
+ return "linux-local";
4182
+ }
4183
+ }
4184
+ async function ensureDir(dirPath) {
4185
+ await fs.mkdir(dirPath, { recursive: true });
4186
+ }
4187
+ class BaseInstaller {
4188
+ config;
4189
+ logger;
4190
+ constructor(config, logger2) {
4119
4191
  this.config = config;
4120
- this.spinner = ora({ color: "cyan" }).start();
4192
+ this.logger = logger2;
4121
4193
  }
4122
- spinner;
4123
- prefixText = "";
4124
- getPackageName() {
4125
- if (this.config.pluginVersion) {
4126
- return `${PLUGIN_PACKAGE_NAME$1}@${this.config.pluginVersion}`;
4194
+ /**
4195
+ * 获取 API 基础地址
4196
+ */
4197
+ getApiBaseUrl() {
4198
+ if (this.config.baseUrl) {
4199
+ return this.config.baseUrl;
4127
4200
  }
4128
- return PLUGIN_PACKAGE_NAME$1;
4201
+ const defaultUrls = {
4202
+ test: "https://test-api.workbrain.cn/backend-api/",
4203
+ prod: "https://workbrain.cn/backend-api/"
4204
+ };
4205
+ return defaultUrls[this.config.env];
4129
4206
  }
4130
- validateConfig() {
4131
- debugLog("[验证配置] 检查手机号码和密码...");
4132
- if (!this.config.phone) {
4133
- throw new AppError$1(ERROR_CODES$1.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4207
+ /**
4208
+ * 获取 WebSocket 地址
4209
+ */
4210
+ getWsUrl() {
4211
+ if (this.config.wsUrl) {
4212
+ return this.config.wsUrl;
4134
4213
  }
4135
- if (!this.config.userPass) {
4136
- throw new AppError$1(ERROR_CODES$1.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
4214
+ const defaultUrls = {
4215
+ test: "wss://test-api.workbrain.cn/backend-api/open-apis/connect/",
4216
+ prod: "wss://workbrain.cn/backend-api/open-apis/connect/"
4217
+ };
4218
+ return defaultUrls[this.config.env];
4219
+ }
4220
+ }
4221
+ class BoxInstaller extends BaseInstaller {
4222
+ scenario;
4223
+ constructor(config) {
4224
+ super(config, logger);
4225
+ this.scenario = config.scenario;
4226
+ }
4227
+ /**
4228
+ * 验证配置
4229
+ */
4230
+ validateConfig() {
4231
+ if (!this.config.appKey || !this.config.appSecret) {
4232
+ logger.error("缺少必填参数: appKey 和 appSecret");
4233
+ return false;
4137
4234
  }
4138
- debugLog("[验证配置] 配置验证通过");
4235
+ return true;
4236
+ }
4237
+ /**
4238
+ * 获取配置路径
4239
+ */
4240
+ getConfigPath() {
4241
+ const paths = getPathInfo(this.scenario);
4242
+ return paths.configFile;
4139
4243
  }
4244
+ /**
4245
+ * 获取场景类型
4246
+ */
4247
+ getScenario() {
4248
+ return this.scenario;
4249
+ }
4250
+ /**
4251
+ * 执行安装
4252
+ */
4140
4253
  async install() {
4254
+ const spinner = logger.start("开始安装 (Box 环境)...");
4141
4255
  try {
4142
- debugLog("[安装开始]");
4143
- this.validateConfig();
4144
- const env = this.config.env || "test";
4145
- const config = getConfig(env);
4146
- const homeDir = getHomeDir$1();
4147
- const paths = {
4148
- home: path.join(homeDir, config.DIRS.OPENCLAW),
4149
- extensions: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS),
4150
- target: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS, config.PLUGIN_NAME),
4151
- config: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.CONFIG_FILE),
4152
- workspace: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.WORKSPACE),
4153
- temp: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.TEMP)
4256
+ if (!this.validateConfig()) {
4257
+ spinner.fail("配置验证失败");
4258
+ return {
4259
+ success: false,
4260
+ error: "配置验证失败"
4261
+ };
4262
+ }
4263
+ spinner.text = "获取 Access Token...";
4264
+ const token = await this.getAccessToken();
4265
+ if (!token) {
4266
+ spinner.fail("获取 Access Token 失败");
4267
+ return {
4268
+ success: false,
4269
+ error: "获取 Access Token 失败"
4270
+ };
4271
+ }
4272
+ spinner.text = "获取 Agent 配置...";
4273
+ const agentConfig = await this.getAgentConfig(token);
4274
+ if (!agentConfig) {
4275
+ spinner.fail("获取 Agent 配置失败");
4276
+ return {
4277
+ success: false,
4278
+ error: "获取 Agent 配置失败"
4279
+ };
4280
+ }
4281
+ spinner.text = "创建配置目录...";
4282
+ const paths = getPathInfo(this.scenario);
4283
+ await ensureDir(paths.configDir);
4284
+ spinner.text = "写入配置文件...";
4285
+ await this.writeConfigFile(token, agentConfig);
4286
+ spinner.succeed("安装成功 (Box 环境)!");
4287
+ logger.success(`配置文件已写入: ${this.getConfigPath()}`);
4288
+ logger.info(`环境: ${this.config.env === "test" ? "测试环境" : "正式环境"}`);
4289
+ return {
4290
+ success: true,
4291
+ message: "安装成功 (Box 环境)"
4154
4292
  };
4155
- debugLog(`[路径配置] home=${paths.home}`);
4156
- debugLog(`[路径配置] extensions=${paths.extensions}`);
4157
- debugLog(`[路径配置] target=${paths.target}`);
4158
- debugLog(`[路径配置] config=${paths.config}`);
4159
- debugLog(`[路径配置] workspace=${paths.workspace}`);
4160
- const token = await this.doLogin(env);
4161
- const boundConfig = await this.doFetchBoundConfig(env, token);
4162
- await this.doCleanOldFiles(paths.target);
4163
- await this.doDownloadFromNpm(paths);
4164
- await this.doUpdateConfig(paths, boundConfig, env);
4165
- this.spinner.stop();
4166
4293
  } catch (error) {
4167
- this.spinner.stop();
4168
- throw error;
4169
- }
4170
- }
4171
- getPrefixText() {
4172
- return this.prefixText;
4173
- }
4174
- async doLogin(env) {
4175
- this.spinner.prefixText = this.prefixText;
4176
- this.spinner.text = `${chalk.cyan("用户登录")} ${chalk.dim("→")} ${chalk.yellow("正在登录...")}`;
4177
- debugLog(`[用户登录] 手机号: ${this.config.phone}`);
4178
- debugLog("[用户登录] RSA 加密密码...");
4179
- const encryptedPassword = rsaEncrypt(this.config.userPass);
4180
- if (!encryptedPassword) {
4181
- throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, "密码加密失败");
4294
+ spinner.fail("安装失败");
4295
+ const errorMessage = error instanceof Error ? error.message : String(error);
4296
+ logger.error(`安装失败: ${errorMessage}`);
4297
+ return {
4298
+ success: false,
4299
+ error: errorMessage
4300
+ };
4182
4301
  }
4183
- debugLog("[用户登录] 密码加密成功");
4184
- debugLog("[用户登录] 调用登录接口...");
4185
- const token = await login(this.config.phone, encryptedPassword, env);
4186
- this.prefixText += chalk.green(`✓ 用户登录成功
4187
- `);
4188
- debugLog("[用户登录] 登录成功");
4189
- return token;
4190
- }
4191
- async doFetchBoundConfig(env, token) {
4192
- this.spinner.prefixText = this.prefixText;
4193
- this.spinner.text = `${chalk.cyan("获取配置")} ${chalk.dim("→")} ${chalk.yellow("正在获取绑定配置...")}`;
4194
- debugLog("[获取配置] 调用绑定配置接口...");
4195
- const boundConfig = await fetchBoundConfig(token, this.config.phone, env);
4196
- debugLog(`[获取配置] agentId=${boundConfig.agentId}, appKey=${boundConfig.appKey?.slice(0, 8)}...`);
4197
- this.prefixText += chalk.green(`✓ 绑定配置获取成功
4198
- `);
4199
- debugLog("[获取配置] 获取成功");
4200
- return boundConfig;
4201
4302
  }
4202
- async doCleanOldFiles(targetPath) {
4203
- this.spinner.prefixText = this.prefixText;
4204
- this.spinner.text = `${chalk.cyan("清理旧版本")} ${chalk.dim("→")} ${chalk.yellow("检查目录...")}`;
4205
- debugLog(`[清理旧版本] 检查目录: ${targetPath}`);
4303
+ /**
4304
+ * 获取 Access Token
4305
+ */
4306
+ async getAccessToken() {
4206
4307
  try {
4207
- await fs.access(targetPath);
4208
- debugLog("[清理旧版本] 发现旧版本,开始清理...");
4209
- this.spinner.prefixText = this.prefixText;
4210
- this.spinner.text = `${chalk.cyan("清理旧版本")} ${chalk.dim("→")} ${chalk.yellow("正在清理...")}`;
4211
- await fs.rm(targetPath, { recursive: true, force: true });
4212
- this.prefixText += chalk.green(`✓ 旧版本清理完成
4213
- `);
4214
- debugLog("[清理旧版本] 清理完成");
4308
+ const client = createHttpClient(this.getApiBaseUrl());
4309
+ const response = await client.post("/v1/auth/token", {
4310
+ appKey: this.config.appKey,
4311
+ appSecret: this.config.appSecret
4312
+ });
4313
+ const data = response.data;
4314
+ return data.accessToken || null;
4215
4315
  } catch {
4216
- this.prefixText += chalk.green(`✓ 未发现旧版本
4217
- `);
4218
- debugLog("[清理旧版本] 未发现旧版本");
4316
+ return null;
4219
4317
  }
4220
4318
  }
4221
- async doDownloadFromNpm(paths) {
4222
- this.spinner.prefixText = this.prefixText;
4223
- this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("准备目录")}`;
4224
- debugLog("[下载插件] 创建扩展目录...");
4225
- await fs.mkdir(paths.extensions, { recursive: true });
4226
- debugLog(`[下载插件] 扩展目录: ${paths.extensions}`);
4227
- const tempDir = path.join(paths.temp, `install-${Date.now()}`);
4228
- debugLog(`[下载插件] 创建临时目录: ${tempDir}`);
4229
- await fs.mkdir(tempDir, { recursive: true });
4230
- this.spinner.prefixText = this.prefixText;
4231
- this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("下载 tarball")}`;
4232
- debugLog("[下载插件] 执行 npm pack 下载源码包");
4233
- debugLog(`[下载插件] 工作目录: ${tempDir}`);
4234
- const maxRetries = 3;
4235
- let lastError = "";
4236
- let tarballPath = "";
4237
- for (let i = 1; i <= maxRetries; i++) {
4238
- debugLog(`[下载插件] 第 ${i} 次尝试下载 tarball...`);
4239
- try {
4240
- const packageName = this.getPackageName();
4241
- debugLog(`[下载插件] 下载包名: ${packageName}`);
4242
- const output = execSync(
4243
- `npm pack ${packageName} --registry=https://mirrors.tencent.com/npm/ --ignore-scripts`,
4244
- { cwd: tempDir, encoding: "utf-8", timeout: 6e4 }
4245
- );
4246
- const filename = output.trim().split("\n").pop() || "";
4247
- tarballPath = path.join(tempDir, filename);
4248
- debugLog(`[下载插件] tarball 下载成功: ${tarballPath}`);
4249
- break;
4250
- } catch (error) {
4251
- lastError = error.stderr?.toString() || error.message || "未知错误";
4252
- debugLog(`[下载插件] 第 ${i} 次尝试失败: ${lastError}`);
4253
- if (i < maxRetries) {
4254
- debugLog("[下载插件] 等待 3 秒后重试...");
4255
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
4256
- }
4257
- }
4258
- }
4259
- if (!tarballPath || !await fs.access(tarballPath).then(() => true).catch(() => false)) {
4260
- throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
4261
- }
4262
- this.spinner.prefixText = this.prefixText;
4263
- this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("解压 tarball")}`;
4264
- debugLog(`[下载插件] 创建插件目录: ${paths.target}`);
4265
- await fs.mkdir(paths.target, { recursive: true });
4266
- debugLog(`[下载插件] 解压 tarball 到: ${paths.target}`);
4319
+ /**
4320
+ * 获取 Agent 配置
4321
+ */
4322
+ async getAgentConfig(token) {
4267
4323
  try {
4268
- await tar.extract({
4269
- file: tarballPath,
4270
- cwd: paths.target,
4271
- strip: 1
4272
- });
4273
- debugLog("[下载插件] tarball 解压成功");
4274
- } catch (error) {
4275
- throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
4276
- }
4277
- const pluginPath = path.join(paths.target, "package.json");
4278
- const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
4279
- if (!pluginExists) {
4280
- throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
4281
- }
4282
- debugLog("[下载插件] 插件解压成功");
4283
- this.spinner.prefixText = this.prefixText;
4284
- this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("安装依赖")}`;
4285
- debugLog("[下载插件] 执行 npm install 安装生产环境依赖");
4286
- try {
4287
- execSync("npm install --omit=dev --ignore-scripts", {
4288
- cwd: paths.target,
4289
- stdio: "pipe",
4290
- timeout: 12e4
4324
+ const client = createHttpClient(this.getApiBaseUrl());
4325
+ const response = await client.get("/v1/agent/config", {
4326
+ headers: {
4327
+ Authorization: `Bearer ${token}`
4328
+ }
4291
4329
  });
4292
- debugLog("[下载插件] npm install 执行成功");
4293
- } catch (error) {
4294
- const stderr = error.stderr?.toString() || "";
4295
- const stdout = error.stdout?.toString() || "";
4296
- const combinedOutput = stderr + stdout;
4297
- if (combinedOutput.includes("npm warn") || combinedOutput.includes("deprecated")) {
4298
- debugLog("[下载插件] npm install 有警告但可能成功,继续流程");
4299
- } else {
4300
- debugLog(`[下载插件] npm install 失败: ${combinedOutput}`);
4301
- }
4302
- }
4303
- this.prefixText += chalk.green(`✓ 插件下载完成
4304
- `);
4305
- try {
4306
- await fs.rm(tempDir, { recursive: true, force: true });
4307
- debugLog("[下载插件] 清理临时目录完成");
4330
+ return response.data;
4308
4331
  } catch {
4309
- debugLog("[下载插件] 清理临时目录失败(忽略)");
4332
+ return null;
4310
4333
  }
4311
4334
  }
4312
- async doUpdateConfig(paths, boundConfig, env) {
4313
- this.spinner.prefixText = this.prefixText;
4314
- this.spinner.text = `${chalk.cyan("更新配置")} ${chalk.dim("→")} ${chalk.yellow("写入配置文件")}`;
4315
- debugLog("[更新配置] 创建目录...");
4316
- await fs.mkdir(paths.home, { recursive: true });
4317
- await fs.mkdir(paths.workspace, { recursive: true });
4318
- debugLog("[更新配置] 目录创建完成");
4319
- debugLog("[更新配置] 读取原有配置...");
4320
- let originalConfig = {};
4321
- try {
4322
- const content = await fs.readFile(paths.config, "utf-8");
4323
- originalConfig = JSON.parse(content);
4324
- debugLog("[更新配置] 读取原有配置成功");
4325
- } catch {
4326
- debugLog("[更新配置] 无原有配置");
4335
+ /**
4336
+ * 写入配置文件
4337
+ */
4338
+ async writeConfigFile(token, agentConfig) {
4339
+ const configPath = this.getConfigPath();
4340
+ let existingConfig = await readConfig(configPath);
4341
+ if (!existingConfig || typeof existingConfig !== "object") {
4342
+ existingConfig = {};
4327
4343
  }
4328
- const config = getConfig(env);
4344
+ const safeConfig = existingConfig;
4345
+ const existingChannels = safeConfig.channels;
4329
4346
  const newConfig = {
4330
- // diagnostics: 诊断配置
4331
- diagnostics: {
4332
- // 启用诊断功能
4333
- enabled: true,
4334
- // 启用所有诊断标志 ['*'] 表示全部,[] 表示禁用所有
4335
- flags: ["*"]
4336
- },
4337
- // browser: 浏览器配置
4338
- browser: {
4339
- // 禁用浏览器工具
4340
- enabled: false
4341
- },
4342
- // models: 模型配置
4343
- models: {
4344
- // 配置合并模式:'merge' 合并 | 'replace' 替换
4345
- mode: "merge",
4346
- providers: {
4347
- "siliconflow-minimax": {
4348
- // 模型 API 基础地址
4349
- baseUrl: boundConfig.modelApiBaseUrl || config.MODEL_BASE_URL,
4350
- // API 密钥(从服务器获取)
4351
- apiKey: boundConfig.modelApiKey,
4352
- // API 类型:'openai-completions' | 'openai-chat' 等
4353
- api: "openai-completions",
4354
- // 是否使用 Authorization header 认证
4355
- authHeader: true,
4356
- models: [{
4357
- // 模型 ID(provider/model 格式)
4358
- id: "Pro/MiniMaxAI/MiniMax-M2.5",
4359
- // 模型显示名称
4360
- name: "MiniMax-M2.5",
4361
- // 是否启用推理能力
4362
- reasoning: false,
4363
- // 支持的输入类型:['text'] | ['text', 'image']
4364
- input: ["text"],
4365
- // 价格(0 表示免费或未设置)
4366
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
4367
- // 上下文窗口大小(token)
4368
- contextWindow: 2e5,
4369
- // 最大输出 token 数
4370
- maxTokens: 65536
4371
- }]
4372
- }
4373
- }
4374
- },
4375
- // agents: 代理配置
4376
- agents: {
4377
- defaults: {
4378
- // 默认使用的模型
4379
- model: { primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5" },
4380
- models: {
4381
- "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5": {
4382
- // 模型别名,用于显示
4383
- alias: "Pro/MiniMaxAI/MiniMax-M2.5"
4384
- }
4385
- },
4386
- // 工作区目录路径
4387
- workspace: paths.workspace,
4388
- // 会话压缩模式:'safeguard' 保守模式
4389
- compaction: { mode: "safeguard" },
4390
- // 详细程度:'full' | 'short' | 'off'
4391
- verboseDefault: "full",
4392
- // 最大并发任务数
4393
- maxConcurrent: 4,
4394
- // 子代理最大并发数
4395
- subagents: { maxConcurrent: 8 }
4396
- },
4397
- // 代理列表
4398
- list: [{ id: "main", workspace: paths.workspace }]
4399
- },
4400
- // tools: 工具配置
4401
- tools: {
4402
- // 禁用的工具列表(优先于 allow)
4403
- deny: ["image", "web_search", "web_fetch"],
4404
- // 图像分析工具
4405
- media: { image: { enabled: false } },
4406
- // 网络搜索和抓取工具
4407
- web: { search: { enabled: false }, fetch: { enabled: false } },
4408
- // 提升权限工具(host=gateway)
4409
- elevated: { enabled: true, allowFrom: { webchat: ["*"] } },
4410
- // exec 工具安全级别:'deny' | 'allowlist' | 'full'
4411
- exec: { security: "full" }
4412
- },
4413
- // bindings: 绑定配置
4414
- bindings: [{
4415
- // 绑定的代理 ID
4416
- agentId: "main",
4417
- // 匹配的通道和账户
4418
- match: { channel: "openclaw-workclaw", accountId: "default" }
4419
- }],
4420
- // messages: 消息配置
4421
- messages: {
4422
- // 消息确认范围
4423
- ackReactionScope: "group-mentions"
4424
- },
4425
- // commands: 命令配置
4426
- commands: {
4427
- // 本机命令:'auto' | 'on' | 'off'
4428
- native: "auto",
4429
- // 本机技能:'auto' | 'on' | 'off'
4430
- nativeSkills: "auto",
4431
- // 是否允许重启命令
4432
- restart: true,
4433
- // 所有者显示格式:'raw' | 'name' | 'hidden'
4434
- ownerDisplay: "raw"
4435
- },
4436
- // session: 会话配置
4437
- session: {
4438
- // DM 作用域
4439
- dmScope: "per-account-channel-peer"
4440
- },
4441
- // hooks: 钩子配置
4442
- hooks: {
4443
- internal: {
4444
- // 启用内部钩子
4445
- enabled: true,
4446
- entries: {
4447
- // 启动 Markdown 钩子
4448
- "boot-md": { enabled: true },
4449
- // 引导额外文件钩子
4450
- "bootstrap-extra-files": { enabled: true },
4451
- // 命令日志钩子
4452
- "command-logger": { enabled: true },
4453
- // 会话记忆钩子
4454
- "session-memory": { enabled: true }
4455
- }
4456
- }
4457
- },
4458
- // channels: 通道配置
4347
+ ...safeConfig,
4459
4348
  channels: {
4349
+ ...existingChannels || {},
4460
4350
  "openclaw-workclaw": {
4461
- // 启用通道
4462
- enabled: true,
4463
- // 连接模式:'websocket' | 'http'
4464
- connectionMode: "websocket",
4465
- // WebSocket 连接策略
4466
- wsConnectionStrategy: "per-appKey",
4467
- // 应用密钥(从服务器获取)
4468
- appKey: boundConfig.appKey,
4469
- // 应用密钥(从服务器获取)
4470
- appSecret: boundConfig.appSecret,
4471
- // API 基础 URL
4472
- baseUrl: this.config.baseUrl || config.DEFAULT_BASE_URL,
4473
- // WebSocket URL
4474
- websocketUrl: this.config.wsUrl || config.DEFAULT_WS_URL,
4475
- // 允许不安全的 TLS 连接
4476
- allowInsecureTls: true,
4477
- // 允许原始 JSON 载荷
4478
- allowRawJsonPayload: true,
4351
+ userId: agentConfig.userId,
4352
+ apiKey: token,
4353
+ baseUrl: this.getApiBaseUrl(),
4354
+ wsUrl: this.getWsUrl(),
4479
4355
  accounts: {
4480
- // 账户配置(agentId 从服务器获取)
4481
- default: { enabled: true, agentId: boundConfig.agentId }
4482
- }
4483
- }
4484
- },
4485
- // gateway: 网关配置
4486
- gateway: {
4487
- // 网关模式:'local' | 'hosted'
4488
- mode: "local",
4489
- // 网关端口
4490
- port: 18789,
4491
- // 绑定地址:'loopback' | 'lan' | 'all'
4492
- bind: "lan",
4493
- // 认证模式(token字段未设置,是因为需要使用原始配置的token)
4494
- auth: {
4495
- mode: "token"
4496
- },
4497
- // Tailscale 配置
4498
- tailscale: { mode: "off", resetOnExit: false },
4499
- nodes: {
4500
- // 节点上禁止的命令
4501
- denyCommands: [
4502
- "camera.snap",
4503
- "camera.clip",
4504
- "screen.record",
4505
- "contacts.add",
4506
- "calendar.add",
4507
- "reminders.add"
4508
- ]
4509
- },
4510
- controlUi: {
4511
- // 允许不安全认证
4512
- allowInsecureAuth: true,
4513
- // 禁用设备认证(仅用于开发)
4514
- dangerouslyDisableDeviceAuth: true,
4515
- // 允许 Host 头源回退
4516
- dangerouslyAllowHostHeaderOriginFallback: true,
4517
- // 允许的源
4518
- allowedOrigins: ["http://localhost:18789", "http://127.0.0.1:18789"]
4519
- }
4520
- },
4521
- // skills: 技能配置
4522
- skills: {
4523
- // 技能加载监视模式
4524
- load: { watch: true }
4525
- },
4526
- // logging: 日志配置
4527
- logging: {
4528
- // 日志级别:'debug' | 'info' | 'warn' | 'error'
4529
- level: "trace",
4530
- // 控制台日志级别
4531
- consoleLevel: "trace",
4532
- // 控制台样式:'pretty' | 'basic' | 'raw'
4533
- consoleStyle: "pretty",
4534
- // 敏感信息脱敏:'off' | 'keys' | 'all'
4535
- redactSensitive: "off",
4536
- // 日志文件路径(空表示不写入文件)
4537
- file: "",
4538
- // 脱敏模式(正则表达式)
4539
- redactPatterns: ["sk-.*"]
4540
- },
4541
- // plugins: 插件配置
4542
- plugins: {
4543
- // 允许的插件列表
4544
- allow: [config.PLUGIN_NAME],
4545
- // 插件安装跟踪(支持 openclaw plugins update 命令)
4546
- installs: {
4547
- [config.PLUGIN_NAME]: {
4548
- // 安装来源
4549
- source: "npm",
4550
- // npm 包名
4551
- spec: PLUGIN_PACKAGE_NAME$1,
4552
- // 安装路径
4553
- installPath: paths.target
4356
+ default: {
4357
+ agentId: agentConfig.agentId
4358
+ }
4554
4359
  }
4555
- },
4556
- load: {
4557
- paths: [paths.target]
4558
- },
4559
- // 插件条目启用状态
4560
- entries: {
4561
- [config.PLUGIN_NAME]: { enabled: true }
4562
4360
  }
4563
4361
  }
4564
4362
  };
4565
- const finalConfig = deepMerge$1(originalConfig, newConfig);
4566
- debugLog("[更新配置] 写入配置文件...");
4567
- await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
4568
- this.prefixText += chalk.green(`✓ 配置文件更新成功
4569
- `);
4570
- debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
4363
+ await writeConfig(configPath, newConfig);
4571
4364
  }
4572
4365
  }
4573
- function checkEnv$1() {
4574
- debugLog("[环境检查] 检测 Node.js 版本...");
4575
- const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
4576
- debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
4577
- if (!semver.gte(nodeVersion, "18.0.0")) {
4578
- throw new AppError$1(ERROR_CODES$1.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
4579
- }
4580
- debugLog("[环境检查] Node.js 版本检查通过");
4581
- debugLog("[环境检查] 检测 npm...");
4582
- try {
4583
- const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
4584
- debugLog(`[环境检查] npm 版本: ${npmVersion}`);
4585
- debugLog("[环境检查] npm 检测通过");
4586
- } catch {
4587
- throw new AppError$1(ERROR_CODES$1.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
4588
- }
4589
- }
4590
- async function createLocalCommand(options) {
4591
- setDebug(!!options.debug);
4592
- debugLog("[初始化] 开始处理...");
4593
- debugLog(`[初始化] 参数: scenario=${options.scenario}, env=${options.env}, phone=${options.phone}, debug=${options.debug}`);
4594
- checkEnv$1();
4595
- debugLog("[初始化] 环境检查通过");
4596
- try {
4597
- let scenario = options.scenario;
4598
- let env = options.env;
4599
- let phone = options.phone;
4600
- let userPass = options.userPass;
4601
- const questions = [];
4602
- if (!scenario) {
4603
- debugLog("[初始化] 需要选择安装场景");
4604
- questions.push({
4605
- type: "list",
4606
- name: "scenario",
4607
- message: `${nodeEmoji.get("desktop_computer")} 请选择安装场景:`,
4608
- default: "windows-local",
4609
- choices: [
4610
- { name: `${chalk.green("Windows")} ${chalk.gray("本地安装")}`, value: "windows-local" },
4611
- { name: `${chalk.green("macOS")} ${chalk.gray("本地安装")}`, value: "mac-local" },
4612
- { name: `${chalk.green("Linux")} ${chalk.gray("本地安装")}`, value: "linux-local" }
4613
- ]
4614
- });
4615
- } else {
4616
- debugLog(`[初始化] 使用命令行参数: scenario=${scenario}`);
4617
- }
4618
- if (!env) {
4619
- debugLog("[初始化] 需要选择环境");
4620
- questions.push({
4621
- type: "list",
4622
- name: "env",
4623
- message: `${nodeEmoji.get("globe_with_meridians")} 请选择环境:`,
4624
- default: "test",
4625
- choices: [
4626
- { name: `${chalk.green("测试环境")} ${chalk.gray("(test)")}`, value: "test" },
4627
- { name: `${chalk.red("正式环境")} ${chalk.gray("(prod)")}`, value: "prod" }
4628
- ]
4629
- });
4630
- } else {
4631
- debugLog(`[初始化] 使用命令行参数: env=${env}`);
4632
- }
4633
- if (!phone) {
4634
- debugLog("[初始化] 需要输入手机号码");
4635
- questions.push({
4636
- type: "input",
4637
- name: "phone",
4638
- message: `${nodeEmoji.get("phone")} 请输入手机号码:`,
4639
- validate: (value) => {
4640
- if (!value || value.trim() === "") {
4641
- return `${nodeEmoji.get("x")} 手机号码不能为空`;
4642
- }
4643
- if (!/^1[3-9]\d{9}$/.test(value)) {
4644
- return `${nodeEmoji.get("warning")} 请输入正确的手机号码`;
4645
- }
4646
- return true;
4647
- }
4648
- });
4649
- } else {
4650
- debugLog("[初始化] 使用命令行参数: phone");
4651
- }
4652
- if (!userPass) {
4653
- debugLog("[初始化] 需要输入用户密码");
4654
- questions.push({
4655
- type: "input",
4656
- name: "userPass",
4657
- message: `${nodeEmoji.get("key")} 请输入用户密码:`,
4658
- validate: (value) => {
4659
- if (!value || value.trim() === "") {
4660
- return `${nodeEmoji.get("x")} 用户密码不能为空`;
4661
- }
4662
- return true;
4663
- }
4664
- });
4665
- } else {
4666
- debugLog("[初始化] 使用命令行参数: userPass");
4667
- }
4668
- if (questions.length > 0) {
4669
- debugLog(`[初始化] 开始交互式问答,共 ${questions.length} 个问题`);
4670
- const answers = await inquirer.prompt(questions);
4671
- debugLog("[初始化] 交互式问答完成");
4672
- scenario = scenario || answers.scenario;
4673
- env = env || answers.env || "test";
4674
- phone = phone || answers.phone;
4675
- userPass = userPass || answers.userPass;
4676
- }
4677
- debugLog(`[初始化] 最终参数: scenario=${scenario}, env=${env}, phone=${phone}, pluginVersion=${options.pluginVersion}`);
4678
- debugLog("[初始化] 创建 LocalInstaller 实例...");
4679
- const installer = new LocalInstaller({
4680
- phone,
4681
- userPass,
4682
- scenario,
4683
- env,
4684
- pluginVersion: options.pluginVersion
4685
- });
4686
- debugLog("[初始化] 开始安装...");
4687
- await installer.install();
4688
- debugLog("[初始化] 安装完成");
4689
- console.log(installer.getPrefixText() + boxen(
4690
- `${nodeEmoji.get("tada")} 插件初始化成功!`,
4691
- {
4692
- title: `${chalk.bold.green("初始化成功")}`,
4693
- titleAlignment: "center",
4694
- padding: { top: 1, bottom: 1, left: 5, right: 5 },
4695
- margin: 1,
4696
- borderStyle: "round",
4697
- borderColor: "green",
4698
- textAlignment: "center"
4699
- }
4700
- ));
4701
- } catch (error) {
4702
- debugLog(`[初始化] 发生错误: ${error.message}`);
4703
- console.error(boxen(
4704
- `${chalk.yellow(error.message)}`,
4705
- {
4706
- title: `${chalk.bold.red("初始化失败")}`,
4707
- titleAlignment: "center",
4708
- padding: { top: 1, bottom: 1, left: 5, right: 5 },
4709
- margin: 1,
4710
- borderStyle: "round",
4711
- borderColor: "red",
4712
- textAlignment: "center"
4713
- }
4714
- ));
4715
- process$1.exit(1);
4716
- }
4717
- }
4718
- function registerCommands$1(program2) {
4719
- program2.command("local").description("本地账户安装(需要登录)").option("-s, --scenario <scenario>", "安装场景").option("-e, --env <env>", "环境 (test/prod)").option("--phone <phone>", "手机号码").option("--user-pass <userPass>", "用户密码").option("--plugin-version <plugin-version>", "插件版本号(默认最新版)").option("--debug", "开启调试日志").action(createLocalCommand);
4720
- }
4721
- const ERROR_CODES = {
4722
- APP_KEY_REQUIRED: "APP_KEY_REQUIRED",
4723
- APP_SECRET_REQUIRED: "APP_SECRET_REQUIRED",
4724
- NODE_VERSION_LOW: "NODE_VERSION_LOW",
4725
- NODE_NOT_FOUND: "NODE_NOT_FOUND",
4726
- NPM_NOT_FOUND: "NPM_NOT_FOUND",
4727
- NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED",
4728
- CONFIG_WRITE_FAILED: "CONFIG_WRITE_FAILED",
4729
- PLUGIN_EXTRACT_FAILED: "PLUGIN_EXTRACT_FAILED"
4730
- };
4731
- class AppError2 extends Error {
4732
- constructor(code, message) {
4733
- super(message);
4734
- this.code = code;
4735
- this.name = "AppError";
4736
- }
4737
- }
4738
- function getHomeDir() {
4739
- return process$1.env.HOME || process$1.env.USERPROFILE || "";
4740
- }
4741
4366
  const PLUGIN_PACKAGE_NAME = "@workclaw/openclaw-workclaw";
4742
- function deepMerge(target, source) {
4743
- const result = { ...target };
4744
- for (const key in source) {
4745
- const sourceValue = source[key];
4746
- const targetValue = target[key];
4747
- if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
4748
- result[key] = deepMerge(targetValue, sourceValue);
4749
- } else if (sourceValue !== void 0) {
4750
- result[key] = sourceValue;
4751
- }
4752
- }
4753
- return result;
4754
- }
4755
- class BoxInstaller {
4367
+ class LocalInstaller extends BaseInstaller {
4756
4368
  constructor(config) {
4757
- this.config = config;
4758
- this.spinner = ora({ color: "cyan" }).start();
4759
- }
4760
- spinner;
4761
- prefixText = "";
4762
- getPackageName() {
4763
- if (this.config.pluginVersion) {
4764
- return `${PLUGIN_PACKAGE_NAME}@${this.config.pluginVersion}`;
4765
- }
4766
- return PLUGIN_PACKAGE_NAME;
4369
+ super(config, logger);
4767
4370
  }
4768
4371
  validateConfig() {
4769
- debugLog("[验证配置] 检查 appKey 和 appSecret...");
4770
- if (!this.config.appKey) {
4771
- throw new AppError2(ERROR_CODES.APP_KEY_REQUIRED, "AppKey 不能为空,请使用 --app-key 参数");
4372
+ if (!this.config.phone) {
4373
+ throw new AppError(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4772
4374
  }
4773
- if (!this.config.appSecret) {
4774
- throw new AppError2(ERROR_CODES.APP_SECRET_REQUIRED, "AppSecret 不能为空,请使用 --app-secret 参数");
4375
+ if (!this.config.userPass) {
4376
+ throw new AppError(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
4775
4377
  }
4776
- debugLog("[验证配置] 配置检查通过");
4777
4378
  }
4778
- getPaths() {
4779
- const home = getHomeDir();
4780
- const openclawDir = path.join(home, ".openclaw");
4781
- const extensionsDir = path.join(openclawDir, "extensions");
4782
- const targetDir = path.join(extensionsDir, "openclaw-workclaw");
4783
- const configFile = path.join(openclawDir, "openclaw.json");
4784
- const workspaceDir = path.join(openclawDir, "workspace");
4785
- const tempDir = path.join(openclawDir, ".temp");
4786
- return {
4787
- home,
4788
- extensions: extensionsDir,
4789
- target: targetDir,
4790
- config: configFile,
4791
- workspace: workspaceDir,
4792
- temp: tempDir
4793
- };
4379
+ getConfigPath() {
4380
+ return path.join(os.homedir(), ".openclaw", "openclaw.json");
4794
4381
  }
4795
- updateSpinner(text) {
4796
- this.spinner.prefixText = this.prefixText;
4797
- this.spinner.text = `${chalk.cyan("安装进度")} ${chalk.dim("→")} ${chalk.yellow(text)}`;
4798
- debugLog(`[Spinner] ${text}`);
4382
+ getScenario() {
4383
+ return this.config.scenario;
4799
4384
  }
4800
- getPrefixText() {
4801
- return this.prefixText;
4385
+ getInstallPath() {
4386
+ return path.join(os.homedir(), ".openclaw", "plugins", "openclaw-workclaw");
4387
+ }
4388
+ getExtensionsPath() {
4389
+ return path.join(os.homedir(), ".openclaw", "extensions");
4390
+ }
4391
+ getWorkspacePath() {
4392
+ return path.join(os.homedir(), ".openclaw", "workspace");
4802
4393
  }
4803
4394
  async install() {
4804
4395
  try {
4805
- debugLog("[盒子安装] 开始安装...");
4396
+ logger.info("开始安装 WorkClaw 插件(NPM 安装模式)");
4397
+ logger.info("=".repeat(40));
4806
4398
  this.validateConfig();
4807
- this.updateSpinner("验证配置完成");
4808
- const paths = this.getPaths();
4809
- await this.doCleanOldFiles(paths);
4810
- this.updateSpinner("清理旧版本完成");
4811
- await this.doDownloadFromNpm(paths);
4812
- this.updateSpinner("下载插件完成");
4813
- await this.doUpdateConfig(paths);
4814
- this.updateSpinner("更新配置完成");
4815
- this.spinner.stop();
4816
- debugLog("[盒子安装] 安装完成");
4399
+ await this.cleanOldFiles();
4400
+ this.checkEnv();
4401
+ await this.downloadFromNpm();
4402
+ await this.fetchConfigAndUpdate();
4403
+ logger.success("安装完成");
4404
+ return { success: true, message: "插件安装成功" };
4817
4405
  } catch (error) {
4818
- this.spinner.stop();
4819
- debugLog(`[盒子安装] 安装失败: ${error.message}`);
4820
- throw error;
4406
+ const errorMessage = formatError(error);
4407
+ if (error instanceof AppError) {
4408
+ logger.error(`安装失败 [${error.code}]: ${errorMessage}`);
4409
+ } else {
4410
+ logger.error(`安装失败: ${errorMessage}`);
4411
+ }
4412
+ return { success: false, error: errorMessage };
4821
4413
  }
4822
4414
  }
4823
- async doCleanOldFiles(paths) {
4824
- debugLog("[清理旧版本] 开始清理旧版本...");
4825
- this.prefixText += chalk.green(`✓ 开始清理旧版本
4826
- `);
4827
- try {
4828
- await fs.access(paths.target);
4829
- debugLog("[清理旧版本] 删除旧版本目录...");
4830
- await fs.rm(paths.target, { recursive: true, force: true });
4831
- this.prefixText += chalk.green(`✓ 旧版本清理成功
4832
- `);
4833
- debugLog("[清理旧版本] 旧版本清理成功");
4834
- } catch {
4835
- debugLog("[清理旧版本] 无旧版本需要清理");
4836
- this.prefixText += chalk.green(`✓ 无旧版本需要清理
4837
- `);
4838
- }
4415
+ async cleanOldFiles() {
4416
+ const spinner = logger.start("步骤 1: 清理旧版本");
4417
+ const targetDir = this.getInstallPath();
4839
4418
  try {
4840
- await fs.access(paths.temp);
4841
- await fs.rm(paths.temp, { recursive: true, force: true });
4419
+ await fs.access(targetDir);
4420
+ spinner.text = "发现旧版本,正在清理...";
4421
+ await fs.rm(targetDir, { recursive: true, force: true });
4422
+ spinner.succeed("旧版本清理完成");
4842
4423
  } catch {
4843
- debugLog("[清理旧版本] 无临时目录需要清理");
4424
+ spinner.succeed("未发现旧版本");
4844
4425
  }
4845
4426
  }
4846
- async doDownloadFromNpm(paths) {
4847
- debugLog("[下载插件] 开始下载插件...");
4848
- this.prefixText += chalk.green(`✓ 开始下载插件
4849
- `);
4850
- const maxRetries = 3;
4851
- let lastError = "";
4852
- let tarballPath = "";
4853
- for (let i = 1; i <= maxRetries; i++) {
4854
- debugLog(`[下载插件] 第 ${i} 次尝试下载 tarball...`);
4855
- try {
4856
- const packageName = this.getPackageName();
4857
- debugLog(`[下载插件] 下载包名: ${packageName}`);
4858
- const { execSync: execSync2 } = await import("node:child_process");
4859
- const output = execSync2(
4860
- `npm pack ${packageName} --registry=https://mirrors.tencent.com/npm/ --ignore-scripts`,
4861
- { cwd: paths.temp, encoding: "utf-8", timeout: 6e4 }
4862
- );
4863
- const filename = output.trim().split("\n").pop() || "";
4864
- tarballPath = path.join(paths.temp, filename);
4865
- debugLog(`[下载插件] tarball 下载成功: ${tarballPath}`);
4866
- break;
4867
- } catch (error) {
4868
- lastError = error.stderr?.toString() || error.message || "未知错误";
4869
- debugLog(`[下载插件] 第 ${i} 次尝试失败: ${lastError}`);
4870
- if (i < maxRetries) {
4871
- debugLog("[下载插件] 等待 3 秒后重试...");
4872
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
4873
- }
4427
+ checkEnv() {
4428
+ const spinner = logger.start("步骤 2: 检查环境");
4429
+ try {
4430
+ const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
4431
+ const majorVersion = Number.parseInt(nodeVersion.replace("v", "").split(".")[0]);
4432
+ if (majorVersion < 18) {
4433
+ spinner.fail(`Node.js 版本过低(当前: ${nodeVersion},需要: v18.0.0 或更高版本)`);
4434
+ throw new AppError(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本过低(当前: ${nodeVersion},需要: v18.0.0 或更高版本)`);
4435
+ }
4436
+ spinner.text = `Node.js 已就绪(${nodeVersion})`;
4437
+ } catch (error) {
4438
+ if (error instanceof AppError) {
4439
+ throw error;
4874
4440
  }
4441
+ spinner.fail("未检测到 Node.js,请检查环境配置");
4442
+ throw new AppError(ERROR_CODES.NODE_NOT_FOUND, "未检测到 Node.js,请检查环境配置");
4875
4443
  }
4876
- if (!tarballPath) {
4877
- this.prefixText += chalk.red(`✗ 下载失败
4878
- `);
4879
- throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `插件下载失败: ${lastError}`);
4444
+ try {
4445
+ execSync("npm --version", { stdio: "ignore" });
4446
+ spinner.succeed("环境检查完成");
4447
+ } catch {
4448
+ spinner.fail("未检测到 npm,请检查环境配置");
4449
+ throw new AppError(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请检查环境配置");
4880
4450
  }
4881
- this.prefixText += chalk.green(`✓ 插件下载成功
4882
- `);
4883
- await this.extractTarball(tarballPath, paths);
4884
4451
  }
4885
- async extractTarball(tarballPath, paths) {
4886
- debugLog("[解压插件] 开始解压 tarball...");
4452
+ async downloadFromNpm() {
4453
+ const spinner = logger.start("步骤 3: 从 NPM 下载插件");
4454
+ const extensionsDir = this.getExtensionsPath();
4455
+ spinner.text = `插件安装目录: ${extensionsDir}`;
4887
4456
  try {
4888
- await fs.mkdir(paths.temp, { recursive: true });
4889
- await tar.extract({
4890
- file: tarballPath,
4891
- cwd: paths.temp
4457
+ await fs.mkdir(extensionsDir, { recursive: true });
4458
+ } catch {
4459
+ }
4460
+ spinner.text = `正在从 NPM 下载插件 ${PLUGIN_PACKAGE_NAME}...`;
4461
+ try {
4462
+ execSync(`npm install ${PLUGIN_PACKAGE_NAME}`, {
4463
+ cwd: extensionsDir,
4464
+ stdio: "pipe"
4892
4465
  });
4893
- const packageDir = path.join(paths.temp, "package");
4894
- const stat = await fs.stat(packageDir);
4895
- if (stat.isDirectory()) {
4896
- await fs.mkdir(paths.extensions, { recursive: true });
4897
- await fs.cp(packageDir, paths.target, { recursive: true });
4898
- debugLog(`[解压插件] 插件解压成功: ${paths.target}`);
4899
- this.prefixText += chalk.green(`✓ 插件解压成功
4900
- `);
4901
- this.spinner.prefixText = this.prefixText;
4902
- this.spinner.text = `${chalk.cyan("安装依赖")} ${chalk.dim("→")} ${chalk.yellow("正在安装...")}`;
4903
- debugLog("[安装依赖] 执行 npm install 安装生产环境依赖");
4904
- try {
4905
- execSync("npm install --omit=dev --ignore-scripts", {
4906
- cwd: paths.target,
4907
- stdio: "pipe",
4908
- timeout: 12e4
4909
- });
4910
- debugLog("[安装依赖] npm install 执行成功");
4911
- this.prefixText += chalk.green(`✓ 依赖安装成功
4912
- `);
4913
- } catch (error) {
4914
- const stderr = error.stderr?.toString() || "";
4915
- const stdout = error.stdout?.toString() || "";
4916
- const combinedOutput = stderr + stdout;
4917
- if (combinedOutput.includes("npm warn") || combinedOutput.includes("deprecated")) {
4918
- debugLog("[安装依赖] npm install 有警告但可能成功,继续流程");
4919
- this.prefixText += chalk.green(`✓ 依赖安装完成
4920
- `);
4921
- } else {
4922
- debugLog(`[安装依赖] npm install 失败: ${combinedOutput}`);
4923
- }
4924
- }
4925
- await fs.rm(paths.temp, { recursive: true, force: true });
4926
- debugLog(`[解压插件] 清理临时文件成功`);
4927
- this.prefixText += chalk.green(`✓ 清理临时文件成功
4928
- `);
4929
- } else {
4930
- throw new AppError2(ERROR_CODES.PLUGIN_EXTRACT_FAILED, "解压失败:package 目录不存在");
4466
+ spinner.succeed("插件下载完成");
4467
+ } catch {
4468
+ spinner.fail("插件下载失败");
4469
+ throw new AppError(ERROR_CODES.NPM_INSTALL_FAILED, `插件 ${PLUGIN_PACKAGE_NAME} 下载失败`);
4470
+ }
4471
+ }
4472
+ async fetchConfigAndUpdate() {
4473
+ const spinner = logger.start("步骤 5: 获取配置并更新");
4474
+ spinner.text = "正在登录...";
4475
+ const token = await this.login();
4476
+ spinner.text = "登录成功";
4477
+ spinner.text = "正在获取绑定配置...";
4478
+ const boundConfig = await this.fetchBoundConfig(token);
4479
+ if (!boundConfig || !boundConfig.agentId) {
4480
+ spinner.fail("获取绑定配置失败:缺少 agentId");
4481
+ throw new Error("获取绑定配置失败:缺少 agentId");
4482
+ }
4483
+ spinner.text = "正在更新配置文件...";
4484
+ await this.updateConfig(boundConfig, token);
4485
+ spinner.succeed("配置文件更新完成");
4486
+ }
4487
+ async login() {
4488
+ const apiUrl = `${this.getApiBaseUrl()}/user/login/pass`;
4489
+ logger.info(`登录接口: ${apiUrl}`);
4490
+ const encryptedPassword = rsaEncrypt(this.config.userPass);
4491
+ if (!encryptedPassword) {
4492
+ throw new Error("密码加密失败");
4493
+ }
4494
+ const response = await httpPost(apiUrl, {
4495
+ phone: this.config.phone,
4496
+ userPass: encryptedPassword
4497
+ });
4498
+ const res = response.data;
4499
+ if (res.code === 200 && res.data && res.data.token) {
4500
+ return res.data.token;
4501
+ }
4502
+ throw new Error(res.message || "登录失败");
4503
+ }
4504
+ async fetchBoundConfig(token) {
4505
+ const apiUrl = `${this.getApiBaseUrl()}/work-bot/local/bound/init`;
4506
+ logger.info(`绑定配置接口: ${apiUrl}`);
4507
+ const response = await httpGet(apiUrl, {
4508
+ headers: {
4509
+ Authorization: token
4931
4510
  }
4932
- } catch (error) {
4933
- debugLog(`[解压插件] 解压失败: ${error.message}`);
4934
- this.prefixText += chalk.red(`✗ 解压失败
4935
- `);
4936
- throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `插件解压失败: ${error.message}`);
4511
+ });
4512
+ const res = response.data;
4513
+ if (res.code === 200 && res.data) {
4514
+ return {
4515
+ appKey: res.data.app_key,
4516
+ appSecret: res.data.app_secret,
4517
+ userId: res.data.user_id,
4518
+ agentId: res.data.agent_id,
4519
+ modelApiKey: res.data.model_api_key,
4520
+ modelApiBaseUrl: res.data.model_api_base_url
4521
+ };
4937
4522
  }
4523
+ throw new Error(res.message || "获取绑定配置失败");
4938
4524
  }
4939
- async doUpdateConfig(paths) {
4940
- debugLog("[更新配置] 开始更新配置...");
4941
- this.prefixText += chalk.green(`✓ 开始更新配置
4942
- `);
4525
+ async updateConfig(boundConfig, token) {
4526
+ const configPath = this.getConfigPath();
4527
+ const workspacePath = this.getWorkspacePath();
4528
+ logger.info(`配置文件路径: ${configPath}`);
4529
+ logger.info(`工作区路径: ${workspacePath}`);
4530
+ logger.info(`Agent ID: ${boundConfig.agentId}`);
4943
4531
  let originalConfig = {};
4944
4532
  try {
4945
- const content = await fs.readFile(paths.config, "utf-8");
4533
+ const content = await fs.readFile(configPath, "utf-8");
4946
4534
  originalConfig = JSON.parse(content);
4947
- debugLog("[更新配置] 读取原有配置成功");
4535
+ logger.info("已读取现有配置文件");
4948
4536
  } catch {
4949
- debugLog("[更新配置] 无原有配置");
4537
+ logger.info("创建新配置文件");
4950
4538
  }
4951
- const config = getConfig(this.config.env);
4952
- const newConfig = {
4953
- // diagnostics: 诊断配置
4539
+ const dynamicConfig = {
4540
+ // 诊断配置
4954
4541
  diagnostics: {
4955
4542
  // 启用诊断功能
4956
4543
  enabled: true,
4957
- // 启用所有诊断标志 ['*'] 表示全部,[] 表示禁用所有
4544
+ // 诊断标志列表
4958
4545
  flags: ["*"]
4959
4546
  },
4960
- // browser: 浏览器配置
4547
+ // 浏览器配置
4961
4548
  browser: {
4962
- // 禁用浏览器工具
4549
+ // 禁用内置浏览器
4963
4550
  enabled: false
4964
4551
  },
4965
- // models: 模型配置
4552
+ // 模型配置
4966
4553
  models: {
4967
- // 配置合并模式:'merge' 合并 | 'replace' 替换
4554
+ // 配置合并模式:merge 会将新配置与现有配置合并
4968
4555
  mode: "merge",
4556
+ // 模型提供商配置
4969
4557
  providers: {
4558
+ // SiliconFlow MiniMax 提供商
4970
4559
  "siliconflow-minimax": {
4971
- // 模型 API 基础地址
4972
- baseUrl: config.MODEL_BASE_URL,
4973
- // API 密钥(留空,由外部提供)
4974
- apiKey: "",
4975
- // API 类型:'openai-completions' | 'openai-chat' 等
4560
+ // API 基础地址,从绑定配置获取或使用默认值
4561
+ baseUrl: boundConfig.modelApiBaseUrl || this.getDefaultModelUrl(),
4562
+ // API 密钥,用于认证
4563
+ apiKey: boundConfig.modelApiKey,
4564
+ // API 类型,这里使用 OpenAI 兼容格式
4976
4565
  api: "openai-completions",
4977
- // 是否使用 Authorization header 认证
4566
+ // 是否使用 Authorization header 进行认证
4978
4567
  authHeader: true,
4979
- models: [{
4980
- // 模型 ID(provider/model 格式)
4981
- id: "Pro/MiniMaxAI/MiniMax-M2.5",
4982
- // 模型显示名称
4983
- name: "MiniMax-M2.5",
4984
- // 是否启用推理能力
4985
- reasoning: false,
4986
- // 支持的输入类型:['text'] | ['text', 'image']
4987
- input: ["text"],
4988
- // 价格(0 表示免费或未设置)
4989
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
4990
- // 上下文窗口大小(token)
4991
- contextWindow: 2e5,
4992
- // 最大输出 token 数
4993
- maxTokens: 65536
4994
- }]
4568
+ // 模型列表配置
4569
+ models: [
4570
+ {
4571
+ // 模型标识符
4572
+ id: "Pro/MiniMaxAI/MiniMax-M2.5",
4573
+ // 模型显示名称
4574
+ name: "MiniMax-M2.5",
4575
+ // 是否支持推理能力
4576
+ reasoning: false,
4577
+ // 支持的输入类型
4578
+ input: [
4579
+ "text"
4580
+ ],
4581
+ // 费用配置(单位:分)
4582
+ cost: {
4583
+ // 输入费用
4584
+ input: 0,
4585
+ // 输出费用
4586
+ output: 0,
4587
+ // 缓存读取费用
4588
+ cacheRead: 0,
4589
+ // 缓存写入费用
4590
+ cacheWrite: 0
4591
+ },
4592
+ // 上下文窗口大小(token 数)
4593
+ contextWindow: 2e5,
4594
+ // 最大输出 token 数
4595
+ maxTokens: 65536
4596
+ }
4597
+ ]
4995
4598
  }
4996
4599
  }
4997
4600
  },
4998
- // agents: 代理配置
4601
+ // Agent 配置
4999
4602
  agents: {
4603
+ // 默认 Agent 设置
5000
4604
  defaults: {
5001
- // 默认使用的模型
5002
- model: { primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5" },
4605
+ // 默认模型配置
4606
+ model: {
4607
+ // 主要使用的模型
4608
+ primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5"
4609
+ },
4610
+ // 模型映射表
5003
4611
  models: {
4612
+ // 模型的别名配置
5004
4613
  "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5": {
5005
- // 模型别名,用于显示
4614
+ // 模型别名
5006
4615
  alias: "Pro/MiniMaxAI/MiniMax-M2.5"
5007
4616
  }
5008
4617
  },
5009
- // 工作区目录路径
5010
- workspace: paths.workspace,
5011
- // 会话压缩模式:'safeguard' 保守模式
5012
- compaction: { mode: "safeguard" },
5013
- // 详细程度:'full' | 'short' | 'off'
4618
+ // 默认工作区路径
4619
+ workspace: workspacePath,
4620
+ // 压缩配置
4621
+ compaction: {
4622
+ // 压缩模式:safeguard 模式
4623
+ mode: "safeguard"
4624
+ },
4625
+ // 默认详细模式:full 表示完整输出
5014
4626
  verboseDefault: "full",
5015
- // 最大并发任务数
4627
+ // 最大并发数
5016
4628
  maxConcurrent: 4,
5017
- // 子代理最大并发数
5018
- subagents: { maxConcurrent: 8 }
4629
+ // 子 Agent 配置
4630
+ subagents: {
4631
+ // 子 Agent 最大并发数
4632
+ maxConcurrent: 8
4633
+ }
5019
4634
  },
5020
- // 代理列表
5021
- list: [{ id: "main", workspace: paths.workspace }]
4635
+ // Agent 列表
4636
+ list: [
4637
+ {
4638
+ // Agent ID
4639
+ id: "main",
4640
+ // Agent 工作区路径
4641
+ workspace: workspacePath
4642
+ }
4643
+ ]
5022
4644
  },
5023
- // tools: 工具配置
4645
+ // 工具权限配置
5024
4646
  tools: {
5025
- // 禁用的工具列表(优先于 allow)
5026
- deny: ["image", "web_search", "web_fetch"],
5027
- // 图像分析工具
5028
- media: { image: { enabled: false } },
5029
- // 网络搜索和抓取工具
5030
- web: { search: { enabled: false }, fetch: { enabled: false } },
5031
- // 提升权限工具(host=gateway)
5032
- elevated: { enabled: true, allowFrom: { webchat: ["*"] } },
5033
- // exec 工具安全级别:'deny' | 'allowlist' | 'full'
5034
- exec: { security: "full" }
4647
+ // 禁止使用的工具类型列表
4648
+ deny: [
4649
+ "image",
4650
+ "web_search",
4651
+ "web_fetch"
4652
+ ],
4653
+ // 媒体工具配置
4654
+ media: {
4655
+ // 图片处理工具
4656
+ image: { enabled: false }
4657
+ },
4658
+ // 网页工具配置
4659
+ web: {
4660
+ // 网络搜索
4661
+ search: { enabled: false },
4662
+ // 网页抓取
4663
+ fetch: { enabled: false }
4664
+ },
4665
+ // 提权工具配置
4666
+ elevated: {
4667
+ // 启用/禁用提权功能
4668
+ enabled: true,
4669
+ // 允许使用提权的来源
4670
+ allowFrom: {
4671
+ // webchat来源允许使用提权
4672
+ webchat: ["*"]
4673
+ }
4674
+ },
4675
+ // 执行工具配置
4676
+ exec: {
4677
+ // 安全模式级别
4678
+ security: "full"
4679
+ }
5035
4680
  },
5036
- // bindings: 绑定配置
5037
- bindings: [{
5038
- // 绑定的代理 ID
5039
- agentId: "main",
5040
- // 匹配的通道和账户
5041
- match: { channel: "openclaw-workclaw", accountId: "default" }
5042
- }],
5043
- // messages: 消息配置
4681
+ // 绑定配置
4682
+ bindings: [
4683
+ {
4684
+ // 绑定的智能体ID
4685
+ agentId: "main",
4686
+ match: {
4687
+ // 匹配通道名称
4688
+ channel: "openclaw-workclaw",
4689
+ // 匹配账号ID
4690
+ accountId: "default"
4691
+ }
4692
+ }
4693
+ ],
4694
+ // 消息配置
5044
4695
  messages: {
5045
- // 消息确认范围
4696
+ // 消息确认反应的作用域
5046
4697
  ackReactionScope: "group-mentions"
5047
4698
  },
5048
- // commands: 命令配置
4699
+ // 命令配置
5049
4700
  commands: {
5050
- // 本机命令:'auto' | 'on' | 'off'
4701
+ // 原生命令处理模式
5051
4702
  native: "auto",
5052
- // 本机技能:'auto' | 'on' | 'off'
4703
+ // 技能命令处理模式
5053
4704
  nativeSkills: "auto",
5054
- // 是否允许重启命令
4705
+ // 允许使用restart命令
5055
4706
  restart: true,
5056
- // 所有者显示格式:'raw' | 'name' | 'hidden'
4707
+ // 所有者信息显示方式
5057
4708
  ownerDisplay: "raw"
5058
4709
  },
5059
- // session: 会话配置
4710
+ // 会话配置
5060
4711
  session: {
5061
- // DM 作用域
4712
+ // 会话隔离策略
5062
4713
  dmScope: "per-account-channel-peer"
5063
4714
  },
5064
- // hooks: 钩子配置
4715
+ // 钩子配置
5065
4716
  hooks: {
5066
4717
  internal: {
5067
- // 启用内部钩子
4718
+ // 启用/禁用内部钩子
5068
4719
  enabled: true,
5069
4720
  entries: {
5070
- // 启动 Markdown 钩子
4721
+ // 启动时显示Markdown
5071
4722
  "boot-md": { enabled: true },
5072
- // 引导额外文件钩子
4723
+ // 加载额外引导文件
5073
4724
  "bootstrap-extra-files": { enabled: true },
5074
- // 命令日志钩子
4725
+ // 记录命令执行日志
5075
4726
  "command-logger": { enabled: true },
5076
- // 会话记忆钩子
4727
+ // 启用会话记忆
5077
4728
  "session-memory": { enabled: true }
5078
4729
  }
5079
4730
  }
5080
4731
  },
5081
- // channels: 通道配置
4732
+ // 渠道配置
5082
4733
  channels: {
4734
+ // WorkClaw 插件渠道
5083
4735
  "openclaw-workclaw": {
5084
- // 启用通道
4736
+ // 是否启用此渠道
5085
4737
  enabled: true,
5086
- // 连接模式:'websocket' | 'http'
4738
+ // 连接模式:使用 WebSocket 连接
5087
4739
  connectionMode: "websocket",
5088
- // WebSocket 连接策略
4740
+ // WebSocket 连接策略:每个 appKey 一个连接
5089
4741
  wsConnectionStrategy: "per-appKey",
5090
- // 应用密钥
5091
- appKey: this.config.appKey,
5092
- // 应用密钥
5093
- appSecret: this.config.appSecret,
5094
- // API 基础 URL
5095
- baseUrl: this.config.baseUrl || config.DEFAULT_BASE_URL,
5096
- // WebSocket URL
5097
- websocketUrl: this.config.wsUrl || config.DEFAULT_WS_URL,
5098
- // 允许不安全的 TLS 连接
4742
+ // 应用密钥,用于身份验证
4743
+ appKey: boundConfig.appKey,
4744
+ // 应用密钥,用于身份验证
4745
+ appSecret: boundConfig.appSecret,
4746
+ // API 基础地址
4747
+ baseUrl: this.config.baseUrl || this.getDefaultBaseUrl(),
4748
+ // WebSocket 连接地址
4749
+ websocketUrl: this.config.wsUrl || this.getDefaultWsUrl(),
4750
+ // 是否允许不安全的 TLS 连接
5099
4751
  allowInsecureTls: true,
5100
- // 允许原始 JSON 载荷
4752
+ // 是否允许原始 JSON 载荷
5101
4753
  allowRawJsonPayload: true,
5102
- // 用户 ID
5103
- userId: "",
4754
+ // 账号配置
5104
4755
  accounts: {
5105
- // 账户配置(agentId 为空表示待分配)
5106
- default: { enabled: true, agentId: "" }
4756
+ // 默认账号
4757
+ default: {
4758
+ // 是否启用默认账号
4759
+ enabled: true,
4760
+ // 代理 ID
4761
+ agentId: boundConfig.agentId
4762
+ }
5107
4763
  }
5108
4764
  }
5109
4765
  },
5110
- // gateway: 网关配置
4766
+ // Gateway 节点配置
5111
4767
  gateway: {
5112
- // 网关模式:'local' | 'hosted'
4768
+ // 运行模式
5113
4769
  mode: "local",
5114
- // 网关端口
4770
+ // 监听端口
5115
4771
  port: 18789,
5116
- // 绑定地址:'loopback' | 'lan' | 'all'
4772
+ // 绑定地址(仅监听本地)
5117
4773
  bind: "loopback",
5118
- // 认证模式(token字段未设置,是因为需要使用原始配置的token)
4774
+ // 认证配置(为空字符串会使用原始配置中的值)
5119
4775
  auth: {
5120
- mode: "token"
4776
+ // 认证模式
4777
+ mode: "token",
4778
+ // 认证令牌(为空会使用原始配置中的值)
4779
+ token: ""
5121
4780
  },
5122
4781
  // Tailscale 配置
5123
- tailscale: { mode: "off", resetOnExit: false },
4782
+ tailscale: {
4783
+ // Tailscale 模式
4784
+ mode: "off",
4785
+ // 退出时重置
4786
+ resetOnExit: false
4787
+ },
5124
4788
  nodes: {
5125
- // 节点上禁止的命令
4789
+ // 禁用的节点命令(出于安全考虑)
5126
4790
  denyCommands: [
5127
4791
  "camera.snap",
5128
4792
  "camera.clip",
@@ -5134,194 +4798,250 @@ class BoxInstaller {
5134
4798
  "sms.search"
5135
4799
  ]
5136
4800
  },
4801
+ // 控制面板配置
5137
4802
  controlUi: {
5138
- // 允许不安全认证
4803
+ // 允许非安全认证
5139
4804
  allowInsecureAuth: true,
5140
- // 禁用设备认证(仅用于开发)
4805
+ // 禁用设备认证
5141
4806
  dangerouslyDisableDeviceAuth: true,
5142
- // 允许 Host 头源回退
4807
+ // 允许 Host header 源回退
5143
4808
  dangerouslyAllowHostHeaderOriginFallback: true,
5144
- // 允许的源
5145
- allowedOrigins: ["http://localhost:18789", "http://127.0.0.1:18789"]
4809
+ // 允许的来源(本地开发)
4810
+ allowedOrigins: [
4811
+ "http://localhost:18789",
4812
+ "http://127.0.0.1:18789"
4813
+ ]
5146
4814
  }
5147
4815
  },
5148
- // skills: 技能配置
4816
+ // 技能配置
5149
4817
  skills: {
5150
- // 技能加载监视模式
5151
- load: { watch: true }
4818
+ load: {
4819
+ // 监听技能文件变化
4820
+ watch: true
4821
+ }
5152
4822
  },
5153
- // logging: 日志配置
4823
+ // 日志配置
5154
4824
  logging: {
5155
- // 日志级别:'debug' | 'info' | 'warn' | 'error'
4825
+ // 日志级别
5156
4826
  level: "info",
5157
4827
  // 控制台日志级别
5158
4828
  consoleLevel: "info",
5159
- // 控制台样式:'pretty' | 'basic' | 'raw'
4829
+ // 控制台样式
5160
4830
  consoleStyle: "pretty",
5161
- // 敏感信息脱敏:'off' | 'keys' | 'all'
4831
+ // 敏感信息脱敏
5162
4832
  redactSensitive: "off",
5163
- // 日志文件路径(空表示不写入文件)
4833
+ // 日志文件路径(空表示使用系统默认)
5164
4834
  file: "",
5165
- // 脱敏模式(正则表达式)
4835
+ // 脱敏模式
5166
4836
  redactPatterns: ["sk-.*"]
5167
4837
  },
5168
- // plugins: 插件配置
4838
+ // 插件配置
5169
4839
  plugins: {
5170
4840
  // 允许的插件列表
5171
- allow: [config.PLUGIN_NAME],
5172
- // 插件安装跟踪(支持 openclaw plugins update 命令)
4841
+ allow: ["openclaw-workclaw"],
4842
+ // 插件安装配置
5173
4843
  installs: {
5174
- [config.PLUGIN_NAME]: {
5175
- // 安装来源
4844
+ // WorkClaw 插件安装信息
4845
+ "openclaw-workclaw": {
4846
+ // 插件来源类型:npm 表示从 NPM 安装
5176
4847
  source: "npm",
5177
- // npm 包名
5178
- spec: PLUGIN_PACKAGE_NAME,
5179
- // 安装路径
5180
- installPath: paths.target
4848
+ // NPM 包名
4849
+ npm: PLUGIN_PACKAGE_NAME,
4850
+ // 插件安装路径
4851
+ installPath: this.getExtensionsPath()
5181
4852
  }
5182
4853
  },
5183
- // 插件加载路径
5184
- load: {
5185
- paths: [paths.target]
5186
- },
5187
- // 插件条目启用状态
4854
+ // 插件入口配置
5188
4855
  entries: {
5189
- [config.PLUGIN_NAME]: { enabled: true }
4856
+ // WorkClaw 插件入口
4857
+ "openclaw-workclaw": {
4858
+ // 是否启用此插件
4859
+ enabled: true
4860
+ }
5190
4861
  }
4862
+ },
4863
+ // 魔导配置(为空字符串会使用原始配置中的值)
4864
+ wizard: {
4865
+ // 上次运行时间(为空会保留原始配置值)
4866
+ lastRunAt: "",
4867
+ // 上次运行版本(为空会保留原始配置值)
4868
+ lastRunVersion: "",
4869
+ // 上次运行命令(为空会保留原始配置值)
4870
+ lastRunCommand: "",
4871
+ // 上次运行模式(为空会保留原始配置值)
4872
+ lastRunMode: ""
4873
+ },
4874
+ // 元数据配置(为空字符串会使用原始配置中的值)
4875
+ meta: {
4876
+ // 上次修改版本(为空会保留原始配置值)
4877
+ lastTouchedVersion: "",
4878
+ // 上次修改时间(为空会保留原始配置值)
4879
+ lastTouchedAt: ""
5191
4880
  }
5192
4881
  };
5193
- const finalConfig = deepMerge(originalConfig, newConfig);
5194
- debugLog("[更新配置] 写入配置文件...");
5195
- await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
5196
- this.prefixText += chalk.green(`✓ 配置文件更新成功
5197
- `);
5198
- debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
4882
+ const finalConfig = this.mergeConfig(originalConfig, dynamicConfig);
4883
+ await fs.writeFile(configPath, JSON.stringify(finalConfig, null, 2), "utf-8");
4884
+ await this.sendCallback(boundConfig.agentId, token);
5199
4885
  }
5200
- }
5201
- function checkEnv() {
5202
- debugLog("[环境检查] 检测 Node.js 版本...");
5203
- const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
5204
- debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
5205
- if (!semver.gte(nodeVersion, "18.0.0")) {
5206
- throw new AppError2(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
4886
+ async sendCallback(agentId, token) {
4887
+ try {
4888
+ const callbackUrl = `${this.getOpenApisUrl()}/instance/agent-created/status`;
4889
+ await httpPost(callbackUrl, {
4890
+ agentId,
4891
+ success: true
4892
+ }, {
4893
+ headers: {
4894
+ Authorization: token
4895
+ }
4896
+ });
4897
+ logger.success("智小途插件初始化回调成功");
4898
+ } catch {
4899
+ logger.warn("回调通知失败,请手动确认");
4900
+ }
5207
4901
  }
5208
- debugLog("[环境检查] Node.js 版本检查通过");
5209
- debugLog("[环境检查] 检测 npm...");
5210
- try {
5211
- const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
5212
- debugLog(`[环境检查] npm 版本: ${npmVersion}`);
5213
- debugLog("[环境检查] npm 检测通过");
5214
- } catch {
5215
- throw new AppError2(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
4902
+ mergeConfig(original, dynamic) {
4903
+ const result = { ...original };
4904
+ for (const key in dynamic) {
4905
+ const dynamicValue = dynamic[key];
4906
+ const originalValue = result[key];
4907
+ if (dynamicValue !== null && typeof dynamicValue === "object") {
4908
+ result[key] = {
4909
+ ...originalValue || {},
4910
+ ...dynamicValue
4911
+ };
4912
+ } else if (dynamicValue === "" || dynamicValue === null || dynamicValue === void 0) ;
4913
+ else {
4914
+ result[key] = dynamicValue;
4915
+ }
4916
+ }
4917
+ return result;
4918
+ }
4919
+ getApiBaseUrl() {
4920
+ if (this.config.baseUrl) {
4921
+ return `${this.config.baseUrl}/tuzai`;
4922
+ }
4923
+ return `${getApiBaseUrl(this.config.env)}/tuzai`;
4924
+ }
4925
+ getOpenApisUrl() {
4926
+ if (this.config.baseUrl) {
4927
+ return `${this.config.baseUrl}/open-apis`;
4928
+ }
4929
+ return getOpenApisUrl(this.config.env);
4930
+ }
4931
+ getDefaultBaseUrl() {
4932
+ return this.config.baseUrl || getApiBaseUrl(this.config.env);
4933
+ }
4934
+ getDefaultWsUrl() {
4935
+ if (this.config.wsUrl) {
4936
+ return this.config.wsUrl;
4937
+ }
4938
+ return getWsUrl(this.config.env);
4939
+ }
4940
+ getDefaultModelUrl() {
4941
+ return getModelUrl(this.config.env);
5216
4942
  }
5217
4943
  }
5218
- async function createBoxCommand(options) {
5219
- setDebug(!!options.debug);
5220
- debugLog("[盒子安装] 开始处理...");
5221
- debugLog(`[盒子安装] 参数: env=${options.env}, appKey=${options.appKey ? "***" : "未提供"}, debug=${options.debug}`);
5222
- checkEnv();
5223
- debugLog("[盒子安装] 环境检查通过");
5224
- try {
5225
- let env = options.env;
5226
- let appKey = options.appKey;
5227
- let appSecret = options.appSecret;
5228
- const questions = [];
5229
- if (!env) {
5230
- debugLog("[盒子安装] 需要选择环境");
5231
- questions.push({
5232
- type: "list",
5233
- name: "env",
5234
- message: `${nodeEmoji.get("globe_with_meridians")} 请选择环境:`,
5235
- default: "test",
5236
- choices: [
5237
- { name: `${chalk.green("测试环境")} ${chalk.gray("(test)")}`, value: "test" },
5238
- { name: `${chalk.red("正式环境")} ${chalk.gray("(prod)")}`, value: "prod" }
5239
- ]
5240
- });
5241
- } else {
5242
- debugLog(`[盒子安装] 使用命令行参数: env=${env}`);
4944
+ class InitCommand extends BaseCommand {
4945
+ command = "init";
4946
+ description = "初始化 WorkClaw 插件";
4947
+ /**
4948
+ * 实现抽象的 action 方法
4949
+ */
4950
+ async action(...args) {
4951
+ const options = args[args.length - 1];
4952
+ await this.handleInit(options);
4953
+ }
4954
+ /**
4955
+ * 获取 Commander 命令实例
4956
+ */
4957
+ getCommand() {
4958
+ const cmd = new Command(this.command);
4959
+ cmd.description(this.description).option("-s, --scenario <scenario>", "安装场景 (windows-local/mac-local/linux-local/linux-box)").option("-e, --env <env>", "环境 (test/prod)", "test").option("--phone <phone>", "手机号码").option("--user-pass <userPass>", "用户密码").option("--base-url <baseUrl>", "API 基础地址").option("--ws-url <wsUrl>", "WebSocket 地址").option("-f, --force", "强制覆盖", false).action(this.handleInit.bind(this)).addHelpText("after", `
4960
+ 示例:
4961
+ $ workclaw init --phone 13800138000 --user-pass 123456
4962
+ $ workclaw init --scenario windows-local --env test
4963
+ $ workclaw init --scenario linux-box --base-url https://api.example.com
4964
+
4965
+ 场景说明:
4966
+ windows-local - Windows 本地安装
4967
+ mac-local - macOS 本地安装
4968
+ linux-local - Linux 本地安装
4969
+ linux-box - Linux Box 环境安装`);
4970
+ return cmd;
4971
+ }
4972
+ /**
4973
+ * 处理 init 命令
4974
+ */
4975
+ async handleInit(options) {
4976
+ logger.info("WorkClaw CLI 插件初始化工具 v2.0.0");
4977
+ logger.info("=".repeat(40));
4978
+ const scenario = this.resolveScenario(options.scenario);
4979
+ const env = this.resolveEnv(options.env);
4980
+ logger.info(`安装场景: ${this.getScenarioLabel(scenario)}`);
4981
+ logger.info(`环境: ${env === "test" ? "测试环境" : "正式环境"}`);
4982
+ const config = {
4983
+ phone: options.phone,
4984
+ userPass: options.userPass,
4985
+ baseUrl: options.baseUrl,
4986
+ wsUrl: options.wsUrl,
4987
+ force: options.force,
4988
+ env,
4989
+ scenario
4990
+ };
4991
+ const installer = this.createInstaller(config);
4992
+ const result = await installer.install();
4993
+ if (!result.success) {
4994
+ process$1.exit(1);
5243
4995
  }
5244
- if (!appKey) {
5245
- debugLog("[盒子安装] 需要输入 AppKey");
5246
- questions.push({
5247
- type: "input",
5248
- name: "appKey",
5249
- message: `${nodeEmoji.get("key")} 请输入 AppKey:`,
5250
- validate: (value) => {
5251
- if (!value || value.trim() === "") {
5252
- return `${nodeEmoji.get("x")} AppKey 不能为空`;
5253
- }
5254
- return true;
5255
- }
5256
- });
5257
- } else {
5258
- debugLog("[盒子安装] 使用命令行参数: appKey");
4996
+ }
4997
+ /**
4998
+ * 解析场景
4999
+ */
5000
+ resolveScenario(scenario) {
5001
+ if (scenario) {
5002
+ const validScenarios = ["windows-local", "mac-local", "linux-local", "linux-box"];
5003
+ if (validScenarios.includes(scenario)) {
5004
+ return scenario;
5005
+ }
5006
+ logger.warn(`无效的场景: ${scenario},将使用自动检测`);
5259
5007
  }
5260
- if (!appSecret) {
5261
- debugLog("[盒子安装] 需要输入 AppSecret");
5262
- questions.push({
5263
- type: "input",
5264
- name: "appSecret",
5265
- message: `${nodeEmoji.get("key")} 请输入 AppSecret:`,
5266
- validate: (value) => {
5267
- if (!value || value.trim() === "") {
5268
- return `${nodeEmoji.get("x")} AppSecret 不能为空`;
5269
- }
5270
- return true;
5271
- }
5272
- });
5273
- } else {
5274
- debugLog("[盒子安装] 使用命令行参数: appSecret");
5008
+ return detectOS();
5009
+ }
5010
+ /**
5011
+ * 解析环境
5012
+ */
5013
+ resolveEnv(env) {
5014
+ if (env === "prod" || env === "test") {
5015
+ return env;
5275
5016
  }
5276
- if (questions.length > 0) {
5277
- debugLog(`[盒子安装] 开始交互式问答,共 ${questions.length} 个问题`);
5278
- const answers = await inquirer.prompt(questions);
5279
- debugLog("[盒子安装] 交互式问答完成");
5280
- env = env || answers.env || "test";
5281
- appKey = appKey || answers.appKey;
5282
- appSecret = appSecret || answers.appSecret;
5017
+ return "test";
5018
+ }
5019
+ /**
5020
+ * 获取场景标签
5021
+ */
5022
+ getScenarioLabel(scenario) {
5023
+ const labels = {
5024
+ "windows-local": "Windows 本地安装",
5025
+ "mac-local": "macOS 本地安装",
5026
+ "linux-local": "Linux 本地安装",
5027
+ "linux-box": "Linux Box 环境安装"
5028
+ };
5029
+ return labels[scenario];
5030
+ }
5031
+ /**
5032
+ * 创建安装器实例
5033
+ */
5034
+ createInstaller(config) {
5035
+ if (config.scenario === "linux-box") {
5036
+ return new BoxInstaller(config);
5283
5037
  }
5284
- debugLog(`[盒子安装] 最终参数: env=${env}`);
5285
- debugLog("[盒子安装] 创建 BoxInstaller 实例...");
5286
- const installer = new BoxInstaller({
5287
- appKey,
5288
- appSecret,
5289
- env
5290
- });
5291
- debugLog("[盒子安装] 开始安装...");
5292
- await installer.install();
5293
- debugLog("[盒子安装] 安装完成");
5294
- console.log(installer.getPrefixText() + boxen(
5295
- `${nodeEmoji.get("tada")} 插件初始化成功!`,
5296
- {
5297
- title: `${chalk.bold.green("初始化成功")}`,
5298
- titleAlignment: "center",
5299
- padding: { top: 1, bottom: 1, left: 5, right: 5 },
5300
- margin: 1,
5301
- borderStyle: "round",
5302
- borderColor: "green",
5303
- textAlignment: "center"
5304
- }
5305
- ));
5306
- } catch (error) {
5307
- debugLog(`[盒子安装] 发生错误: ${error.message}`);
5308
- console.error(boxen(
5309
- `${chalk.yellow(error.message)}`,
5310
- {
5311
- title: `${chalk.bold.red("初始化失败")}`,
5312
- titleAlignment: "center",
5313
- padding: { top: 1, bottom: 1, left: 5, right: 5 },
5314
- margin: 1,
5315
- borderStyle: "round",
5316
- borderColor: "red",
5317
- textAlignment: "center"
5318
- }
5319
- ));
5320
- process$1.exit(1);
5038
+ return new LocalInstaller(config);
5321
5039
  }
5322
5040
  }
5323
- function registerCommands(program2) {
5324
- program2.command("box").description("盒子设备安装(无需登录)").option("-e, --env <env>", "环境 (test/prod)").option("--app-key <appKey>", "App Key").option("--app-secret <appSecret>", "App Secret").option("--plugin-version <plugin-version>", "插件版本号(默认最新版)").option("--debug", "开启调试日志").action(createBoxCommand);
5041
+ function createInitCommandInstance(program2) {
5042
+ const initCommand = new InitCommand(program2);
5043
+ const cmd = initCommand.getCommand();
5044
+ program2.addCommand(cmd);
5325
5045
  }
5326
5046
  const __filename$1 = fileURLToPath(import.meta.url);
5327
5047
  const __dirname$1 = dirname(__filename$1);
@@ -5329,8 +5049,7 @@ const pkg = JSON.parse(readFileSync(resolve(__dirname$1, "../package.json"), "ut
5329
5049
  const program = new Command();
5330
5050
  function index() {
5331
5051
  program.name(Object.keys(pkg.bin)[0]).version(pkg.version).description(pkg.description);
5332
- registerCommands$1(program);
5333
- registerCommands(program);
5052
+ createInitCommandInstance(program);
5334
5053
  program.parse(process$1.argv);
5335
5054
  }
5336
5055
  export {