@workclaw/cli 1.0.26 → 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.
- package/README.md +119 -161
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/box/error/index.d.ts +30 -0
- package/dist/box/error/index.d.ts.map +1 -0
- package/dist/box/index.d.ts +3 -0
- package/dist/box/index.d.ts.map +1 -0
- package/dist/box/installer/index.d.ts +2 -0
- package/dist/box/installer/index.d.ts.map +1 -0
- package/dist/box/installer/installer.d.ts +16 -0
- package/dist/box/installer/installer.d.ts.map +1 -0
- package/dist/box/src/box.d.ts +3 -0
- package/dist/box/src/box.d.ts.map +1 -0
- package/dist/box/types/index.d.ts +30 -0
- package/dist/box/types/index.d.ts.map +1 -0
- package/dist/box/utils/index.d.ts +2 -0
- package/dist/box/utils/index.d.ts.map +1 -0
- package/dist/box/utils/path.d.ts +2 -0
- package/dist/box/utils/path.d.ts.map +1 -0
- package/dist/command/index.d.ts +3 -0
- package/dist/command/index.d.ts.map +1 -0
- package/dist/command/src/base-command.d.ts +12 -0
- package/dist/command/src/base-command.d.ts.map +1 -0
- package/dist/command/types/index.d.ts +14 -0
- package/dist/command/types/index.d.ts.map +1 -0
- package/dist/{index-BWLa_Wav.js → index-0P3M9kKr.js} +949 -1230
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/init/index.d.ts +6 -0
- package/dist/init/index.d.ts.map +1 -0
- package/dist/init/src/init.d.ts +39 -0
- package/dist/init/src/init.d.ts.map +1 -0
- package/dist/init/src/installer/base-installer.d.ts +36 -0
- package/dist/init/src/installer/base-installer.d.ts.map +1 -0
- package/dist/init/src/installer/box-installer.d.ts +39 -0
- package/dist/init/src/installer/box-installer.d.ts.map +1 -0
- package/dist/init/src/installer/index.d.ts +4 -0
- package/dist/init/src/installer/index.d.ts.map +1 -0
- package/dist/init/src/installer/local-installer.d.ts +27 -0
- package/dist/init/src/installer/local-installer.d.ts.map +1 -0
- package/dist/init/src/types/index.d.ts +36 -0
- package/dist/init/src/types/index.d.ts.map +1 -0
- package/dist/lib/command/base-command.d.ts +16 -0
- package/dist/lib/command/base-command.d.ts.map +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/local/apis/index.d.ts +11 -0
- package/dist/local/apis/index.d.ts.map +1 -0
- package/dist/local/error/index.d.ts +33 -0
- package/dist/local/error/index.d.ts.map +1 -0
- package/dist/local/index.d.ts +3 -0
- package/dist/local/index.d.ts.map +1 -0
- package/dist/local/installer/index.d.ts +2 -0
- package/dist/local/installer/index.d.ts.map +1 -0
- package/dist/local/installer/installer.d.ts +16 -0
- package/dist/local/installer/installer.d.ts.map +1 -0
- package/dist/local/src/local.d.ts +3 -0
- package/dist/local/src/local.d.ts.map +1 -0
- package/dist/local/types/index.d.ts +41 -0
- package/dist/local/types/index.d.ts.map +1 -0
- package/dist/local/utils/crypto.d.ts +2 -0
- package/dist/local/utils/crypto.d.ts.map +1 -0
- package/dist/local/utils/index.d.ts +4 -0
- package/dist/local/utils/index.d.ts.map +1 -0
- package/dist/local/utils/path.d.ts +9 -0
- package/dist/local/utils/path.d.ts.map +1 -0
- package/dist/shared/config/index.d.ts +48 -0
- package/dist/shared/config/index.d.ts.map +1 -0
- package/dist/shared/utils/debug.d.ts +4 -0
- package/dist/shared/utils/debug.d.ts.map +1 -0
- package/dist/shared/utils/index.d.ts +4 -0
- package/dist/shared/utils/index.d.ts.map +1 -0
- package/dist/shared/utils/logger.d.ts +14 -0
- package/dist/shared/utils/logger.d.ts.map +1 -0
- package/dist/utils/config-default.d.ts +28 -0
- package/dist/utils/config-default.d.ts.map +1 -0
- package/dist/utils/config.d.ts +21 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/crypto.d.ts +13 -0
- package/dist/utils/crypto.d.ts.map +1 -0
- package/dist/utils/error.d.ts +34 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/http.d.ts +27 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/path.d.ts +23 -0
- package/dist/utils/path.d.ts.map +1 -0
- 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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
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
|
-
|
|
4101
|
-
|
|
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
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4118
|
-
|
|
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.
|
|
4192
|
+
this.logger = logger2;
|
|
4121
4193
|
}
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4194
|
+
/**
|
|
4195
|
+
* 获取 API 基础地址
|
|
4196
|
+
*/
|
|
4197
|
+
getApiBaseUrl() {
|
|
4198
|
+
if (this.config.baseUrl) {
|
|
4199
|
+
return this.config.baseUrl;
|
|
4127
4200
|
}
|
|
4128
|
-
|
|
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
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4207
|
+
/**
|
|
4208
|
+
* 获取 WebSocket 地址
|
|
4209
|
+
*/
|
|
4210
|
+
getWsUrl() {
|
|
4211
|
+
if (this.config.wsUrl) {
|
|
4212
|
+
return this.config.wsUrl;
|
|
4134
4213
|
}
|
|
4135
|
-
|
|
4136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
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
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
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
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4303
|
+
/**
|
|
4304
|
+
* 获取 Access Token
|
|
4305
|
+
*/
|
|
4306
|
+
async getAccessToken() {
|
|
4206
4307
|
try {
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
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
|
-
|
|
4217
|
-
`);
|
|
4218
|
-
debugLog("[清理旧版本] 未发现旧版本");
|
|
4316
|
+
return null;
|
|
4219
4317
|
}
|
|
4220
4318
|
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
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
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4332
|
+
return null;
|
|
4310
4333
|
}
|
|
4311
4334
|
}
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
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
|
|
4344
|
+
const safeConfig = existingConfig;
|
|
4345
|
+
const existingChannels = safeConfig.channels;
|
|
4329
4346
|
const newConfig = {
|
|
4330
|
-
|
|
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
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
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
|
-
|
|
4481
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4770
|
-
|
|
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.
|
|
4774
|
-
throw new
|
|
4375
|
+
if (!this.config.userPass) {
|
|
4376
|
+
throw new AppError(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
|
|
4775
4377
|
}
|
|
4776
|
-
debugLog("[验证配置] 配置检查通过");
|
|
4777
4378
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
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
|
-
|
|
4796
|
-
this.
|
|
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
|
-
|
|
4801
|
-
return
|
|
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
|
-
|
|
4396
|
+
logger.info("开始安装 WorkClaw 插件(NPM 安装模式)");
|
|
4397
|
+
logger.info("=".repeat(40));
|
|
4806
4398
|
this.validateConfig();
|
|
4807
|
-
this.
|
|
4808
|
-
|
|
4809
|
-
await this.
|
|
4810
|
-
this.
|
|
4811
|
-
|
|
4812
|
-
|
|
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
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
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
|
|
4824
|
-
|
|
4825
|
-
|
|
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(
|
|
4841
|
-
|
|
4419
|
+
await fs.access(targetDir);
|
|
4420
|
+
spinner.text = "发现旧版本,正在清理...";
|
|
4421
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
4422
|
+
spinner.succeed("旧版本清理完成");
|
|
4842
4423
|
} catch {
|
|
4843
|
-
|
|
4424
|
+
spinner.succeed("未发现旧版本");
|
|
4844
4425
|
}
|
|
4845
4426
|
}
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
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
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
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
|
|
4886
|
-
|
|
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(
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
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
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
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
|
-
}
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
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
|
|
4940
|
-
|
|
4941
|
-
|
|
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(
|
|
4533
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
4946
4534
|
originalConfig = JSON.parse(content);
|
|
4947
|
-
|
|
4535
|
+
logger.info("已读取现有配置文件");
|
|
4948
4536
|
} catch {
|
|
4949
|
-
|
|
4537
|
+
logger.info("创建新配置文件");
|
|
4950
4538
|
}
|
|
4951
|
-
const
|
|
4952
|
-
|
|
4953
|
-
// diagnostics: 诊断配置
|
|
4539
|
+
const dynamicConfig = {
|
|
4540
|
+
// 诊断配置
|
|
4954
4541
|
diagnostics: {
|
|
4955
4542
|
// 启用诊断功能
|
|
4956
4543
|
enabled: true,
|
|
4957
|
-
//
|
|
4544
|
+
// 诊断标志列表
|
|
4958
4545
|
flags: ["*"]
|
|
4959
4546
|
},
|
|
4960
|
-
//
|
|
4547
|
+
// 浏览器配置
|
|
4961
4548
|
browser: {
|
|
4962
|
-
//
|
|
4549
|
+
// 禁用内置浏览器
|
|
4963
4550
|
enabled: false
|
|
4964
4551
|
},
|
|
4965
|
-
//
|
|
4552
|
+
// 模型配置
|
|
4966
4553
|
models: {
|
|
4967
|
-
// 配置合并模式:
|
|
4554
|
+
// 配置合并模式:merge 会将新配置与现有配置合并
|
|
4968
4555
|
mode: "merge",
|
|
4556
|
+
// 模型提供商配置
|
|
4969
4557
|
providers: {
|
|
4558
|
+
// SiliconFlow MiniMax 提供商
|
|
4970
4559
|
"siliconflow-minimax": {
|
|
4971
|
-
//
|
|
4972
|
-
baseUrl:
|
|
4973
|
-
// API
|
|
4974
|
-
apiKey:
|
|
4975
|
-
// API
|
|
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
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
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
|
-
//
|
|
4601
|
+
// Agent 配置
|
|
4999
4602
|
agents: {
|
|
4603
|
+
// 默认 Agent 设置
|
|
5000
4604
|
defaults: {
|
|
5001
|
-
//
|
|
5002
|
-
model: {
|
|
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:
|
|
5011
|
-
//
|
|
5012
|
-
compaction: {
|
|
5013
|
-
|
|
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: {
|
|
4629
|
+
// 子 Agent 配置
|
|
4630
|
+
subagents: {
|
|
4631
|
+
// 子 Agent 最大并发数
|
|
4632
|
+
maxConcurrent: 8
|
|
4633
|
+
}
|
|
5019
4634
|
},
|
|
5020
|
-
//
|
|
5021
|
-
list: [
|
|
4635
|
+
// Agent 列表
|
|
4636
|
+
list: [
|
|
4637
|
+
{
|
|
4638
|
+
// Agent ID
|
|
4639
|
+
id: "main",
|
|
4640
|
+
// Agent 工作区路径
|
|
4641
|
+
workspace: workspacePath
|
|
4642
|
+
}
|
|
4643
|
+
]
|
|
5022
4644
|
},
|
|
5023
|
-
//
|
|
4645
|
+
// 工具权限配置
|
|
5024
4646
|
tools: {
|
|
5025
|
-
//
|
|
5026
|
-
deny: [
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
//
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
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
|
-
//
|
|
5037
|
-
bindings: [
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
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
|
-
//
|
|
4699
|
+
// 命令配置
|
|
5049
4700
|
commands: {
|
|
5050
|
-
//
|
|
4701
|
+
// 原生命令处理模式
|
|
5051
4702
|
native: "auto",
|
|
5052
|
-
//
|
|
4703
|
+
// 技能命令处理模式
|
|
5053
4704
|
nativeSkills: "auto",
|
|
5054
|
-
//
|
|
4705
|
+
// 允许使用restart命令
|
|
5055
4706
|
restart: true,
|
|
5056
|
-
//
|
|
4707
|
+
// 所有者信息显示方式
|
|
5057
4708
|
ownerDisplay: "raw"
|
|
5058
4709
|
},
|
|
5059
|
-
//
|
|
4710
|
+
// 会话配置
|
|
5060
4711
|
session: {
|
|
5061
|
-
//
|
|
4712
|
+
// 会话隔离策略
|
|
5062
4713
|
dmScope: "per-account-channel-peer"
|
|
5063
4714
|
},
|
|
5064
|
-
//
|
|
4715
|
+
// 钩子配置
|
|
5065
4716
|
hooks: {
|
|
5066
4717
|
internal: {
|
|
5067
|
-
//
|
|
4718
|
+
// 启用/禁用内部钩子
|
|
5068
4719
|
enabled: true,
|
|
5069
4720
|
entries: {
|
|
5070
|
-
//
|
|
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
|
-
//
|
|
4732
|
+
// 渠道配置
|
|
5082
4733
|
channels: {
|
|
4734
|
+
// WorkClaw 插件渠道
|
|
5083
4735
|
"openclaw-workclaw": {
|
|
5084
|
-
//
|
|
4736
|
+
// 是否启用此渠道
|
|
5085
4737
|
enabled: true,
|
|
5086
|
-
//
|
|
4738
|
+
// 连接模式:使用 WebSocket 连接
|
|
5087
4739
|
connectionMode: "websocket",
|
|
5088
|
-
// WebSocket
|
|
4740
|
+
// WebSocket 连接策略:每个 appKey 一个连接
|
|
5089
4741
|
wsConnectionStrategy: "per-appKey",
|
|
5090
|
-
//
|
|
5091
|
-
appKey:
|
|
5092
|
-
//
|
|
5093
|
-
appSecret:
|
|
5094
|
-
// API
|
|
5095
|
-
baseUrl: this.config.baseUrl ||
|
|
5096
|
-
// WebSocket
|
|
5097
|
-
websocketUrl: this.config.wsUrl ||
|
|
5098
|
-
//
|
|
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
|
-
//
|
|
4752
|
+
// 是否允许原始 JSON 载荷
|
|
5101
4753
|
allowRawJsonPayload: true,
|
|
5102
|
-
//
|
|
5103
|
-
userId: "",
|
|
4754
|
+
// 账号配置
|
|
5104
4755
|
accounts: {
|
|
5105
|
-
//
|
|
5106
|
-
default: {
|
|
4756
|
+
// 默认账号
|
|
4757
|
+
default: {
|
|
4758
|
+
// 是否启用默认账号
|
|
4759
|
+
enabled: true,
|
|
4760
|
+
// 代理 ID
|
|
4761
|
+
agentId: boundConfig.agentId
|
|
4762
|
+
}
|
|
5107
4763
|
}
|
|
5108
4764
|
}
|
|
5109
4765
|
},
|
|
5110
|
-
//
|
|
4766
|
+
// Gateway 节点配置
|
|
5111
4767
|
gateway: {
|
|
5112
|
-
//
|
|
4768
|
+
// 运行模式
|
|
5113
4769
|
mode: "local",
|
|
5114
|
-
//
|
|
4770
|
+
// 监听端口
|
|
5115
4771
|
port: 18789,
|
|
5116
|
-
//
|
|
4772
|
+
// 绑定地址(仅监听本地)
|
|
5117
4773
|
bind: "loopback",
|
|
5118
|
-
//
|
|
4774
|
+
// 认证配置(为空字符串会使用原始配置中的值)
|
|
5119
4775
|
auth: {
|
|
5120
|
-
|
|
4776
|
+
// 认证模式
|
|
4777
|
+
mode: "token",
|
|
4778
|
+
// 认证令牌(为空会使用原始配置中的值)
|
|
4779
|
+
token: ""
|
|
5121
4780
|
},
|
|
5122
4781
|
// Tailscale 配置
|
|
5123
|
-
tailscale: {
|
|
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: [
|
|
4809
|
+
// 允许的来源(本地开发)
|
|
4810
|
+
allowedOrigins: [
|
|
4811
|
+
"http://localhost:18789",
|
|
4812
|
+
"http://127.0.0.1:18789"
|
|
4813
|
+
]
|
|
5146
4814
|
}
|
|
5147
4815
|
},
|
|
5148
|
-
//
|
|
4816
|
+
// 技能配置
|
|
5149
4817
|
skills: {
|
|
5150
|
-
|
|
5151
|
-
|
|
4818
|
+
load: {
|
|
4819
|
+
// 监听技能文件变化
|
|
4820
|
+
watch: true
|
|
4821
|
+
}
|
|
5152
4822
|
},
|
|
5153
|
-
//
|
|
4823
|
+
// 日志配置
|
|
5154
4824
|
logging: {
|
|
5155
|
-
//
|
|
4825
|
+
// 日志级别
|
|
5156
4826
|
level: "info",
|
|
5157
4827
|
// 控制台日志级别
|
|
5158
4828
|
consoleLevel: "info",
|
|
5159
|
-
//
|
|
4829
|
+
// 控制台样式
|
|
5160
4830
|
consoleStyle: "pretty",
|
|
5161
|
-
//
|
|
4831
|
+
// 敏感信息脱敏
|
|
5162
4832
|
redactSensitive: "off",
|
|
5163
|
-
//
|
|
4833
|
+
// 日志文件路径(空表示使用系统默认)
|
|
5164
4834
|
file: "",
|
|
5165
|
-
//
|
|
4835
|
+
// 脱敏模式
|
|
5166
4836
|
redactPatterns: ["sk-.*"]
|
|
5167
4837
|
},
|
|
5168
|
-
//
|
|
4838
|
+
// 插件配置
|
|
5169
4839
|
plugins: {
|
|
5170
4840
|
// 允许的插件列表
|
|
5171
|
-
allow: [
|
|
5172
|
-
//
|
|
4841
|
+
allow: ["openclaw-workclaw"],
|
|
4842
|
+
// 插件安装配置
|
|
5173
4843
|
installs: {
|
|
5174
|
-
|
|
5175
|
-
|
|
4844
|
+
// WorkClaw 插件安装信息
|
|
4845
|
+
"openclaw-workclaw": {
|
|
4846
|
+
// 插件来源类型:npm 表示从 NPM 安装
|
|
5176
4847
|
source: "npm",
|
|
5177
|
-
//
|
|
5178
|
-
|
|
5179
|
-
//
|
|
5180
|
-
installPath:
|
|
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
|
-
|
|
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 =
|
|
5194
|
-
|
|
5195
|
-
await
|
|
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
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
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
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
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
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
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
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
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
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
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
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
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
|
-
|
|
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
|
|
5324
|
-
|
|
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
|
-
|
|
5333
|
-
registerCommands(program);
|
|
5052
|
+
createInitCommandInstance(program);
|
|
5334
5053
|
program.parse(process$1.argv);
|
|
5335
5054
|
}
|
|
5336
5055
|
export {
|