@workclaw/cli 1.0.1 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -119
- package/dist/index-RP7oF6iQ.js +5032 -0
- package/dist/index.js +1 -1
- package/package.json +7 -5
- package/dist/bin/cli.d.ts +0 -3
- package/dist/bin/cli.d.ts.map +0 -1
- package/dist/index-M9uYCvsL.js +0 -1175
- package/dist/index.d.ts +0 -1
- package/dist/init/index.d.ts +0 -6
- package/dist/init/index.d.ts.map +0 -1
- package/dist/init/src/init.d.ts +0 -39
- package/dist/init/src/init.d.ts.map +0 -1
- package/dist/init/src/installer/base-installer.d.ts +0 -36
- package/dist/init/src/installer/base-installer.d.ts.map +0 -1
- package/dist/init/src/installer/box-installer.d.ts +0 -39
- package/dist/init/src/installer/box-installer.d.ts.map +0 -1
- package/dist/init/src/installer/index.d.ts +0 -4
- package/dist/init/src/installer/index.d.ts.map +0 -1
- package/dist/init/src/installer/local-installer.d.ts +0 -27
- package/dist/init/src/installer/local-installer.d.ts.map +0 -1
- package/dist/init/src/types/index.d.ts +0 -36
- package/dist/init/src/types/index.d.ts.map +0 -1
- package/dist/lib/command/base-command.d.ts +0 -16
- package/dist/lib/command/base-command.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -2
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/utils/config-default.d.ts +0 -28
- package/dist/utils/config-default.d.ts.map +0 -1
- package/dist/utils/config.d.ts +0 -21
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/crypto.d.ts +0 -13
- package/dist/utils/crypto.d.ts.map +0 -1
- package/dist/utils/error.d.ts +0 -34
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/http.d.ts +0 -27
- package/dist/utils/http.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -16
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/path.d.ts +0 -23
- package/dist/utils/path.d.ts.map +0 -1
package/dist/index-M9uYCvsL.js
DELETED
|
@@ -1,1175 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import path, { resolve, dirname } from "node:path";
|
|
3
|
-
import process from "node:process";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { Command } from "commander";
|
|
6
|
-
import fs from "node:fs/promises";
|
|
7
|
-
import chalk from "chalk";
|
|
8
|
-
import ora from "ora";
|
|
9
|
-
import JSEncrypt from "jsencrypt";
|
|
10
|
-
import axios from "axios";
|
|
11
|
-
import os from "node:os";
|
|
12
|
-
import { execSync } from "node:child_process";
|
|
13
|
-
class BaseCommand {
|
|
14
|
-
program;
|
|
15
|
-
options = [];
|
|
16
|
-
constructor(program2) {
|
|
17
|
-
this.program = program2;
|
|
18
|
-
}
|
|
19
|
-
init() {
|
|
20
|
-
const cmd = this.program.command(this.command);
|
|
21
|
-
cmd.description(this.description);
|
|
22
|
-
this.options.forEach(({ name, description }) => {
|
|
23
|
-
cmd.option(name, description);
|
|
24
|
-
});
|
|
25
|
-
cmd.action(this.action.bind(this));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function createLogger() {
|
|
29
|
-
return {
|
|
30
|
-
info(message) {
|
|
31
|
-
console.log(chalk.blue("•"), message);
|
|
32
|
-
},
|
|
33
|
-
success(message) {
|
|
34
|
-
console.log(chalk.green("✓"), message);
|
|
35
|
-
},
|
|
36
|
-
warn(message) {
|
|
37
|
-
console.log(chalk.yellow("!"), message);
|
|
38
|
-
},
|
|
39
|
-
error(message) {
|
|
40
|
-
console.log(chalk.red("✗"), message);
|
|
41
|
-
},
|
|
42
|
-
step(message) {
|
|
43
|
-
console.log(chalk.cyan("›"), chalk.cyan(message));
|
|
44
|
-
},
|
|
45
|
-
start(message) {
|
|
46
|
-
return ora({
|
|
47
|
-
text: message,
|
|
48
|
-
color: "cyan"
|
|
49
|
-
}).start();
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const logger = createLogger();
|
|
54
|
-
async function readConfig(configPath) {
|
|
55
|
-
try {
|
|
56
|
-
const content = await fs.readFile(configPath, "utf-8");
|
|
57
|
-
return JSON.parse(content);
|
|
58
|
-
} catch {
|
|
59
|
-
logger.warn(`配置文件不存在或读取失败: ${configPath}`);
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
async function writeConfig(configPath, config) {
|
|
64
|
-
const dir = path.dirname(configPath);
|
|
65
|
-
await fs.mkdir(dir, { recursive: true });
|
|
66
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
67
|
-
}
|
|
68
|
-
const defaultConfig = {
|
|
69
|
-
modelUrl: {
|
|
70
|
-
test: "http://172.168.80.30:30005/v1",
|
|
71
|
-
prod: "https://maas-api.workbrain.cn/v1/"
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
const openApisUrl = {
|
|
75
|
-
test: "http://172.168.80.30:32005/open-apis",
|
|
76
|
-
prod: "https://workbrain.cn/backend-api/open-apis"
|
|
77
|
-
};
|
|
78
|
-
const apiBaseUrl = {
|
|
79
|
-
test: "https://test-api.workbrain.cn/backend-api/",
|
|
80
|
-
prod: "https://workbrain.cn/backend-api/"
|
|
81
|
-
};
|
|
82
|
-
function getApiBaseUrl(env) {
|
|
83
|
-
return env === "test" ? apiBaseUrl.test : apiBaseUrl.prod;
|
|
84
|
-
}
|
|
85
|
-
function getWsUrl(env) {
|
|
86
|
-
return env === "test" ? openApisUrl.test : openApisUrl.prod;
|
|
87
|
-
}
|
|
88
|
-
function getOpenApisUrl(env) {
|
|
89
|
-
return env === "test" ? openApisUrl.test : openApisUrl.prod;
|
|
90
|
-
}
|
|
91
|
-
function getModelUrl(env) {
|
|
92
|
-
return env === "test" ? defaultConfig.modelUrl.test : defaultConfig.modelUrl.prod;
|
|
93
|
-
}
|
|
94
|
-
const PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBSYtNb6neLrrwsuPBsIFjpZBrffdkA8bpp2S35o2TCdhfdxS0nc4pkv9cJLkUvFa+gdQ5nLifnK9B1XoVbIQwY212QAftTDbl77bcHu7GAbv2TZr9pelSeUm1SrtMK5HDr/LzxTutGr4DovVHiDgEn45GQ1X5U+zC0Jp4Awn6ZwIDAQAB";
|
|
95
|
-
function rsaEncrypt(txt) {
|
|
96
|
-
try {
|
|
97
|
-
const encrypt = new JSEncrypt();
|
|
98
|
-
encrypt.setPublicKey(PUBLIC_KEY);
|
|
99
|
-
const encrypted = encrypt.encrypt(txt);
|
|
100
|
-
if (!encrypted) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
return encrypted;
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error("RSA 加密失败:", error);
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const ERROR_CODES = {
|
|
110
|
-
// 环境检查错误 (1000-1999)
|
|
111
|
-
NODE_VERSION_LOW: 1001,
|
|
112
|
-
NODE_NOT_FOUND: 1002,
|
|
113
|
-
NPM_NOT_FOUND: 1003,
|
|
114
|
-
// 配置错误 (2000-2999)
|
|
115
|
-
PHONE_REQUIRED: 2001,
|
|
116
|
-
USER_PASS_REQUIRED: 2002,
|
|
117
|
-
MISSING_AGENT_ID: 2003,
|
|
118
|
-
MISSING_APP_KEY: 2004,
|
|
119
|
-
MISSING_APP_SECRET: 2005,
|
|
120
|
-
// 网络错误 (3000-3999)
|
|
121
|
-
LOGIN_FAILED: 3001,
|
|
122
|
-
GET_BOUND_CONFIG_FAILED: 3002,
|
|
123
|
-
FETCH_CONFIG_FAILED: 3003,
|
|
124
|
-
// 文件系统错误 (4000-4999)
|
|
125
|
-
CONFIG_WRITE_FAILED: 4001,
|
|
126
|
-
FILE_COPY_FAILED: 4002,
|
|
127
|
-
NPM_INSTALL_FAILED: 4003,
|
|
128
|
-
// 加密错误 (5000-5999)
|
|
129
|
-
PASSWORD_ENCRYPT_FAILED: 5001
|
|
130
|
-
};
|
|
131
|
-
class AppError extends Error {
|
|
132
|
-
code;
|
|
133
|
-
details;
|
|
134
|
-
constructor(code, message, details) {
|
|
135
|
-
super(message);
|
|
136
|
-
this.name = "AppError";
|
|
137
|
-
this.code = code;
|
|
138
|
-
this.details = details;
|
|
139
|
-
}
|
|
140
|
-
toJSON() {
|
|
141
|
-
return {
|
|
142
|
-
code: this.code,
|
|
143
|
-
message: this.message,
|
|
144
|
-
details: this.details
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
toString() {
|
|
148
|
-
return `[${this.code}] ${this.message}`;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function formatError(error) {
|
|
152
|
-
if (error instanceof AppError) {
|
|
153
|
-
return error.toString();
|
|
154
|
-
}
|
|
155
|
-
if (error instanceof Error) {
|
|
156
|
-
return error.message;
|
|
157
|
-
}
|
|
158
|
-
return String(error);
|
|
159
|
-
}
|
|
160
|
-
function createHttpClient(baseURL) {
|
|
161
|
-
return axios.create({
|
|
162
|
-
baseURL,
|
|
163
|
-
timeout: 3e4,
|
|
164
|
-
headers: {
|
|
165
|
-
"Content-Type": "application/json"
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
function printRequestLog(log) {
|
|
170
|
-
console.log();
|
|
171
|
-
console.log(chalk.cyan("=".repeat(50)));
|
|
172
|
-
console.log(chalk.bold(`HTTP ${log.method} 请求`));
|
|
173
|
-
console.log(chalk.cyan("=".repeat(50)));
|
|
174
|
-
console.log(chalk.blue("[请求地址]"), log.url);
|
|
175
|
-
if (log.headers) {
|
|
176
|
-
const headersStr = JSON.stringify(log.headers, null, 2);
|
|
177
|
-
console.log(chalk.blue("[请求头]"), headersStr);
|
|
178
|
-
}
|
|
179
|
-
if (log.data) {
|
|
180
|
-
const dataStr = JSON.stringify(log.data, null, 2);
|
|
181
|
-
console.log(chalk.blue("[请求参数]"), dataStr);
|
|
182
|
-
}
|
|
183
|
-
if (log.duration) {
|
|
184
|
-
console.log(chalk.yellow("[耗时]"), `${log.duration}ms`);
|
|
185
|
-
}
|
|
186
|
-
if (log.error) {
|
|
187
|
-
console.log(chalk.red("[错误]"), log.error);
|
|
188
|
-
}
|
|
189
|
-
console.log(chalk.cyan("=".repeat(50)));
|
|
190
|
-
console.log();
|
|
191
|
-
}
|
|
192
|
-
function printResponseLog(log) {
|
|
193
|
-
if (log.response) {
|
|
194
|
-
console.log();
|
|
195
|
-
console.log(chalk.green("=".repeat(50)));
|
|
196
|
-
console.log(chalk.bold(`HTTP ${log.method} 响应`));
|
|
197
|
-
console.log(chalk.green("=".repeat(50)));
|
|
198
|
-
console.log(chalk.green("[状态码]"), log.response.status);
|
|
199
|
-
const responseStr = JSON.stringify(log.response.data, null, 2);
|
|
200
|
-
console.log(chalk.green("[响应数据]"), responseStr);
|
|
201
|
-
console.log(chalk.green("=".repeat(50)));
|
|
202
|
-
console.log();
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
async function httpGet(url, config) {
|
|
206
|
-
const startTime = Date.now();
|
|
207
|
-
printRequestLog({
|
|
208
|
-
method: "GET",
|
|
209
|
-
url,
|
|
210
|
-
headers: config?.headers
|
|
211
|
-
});
|
|
212
|
-
try {
|
|
213
|
-
const client = createHttpClient();
|
|
214
|
-
const response = await client.get(url, config);
|
|
215
|
-
const duration = Date.now() - startTime;
|
|
216
|
-
const result = {
|
|
217
|
-
status: response.status,
|
|
218
|
-
data: response.data
|
|
219
|
-
};
|
|
220
|
-
printResponseLog({
|
|
221
|
-
method: "GET",
|
|
222
|
-
url,
|
|
223
|
-
headers: config?.headers,
|
|
224
|
-
response: result,
|
|
225
|
-
duration
|
|
226
|
-
});
|
|
227
|
-
return result;
|
|
228
|
-
} catch (error) {
|
|
229
|
-
const duration = Date.now() - startTime;
|
|
230
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
231
|
-
printRequestLog({
|
|
232
|
-
method: "GET",
|
|
233
|
-
url,
|
|
234
|
-
headers: config?.headers,
|
|
235
|
-
error: errorMessage,
|
|
236
|
-
duration
|
|
237
|
-
});
|
|
238
|
-
throw error;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function httpPost(url, data, config) {
|
|
242
|
-
const startTime = Date.now();
|
|
243
|
-
printRequestLog({
|
|
244
|
-
method: "POST",
|
|
245
|
-
url,
|
|
246
|
-
headers: config?.headers,
|
|
247
|
-
data
|
|
248
|
-
});
|
|
249
|
-
try {
|
|
250
|
-
const client = createHttpClient();
|
|
251
|
-
const response = await client.post(url, data, config);
|
|
252
|
-
const duration = Date.now() - startTime;
|
|
253
|
-
const result = {
|
|
254
|
-
status: response.status,
|
|
255
|
-
data: response.data
|
|
256
|
-
};
|
|
257
|
-
printResponseLog({
|
|
258
|
-
method: "POST",
|
|
259
|
-
url,
|
|
260
|
-
headers: config?.headers,
|
|
261
|
-
data,
|
|
262
|
-
response: result,
|
|
263
|
-
duration
|
|
264
|
-
});
|
|
265
|
-
return result;
|
|
266
|
-
} catch (error) {
|
|
267
|
-
const duration = Date.now() - startTime;
|
|
268
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
269
|
-
printRequestLog({
|
|
270
|
-
method: "POST",
|
|
271
|
-
url,
|
|
272
|
-
headers: config?.headers,
|
|
273
|
-
data,
|
|
274
|
-
error: errorMessage,
|
|
275
|
-
duration
|
|
276
|
-
});
|
|
277
|
-
throw error;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function getPathInfo(_scenario) {
|
|
281
|
-
const homeDir = os.homedir();
|
|
282
|
-
const configDir = path.join(homeDir, ".openclaw");
|
|
283
|
-
return {
|
|
284
|
-
configDir,
|
|
285
|
-
configFile: path.join(configDir, "openclaw.json"),
|
|
286
|
-
pluginDir: path.join(configDir, "plugins")
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
function detectOS() {
|
|
290
|
-
const platform = os.platform();
|
|
291
|
-
switch (platform) {
|
|
292
|
-
case "win32":
|
|
293
|
-
return "windows-local";
|
|
294
|
-
case "darwin":
|
|
295
|
-
return "mac-local";
|
|
296
|
-
case "linux":
|
|
297
|
-
return "linux-local";
|
|
298
|
-
default:
|
|
299
|
-
return "linux-local";
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
async function ensureDir(dirPath) {
|
|
303
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
304
|
-
}
|
|
305
|
-
class BaseInstaller {
|
|
306
|
-
config;
|
|
307
|
-
logger;
|
|
308
|
-
constructor(config, logger2) {
|
|
309
|
-
this.config = config;
|
|
310
|
-
this.logger = logger2;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* 获取 API 基础地址
|
|
314
|
-
*/
|
|
315
|
-
getApiBaseUrl() {
|
|
316
|
-
if (this.config.baseUrl) {
|
|
317
|
-
return this.config.baseUrl;
|
|
318
|
-
}
|
|
319
|
-
const defaultUrls = {
|
|
320
|
-
test: "https://test-api.workbrain.cn/backend-api/",
|
|
321
|
-
prod: "https://workbrain.cn/backend-api/"
|
|
322
|
-
};
|
|
323
|
-
return defaultUrls[this.config.env];
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* 获取 WebSocket 地址
|
|
327
|
-
*/
|
|
328
|
-
getWsUrl() {
|
|
329
|
-
if (this.config.wsUrl) {
|
|
330
|
-
return this.config.wsUrl;
|
|
331
|
-
}
|
|
332
|
-
const defaultUrls = {
|
|
333
|
-
test: "wss://test-api.workbrain.cn/backend-api/open-apis/connect/",
|
|
334
|
-
prod: "wss://workbrain.cn/backend-api/open-apis/connect/"
|
|
335
|
-
};
|
|
336
|
-
return defaultUrls[this.config.env];
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
class BoxInstaller extends BaseInstaller {
|
|
340
|
-
scenario;
|
|
341
|
-
constructor(config) {
|
|
342
|
-
super(config, logger);
|
|
343
|
-
this.scenario = config.scenario;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* 验证配置
|
|
347
|
-
*/
|
|
348
|
-
validateConfig() {
|
|
349
|
-
if (!this.config.appKey || !this.config.appSecret) {
|
|
350
|
-
logger.error("缺少必填参数: appKey 和 appSecret");
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* 获取配置路径
|
|
357
|
-
*/
|
|
358
|
-
getConfigPath() {
|
|
359
|
-
const paths = getPathInfo(this.scenario);
|
|
360
|
-
return paths.configFile;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* 获取场景类型
|
|
364
|
-
*/
|
|
365
|
-
getScenario() {
|
|
366
|
-
return this.scenario;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* 执行安装
|
|
370
|
-
*/
|
|
371
|
-
async install() {
|
|
372
|
-
const spinner = logger.start("开始安装 (Box 环境)...");
|
|
373
|
-
try {
|
|
374
|
-
if (!this.validateConfig()) {
|
|
375
|
-
spinner.fail("配置验证失败");
|
|
376
|
-
return {
|
|
377
|
-
success: false,
|
|
378
|
-
error: "配置验证失败"
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
spinner.text = "获取 Access Token...";
|
|
382
|
-
const token = await this.getAccessToken();
|
|
383
|
-
if (!token) {
|
|
384
|
-
spinner.fail("获取 Access Token 失败");
|
|
385
|
-
return {
|
|
386
|
-
success: false,
|
|
387
|
-
error: "获取 Access Token 失败"
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
spinner.text = "获取 Agent 配置...";
|
|
391
|
-
const agentConfig = await this.getAgentConfig(token);
|
|
392
|
-
if (!agentConfig) {
|
|
393
|
-
spinner.fail("获取 Agent 配置失败");
|
|
394
|
-
return {
|
|
395
|
-
success: false,
|
|
396
|
-
error: "获取 Agent 配置失败"
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
spinner.text = "创建配置目录...";
|
|
400
|
-
const paths = getPathInfo(this.scenario);
|
|
401
|
-
await ensureDir(paths.configDir);
|
|
402
|
-
spinner.text = "写入配置文件...";
|
|
403
|
-
await this.writeConfigFile(token, agentConfig);
|
|
404
|
-
spinner.succeed("安装成功 (Box 环境)!");
|
|
405
|
-
logger.success(`配置文件已写入: ${this.getConfigPath()}`);
|
|
406
|
-
logger.info(`环境: ${this.config.env === "test" ? "测试环境" : "正式环境"}`);
|
|
407
|
-
return {
|
|
408
|
-
success: true,
|
|
409
|
-
message: "安装成功 (Box 环境)"
|
|
410
|
-
};
|
|
411
|
-
} catch (error) {
|
|
412
|
-
spinner.fail("安装失败");
|
|
413
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
414
|
-
logger.error(`安装失败: ${errorMessage}`);
|
|
415
|
-
return {
|
|
416
|
-
success: false,
|
|
417
|
-
error: errorMessage
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* 获取 Access Token
|
|
423
|
-
*/
|
|
424
|
-
async getAccessToken() {
|
|
425
|
-
try {
|
|
426
|
-
const client = createHttpClient(this.getApiBaseUrl());
|
|
427
|
-
const response = await client.post("/v1/auth/token", {
|
|
428
|
-
appKey: this.config.appKey,
|
|
429
|
-
appSecret: this.config.appSecret
|
|
430
|
-
});
|
|
431
|
-
const data = response.data;
|
|
432
|
-
return data.accessToken || null;
|
|
433
|
-
} catch {
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* 获取 Agent 配置
|
|
439
|
-
*/
|
|
440
|
-
async getAgentConfig(token) {
|
|
441
|
-
try {
|
|
442
|
-
const client = createHttpClient(this.getApiBaseUrl());
|
|
443
|
-
const response = await client.get("/v1/agent/config", {
|
|
444
|
-
headers: {
|
|
445
|
-
Authorization: `Bearer ${token}`
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
return response.data;
|
|
449
|
-
} catch {
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* 写入配置文件
|
|
455
|
-
*/
|
|
456
|
-
async writeConfigFile(token, agentConfig) {
|
|
457
|
-
const configPath = this.getConfigPath();
|
|
458
|
-
let existingConfig = await readConfig(configPath);
|
|
459
|
-
if (!existingConfig || typeof existingConfig !== "object") {
|
|
460
|
-
existingConfig = {};
|
|
461
|
-
}
|
|
462
|
-
const safeConfig = existingConfig;
|
|
463
|
-
const existingChannels = safeConfig.channels;
|
|
464
|
-
const newConfig = {
|
|
465
|
-
...safeConfig,
|
|
466
|
-
channels: {
|
|
467
|
-
...existingChannels || {},
|
|
468
|
-
"openclaw-workclaw": {
|
|
469
|
-
userId: agentConfig.userId,
|
|
470
|
-
apiKey: token,
|
|
471
|
-
baseUrl: this.getApiBaseUrl(),
|
|
472
|
-
wsUrl: this.getWsUrl(),
|
|
473
|
-
accounts: {
|
|
474
|
-
default: {
|
|
475
|
-
agentId: agentConfig.agentId
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
await writeConfig(configPath, newConfig);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
const PLUGIN_PACKAGE_NAME = "@workclaw/openclaw-workclaw";
|
|
485
|
-
class LocalInstaller extends BaseInstaller {
|
|
486
|
-
constructor(config) {
|
|
487
|
-
super(config, logger);
|
|
488
|
-
}
|
|
489
|
-
validateConfig() {
|
|
490
|
-
if (!this.config.phone) {
|
|
491
|
-
throw new AppError(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
|
|
492
|
-
}
|
|
493
|
-
if (!this.config.userPass) {
|
|
494
|
-
throw new AppError(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
getConfigPath() {
|
|
498
|
-
return path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
499
|
-
}
|
|
500
|
-
getScenario() {
|
|
501
|
-
return this.config.scenario;
|
|
502
|
-
}
|
|
503
|
-
getInstallPath() {
|
|
504
|
-
return path.join(os.homedir(), ".openclaw", "plugins", "openclaw-workclaw");
|
|
505
|
-
}
|
|
506
|
-
getExtensionsPath() {
|
|
507
|
-
return path.join(os.homedir(), ".openclaw", "extensions");
|
|
508
|
-
}
|
|
509
|
-
getWorkspacePath() {
|
|
510
|
-
return path.join(os.homedir(), ".openclaw", "workspace");
|
|
511
|
-
}
|
|
512
|
-
async install() {
|
|
513
|
-
try {
|
|
514
|
-
logger.info("开始安装 WorkClaw 插件(NPM 安装模式)");
|
|
515
|
-
logger.info("=".repeat(40));
|
|
516
|
-
this.validateConfig();
|
|
517
|
-
await this.cleanOldFiles();
|
|
518
|
-
this.checkEnv();
|
|
519
|
-
await this.downloadFromNpm();
|
|
520
|
-
await this.fetchConfigAndUpdate();
|
|
521
|
-
logger.success("安装完成");
|
|
522
|
-
return { success: true, message: "插件安装成功" };
|
|
523
|
-
} catch (error) {
|
|
524
|
-
const errorMessage = formatError(error);
|
|
525
|
-
if (error instanceof AppError) {
|
|
526
|
-
logger.error(`安装失败 [${error.code}]: ${errorMessage}`);
|
|
527
|
-
} else {
|
|
528
|
-
logger.error(`安装失败: ${errorMessage}`);
|
|
529
|
-
}
|
|
530
|
-
return { success: false, error: errorMessage };
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
async cleanOldFiles() {
|
|
534
|
-
const spinner = logger.start("步骤 1: 清理旧版本");
|
|
535
|
-
const targetDir = this.getInstallPath();
|
|
536
|
-
try {
|
|
537
|
-
await fs.access(targetDir);
|
|
538
|
-
spinner.text = "发现旧版本,正在清理...";
|
|
539
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
540
|
-
spinner.succeed("旧版本清理完成");
|
|
541
|
-
} catch {
|
|
542
|
-
spinner.succeed("未发现旧版本");
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
checkEnv() {
|
|
546
|
-
const spinner = logger.start("步骤 2: 检查环境");
|
|
547
|
-
try {
|
|
548
|
-
const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
|
|
549
|
-
const majorVersion = Number.parseInt(nodeVersion.replace("v", "").split(".")[0]);
|
|
550
|
-
if (majorVersion < 18) {
|
|
551
|
-
spinner.fail(`Node.js 版本过低(当前: ${nodeVersion},需要: v18.0.0 或更高版本)`);
|
|
552
|
-
throw new AppError(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本过低(当前: ${nodeVersion},需要: v18.0.0 或更高版本)`);
|
|
553
|
-
}
|
|
554
|
-
spinner.text = `Node.js 已就绪(${nodeVersion})`;
|
|
555
|
-
} catch (error) {
|
|
556
|
-
if (error instanceof AppError) {
|
|
557
|
-
throw error;
|
|
558
|
-
}
|
|
559
|
-
spinner.fail("未检测到 Node.js,请检查环境配置");
|
|
560
|
-
throw new AppError(ERROR_CODES.NODE_NOT_FOUND, "未检测到 Node.js,请检查环境配置");
|
|
561
|
-
}
|
|
562
|
-
try {
|
|
563
|
-
execSync("npm --version", { stdio: "ignore" });
|
|
564
|
-
spinner.succeed("环境检查完成");
|
|
565
|
-
} catch {
|
|
566
|
-
spinner.fail("未检测到 npm,请检查环境配置");
|
|
567
|
-
throw new AppError(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请检查环境配置");
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
async downloadFromNpm() {
|
|
571
|
-
const spinner = logger.start("步骤 3: 从 NPM 下载插件");
|
|
572
|
-
const extensionsDir = this.getExtensionsPath();
|
|
573
|
-
spinner.text = `插件安装目录: ${extensionsDir}`;
|
|
574
|
-
try {
|
|
575
|
-
await fs.mkdir(extensionsDir, { recursive: true });
|
|
576
|
-
} catch {
|
|
577
|
-
}
|
|
578
|
-
spinner.text = `正在从 NPM 下载插件 ${PLUGIN_PACKAGE_NAME}...`;
|
|
579
|
-
try {
|
|
580
|
-
execSync(`npm install ${PLUGIN_PACKAGE_NAME}`, {
|
|
581
|
-
cwd: extensionsDir,
|
|
582
|
-
stdio: "pipe"
|
|
583
|
-
});
|
|
584
|
-
spinner.succeed("插件下载完成");
|
|
585
|
-
} catch {
|
|
586
|
-
spinner.fail("插件下载失败");
|
|
587
|
-
throw new AppError(ERROR_CODES.NPM_INSTALL_FAILED, `插件 ${PLUGIN_PACKAGE_NAME} 下载失败`);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
async fetchConfigAndUpdate() {
|
|
591
|
-
const spinner = logger.start("步骤 5: 获取配置并更新");
|
|
592
|
-
spinner.text = "正在登录...";
|
|
593
|
-
const token = await this.login();
|
|
594
|
-
spinner.text = "登录成功";
|
|
595
|
-
spinner.text = "正在获取绑定配置...";
|
|
596
|
-
const boundConfig = await this.fetchBoundConfig(token);
|
|
597
|
-
if (!boundConfig || !boundConfig.agentId) {
|
|
598
|
-
spinner.fail("获取绑定配置失败:缺少 agentId");
|
|
599
|
-
throw new Error("获取绑定配置失败:缺少 agentId");
|
|
600
|
-
}
|
|
601
|
-
spinner.text = "正在更新配置文件...";
|
|
602
|
-
await this.updateConfig(boundConfig, token);
|
|
603
|
-
spinner.succeed("配置文件更新完成");
|
|
604
|
-
}
|
|
605
|
-
async login() {
|
|
606
|
-
const apiUrl = `${this.getApiBaseUrl()}/user/login/pass`;
|
|
607
|
-
logger.info(`登录接口: ${apiUrl}`);
|
|
608
|
-
const encryptedPassword = rsaEncrypt(this.config.userPass);
|
|
609
|
-
if (!encryptedPassword) {
|
|
610
|
-
throw new Error("密码加密失败");
|
|
611
|
-
}
|
|
612
|
-
const response = await httpPost(apiUrl, {
|
|
613
|
-
phone: this.config.phone,
|
|
614
|
-
userPass: encryptedPassword
|
|
615
|
-
});
|
|
616
|
-
const res = response.data;
|
|
617
|
-
if (res.code === 200 && res.data && res.data.token) {
|
|
618
|
-
return res.data.token;
|
|
619
|
-
}
|
|
620
|
-
throw new Error(res.message || "登录失败");
|
|
621
|
-
}
|
|
622
|
-
async fetchBoundConfig(token) {
|
|
623
|
-
const apiUrl = `${this.getApiBaseUrl()}/work-bot/local/bound/init`;
|
|
624
|
-
logger.info(`绑定配置接口: ${apiUrl}`);
|
|
625
|
-
const response = await httpGet(apiUrl, {
|
|
626
|
-
headers: {
|
|
627
|
-
Authorization: token
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
const res = response.data;
|
|
631
|
-
if (res.code === 200 && res.data) {
|
|
632
|
-
return {
|
|
633
|
-
appKey: res.data.app_key,
|
|
634
|
-
appSecret: res.data.app_secret,
|
|
635
|
-
userId: res.data.user_id,
|
|
636
|
-
agentId: res.data.agent_id,
|
|
637
|
-
modelApiKey: res.data.model_api_key,
|
|
638
|
-
modelApiBaseUrl: res.data.model_api_base_url
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
throw new Error(res.message || "获取绑定配置失败");
|
|
642
|
-
}
|
|
643
|
-
async updateConfig(boundConfig, token) {
|
|
644
|
-
const configPath = this.getConfigPath();
|
|
645
|
-
const workspacePath = this.getWorkspacePath();
|
|
646
|
-
logger.info(`配置文件路径: ${configPath}`);
|
|
647
|
-
logger.info(`工作区路径: ${workspacePath}`);
|
|
648
|
-
logger.info(`Agent ID: ${boundConfig.agentId}`);
|
|
649
|
-
let originalConfig = {};
|
|
650
|
-
try {
|
|
651
|
-
const content = await fs.readFile(configPath, "utf-8");
|
|
652
|
-
originalConfig = JSON.parse(content);
|
|
653
|
-
logger.info("已读取现有配置文件");
|
|
654
|
-
} catch {
|
|
655
|
-
logger.info("创建新配置文件");
|
|
656
|
-
}
|
|
657
|
-
const dynamicConfig = {
|
|
658
|
-
// 诊断配置
|
|
659
|
-
diagnostics: {
|
|
660
|
-
// 启用诊断功能
|
|
661
|
-
enabled: true,
|
|
662
|
-
// 诊断标志列表
|
|
663
|
-
flags: ["*"]
|
|
664
|
-
},
|
|
665
|
-
// 浏览器配置
|
|
666
|
-
browser: {
|
|
667
|
-
// 禁用内置浏览器
|
|
668
|
-
enabled: false
|
|
669
|
-
},
|
|
670
|
-
// 模型配置
|
|
671
|
-
models: {
|
|
672
|
-
// 配置合并模式:merge 会将新配置与现有配置合并
|
|
673
|
-
mode: "merge",
|
|
674
|
-
// 模型提供商配置
|
|
675
|
-
providers: {
|
|
676
|
-
// SiliconFlow MiniMax 提供商
|
|
677
|
-
"siliconflow-minimax": {
|
|
678
|
-
// API 基础地址,从绑定配置获取或使用默认值
|
|
679
|
-
baseUrl: boundConfig.modelApiBaseUrl || this.getDefaultModelUrl(),
|
|
680
|
-
// API 密钥,用于认证
|
|
681
|
-
apiKey: boundConfig.modelApiKey,
|
|
682
|
-
// API 类型,这里使用 OpenAI 兼容格式
|
|
683
|
-
api: "openai-completions",
|
|
684
|
-
// 是否使用 Authorization header 进行认证
|
|
685
|
-
authHeader: true,
|
|
686
|
-
// 模型列表配置
|
|
687
|
-
models: [
|
|
688
|
-
{
|
|
689
|
-
// 模型标识符
|
|
690
|
-
id: "Pro/MiniMaxAI/MiniMax-M2.5",
|
|
691
|
-
// 模型显示名称
|
|
692
|
-
name: "MiniMax-M2.5",
|
|
693
|
-
// 是否支持推理能力
|
|
694
|
-
reasoning: false,
|
|
695
|
-
// 支持的输入类型
|
|
696
|
-
input: [
|
|
697
|
-
"text"
|
|
698
|
-
],
|
|
699
|
-
// 费用配置(单位:分)
|
|
700
|
-
cost: {
|
|
701
|
-
// 输入费用
|
|
702
|
-
input: 0,
|
|
703
|
-
// 输出费用
|
|
704
|
-
output: 0,
|
|
705
|
-
// 缓存读取费用
|
|
706
|
-
cacheRead: 0,
|
|
707
|
-
// 缓存写入费用
|
|
708
|
-
cacheWrite: 0
|
|
709
|
-
},
|
|
710
|
-
// 上下文窗口大小(token 数)
|
|
711
|
-
contextWindow: 2e5,
|
|
712
|
-
// 最大输出 token 数
|
|
713
|
-
maxTokens: 65536
|
|
714
|
-
}
|
|
715
|
-
]
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
// Agent 配置
|
|
720
|
-
agents: {
|
|
721
|
-
// 默认 Agent 设置
|
|
722
|
-
defaults: {
|
|
723
|
-
// 默认模型配置
|
|
724
|
-
model: {
|
|
725
|
-
// 主要使用的模型
|
|
726
|
-
primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5"
|
|
727
|
-
},
|
|
728
|
-
// 模型映射表
|
|
729
|
-
models: {
|
|
730
|
-
// 模型的别名配置
|
|
731
|
-
"siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5": {
|
|
732
|
-
// 模型别名
|
|
733
|
-
alias: "Pro/MiniMaxAI/MiniMax-M2.5"
|
|
734
|
-
}
|
|
735
|
-
},
|
|
736
|
-
// 默认工作区路径
|
|
737
|
-
workspace: workspacePath,
|
|
738
|
-
// 压缩配置
|
|
739
|
-
compaction: {
|
|
740
|
-
// 压缩模式:safeguard 模式
|
|
741
|
-
mode: "safeguard"
|
|
742
|
-
},
|
|
743
|
-
// 默认详细模式:full 表示完整输出
|
|
744
|
-
verboseDefault: "full",
|
|
745
|
-
// 最大并发数
|
|
746
|
-
maxConcurrent: 4,
|
|
747
|
-
// 子 Agent 配置
|
|
748
|
-
subagents: {
|
|
749
|
-
// 子 Agent 最大并发数
|
|
750
|
-
maxConcurrent: 8
|
|
751
|
-
}
|
|
752
|
-
},
|
|
753
|
-
// Agent 列表
|
|
754
|
-
list: [
|
|
755
|
-
{
|
|
756
|
-
// Agent ID
|
|
757
|
-
id: "main",
|
|
758
|
-
// Agent 工作区路径
|
|
759
|
-
workspace: workspacePath
|
|
760
|
-
}
|
|
761
|
-
]
|
|
762
|
-
},
|
|
763
|
-
// 工具权限配置
|
|
764
|
-
tools: {
|
|
765
|
-
// 禁止使用的工具类型列表
|
|
766
|
-
deny: [
|
|
767
|
-
"image",
|
|
768
|
-
"web_search",
|
|
769
|
-
"web_fetch"
|
|
770
|
-
],
|
|
771
|
-
// 媒体工具配置
|
|
772
|
-
media: {
|
|
773
|
-
// 图片处理工具
|
|
774
|
-
image: { enabled: false }
|
|
775
|
-
},
|
|
776
|
-
// 网页工具配置
|
|
777
|
-
web: {
|
|
778
|
-
// 网络搜索
|
|
779
|
-
search: { enabled: false },
|
|
780
|
-
// 网页抓取
|
|
781
|
-
fetch: { enabled: false }
|
|
782
|
-
},
|
|
783
|
-
// 提权工具配置
|
|
784
|
-
elevated: {
|
|
785
|
-
// 启用/禁用提权功能
|
|
786
|
-
enabled: true,
|
|
787
|
-
// 允许使用提权的来源
|
|
788
|
-
allowFrom: {
|
|
789
|
-
// webchat来源允许使用提权
|
|
790
|
-
webchat: ["*"]
|
|
791
|
-
}
|
|
792
|
-
},
|
|
793
|
-
// 执行工具配置
|
|
794
|
-
exec: {
|
|
795
|
-
// 安全模式级别
|
|
796
|
-
security: "full"
|
|
797
|
-
}
|
|
798
|
-
},
|
|
799
|
-
// 绑定配置
|
|
800
|
-
bindings: [
|
|
801
|
-
{
|
|
802
|
-
// 绑定的智能体ID
|
|
803
|
-
agentId: "main",
|
|
804
|
-
match: {
|
|
805
|
-
// 匹配通道名称
|
|
806
|
-
channel: "openclaw-workclaw",
|
|
807
|
-
// 匹配账号ID
|
|
808
|
-
accountId: "default"
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
],
|
|
812
|
-
// 消息配置
|
|
813
|
-
messages: {
|
|
814
|
-
// 消息确认反应的作用域
|
|
815
|
-
ackReactionScope: "group-mentions"
|
|
816
|
-
},
|
|
817
|
-
// 命令配置
|
|
818
|
-
commands: {
|
|
819
|
-
// 原生命令处理模式
|
|
820
|
-
native: "auto",
|
|
821
|
-
// 技能命令处理模式
|
|
822
|
-
nativeSkills: "auto",
|
|
823
|
-
// 允许使用restart命令
|
|
824
|
-
restart: true,
|
|
825
|
-
// 所有者信息显示方式
|
|
826
|
-
ownerDisplay: "raw"
|
|
827
|
-
},
|
|
828
|
-
// 会话配置
|
|
829
|
-
session: {
|
|
830
|
-
// 会话隔离策略
|
|
831
|
-
dmScope: "per-account-channel-peer"
|
|
832
|
-
},
|
|
833
|
-
// 钩子配置
|
|
834
|
-
hooks: {
|
|
835
|
-
internal: {
|
|
836
|
-
// 启用/禁用内部钩子
|
|
837
|
-
enabled: true,
|
|
838
|
-
entries: {
|
|
839
|
-
// 启动时显示Markdown
|
|
840
|
-
"boot-md": { enabled: true },
|
|
841
|
-
// 加载额外引导文件
|
|
842
|
-
"bootstrap-extra-files": { enabled: true },
|
|
843
|
-
// 记录命令执行日志
|
|
844
|
-
"command-logger": { enabled: true },
|
|
845
|
-
// 启用会话记忆
|
|
846
|
-
"session-memory": { enabled: true }
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
// 渠道配置
|
|
851
|
-
channels: {
|
|
852
|
-
// WorkClaw 插件渠道
|
|
853
|
-
"openclaw-workclaw": {
|
|
854
|
-
// 是否启用此渠道
|
|
855
|
-
enabled: true,
|
|
856
|
-
// 连接模式:使用 WebSocket 连接
|
|
857
|
-
connectionMode: "websocket",
|
|
858
|
-
// WebSocket 连接策略:每个 appKey 一个连接
|
|
859
|
-
wsConnectionStrategy: "per-appKey",
|
|
860
|
-
// 应用密钥,用于身份验证
|
|
861
|
-
appKey: boundConfig.appKey,
|
|
862
|
-
// 应用密钥,用于身份验证
|
|
863
|
-
appSecret: boundConfig.appSecret,
|
|
864
|
-
// API 基础地址
|
|
865
|
-
baseUrl: this.config.baseUrl || this.getDefaultBaseUrl(),
|
|
866
|
-
// WebSocket 连接地址
|
|
867
|
-
websocketUrl: this.config.wsUrl || this.getDefaultWsUrl(),
|
|
868
|
-
// 是否允许不安全的 TLS 连接
|
|
869
|
-
allowInsecureTls: true,
|
|
870
|
-
// 是否允许原始 JSON 载荷
|
|
871
|
-
allowRawJsonPayload: true,
|
|
872
|
-
// 账号配置
|
|
873
|
-
accounts: {
|
|
874
|
-
// 默认账号
|
|
875
|
-
default: {
|
|
876
|
-
// 是否启用默认账号
|
|
877
|
-
enabled: true,
|
|
878
|
-
// 代理 ID
|
|
879
|
-
agentId: boundConfig.agentId
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
},
|
|
884
|
-
// Gateway 节点配置
|
|
885
|
-
gateway: {
|
|
886
|
-
// 运行模式
|
|
887
|
-
mode: "local",
|
|
888
|
-
// 监听端口
|
|
889
|
-
port: 18789,
|
|
890
|
-
// 绑定地址(仅监听本地)
|
|
891
|
-
bind: "loopback",
|
|
892
|
-
// 认证配置(为空字符串会使用原始配置中的值)
|
|
893
|
-
auth: {
|
|
894
|
-
// 认证模式
|
|
895
|
-
mode: "token",
|
|
896
|
-
// 认证令牌(为空会使用原始配置中的值)
|
|
897
|
-
token: ""
|
|
898
|
-
},
|
|
899
|
-
// Tailscale 配置
|
|
900
|
-
tailscale: {
|
|
901
|
-
// Tailscale 模式
|
|
902
|
-
mode: "off",
|
|
903
|
-
// 退出时重置
|
|
904
|
-
resetOnExit: false
|
|
905
|
-
},
|
|
906
|
-
nodes: {
|
|
907
|
-
// 禁用的节点命令(出于安全考虑)
|
|
908
|
-
denyCommands: [
|
|
909
|
-
"camera.snap",
|
|
910
|
-
"camera.clip",
|
|
911
|
-
"screen.record",
|
|
912
|
-
"contacts.add",
|
|
913
|
-
"calendar.add",
|
|
914
|
-
"reminders.add",
|
|
915
|
-
"sms.send",
|
|
916
|
-
"sms.search"
|
|
917
|
-
]
|
|
918
|
-
},
|
|
919
|
-
// 控制面板配置
|
|
920
|
-
controlUi: {
|
|
921
|
-
// 允许非安全认证
|
|
922
|
-
allowInsecureAuth: true,
|
|
923
|
-
// 禁用设备认证
|
|
924
|
-
dangerouslyDisableDeviceAuth: true,
|
|
925
|
-
// 允许 Host header 源回退
|
|
926
|
-
dangerouslyAllowHostHeaderOriginFallback: true,
|
|
927
|
-
// 允许的来源(本地开发)
|
|
928
|
-
allowedOrigins: [
|
|
929
|
-
"http://localhost:18789",
|
|
930
|
-
"http://127.0.0.1:18789"
|
|
931
|
-
]
|
|
932
|
-
}
|
|
933
|
-
},
|
|
934
|
-
// 技能配置
|
|
935
|
-
skills: {
|
|
936
|
-
load: {
|
|
937
|
-
// 监听技能文件变化
|
|
938
|
-
watch: true
|
|
939
|
-
}
|
|
940
|
-
},
|
|
941
|
-
// 日志配置
|
|
942
|
-
logging: {
|
|
943
|
-
// 日志级别
|
|
944
|
-
level: "info",
|
|
945
|
-
// 控制台日志级别
|
|
946
|
-
consoleLevel: "info",
|
|
947
|
-
// 控制台样式
|
|
948
|
-
consoleStyle: "pretty",
|
|
949
|
-
// 敏感信息脱敏
|
|
950
|
-
redactSensitive: "off",
|
|
951
|
-
// 日志文件路径(空表示使用系统默认)
|
|
952
|
-
file: "",
|
|
953
|
-
// 脱敏模式
|
|
954
|
-
redactPatterns: ["sk-.*"]
|
|
955
|
-
},
|
|
956
|
-
// 插件配置
|
|
957
|
-
plugins: {
|
|
958
|
-
// 允许的插件列表
|
|
959
|
-
allow: ["openclaw-workclaw"],
|
|
960
|
-
// 插件安装配置
|
|
961
|
-
installs: {
|
|
962
|
-
// WorkClaw 插件安装信息
|
|
963
|
-
"openclaw-workclaw": {
|
|
964
|
-
// 插件来源类型:npm 表示从 NPM 安装
|
|
965
|
-
source: "npm",
|
|
966
|
-
// NPM 包名
|
|
967
|
-
npm: PLUGIN_PACKAGE_NAME,
|
|
968
|
-
// 插件安装路径
|
|
969
|
-
installPath: this.getExtensionsPath()
|
|
970
|
-
}
|
|
971
|
-
},
|
|
972
|
-
// 插件入口配置
|
|
973
|
-
entries: {
|
|
974
|
-
// WorkClaw 插件入口
|
|
975
|
-
"openclaw-workclaw": {
|
|
976
|
-
// 是否启用此插件
|
|
977
|
-
enabled: true
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
},
|
|
981
|
-
// 魔导配置(为空字符串会使用原始配置中的值)
|
|
982
|
-
wizard: {
|
|
983
|
-
// 上次运行时间(为空会保留原始配置值)
|
|
984
|
-
lastRunAt: "",
|
|
985
|
-
// 上次运行版本(为空会保留原始配置值)
|
|
986
|
-
lastRunVersion: "",
|
|
987
|
-
// 上次运行命令(为空会保留原始配置值)
|
|
988
|
-
lastRunCommand: "",
|
|
989
|
-
// 上次运行模式(为空会保留原始配置值)
|
|
990
|
-
lastRunMode: ""
|
|
991
|
-
},
|
|
992
|
-
// 元数据配置(为空字符串会使用原始配置中的值)
|
|
993
|
-
meta: {
|
|
994
|
-
// 上次修改版本(为空会保留原始配置值)
|
|
995
|
-
lastTouchedVersion: "",
|
|
996
|
-
// 上次修改时间(为空会保留原始配置值)
|
|
997
|
-
lastTouchedAt: ""
|
|
998
|
-
}
|
|
999
|
-
};
|
|
1000
|
-
const finalConfig = this.mergeConfig(originalConfig, dynamicConfig);
|
|
1001
|
-
await fs.writeFile(configPath, JSON.stringify(finalConfig, null, 2), "utf-8");
|
|
1002
|
-
await this.sendCallback(boundConfig.agentId, token);
|
|
1003
|
-
}
|
|
1004
|
-
async sendCallback(agentId, token) {
|
|
1005
|
-
try {
|
|
1006
|
-
const callbackUrl = `${this.getOpenApisUrl()}/instance/agent-created/status`;
|
|
1007
|
-
await httpPost(callbackUrl, {
|
|
1008
|
-
agentId,
|
|
1009
|
-
success: true
|
|
1010
|
-
}, {
|
|
1011
|
-
headers: {
|
|
1012
|
-
Authorization: token
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
logger.success("智小途插件初始化回调成功");
|
|
1016
|
-
} catch {
|
|
1017
|
-
logger.warn("回调通知失败,请手动确认");
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
mergeConfig(original, dynamic) {
|
|
1021
|
-
const result = { ...original };
|
|
1022
|
-
for (const key in dynamic) {
|
|
1023
|
-
const dynamicValue = dynamic[key];
|
|
1024
|
-
const originalValue = result[key];
|
|
1025
|
-
if (dynamicValue !== null && typeof dynamicValue === "object") {
|
|
1026
|
-
result[key] = {
|
|
1027
|
-
...originalValue || {},
|
|
1028
|
-
...dynamicValue
|
|
1029
|
-
};
|
|
1030
|
-
} else if (dynamicValue === "" || dynamicValue === null || dynamicValue === void 0) ;
|
|
1031
|
-
else {
|
|
1032
|
-
result[key] = dynamicValue;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return result;
|
|
1036
|
-
}
|
|
1037
|
-
getApiBaseUrl() {
|
|
1038
|
-
if (this.config.baseUrl) {
|
|
1039
|
-
return `${this.config.baseUrl}/tuzai`;
|
|
1040
|
-
}
|
|
1041
|
-
return `${getApiBaseUrl(this.config.env)}/tuzai`;
|
|
1042
|
-
}
|
|
1043
|
-
getOpenApisUrl() {
|
|
1044
|
-
if (this.config.baseUrl) {
|
|
1045
|
-
return `${this.config.baseUrl}/open-apis`;
|
|
1046
|
-
}
|
|
1047
|
-
return getOpenApisUrl(this.config.env);
|
|
1048
|
-
}
|
|
1049
|
-
getDefaultBaseUrl() {
|
|
1050
|
-
return this.config.baseUrl || getApiBaseUrl(this.config.env);
|
|
1051
|
-
}
|
|
1052
|
-
getDefaultWsUrl() {
|
|
1053
|
-
if (this.config.wsUrl) {
|
|
1054
|
-
return this.config.wsUrl;
|
|
1055
|
-
}
|
|
1056
|
-
return getWsUrl(this.config.env);
|
|
1057
|
-
}
|
|
1058
|
-
getDefaultModelUrl() {
|
|
1059
|
-
return getModelUrl(this.config.env);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
class InitCommand extends BaseCommand {
|
|
1063
|
-
command = "init";
|
|
1064
|
-
description = "初始化 WorkClaw 插件";
|
|
1065
|
-
/**
|
|
1066
|
-
* 实现抽象的 action 方法
|
|
1067
|
-
*/
|
|
1068
|
-
async action(...args) {
|
|
1069
|
-
const options = args[args.length - 1];
|
|
1070
|
-
await this.handleInit(options);
|
|
1071
|
-
}
|
|
1072
|
-
/**
|
|
1073
|
-
* 获取 Commander 命令实例
|
|
1074
|
-
*/
|
|
1075
|
-
getCommand() {
|
|
1076
|
-
const cmd = new Command(this.command);
|
|
1077
|
-
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", `
|
|
1078
|
-
示例:
|
|
1079
|
-
$ workclaw init --phone 13800138000 --user-pass 123456
|
|
1080
|
-
$ workclaw init --scenario windows-local --env test
|
|
1081
|
-
$ workclaw init --scenario linux-box --base-url https://api.example.com
|
|
1082
|
-
|
|
1083
|
-
场景说明:
|
|
1084
|
-
windows-local - Windows 本地安装
|
|
1085
|
-
mac-local - macOS 本地安装
|
|
1086
|
-
linux-local - Linux 本地安装
|
|
1087
|
-
linux-box - Linux Box 环境安装`);
|
|
1088
|
-
return cmd;
|
|
1089
|
-
}
|
|
1090
|
-
/**
|
|
1091
|
-
* 处理 init 命令
|
|
1092
|
-
*/
|
|
1093
|
-
async handleInit(options) {
|
|
1094
|
-
logger.info("WorkClaw CLI 插件初始化工具 v2.0.0");
|
|
1095
|
-
logger.info("=".repeat(40));
|
|
1096
|
-
const scenario = this.resolveScenario(options.scenario);
|
|
1097
|
-
const env = this.resolveEnv(options.env);
|
|
1098
|
-
logger.info(`安装场景: ${this.getScenarioLabel(scenario)}`);
|
|
1099
|
-
logger.info(`环境: ${env === "test" ? "测试环境" : "正式环境"}`);
|
|
1100
|
-
const config = {
|
|
1101
|
-
phone: options.phone,
|
|
1102
|
-
userPass: options.userPass,
|
|
1103
|
-
baseUrl: options.baseUrl,
|
|
1104
|
-
wsUrl: options.wsUrl,
|
|
1105
|
-
force: options.force,
|
|
1106
|
-
env,
|
|
1107
|
-
scenario
|
|
1108
|
-
};
|
|
1109
|
-
const installer = this.createInstaller(config);
|
|
1110
|
-
const result = await installer.install();
|
|
1111
|
-
if (!result.success) {
|
|
1112
|
-
process.exit(1);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
|
-
* 解析场景
|
|
1117
|
-
*/
|
|
1118
|
-
resolveScenario(scenario) {
|
|
1119
|
-
if (scenario) {
|
|
1120
|
-
const validScenarios = ["windows-local", "mac-local", "linux-local", "linux-box"];
|
|
1121
|
-
if (validScenarios.includes(scenario)) {
|
|
1122
|
-
return scenario;
|
|
1123
|
-
}
|
|
1124
|
-
logger.warn(`无效的场景: ${scenario},将使用自动检测`);
|
|
1125
|
-
}
|
|
1126
|
-
return detectOS();
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* 解析环境
|
|
1130
|
-
*/
|
|
1131
|
-
resolveEnv(env) {
|
|
1132
|
-
if (env === "prod" || env === "test") {
|
|
1133
|
-
return env;
|
|
1134
|
-
}
|
|
1135
|
-
return "test";
|
|
1136
|
-
}
|
|
1137
|
-
/**
|
|
1138
|
-
* 获取场景标签
|
|
1139
|
-
*/
|
|
1140
|
-
getScenarioLabel(scenario) {
|
|
1141
|
-
const labels = {
|
|
1142
|
-
"windows-local": "Windows 本地安装",
|
|
1143
|
-
"mac-local": "macOS 本地安装",
|
|
1144
|
-
"linux-local": "Linux 本地安装",
|
|
1145
|
-
"linux-box": "Linux Box 环境安装"
|
|
1146
|
-
};
|
|
1147
|
-
return labels[scenario];
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* 创建安装器实例
|
|
1151
|
-
*/
|
|
1152
|
-
createInstaller(config) {
|
|
1153
|
-
if (config.scenario === "linux-box") {
|
|
1154
|
-
return new BoxInstaller(config);
|
|
1155
|
-
}
|
|
1156
|
-
return new LocalInstaller(config);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
function createInitCommandInstance(program2) {
|
|
1160
|
-
const initCommand = new InitCommand(program2);
|
|
1161
|
-
const cmd = initCommand.getCommand();
|
|
1162
|
-
program2.addCommand(cmd);
|
|
1163
|
-
}
|
|
1164
|
-
const __filename$1 = fileURLToPath(import.meta.url);
|
|
1165
|
-
const __dirname$1 = dirname(__filename$1);
|
|
1166
|
-
const pkg = JSON.parse(readFileSync(resolve(__dirname$1, "../package.json"), "utf8"));
|
|
1167
|
-
const program = new Command();
|
|
1168
|
-
function index() {
|
|
1169
|
-
program.name(Object.keys(pkg.bin)[0]).version(pkg.version).description(pkg.description);
|
|
1170
|
-
createInitCommandInstance(program);
|
|
1171
|
-
program.parse(process.argv);
|
|
1172
|
-
}
|
|
1173
|
-
export {
|
|
1174
|
-
index as default
|
|
1175
|
-
};
|