@workclaw/cli 1.0.29 → 1.0.30
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/dist/box/installer/installer.d.ts.map +1 -1
- package/dist/{index-0P3M9kKr.js → index-DCWFh-f4.js} +1039 -986
- package/dist/index.js +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/local/installer/installer.d.ts.map +1 -1
- package/package.json +1 -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
|
@@ -3,92 +3,725 @@ 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
|
|
6
|
+
import { exec, execSync } from "node:child_process";
|
|
7
|
+
import boxen from "boxen";
|
|
7
8
|
import chalk from "chalk";
|
|
9
|
+
import inquirer from "inquirer";
|
|
10
|
+
import semver from "semver";
|
|
11
|
+
import fs from "node:fs/promises";
|
|
8
12
|
import ora from "ora";
|
|
9
13
|
import axios from "axios";
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
import "node:os";
|
|
15
|
+
let isDebug = false;
|
|
16
|
+
function setDebug(debug) {
|
|
17
|
+
isDebug = debug;
|
|
18
|
+
}
|
|
19
|
+
function debugLog(...args) {
|
|
20
|
+
if (isDebug) {
|
|
21
|
+
console.log(chalk.gray(`[DEBUG]`), ...args);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const ERROR_CODES$1 = {
|
|
25
|
+
APP_KEY_REQUIRED: "APP_KEY_REQUIRED",
|
|
26
|
+
APP_SECRET_REQUIRED: "APP_SECRET_REQUIRED",
|
|
27
|
+
NODE_VERSION_LOW: "NODE_VERSION_LOW",
|
|
28
|
+
NPM_NOT_FOUND: "NPM_NOT_FOUND",
|
|
29
|
+
NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED"
|
|
30
|
+
};
|
|
31
|
+
let AppError$1 = class AppError extends Error {
|
|
32
|
+
constructor(code, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.name = "AppError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const CONFIG = {
|
|
39
|
+
PLUGIN_NAME: "openclaw-workclaw",
|
|
40
|
+
DEFAULT_BASE_URL: "https://workbrain.cn/backend-api",
|
|
41
|
+
DEFAULT_WS_URL: "wss://workbrain.cn/backend-api/open-apis/connect",
|
|
42
|
+
MODEL_BASE_URL: "https://maas-api.workbrain.cn/v1/",
|
|
43
|
+
DIRS: {
|
|
44
|
+
OPENCLAW: ".openclaw",
|
|
45
|
+
EXTENSIONS: "extensions",
|
|
46
|
+
CONFIG_FILE: "openclaw.json",
|
|
47
|
+
WORKSPACE: "workspace",
|
|
48
|
+
TEMP: ".temp"
|
|
49
|
+
},
|
|
50
|
+
IGNORE_FILES: ["node_modules", ".git", ".trae", ".vscode"],
|
|
51
|
+
API: {
|
|
52
|
+
TUZAI_BASE_URL: "https://workbrain.cn/backend-api/tuzai",
|
|
53
|
+
BASE_URL: "https://workbrain.cn/backend-api/open-apis",
|
|
54
|
+
TIMEOUT: 1e4
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const TEST_CONFIG = {
|
|
58
|
+
PLUGIN_NAME: "openclaw-workclaw",
|
|
59
|
+
DEFAULT_BASE_URL: "http://172.168.80.30:32005",
|
|
60
|
+
DEFAULT_WS_URL: "ws://172.168.80.30:32005/open-apis/connect",
|
|
61
|
+
MODEL_BASE_URL: "http://172.168.80.30:30005/v1",
|
|
62
|
+
DIRS: {
|
|
63
|
+
OPENCLAW: ".openclaw",
|
|
64
|
+
EXTENSIONS: "extensions",
|
|
65
|
+
CONFIG_FILE: "openclaw.json",
|
|
66
|
+
WORKSPACE: "workspace",
|
|
67
|
+
TEMP: ".temp"
|
|
68
|
+
},
|
|
69
|
+
IGNORE_FILES: ["node_modules", ".git", ".trae", ".vscode"],
|
|
70
|
+
API: {
|
|
71
|
+
TUZAI_BASE_URL: "http://172.168.80.30:32005/tuzai",
|
|
72
|
+
BASE_URL: "http://172.168.80.30:32005/open-apis",
|
|
73
|
+
TIMEOUT: 1e4
|
|
17
74
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
75
|
+
};
|
|
76
|
+
function getConfig(env) {
|
|
77
|
+
return env === "test" ? TEST_CONFIG : CONFIG;
|
|
78
|
+
}
|
|
79
|
+
function getHomeDir$1() {
|
|
80
|
+
return process$1.env.HOME || process$1.env.USERPROFILE || "";
|
|
81
|
+
}
|
|
82
|
+
const PLUGIN_PACKAGE_NAME$1 = "@workclaw/openclaw-workclaw";
|
|
83
|
+
function execAsync$1(command, options) {
|
|
84
|
+
return new Promise((resolve2, reject) => {
|
|
85
|
+
exec(command, { cwd: options.cwd, timeout: options.timeout }, (error, stdout, stderr) => {
|
|
86
|
+
if (error) {
|
|
87
|
+
reject(new Error(stderr || error.message));
|
|
88
|
+
} else {
|
|
89
|
+
resolve2(stdout);
|
|
90
|
+
}
|
|
23
91
|
});
|
|
24
|
-
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function deepMerge$1(target, source) {
|
|
95
|
+
const result = { ...target };
|
|
96
|
+
for (const key in source) {
|
|
97
|
+
const sourceValue = source[key];
|
|
98
|
+
const targetValue = target[key];
|
|
99
|
+
if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
100
|
+
result[key] = deepMerge$1(targetValue, sourceValue);
|
|
101
|
+
} else if (sourceValue !== void 0) {
|
|
102
|
+
result[key] = sourceValue;
|
|
103
|
+
}
|
|
25
104
|
}
|
|
105
|
+
return result;
|
|
26
106
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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();
|
|
107
|
+
class BoxInstaller {
|
|
108
|
+
constructor(config) {
|
|
109
|
+
this.config = config;
|
|
110
|
+
this.spinner = ora({ color: "cyan" }).start();
|
|
111
|
+
}
|
|
112
|
+
spinner;
|
|
113
|
+
prefixText = "";
|
|
114
|
+
validateConfig() {
|
|
115
|
+
debugLog("[验证配置] 检查 appKey 和 appSecret...");
|
|
116
|
+
if (!this.config.appKey) {
|
|
117
|
+
throw new AppError$1(ERROR_CODES$1.APP_KEY_REQUIRED, "AppKey 不能为空,请使用 --app-key 参数");
|
|
49
118
|
}
|
|
50
|
-
|
|
119
|
+
if (!this.config.appSecret) {
|
|
120
|
+
throw new AppError$1(ERROR_CODES$1.APP_SECRET_REQUIRED, "AppSecret 不能为空,请使用 --app-secret 参数");
|
|
121
|
+
}
|
|
122
|
+
debugLog("[验证配置] 配置检查通过");
|
|
123
|
+
}
|
|
124
|
+
getPaths() {
|
|
125
|
+
const home = getHomeDir$1();
|
|
126
|
+
const openclawDir = path.join(home, ".openclaw");
|
|
127
|
+
const extensionsDir = path.join(openclawDir, "extensions");
|
|
128
|
+
const targetDir = path.join(extensionsDir, "openclaw-workclaw");
|
|
129
|
+
const configFile = path.join(openclawDir, "openclaw.json");
|
|
130
|
+
const workspaceDir = path.join(openclawDir, "workspace");
|
|
131
|
+
const tempDir = path.join(openclawDir, ".temp");
|
|
132
|
+
return {
|
|
133
|
+
home,
|
|
134
|
+
extensions: extensionsDir,
|
|
135
|
+
target: targetDir,
|
|
136
|
+
config: configFile,
|
|
137
|
+
workspace: workspaceDir,
|
|
138
|
+
temp: tempDir
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
updateSpinner(text) {
|
|
142
|
+
this.spinner.prefixText = this.prefixText;
|
|
143
|
+
this.spinner.text = chalk.cyan(text);
|
|
144
|
+
debugLog(`[Spinner] ${text}`);
|
|
145
|
+
}
|
|
146
|
+
getPrefixText() {
|
|
147
|
+
return this.prefixText;
|
|
148
|
+
}
|
|
149
|
+
async install() {
|
|
150
|
+
try {
|
|
151
|
+
debugLog("[盒子安装] 开始安装...");
|
|
152
|
+
this.validateConfig();
|
|
153
|
+
this.prefixText += chalk.green(` ✓ 配置验证完成
|
|
154
|
+
`);
|
|
155
|
+
this.updateSpinner("配置验证完成");
|
|
156
|
+
const paths = this.getPaths();
|
|
157
|
+
await this.doCleanOldFiles(paths);
|
|
158
|
+
await this.doDownloadFromNpm();
|
|
159
|
+
await this.doUpdateConfig(paths);
|
|
160
|
+
this.spinner.stop();
|
|
161
|
+
debugLog("[盒子安装] 安装完成");
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.spinner.stop();
|
|
164
|
+
debugLog(`[盒子安装] 安装失败: ${error.message}`);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async doCleanOldFiles(paths) {
|
|
169
|
+
debugLog("[清理旧版本] 开始清理旧版本...");
|
|
170
|
+
this.prefixText += chalk.green(` ✓ 开始清理旧版本
|
|
171
|
+
`);
|
|
172
|
+
this.updateSpinner("正在清理旧版本...");
|
|
173
|
+
try {
|
|
174
|
+
await fs.access(paths.target);
|
|
175
|
+
debugLog("[清理旧版本] 删除旧版本目录...");
|
|
176
|
+
await fs.rm(paths.target, { recursive: true, force: true });
|
|
177
|
+
this.prefixText += chalk.green(` ✓ 旧版本清理成功
|
|
178
|
+
`);
|
|
179
|
+
debugLog("[清理旧版本] 旧版本清理成功");
|
|
180
|
+
} catch {
|
|
181
|
+
debugLog("[清理旧版本] 无旧版本需要清理");
|
|
182
|
+
this.prefixText += chalk.green(` ✓ 无旧版本需要清理
|
|
183
|
+
`);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await fs.access(paths.temp);
|
|
187
|
+
await fs.rm(paths.temp, { recursive: true, force: true });
|
|
188
|
+
} catch {
|
|
189
|
+
debugLog("[清理旧版本] 无临时目录需要清理");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async doDownloadFromNpm() {
|
|
193
|
+
debugLog("[安装插件] 开始安装插件...");
|
|
194
|
+
this.prefixText += chalk.green(` ✓ 开始安装插件
|
|
195
|
+
`);
|
|
196
|
+
this.spinner.prefixText = this.prefixText;
|
|
197
|
+
this.spinner.text = chalk.cyan("正在安装插件...");
|
|
198
|
+
try {
|
|
199
|
+
await execAsync$1(
|
|
200
|
+
`openclaw plugins install ${PLUGIN_PACKAGE_NAME$1} --dangerously-force-unsafe-install`,
|
|
201
|
+
{ timeout: 12e4 }
|
|
202
|
+
);
|
|
203
|
+
debugLog("[安装插件] openclaw plugins install 执行成功");
|
|
204
|
+
this.prefixText += chalk.green(` ✓ 插件安装完成
|
|
205
|
+
`);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const errorMsg = error.message || "未知错误";
|
|
208
|
+
debugLog(`[安装插件] 安装失败: ${errorMsg}`);
|
|
209
|
+
this.prefixText += chalk.red(`✗ 安装失败
|
|
210
|
+
`);
|
|
211
|
+
throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `插件安装失败: ${errorMsg}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async doUpdateConfig(paths) {
|
|
215
|
+
debugLog("[更新配置] 开始更新配置...");
|
|
216
|
+
this.prefixText += chalk.green(` ✓ 开始更新配置
|
|
217
|
+
`);
|
|
218
|
+
this.updateSpinner("正在生成配置...");
|
|
219
|
+
let originalConfig = {};
|
|
220
|
+
try {
|
|
221
|
+
const content = await fs.readFile(paths.config, "utf-8");
|
|
222
|
+
originalConfig = JSON.parse(content);
|
|
223
|
+
debugLog("[更新配置] 读取原有配置成功");
|
|
224
|
+
} catch {
|
|
225
|
+
debugLog("[更新配置] 无原有配置");
|
|
226
|
+
}
|
|
227
|
+
const config = getConfig(this.config.env);
|
|
228
|
+
const newConfig = {
|
|
229
|
+
// diagnostics: 诊断配置
|
|
230
|
+
diagnostics: {
|
|
231
|
+
// 启用诊断功能
|
|
232
|
+
enabled: true,
|
|
233
|
+
// 启用所有诊断标志 ['*'] 表示全部,[] 表示禁用所有
|
|
234
|
+
flags: ["*"]
|
|
235
|
+
},
|
|
236
|
+
// browser: 浏览器配置
|
|
237
|
+
browser: {
|
|
238
|
+
// 禁用浏览器工具
|
|
239
|
+
enabled: false
|
|
240
|
+
},
|
|
241
|
+
// models: 模型配置
|
|
242
|
+
models: {
|
|
243
|
+
// 配置合并模式:'merge' 合并 | 'replace' 替换
|
|
244
|
+
mode: "merge",
|
|
245
|
+
providers: {
|
|
246
|
+
"siliconflow-minimax": {
|
|
247
|
+
// 模型 API 基础地址
|
|
248
|
+
baseUrl: config.MODEL_BASE_URL,
|
|
249
|
+
// API 密钥(留空,由外部提供)
|
|
250
|
+
apiKey: "",
|
|
251
|
+
// API 类型:'openai-completions' | 'openai-chat' 等
|
|
252
|
+
api: "openai-completions",
|
|
253
|
+
// 是否使用 Authorization header 认证
|
|
254
|
+
authHeader: true,
|
|
255
|
+
models: [{
|
|
256
|
+
// 模型 ID(provider/model 格式)
|
|
257
|
+
id: "Pro/MiniMaxAI/MiniMax-M2.5",
|
|
258
|
+
// 模型显示名称
|
|
259
|
+
name: "MiniMax-M2.5",
|
|
260
|
+
// 是否启用推理能力
|
|
261
|
+
reasoning: false,
|
|
262
|
+
// 支持的输入类型:['text'] | ['text', 'image']
|
|
263
|
+
input: ["text"],
|
|
264
|
+
// 价格(0 表示免费或未设置)
|
|
265
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
266
|
+
// 上下文窗口大小(token)
|
|
267
|
+
contextWindow: 2e5,
|
|
268
|
+
// 最大输出 token 数
|
|
269
|
+
maxTokens: 65536
|
|
270
|
+
}]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
// agents: 代理配置
|
|
275
|
+
agents: {
|
|
276
|
+
defaults: {
|
|
277
|
+
// 默认使用的模型
|
|
278
|
+
model: { primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5" },
|
|
279
|
+
models: {
|
|
280
|
+
"siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5": {
|
|
281
|
+
// 模型别名,用于显示
|
|
282
|
+
alias: "Pro/MiniMaxAI/MiniMax-M2.5"
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
// 工作区目录路径
|
|
286
|
+
workspace: paths.workspace,
|
|
287
|
+
// 会话压缩模式:'safeguard' 保守模式
|
|
288
|
+
compaction: { mode: "safeguard" },
|
|
289
|
+
// 详细程度:'full' | 'short' | 'off'
|
|
290
|
+
verboseDefault: "full",
|
|
291
|
+
// 最大并发任务数
|
|
292
|
+
maxConcurrent: 4,
|
|
293
|
+
// 子代理最大并发数
|
|
294
|
+
subagents: { maxConcurrent: 8 }
|
|
295
|
+
},
|
|
296
|
+
// 代理列表
|
|
297
|
+
list: [{ id: "main", workspace: paths.workspace }]
|
|
298
|
+
},
|
|
299
|
+
// tools: 工具配置
|
|
300
|
+
tools: {
|
|
301
|
+
// 禁用的工具列表(优先于 allow)
|
|
302
|
+
deny: ["image", "web_search", "web_fetch"],
|
|
303
|
+
// 图像分析工具
|
|
304
|
+
media: { image: { enabled: false } },
|
|
305
|
+
// 网络搜索和抓取工具
|
|
306
|
+
web: { search: { enabled: false }, fetch: { enabled: false } },
|
|
307
|
+
// 提升权限工具(host=gateway)
|
|
308
|
+
elevated: { enabled: true, allowFrom: { webchat: ["*"] } },
|
|
309
|
+
// exec 工具安全级别:'deny' | 'allowlist' | 'full'
|
|
310
|
+
exec: { security: "full" }
|
|
311
|
+
},
|
|
312
|
+
// bindings: 绑定配置
|
|
313
|
+
bindings: [{
|
|
314
|
+
// 绑定的代理 ID
|
|
315
|
+
agentId: "main",
|
|
316
|
+
// 匹配的通道和账户
|
|
317
|
+
match: { channel: "openclaw-workclaw", accountId: "default" }
|
|
318
|
+
}],
|
|
319
|
+
// messages: 消息配置
|
|
320
|
+
messages: {
|
|
321
|
+
// 消息确认范围
|
|
322
|
+
ackReactionScope: "group-mentions"
|
|
323
|
+
},
|
|
324
|
+
// commands: 命令配置
|
|
325
|
+
commands: {
|
|
326
|
+
// 本机命令:'auto' | 'on' | 'off'
|
|
327
|
+
native: "auto",
|
|
328
|
+
// 本机技能:'auto' | 'on' | 'off'
|
|
329
|
+
nativeSkills: "auto",
|
|
330
|
+
// 是否允许重启命令
|
|
331
|
+
restart: true,
|
|
332
|
+
// 所有者显示格式:'raw' | 'name' | 'hidden'
|
|
333
|
+
ownerDisplay: "raw"
|
|
334
|
+
},
|
|
335
|
+
// session: 会话配置
|
|
336
|
+
session: {
|
|
337
|
+
// DM 作用域
|
|
338
|
+
dmScope: "per-account-channel-peer"
|
|
339
|
+
},
|
|
340
|
+
// hooks: 钩子配置
|
|
341
|
+
hooks: {
|
|
342
|
+
internal: {
|
|
343
|
+
// 启用内部钩子
|
|
344
|
+
enabled: true,
|
|
345
|
+
entries: {
|
|
346
|
+
// 启动 Markdown 钩子
|
|
347
|
+
"boot-md": { enabled: true },
|
|
348
|
+
// 引导额外文件钩子
|
|
349
|
+
"bootstrap-extra-files": { enabled: true },
|
|
350
|
+
// 命令日志钩子
|
|
351
|
+
"command-logger": { enabled: true },
|
|
352
|
+
// 会话记忆钩子
|
|
353
|
+
"session-memory": { enabled: true }
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
// channels: 通道配置
|
|
358
|
+
channels: {
|
|
359
|
+
"openclaw-workclaw": {
|
|
360
|
+
// 启用通道
|
|
361
|
+
enabled: true,
|
|
362
|
+
// 连接模式:'websocket' | 'http'
|
|
363
|
+
connectionMode: "websocket",
|
|
364
|
+
// 应用密钥
|
|
365
|
+
appKey: this.config.appKey,
|
|
366
|
+
// 应用密钥
|
|
367
|
+
appSecret: this.config.appSecret,
|
|
368
|
+
// API 基础 URL
|
|
369
|
+
baseUrl: this.config.baseUrl || config.DEFAULT_BASE_URL,
|
|
370
|
+
// WebSocket URL
|
|
371
|
+
websocketUrl: this.config.wsUrl || config.DEFAULT_WS_URL,
|
|
372
|
+
// 允许不安全的 TLS 连接
|
|
373
|
+
allowInsecureTls: true,
|
|
374
|
+
// 允许原始 JSON 载荷
|
|
375
|
+
allowRawJsonPayload: true,
|
|
376
|
+
// 用户 ID
|
|
377
|
+
userId: "",
|
|
378
|
+
// DM 策略
|
|
379
|
+
dmPolicy: "open",
|
|
380
|
+
// 允许所有来源
|
|
381
|
+
allowFrom: ["*"],
|
|
382
|
+
accounts: {
|
|
383
|
+
// 账户配置(agentId 为空表示待分配)
|
|
384
|
+
default: { enabled: true, agentId: "" }
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
// gateway: 网关配置
|
|
389
|
+
gateway: {
|
|
390
|
+
// 网关模式:'local' | 'hosted'
|
|
391
|
+
mode: "local",
|
|
392
|
+
// 网关端口
|
|
393
|
+
port: 18789,
|
|
394
|
+
// 绑定地址:'loopback' | 'lan' | 'all'
|
|
395
|
+
bind: "loopback",
|
|
396
|
+
// 认证模式(token字段未设置,是因为需要使用原始配置的token)
|
|
397
|
+
auth: {
|
|
398
|
+
mode: "token"
|
|
399
|
+
},
|
|
400
|
+
// Tailscale 配置
|
|
401
|
+
tailscale: { mode: "off", resetOnExit: false },
|
|
402
|
+
nodes: {
|
|
403
|
+
// 节点上禁止的命令
|
|
404
|
+
denyCommands: [
|
|
405
|
+
"camera.snap",
|
|
406
|
+
"camera.clip",
|
|
407
|
+
"screen.record",
|
|
408
|
+
"contacts.add",
|
|
409
|
+
"calendar.add",
|
|
410
|
+
"reminders.add",
|
|
411
|
+
"sms.send",
|
|
412
|
+
"sms.search"
|
|
413
|
+
]
|
|
414
|
+
},
|
|
415
|
+
controlUi: {
|
|
416
|
+
// 允许不安全认证
|
|
417
|
+
allowInsecureAuth: true,
|
|
418
|
+
// 禁用设备认证(仅用于开发)
|
|
419
|
+
dangerouslyDisableDeviceAuth: true,
|
|
420
|
+
// 允许 Host 头源回退
|
|
421
|
+
dangerouslyAllowHostHeaderOriginFallback: true,
|
|
422
|
+
// 允许的源
|
|
423
|
+
allowedOrigins: ["http://localhost:18789", "http://127.0.0.1:18789"]
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
// skills: 技能配置
|
|
427
|
+
skills: {
|
|
428
|
+
// 技能加载监视模式
|
|
429
|
+
load: { watch: true }
|
|
430
|
+
},
|
|
431
|
+
// logging: 日志配置
|
|
432
|
+
logging: {
|
|
433
|
+
// 日志级别:'debug' | 'info' | 'warn' | 'error'
|
|
434
|
+
level: "info",
|
|
435
|
+
// 控制台日志级别
|
|
436
|
+
consoleLevel: "info",
|
|
437
|
+
// 控制台样式:'pretty' | 'basic' | 'raw'
|
|
438
|
+
consoleStyle: "pretty",
|
|
439
|
+
// 敏感信息脱敏:'off' | 'keys' | 'all'
|
|
440
|
+
redactSensitive: "off",
|
|
441
|
+
// 日志文件路径(空表示不写入文件)
|
|
442
|
+
file: "",
|
|
443
|
+
// 脱敏模式(正则表达式)
|
|
444
|
+
redactPatterns: ["sk-.*"]
|
|
445
|
+
},
|
|
446
|
+
// plugins: 插件配置
|
|
447
|
+
plugins: {}
|
|
448
|
+
};
|
|
449
|
+
const finalConfig = deepMerge$1(originalConfig, newConfig);
|
|
450
|
+
this.updateSpinner("正在写入配置...");
|
|
451
|
+
debugLog("[更新配置] 写入配置文件...");
|
|
452
|
+
await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
|
|
453
|
+
this.prefixText += chalk.green(` ✓ 配置文件更新成功
|
|
454
|
+
`);
|
|
455
|
+
debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
|
|
456
|
+
}
|
|
51
457
|
}
|
|
52
|
-
|
|
53
|
-
|
|
458
|
+
function checkEnv$1() {
|
|
459
|
+
debugLog("[环境检查] 检测 Node.js 版本...");
|
|
460
|
+
const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
|
|
461
|
+
debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
|
|
462
|
+
if (!semver.gte(nodeVersion, "18.0.0")) {
|
|
463
|
+
throw new AppError$1(ERROR_CODES$1.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
|
|
464
|
+
}
|
|
465
|
+
debugLog("[环境检查] Node.js 版本检查通过");
|
|
466
|
+
debugLog("[环境检查] 检测 npm...");
|
|
54
467
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
468
|
+
const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
|
|
469
|
+
debugLog(`[环境检查] npm 版本: ${npmVersion}`);
|
|
470
|
+
debugLog("[环境检查] npm 检测通过");
|
|
57
471
|
} catch {
|
|
58
|
-
|
|
59
|
-
return null;
|
|
472
|
+
throw new AppError$1(ERROR_CODES$1.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
|
|
60
473
|
}
|
|
61
474
|
}
|
|
62
|
-
async function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
475
|
+
async function createBoxCommand(options) {
|
|
476
|
+
setDebug(!!options.debug);
|
|
477
|
+
debugLog("[盒子安装] 开始处理...");
|
|
478
|
+
debugLog(`[盒子安装] 参数: env=${options.env}, appKey=${options.appKey ? "***" : "未提供"}, debug=${options.debug}`);
|
|
479
|
+
checkEnv$1();
|
|
480
|
+
debugLog("[盒子安装] 环境检查通过");
|
|
481
|
+
try {
|
|
482
|
+
let env = options.env;
|
|
483
|
+
let appKey = options.appKey;
|
|
484
|
+
let appSecret = options.appSecret;
|
|
485
|
+
const questions = [];
|
|
486
|
+
if (!env) {
|
|
487
|
+
debugLog("[盒子安装] 需要选择环境");
|
|
488
|
+
questions.push({
|
|
489
|
+
type: "list",
|
|
490
|
+
name: "env",
|
|
491
|
+
message: chalk.cyan("请选择环境:"),
|
|
492
|
+
default: "test",
|
|
493
|
+
choices: [
|
|
494
|
+
{ name: `${chalk.green("测试环境")} ${chalk.dim("(test)")}`, value: "test" },
|
|
495
|
+
{ name: `${chalk.red("正式环境")} ${chalk.dim("(prod)")}`, value: "prod" }
|
|
496
|
+
]
|
|
497
|
+
});
|
|
498
|
+
} else {
|
|
499
|
+
debugLog(`[盒子安装] 使用命令行参数: env=${env}`);
|
|
500
|
+
}
|
|
501
|
+
if (!appKey) {
|
|
502
|
+
debugLog("[盒子安装] 需要输入 AppKey");
|
|
503
|
+
questions.push({
|
|
504
|
+
type: "input",
|
|
505
|
+
name: "appKey",
|
|
506
|
+
message: chalk.cyan("请输入 AppKey:"),
|
|
507
|
+
validate: (value) => {
|
|
508
|
+
if (!value || value.trim() === "") {
|
|
509
|
+
return chalk.red("AppKey 不能为空");
|
|
510
|
+
}
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
} else {
|
|
515
|
+
debugLog("[盒子安装] 使用命令行参数: appKey");
|
|
516
|
+
}
|
|
517
|
+
if (!appSecret) {
|
|
518
|
+
debugLog("[盒子安装] 需要输入 AppSecret");
|
|
519
|
+
questions.push({
|
|
520
|
+
type: "input",
|
|
521
|
+
name: "appSecret",
|
|
522
|
+
message: chalk.cyan("请输入 AppSecret:"),
|
|
523
|
+
validate: (value) => {
|
|
524
|
+
if (!value || value.trim() === "") {
|
|
525
|
+
return chalk.red("AppSecret 不能为空");
|
|
526
|
+
}
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
debugLog("[盒子安装] 使用命令行参数: appSecret");
|
|
532
|
+
}
|
|
533
|
+
if (questions.length > 0) {
|
|
534
|
+
debugLog(`[盒子安装] 开始交互式问答,共 ${questions.length} 个问题`);
|
|
535
|
+
const answers = await inquirer.prompt(questions);
|
|
536
|
+
debugLog("[盒子安装] 交互式问答完成");
|
|
537
|
+
env = env || answers.env || "test";
|
|
538
|
+
appKey = appKey || answers.appKey;
|
|
539
|
+
appSecret = appSecret || answers.appSecret;
|
|
540
|
+
}
|
|
541
|
+
debugLog(`[盒子安装] 最终参数: env=${env}`);
|
|
542
|
+
debugLog("[盒子安装] 创建 BoxInstaller 实例...");
|
|
543
|
+
const installer = new BoxInstaller({
|
|
544
|
+
appKey,
|
|
545
|
+
appSecret,
|
|
546
|
+
env
|
|
547
|
+
});
|
|
548
|
+
debugLog("[盒子安装] 开始安装...");
|
|
549
|
+
await installer.install();
|
|
550
|
+
debugLog("[盒子安装] 安装完成");
|
|
551
|
+
console.log(installer.getPrefixText() + boxen(
|
|
552
|
+
`${chalk.green("✦")} 插件初始化成功!`,
|
|
553
|
+
{
|
|
554
|
+
title: `${chalk.bold.green("初始化成功")}`,
|
|
555
|
+
titleAlignment: "center",
|
|
556
|
+
padding: { top: 1, bottom: 1, left: 5, right: 5 },
|
|
557
|
+
margin: 1,
|
|
558
|
+
borderStyle: "round",
|
|
559
|
+
borderColor: "green",
|
|
560
|
+
textAlignment: "center"
|
|
561
|
+
}
|
|
562
|
+
));
|
|
563
|
+
} catch (error) {
|
|
564
|
+
debugLog(`[盒子安装] 发生错误: ${error.message}`);
|
|
565
|
+
console.error(boxen(
|
|
566
|
+
`${chalk.yellow(error.message)}`,
|
|
567
|
+
{
|
|
568
|
+
title: `${chalk.bold.red("初始化失败")}`,
|
|
569
|
+
titleAlignment: "center",
|
|
570
|
+
padding: { top: 1, bottom: 1, left: 5, right: 5 },
|
|
571
|
+
margin: 1,
|
|
572
|
+
borderStyle: "round",
|
|
573
|
+
borderColor: "red",
|
|
574
|
+
textAlignment: "center"
|
|
575
|
+
}
|
|
576
|
+
));
|
|
577
|
+
process$1.exit(1);
|
|
71
578
|
}
|
|
579
|
+
}
|
|
580
|
+
function registerCommands$1(program2) {
|
|
581
|
+
program2.command("box").description("盒子设备安装(无需登录)").option("-e, --env <env>", "环境 (test/prod)").option("--app-key <appKey>", "App Key").option("--app-secret <appSecret>", "App Secret").option("--plugin-version <plugin-version>", "插件版本号(默认最新版)").option("--debug", "开启调试日志").action(createBoxCommand);
|
|
582
|
+
}
|
|
583
|
+
const ERROR_CODES = {
|
|
584
|
+
PHONE_REQUIRED: "PHONE_REQUIRED",
|
|
585
|
+
USER_PASS_REQUIRED: "USER_PASS_REQUIRED",
|
|
586
|
+
LOGIN_FAILED: "LOGIN_FAILED",
|
|
587
|
+
GET_BOUND_CONFIG_FAILED: "GET_BOUND_CONFIG_FAILED",
|
|
588
|
+
NODE_VERSION_LOW: "NODE_VERSION_LOW",
|
|
589
|
+
NODE_NOT_FOUND: "NODE_NOT_FOUND",
|
|
590
|
+
NPM_NOT_FOUND: "NPM_NOT_FOUND",
|
|
591
|
+
NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED",
|
|
592
|
+
CONFIG_WRITE_FAILED: "CONFIG_WRITE_FAILED",
|
|
593
|
+
HTTP_ERROR: "HTTP_ERROR",
|
|
594
|
+
NETWORK_ERROR: "NETWORK_ERROR"
|
|
72
595
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
596
|
+
class AppError2 extends Error {
|
|
597
|
+
constructor(code, message) {
|
|
598
|
+
super(message);
|
|
599
|
+
this.code = code;
|
|
600
|
+
this.name = "AppError";
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function createHttpClient() {
|
|
604
|
+
return axios.create({
|
|
605
|
+
timeout: 1e4,
|
|
606
|
+
headers: {
|
|
607
|
+
"Content-Type": "application/json"
|
|
608
|
+
}
|
|
609
|
+
});
|
|
83
610
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
611
|
+
async function httpPost(url, data, config) {
|
|
612
|
+
const client = createHttpClient();
|
|
613
|
+
debugLog(`[HTTP POST] 请求地址: ${url}`);
|
|
614
|
+
debugLog(`[HTTP POST] 请求参数: ${JSON.stringify(data)}`);
|
|
615
|
+
debugLog(`[HTTP POST] 请求头: ${JSON.stringify(config?.headers || {})}`);
|
|
616
|
+
try {
|
|
617
|
+
const response = await client.post(url, data, config);
|
|
618
|
+
debugLog(`[HTTP POST] 响应状态: ${response.status}`);
|
|
619
|
+
debugLog(`[HTTP POST] 响应内容: ${JSON.stringify(response.data)}`);
|
|
620
|
+
return {
|
|
621
|
+
status: response.status,
|
|
622
|
+
data: response.data
|
|
623
|
+
};
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const axiosError = error;
|
|
626
|
+
if (axiosError.response) {
|
|
627
|
+
const responseData = axiosError.response.data;
|
|
628
|
+
debugLog(`[HTTP POST] 响应状态: ${axiosError.response.status}`);
|
|
629
|
+
debugLog(`[HTTP POST] 响应状态文本: ${axiosError.response.statusText}`);
|
|
630
|
+
debugLog(`[HTTP POST] 响应数据: ${JSON.stringify(responseData)}`);
|
|
631
|
+
if (typeof responseData === "string") {
|
|
632
|
+
throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData}`);
|
|
633
|
+
} else if (responseData && typeof responseData === "object" && "message" in responseData) {
|
|
634
|
+
throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${responseData.message}`);
|
|
635
|
+
} else {
|
|
636
|
+
throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求失败 (${axiosError.response.status}): ${axiosError.response.statusText}`);
|
|
637
|
+
}
|
|
638
|
+
} else if (axiosError.request) {
|
|
639
|
+
debugLog(`[HTTP POST] 未收到响应`);
|
|
640
|
+
debugLog(`[HTTP POST] 错误信息: ${axiosError.message}`);
|
|
641
|
+
throw new AppError2(ERROR_CODES.NETWORK_ERROR, `网络错误: ${axiosError.message}`);
|
|
642
|
+
} else {
|
|
643
|
+
debugLog(`[HTTP POST] 请求配置错误`);
|
|
644
|
+
debugLog(`[HTTP POST] 错误信息: ${axiosError.message}`);
|
|
645
|
+
throw new AppError2(ERROR_CODES.HTTP_ERROR, `请求配置错误: ${axiosError.message}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
86
648
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
649
|
+
async function login(phone, password, env) {
|
|
650
|
+
const config = getConfig(env);
|
|
651
|
+
const url = `${config.API.TUZAI_BASE_URL}/user/login/pass`;
|
|
652
|
+
debugLog("[登录] 开始登录...");
|
|
653
|
+
debugLog(`[登录] 请求地址: ${url}`);
|
|
654
|
+
debugLog(`[登录] 请求参数: phone=${phone}, password=***`);
|
|
655
|
+
try {
|
|
656
|
+
const response = await httpPost(url, {
|
|
657
|
+
phone,
|
|
658
|
+
userPass: password
|
|
659
|
+
});
|
|
660
|
+
const data = response.data;
|
|
661
|
+
debugLog(`[登录] 响应数据: ${JSON.stringify(data)}`);
|
|
662
|
+
if (data.code === 200 && data.data?.token) {
|
|
663
|
+
debugLog("[登录] 登录成功,获取到 token");
|
|
664
|
+
return data.data.token;
|
|
665
|
+
}
|
|
666
|
+
debugLog("[登录] 登录失败");
|
|
667
|
+
throw new AppError2(ERROR_CODES.LOGIN_FAILED, data.message || "登录失败");
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (error instanceof AppError2) {
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
debugLog(`[登录] 发生错误: ${error.message}`);
|
|
673
|
+
throw new AppError2(ERROR_CODES.LOGIN_FAILED, error.message);
|
|
674
|
+
}
|
|
89
675
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
676
|
+
async function fetchBoundConfig(token, phone, env) {
|
|
677
|
+
const config = getConfig(env);
|
|
678
|
+
const url = `${config.API.TUZAI_BASE_URL}/work-bot/local/bound/init`;
|
|
679
|
+
debugLog("[获取绑定配置] 开始获取绑定配置...");
|
|
680
|
+
debugLog(`[获取绑定配置] 请求地址: ${url}`);
|
|
681
|
+
debugLog(`[获取绑定配置] 请求参数: phone=${phone}, localCode=001`);
|
|
682
|
+
debugLog(`[获取绑定配置] 请求头: Authorization=${token.substring(0, 20)}...`);
|
|
683
|
+
try {
|
|
684
|
+
const response = await httpPost(url, {
|
|
685
|
+
phone,
|
|
686
|
+
localCode: "001"
|
|
687
|
+
}, {
|
|
688
|
+
headers: {
|
|
689
|
+
Authorization: token
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
const data = response.data;
|
|
693
|
+
debugLog(`[获取绑定配置] 响应数据: ${JSON.stringify(data)}`);
|
|
694
|
+
if (data.code === 200 && data.data) {
|
|
695
|
+
const boundConfig = {
|
|
696
|
+
appKey: data.data.app_key,
|
|
697
|
+
appSecret: data.data.app_secret,
|
|
698
|
+
userId: data.data.user_id,
|
|
699
|
+
agentId: data.data.agent_id || data.data.agents?.[0]?.id,
|
|
700
|
+
modelApiKey: data.data.model_api_key,
|
|
701
|
+
modelApiBaseUrl: data.data.model_api_base_url
|
|
702
|
+
};
|
|
703
|
+
debugLog("[获取绑定配置] 获取绑定配置成功");
|
|
704
|
+
debugLog(`[获取绑定配置] appKey: ${boundConfig.appKey}`);
|
|
705
|
+
debugLog(`[获取绑定配置] appSecret: ${boundConfig.appSecret ? "***" : "undefined"}`);
|
|
706
|
+
debugLog(`[获取绑定配置] userId: ${boundConfig.userId}`);
|
|
707
|
+
debugLog(`[获取绑定配置] agentId: ${boundConfig.agentId}`);
|
|
708
|
+
debugLog(`[获取绑定配置] modelApiKey: ${boundConfig.modelApiKey || "undefined"}`);
|
|
709
|
+
debugLog(`[获取绑定配置] modelApiBaseUrl: ${boundConfig.modelApiBaseUrl || "undefined"}`);
|
|
710
|
+
if (!boundConfig.appKey || !boundConfig.appSecret || !boundConfig.agentId) {
|
|
711
|
+
debugLog("[获取绑定配置] 缺少必要字段");
|
|
712
|
+
throw new AppError2(ERROR_CODES.GET_BOUND_CONFIG_FAILED, "获取绑定配置失败:缺少必要字段");
|
|
713
|
+
}
|
|
714
|
+
return boundConfig;
|
|
715
|
+
}
|
|
716
|
+
debugLog("[获取绑定配置] 获取绑定配置失败");
|
|
717
|
+
throw new AppError2(ERROR_CODES.GET_BOUND_CONFIG_FAILED, data.message || data.msg || "获取绑定配置失败");
|
|
718
|
+
} catch (error) {
|
|
719
|
+
if (error instanceof AppError2) {
|
|
720
|
+
throw error;
|
|
721
|
+
}
|
|
722
|
+
debugLog(`[获取绑定配置] 发生错误: ${error.message}`);
|
|
723
|
+
throw new AppError2(ERROR_CODES.GET_BOUND_CONFIG_FAILED, error.message);
|
|
724
|
+
}
|
|
92
725
|
}
|
|
93
726
|
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
94
727
|
function int2char(n) {
|
|
@@ -3975,1073 +4608,492 @@ var JSEncrypt = (
|
|
|
3975
4608
|
);
|
|
3976
4609
|
const PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBSYtNb6neLrrwsuPBsIFjpZBrffdkA8bpp2S35o2TCdhfdxS0nc4pkv9cJLkUvFa+gdQ5nLifnK9B1XoVbIQwY212QAftTDbl77bcHu7GAbv2TZr9pelSeUm1SrtMK5HDr/LzxTutGr4DovVHiDgEn45GQ1X5U+zC0Jp4Awn6ZwIDAQAB";
|
|
3977
4610
|
function rsaEncrypt(txt) {
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
if (!encrypted) {
|
|
3983
|
-
return null;
|
|
3984
|
-
}
|
|
3985
|
-
return encrypted;
|
|
3986
|
-
} catch (error) {
|
|
3987
|
-
console.error("RSA 加密失败:", error);
|
|
3988
|
-
return null;
|
|
3989
|
-
}
|
|
4611
|
+
const encrypt = new JSEncrypt();
|
|
4612
|
+
encrypt.setPublicKey(PUBLIC_KEY);
|
|
4613
|
+
const result = encrypt.encrypt(txt);
|
|
4614
|
+
return result || null;
|
|
3990
4615
|
}
|
|
3991
|
-
|
|
3992
|
-
|
|
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
|
-
}
|
|
4032
|
-
}
|
|
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"
|
|
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);
|
|
4060
|
-
}
|
|
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();
|
|
4073
|
-
}
|
|
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
|
-
}
|
|
4616
|
+
function getHomeDir() {
|
|
4617
|
+
return process$1.env.HOME || process$1.env.USERPROFILE || "";
|
|
4086
4618
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
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
|
|
4619
|
+
const PLUGIN_PACKAGE_NAME = "@workclaw/openclaw-workclaw";
|
|
4620
|
+
function execAsync(command, options) {
|
|
4621
|
+
return new Promise((resolve2, reject) => {
|
|
4622
|
+
exec(command, { cwd: options.cwd, timeout: options.timeout }, (error, stdout, stderr) => {
|
|
4623
|
+
if (error) {
|
|
4624
|
+
reject(new Error(stderr || error.message));
|
|
4625
|
+
} else {
|
|
4626
|
+
resolve2(stdout);
|
|
4627
|
+
}
|
|
4119
4628
|
});
|
|
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
4629
|
});
|
|
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
4630
|
}
|
|
4171
|
-
function
|
|
4172
|
-
const
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
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) {
|
|
4191
|
-
this.config = config;
|
|
4192
|
-
this.logger = logger2;
|
|
4193
|
-
}
|
|
4194
|
-
/**
|
|
4195
|
-
* 获取 API 基础地址
|
|
4196
|
-
*/
|
|
4197
|
-
getApiBaseUrl() {
|
|
4198
|
-
if (this.config.baseUrl) {
|
|
4199
|
-
return this.config.baseUrl;
|
|
4200
|
-
}
|
|
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];
|
|
4206
|
-
}
|
|
4207
|
-
/**
|
|
4208
|
-
* 获取 WebSocket 地址
|
|
4209
|
-
*/
|
|
4210
|
-
getWsUrl() {
|
|
4211
|
-
if (this.config.wsUrl) {
|
|
4212
|
-
return this.config.wsUrl;
|
|
4213
|
-
}
|
|
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;
|
|
4234
|
-
}
|
|
4235
|
-
return true;
|
|
4236
|
-
}
|
|
4237
|
-
/**
|
|
4238
|
-
* 获取配置路径
|
|
4239
|
-
*/
|
|
4240
|
-
getConfigPath() {
|
|
4241
|
-
const paths = getPathInfo(this.scenario);
|
|
4242
|
-
return paths.configFile;
|
|
4243
|
-
}
|
|
4244
|
-
/**
|
|
4245
|
-
* 获取场景类型
|
|
4246
|
-
*/
|
|
4247
|
-
getScenario() {
|
|
4248
|
-
return this.scenario;
|
|
4249
|
-
}
|
|
4250
|
-
/**
|
|
4251
|
-
* 执行安装
|
|
4252
|
-
*/
|
|
4253
|
-
async install() {
|
|
4254
|
-
const spinner = logger.start("开始安装 (Box 环境)...");
|
|
4255
|
-
try {
|
|
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 环境)"
|
|
4292
|
-
};
|
|
4293
|
-
} catch (error) {
|
|
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
|
-
};
|
|
4301
|
-
}
|
|
4302
|
-
}
|
|
4303
|
-
/**
|
|
4304
|
-
* 获取 Access Token
|
|
4305
|
-
*/
|
|
4306
|
-
async getAccessToken() {
|
|
4307
|
-
try {
|
|
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;
|
|
4315
|
-
} catch {
|
|
4316
|
-
return null;
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
/**
|
|
4320
|
-
* 获取 Agent 配置
|
|
4321
|
-
*/
|
|
4322
|
-
async getAgentConfig(token) {
|
|
4323
|
-
try {
|
|
4324
|
-
const client = createHttpClient(this.getApiBaseUrl());
|
|
4325
|
-
const response = await client.get("/v1/agent/config", {
|
|
4326
|
-
headers: {
|
|
4327
|
-
Authorization: `Bearer ${token}`
|
|
4328
|
-
}
|
|
4329
|
-
});
|
|
4330
|
-
return response.data;
|
|
4331
|
-
} catch {
|
|
4332
|
-
return null;
|
|
4631
|
+
function deepMerge(target, source) {
|
|
4632
|
+
const result = { ...target };
|
|
4633
|
+
for (const key in source) {
|
|
4634
|
+
const sourceValue = source[key];
|
|
4635
|
+
const targetValue = target[key];
|
|
4636
|
+
if (sourceValue !== void 0 && sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
4637
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
4638
|
+
} else if (sourceValue !== void 0) {
|
|
4639
|
+
result[key] = sourceValue;
|
|
4333
4640
|
}
|
|
4334
4641
|
}
|
|
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 = {};
|
|
4343
|
-
}
|
|
4344
|
-
const safeConfig = existingConfig;
|
|
4345
|
-
const existingChannels = safeConfig.channels;
|
|
4346
|
-
const newConfig = {
|
|
4347
|
-
...safeConfig,
|
|
4348
|
-
channels: {
|
|
4349
|
-
...existingChannels || {},
|
|
4350
|
-
"openclaw-workclaw": {
|
|
4351
|
-
userId: agentConfig.userId,
|
|
4352
|
-
apiKey: token,
|
|
4353
|
-
baseUrl: this.getApiBaseUrl(),
|
|
4354
|
-
wsUrl: this.getWsUrl(),
|
|
4355
|
-
accounts: {
|
|
4356
|
-
default: {
|
|
4357
|
-
agentId: agentConfig.agentId
|
|
4358
|
-
}
|
|
4359
|
-
}
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
};
|
|
4363
|
-
await writeConfig(configPath, newConfig);
|
|
4364
|
-
}
|
|
4642
|
+
return result;
|
|
4365
4643
|
}
|
|
4366
|
-
|
|
4367
|
-
class LocalInstaller extends BaseInstaller {
|
|
4644
|
+
class LocalInstaller {
|
|
4368
4645
|
constructor(config) {
|
|
4369
|
-
|
|
4646
|
+
this.config = config;
|
|
4647
|
+
this.spinner = ora({ color: "cyan" }).start();
|
|
4370
4648
|
}
|
|
4649
|
+
spinner;
|
|
4650
|
+
prefixText = "";
|
|
4371
4651
|
validateConfig() {
|
|
4652
|
+
debugLog("[验证配置] 检查手机号码和密码...");
|
|
4372
4653
|
if (!this.config.phone) {
|
|
4373
|
-
throw new
|
|
4654
|
+
throw new AppError2(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
|
|
4374
4655
|
}
|
|
4375
4656
|
if (!this.config.userPass) {
|
|
4376
|
-
throw new
|
|
4657
|
+
throw new AppError2(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
|
|
4377
4658
|
}
|
|
4378
|
-
|
|
4379
|
-
getConfigPath() {
|
|
4380
|
-
return path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
4381
|
-
}
|
|
4382
|
-
getScenario() {
|
|
4383
|
-
return this.config.scenario;
|
|
4384
|
-
}
|
|
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");
|
|
4659
|
+
debugLog("[验证配置] 配置验证通过");
|
|
4393
4660
|
}
|
|
4394
4661
|
async install() {
|
|
4395
4662
|
try {
|
|
4396
|
-
|
|
4397
|
-
logger.info("=".repeat(40));
|
|
4663
|
+
debugLog("[安装开始]");
|
|
4398
4664
|
this.validateConfig();
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4665
|
+
const env = this.config.env || "test";
|
|
4666
|
+
const config = getConfig(env);
|
|
4667
|
+
const homeDir = getHomeDir();
|
|
4668
|
+
const paths = {
|
|
4669
|
+
home: path.join(homeDir, config.DIRS.OPENCLAW),
|
|
4670
|
+
extensions: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS),
|
|
4671
|
+
target: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS, config.PLUGIN_NAME),
|
|
4672
|
+
config: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.CONFIG_FILE),
|
|
4673
|
+
workspace: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.WORKSPACE),
|
|
4674
|
+
temp: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.TEMP)
|
|
4675
|
+
};
|
|
4676
|
+
debugLog(`[路径配置] home=${paths.home}`);
|
|
4677
|
+
debugLog(`[路径配置] extensions=${paths.extensions}`);
|
|
4678
|
+
debugLog(`[路径配置] target=${paths.target}`);
|
|
4679
|
+
debugLog(`[路径配置] config=${paths.config}`);
|
|
4680
|
+
debugLog(`[路径配置] workspace=${paths.workspace}`);
|
|
4681
|
+
const token = await this.doLogin(env);
|
|
4682
|
+
const boundConfig = await this.doFetchBoundConfig(env, token);
|
|
4683
|
+
await this.doCleanOldFiles(paths.target);
|
|
4684
|
+
await this.doDownloadFromNpm(paths);
|
|
4685
|
+
await this.doUpdateConfig(paths, boundConfig, env);
|
|
4686
|
+
this.spinner.stop();
|
|
4405
4687
|
} catch (error) {
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
logger.error(`安装失败 [${error.code}]: ${errorMessage}`);
|
|
4409
|
-
} else {
|
|
4410
|
-
logger.error(`安装失败: ${errorMessage}`);
|
|
4411
|
-
}
|
|
4412
|
-
return { success: false, error: errorMessage };
|
|
4688
|
+
this.spinner.stop();
|
|
4689
|
+
throw error;
|
|
4413
4690
|
}
|
|
4414
4691
|
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
const targetDir = this.getInstallPath();
|
|
4418
|
-
try {
|
|
4419
|
-
await fs.access(targetDir);
|
|
4420
|
-
spinner.text = "发现旧版本,正在清理...";
|
|
4421
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
4422
|
-
spinner.succeed("旧版本清理完成");
|
|
4423
|
-
} catch {
|
|
4424
|
-
spinner.succeed("未发现旧版本");
|
|
4425
|
-
}
|
|
4692
|
+
getPrefixText() {
|
|
4693
|
+
return this.prefixText;
|
|
4426
4694
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
}
|
|
4436
|
-
spinner.text = `Node.js 已就绪(${nodeVersion})`;
|
|
4437
|
-
} catch (error) {
|
|
4438
|
-
if (error instanceof AppError) {
|
|
4439
|
-
throw error;
|
|
4440
|
-
}
|
|
4441
|
-
spinner.fail("未检测到 Node.js,请检查环境配置");
|
|
4442
|
-
throw new AppError(ERROR_CODES.NODE_NOT_FOUND, "未检测到 Node.js,请检查环境配置");
|
|
4443
|
-
}
|
|
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,请检查环境配置");
|
|
4695
|
+
async doLogin(env) {
|
|
4696
|
+
this.spinner.prefixText = this.prefixText;
|
|
4697
|
+
this.spinner.text = chalk.cyan("正在验证账号...");
|
|
4698
|
+
debugLog(`[用户登录] 手机号: ${this.config.phone}`);
|
|
4699
|
+
debugLog("[用户登录] RSA 加密密码...");
|
|
4700
|
+
const encryptedPassword = rsaEncrypt(this.config.userPass);
|
|
4701
|
+
if (!encryptedPassword) {
|
|
4702
|
+
throw new AppError2(ERROR_CODES.LOGIN_FAILED, "密码加密失败");
|
|
4450
4703
|
}
|
|
4704
|
+
debugLog("[用户登录] 密码加密成功");
|
|
4705
|
+
debugLog("[用户登录] 调用登录接口...");
|
|
4706
|
+
const token = await login(this.config.phone, encryptedPassword, env);
|
|
4707
|
+
this.prefixText += chalk.green(` ✓ 账号验证成功
|
|
4708
|
+
`);
|
|
4709
|
+
debugLog("[用户登录] 登录成功");
|
|
4710
|
+
return token;
|
|
4451
4711
|
}
|
|
4452
|
-
async
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4712
|
+
async doFetchBoundConfig(env, token) {
|
|
4713
|
+
this.spinner.prefixText = this.prefixText;
|
|
4714
|
+
this.spinner.text = chalk.cyan("正在获取绑定信息...");
|
|
4715
|
+
debugLog("[获取配置] 调用绑定配置接口...");
|
|
4716
|
+
const boundConfig = await fetchBoundConfig(token, this.config.phone, env);
|
|
4717
|
+
debugLog(`[获取配置] agentId=${boundConfig.agentId}, appKey=${boundConfig.appKey?.slice(0, 8)}...`);
|
|
4718
|
+
this.prefixText += chalk.green(` ✓ 绑定信息获取成功
|
|
4719
|
+
`);
|
|
4720
|
+
debugLog("[获取配置] 获取成功");
|
|
4721
|
+
return boundConfig;
|
|
4722
|
+
}
|
|
4723
|
+
async doCleanOldFiles(targetPath) {
|
|
4724
|
+
this.spinner.prefixText = this.prefixText;
|
|
4725
|
+
this.spinner.text = chalk.cyan("正在检查已安装版本...");
|
|
4726
|
+
debugLog(`[清理旧版本] 检查目录: ${targetPath}`);
|
|
4461
4727
|
try {
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
});
|
|
4466
|
-
|
|
4728
|
+
await fs.access(targetPath);
|
|
4729
|
+
this.spinner.text = chalk.cyan("正在清理旧文件...");
|
|
4730
|
+
debugLog("[清理旧版本] 发现旧版本,开始清理...");
|
|
4731
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
4732
|
+
this.prefixText += chalk.green(` ✓ 旧版本清理完成
|
|
4733
|
+
`);
|
|
4734
|
+
debugLog("[清理旧版本] 清理完成");
|
|
4467
4735
|
} catch {
|
|
4468
|
-
|
|
4469
|
-
|
|
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;
|
|
4736
|
+
this.prefixText += chalk.green(` ✓ 无旧版本需要清理
|
|
4737
|
+
`);
|
|
4738
|
+
debugLog("[清理旧版本] 未发现旧版本");
|
|
4501
4739
|
}
|
|
4502
|
-
throw new Error(res.message || "登录失败");
|
|
4503
4740
|
}
|
|
4504
|
-
async
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
};
|
|
4741
|
+
async doDownloadFromNpm(paths) {
|
|
4742
|
+
this.spinner.prefixText = this.prefixText;
|
|
4743
|
+
this.spinner.text = chalk.cyan("正在安装插件...");
|
|
4744
|
+
debugLog("[安装插件] 创建扩展目录...");
|
|
4745
|
+
await fs.mkdir(paths.extensions, { recursive: true });
|
|
4746
|
+
debugLog(`[安装插件] 扩展目录: ${paths.extensions}`);
|
|
4747
|
+
debugLog("[安装插件] 执行 openclaw plugins install 命令");
|
|
4748
|
+
try {
|
|
4749
|
+
await execAsync(
|
|
4750
|
+
`openclaw plugins install ${PLUGIN_PACKAGE_NAME} --dangerously-force-unsafe-install`,
|
|
4751
|
+
{ timeout: 12e4 }
|
|
4752
|
+
);
|
|
4753
|
+
debugLog("[安装插件] openclaw plugins install 执行成功");
|
|
4754
|
+
this.prefixText += chalk.green(` ✓ 插件安装完成
|
|
4755
|
+
`);
|
|
4756
|
+
} catch (error) {
|
|
4757
|
+
const errorMsg = error.message || "未知错误";
|
|
4758
|
+
debugLog(`[安装插件] 安装失败: ${errorMsg}`);
|
|
4759
|
+
throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `插件安装失败: ${errorMsg}`);
|
|
4522
4760
|
}
|
|
4523
|
-
throw new Error(res.message || "获取绑定配置失败");
|
|
4524
4761
|
}
|
|
4525
|
-
async
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4762
|
+
async doUpdateConfig(paths, boundConfig, env) {
|
|
4763
|
+
this.spinner.prefixText = this.prefixText;
|
|
4764
|
+
this.spinner.text = chalk.cyan("正在生成配置...");
|
|
4765
|
+
debugLog("[更新配置] 创建目录...");
|
|
4766
|
+
await fs.mkdir(paths.home, { recursive: true });
|
|
4767
|
+
await fs.mkdir(paths.workspace, { recursive: true });
|
|
4768
|
+
debugLog("[更新配置] 目录创建完成");
|
|
4769
|
+
debugLog("[更新配置] 读取原有配置...");
|
|
4531
4770
|
let originalConfig = {};
|
|
4532
4771
|
try {
|
|
4533
|
-
const content = await fs.readFile(
|
|
4772
|
+
const content = await fs.readFile(paths.config, "utf-8");
|
|
4534
4773
|
originalConfig = JSON.parse(content);
|
|
4535
|
-
|
|
4774
|
+
debugLog("[更新配置] 读取原有配置成功");
|
|
4536
4775
|
} catch {
|
|
4537
|
-
|
|
4776
|
+
debugLog("[更新配置] 无原有配置");
|
|
4538
4777
|
}
|
|
4539
|
-
const
|
|
4540
|
-
|
|
4778
|
+
const config = getConfig(env);
|
|
4779
|
+
const newConfig = {
|
|
4780
|
+
// diagnostics: 诊断配置
|
|
4541
4781
|
diagnostics: {
|
|
4542
|
-
// 启用诊断功能
|
|
4543
4782
|
enabled: true,
|
|
4544
|
-
// 诊断标志列表
|
|
4545
4783
|
flags: ["*"]
|
|
4546
4784
|
},
|
|
4547
|
-
// 浏览器配置
|
|
4785
|
+
// browser: 浏览器配置
|
|
4548
4786
|
browser: {
|
|
4549
|
-
// 禁用内置浏览器
|
|
4550
4787
|
enabled: false
|
|
4551
4788
|
},
|
|
4552
|
-
// 模型配置
|
|
4789
|
+
// models: 模型配置
|
|
4553
4790
|
models: {
|
|
4554
|
-
// 配置合并模式:merge 会将新配置与现有配置合并
|
|
4555
4791
|
mode: "merge",
|
|
4556
|
-
// 模型提供商配置
|
|
4557
4792
|
providers: {
|
|
4558
|
-
// SiliconFlow MiniMax 提供商
|
|
4559
4793
|
"siliconflow-minimax": {
|
|
4560
|
-
|
|
4561
|
-
baseUrl: boundConfig.modelApiBaseUrl || this.getDefaultModelUrl(),
|
|
4562
|
-
// API 密钥,用于认证
|
|
4794
|
+
baseUrl: boundConfig.modelApiBaseUrl || config.MODEL_BASE_URL,
|
|
4563
4795
|
apiKey: boundConfig.modelApiKey,
|
|
4564
|
-
// API 类型,这里使用 OpenAI 兼容格式
|
|
4565
4796
|
api: "openai-completions",
|
|
4566
|
-
// 是否使用 Authorization header 进行认证
|
|
4567
4797
|
authHeader: true,
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
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
|
-
]
|
|
4798
|
+
models: [{
|
|
4799
|
+
id: "Pro/MiniMaxAI/MiniMax-M2.5",
|
|
4800
|
+
name: "MiniMax-M2.5",
|
|
4801
|
+
reasoning: false,
|
|
4802
|
+
input: ["text"],
|
|
4803
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
4804
|
+
contextWindow: 2e5,
|
|
4805
|
+
maxTokens: 65536
|
|
4806
|
+
}]
|
|
4598
4807
|
}
|
|
4599
4808
|
}
|
|
4600
4809
|
},
|
|
4601
|
-
//
|
|
4810
|
+
// agents: 代理配置
|
|
4602
4811
|
agents: {
|
|
4603
|
-
// 默认 Agent 设置
|
|
4604
4812
|
defaults: {
|
|
4605
|
-
|
|
4606
|
-
model: {
|
|
4607
|
-
// 主要使用的模型
|
|
4608
|
-
primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5"
|
|
4609
|
-
},
|
|
4610
|
-
// 模型映射表
|
|
4813
|
+
model: { primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5" },
|
|
4611
4814
|
models: {
|
|
4612
|
-
// 模型的别名配置
|
|
4613
4815
|
"siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5": {
|
|
4614
|
-
// 模型别名
|
|
4615
4816
|
alias: "Pro/MiniMaxAI/MiniMax-M2.5"
|
|
4616
4817
|
}
|
|
4617
4818
|
},
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
// 压缩配置
|
|
4621
|
-
compaction: {
|
|
4622
|
-
// 压缩模式:safeguard 模式
|
|
4623
|
-
mode: "safeguard"
|
|
4624
|
-
},
|
|
4625
|
-
// 默认详细模式:full 表示完整输出
|
|
4819
|
+
workspace: paths.workspace,
|
|
4820
|
+
compaction: { mode: "safeguard" },
|
|
4626
4821
|
verboseDefault: "full",
|
|
4627
|
-
// 最大并发数
|
|
4628
4822
|
maxConcurrent: 4,
|
|
4629
|
-
|
|
4630
|
-
subagents: {
|
|
4631
|
-
// 子 Agent 最大并发数
|
|
4632
|
-
maxConcurrent: 8
|
|
4633
|
-
}
|
|
4823
|
+
subagents: { maxConcurrent: 8 }
|
|
4634
4824
|
},
|
|
4635
|
-
|
|
4636
|
-
list: [
|
|
4637
|
-
{
|
|
4638
|
-
// Agent ID
|
|
4639
|
-
id: "main",
|
|
4640
|
-
// Agent 工作区路径
|
|
4641
|
-
workspace: workspacePath
|
|
4642
|
-
}
|
|
4643
|
-
]
|
|
4825
|
+
list: [{ id: "main", workspace: paths.workspace }]
|
|
4644
4826
|
},
|
|
4645
|
-
//
|
|
4827
|
+
// tools: 工具配置
|
|
4646
4828
|
tools: {
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
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
|
-
}
|
|
4829
|
+
deny: ["image", "web_search", "web_fetch"],
|
|
4830
|
+
media: { image: { enabled: false } },
|
|
4831
|
+
web: { search: { enabled: false }, fetch: { enabled: false } },
|
|
4832
|
+
elevated: { enabled: true, allowFrom: { webchat: ["*"] } },
|
|
4833
|
+
exec: { security: "full" }
|
|
4680
4834
|
},
|
|
4681
|
-
// 绑定配置
|
|
4682
|
-
bindings: [
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
// 匹配通道名称
|
|
4688
|
-
channel: "openclaw-workclaw",
|
|
4689
|
-
// 匹配账号ID
|
|
4690
|
-
accountId: "default"
|
|
4691
|
-
}
|
|
4692
|
-
}
|
|
4693
|
-
],
|
|
4694
|
-
// 消息配置
|
|
4835
|
+
// bindings: 绑定配置
|
|
4836
|
+
bindings: [{
|
|
4837
|
+
agentId: "main",
|
|
4838
|
+
match: { channel: "openclaw-workclaw", accountId: "default" }
|
|
4839
|
+
}],
|
|
4840
|
+
// messages: 消息配置
|
|
4695
4841
|
messages: {
|
|
4696
|
-
// 消息确认反应的作用域
|
|
4697
4842
|
ackReactionScope: "group-mentions"
|
|
4698
4843
|
},
|
|
4699
|
-
// 命令配置
|
|
4844
|
+
// commands: 命令配置
|
|
4700
4845
|
commands: {
|
|
4701
|
-
// 原生命令处理模式
|
|
4702
4846
|
native: "auto",
|
|
4703
|
-
// 技能命令处理模式
|
|
4704
4847
|
nativeSkills: "auto",
|
|
4705
|
-
// 允许使用restart命令
|
|
4706
4848
|
restart: true,
|
|
4707
|
-
// 所有者信息显示方式
|
|
4708
4849
|
ownerDisplay: "raw"
|
|
4709
4850
|
},
|
|
4710
|
-
// 会话配置
|
|
4851
|
+
// session: 会话配置
|
|
4711
4852
|
session: {
|
|
4712
|
-
// 会话隔离策略
|
|
4713
4853
|
dmScope: "per-account-channel-peer"
|
|
4714
4854
|
},
|
|
4715
|
-
// 钩子配置
|
|
4855
|
+
// hooks: 钩子配置
|
|
4716
4856
|
hooks: {
|
|
4717
4857
|
internal: {
|
|
4718
|
-
// 启用/禁用内部钩子
|
|
4719
4858
|
enabled: true,
|
|
4720
4859
|
entries: {
|
|
4721
|
-
// 启动时显示Markdown
|
|
4722
4860
|
"boot-md": { enabled: true },
|
|
4723
|
-
// 加载额外引导文件
|
|
4724
4861
|
"bootstrap-extra-files": { enabled: true },
|
|
4725
|
-
// 记录命令执行日志
|
|
4726
4862
|
"command-logger": { enabled: true },
|
|
4727
|
-
// 启用会话记忆
|
|
4728
4863
|
"session-memory": { enabled: true }
|
|
4729
4864
|
}
|
|
4730
4865
|
}
|
|
4731
4866
|
},
|
|
4732
|
-
//
|
|
4867
|
+
// channels: 通道配置
|
|
4733
4868
|
channels: {
|
|
4734
|
-
// WorkClaw 插件渠道
|
|
4735
4869
|
"openclaw-workclaw": {
|
|
4736
|
-
// 是否启用此渠道
|
|
4737
4870
|
enabled: true,
|
|
4738
|
-
// 连接模式:使用 WebSocket 连接
|
|
4739
4871
|
connectionMode: "websocket",
|
|
4740
|
-
// WebSocket 连接策略:每个 appKey 一个连接
|
|
4741
|
-
wsConnectionStrategy: "per-appKey",
|
|
4742
|
-
// 应用密钥,用于身份验证
|
|
4743
4872
|
appKey: boundConfig.appKey,
|
|
4744
|
-
// 应用密钥,用于身份验证
|
|
4745
4873
|
appSecret: boundConfig.appSecret,
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
// WebSocket 连接地址
|
|
4749
|
-
websocketUrl: this.config.wsUrl || this.getDefaultWsUrl(),
|
|
4750
|
-
// 是否允许不安全的 TLS 连接
|
|
4874
|
+
baseUrl: this.config.baseUrl || config.DEFAULT_BASE_URL,
|
|
4875
|
+
websocketUrl: this.config.wsUrl || config.DEFAULT_WS_URL,
|
|
4751
4876
|
allowInsecureTls: true,
|
|
4752
|
-
// 是否允许原始 JSON 载荷
|
|
4753
4877
|
allowRawJsonPayload: true,
|
|
4754
|
-
|
|
4878
|
+
userId: boundConfig.userId,
|
|
4879
|
+
dmPolicy: "open",
|
|
4880
|
+
allowFrom: ["*"],
|
|
4755
4881
|
accounts: {
|
|
4756
|
-
|
|
4757
|
-
default: {
|
|
4758
|
-
// 是否启用默认账号
|
|
4759
|
-
enabled: true,
|
|
4760
|
-
// 代理 ID
|
|
4761
|
-
agentId: boundConfig.agentId
|
|
4762
|
-
}
|
|
4882
|
+
default: { enabled: true, agentId: boundConfig.agentId }
|
|
4763
4883
|
}
|
|
4764
4884
|
}
|
|
4765
4885
|
},
|
|
4766
|
-
//
|
|
4886
|
+
// gateway: 网关配置
|
|
4767
4887
|
gateway: {
|
|
4768
|
-
// 运行模式
|
|
4769
4888
|
mode: "local",
|
|
4770
|
-
// 监听端口
|
|
4771
4889
|
port: 18789,
|
|
4772
|
-
|
|
4773
|
-
bind: "loopback",
|
|
4774
|
-
// 认证配置(为空字符串会使用原始配置中的值)
|
|
4890
|
+
bind: "lan",
|
|
4775
4891
|
auth: {
|
|
4776
|
-
|
|
4777
|
-
mode: "token",
|
|
4778
|
-
// 认证令牌(为空会使用原始配置中的值)
|
|
4779
|
-
token: ""
|
|
4780
|
-
},
|
|
4781
|
-
// Tailscale 配置
|
|
4782
|
-
tailscale: {
|
|
4783
|
-
// Tailscale 模式
|
|
4784
|
-
mode: "off",
|
|
4785
|
-
// 退出时重置
|
|
4786
|
-
resetOnExit: false
|
|
4892
|
+
mode: "token"
|
|
4787
4893
|
},
|
|
4894
|
+
tailscale: { mode: "off", resetOnExit: false },
|
|
4788
4895
|
nodes: {
|
|
4789
|
-
// 禁用的节点命令(出于安全考虑)
|
|
4790
4896
|
denyCommands: [
|
|
4791
4897
|
"camera.snap",
|
|
4792
4898
|
"camera.clip",
|
|
4793
4899
|
"screen.record",
|
|
4794
4900
|
"contacts.add",
|
|
4795
4901
|
"calendar.add",
|
|
4796
|
-
"reminders.add"
|
|
4797
|
-
"sms.send",
|
|
4798
|
-
"sms.search"
|
|
4902
|
+
"reminders.add"
|
|
4799
4903
|
]
|
|
4800
4904
|
},
|
|
4801
|
-
// 控制面板配置
|
|
4802
4905
|
controlUi: {
|
|
4803
|
-
// 允许非安全认证
|
|
4804
4906
|
allowInsecureAuth: true,
|
|
4805
|
-
// 禁用设备认证
|
|
4806
4907
|
dangerouslyDisableDeviceAuth: true,
|
|
4807
|
-
// 允许 Host header 源回退
|
|
4808
4908
|
dangerouslyAllowHostHeaderOriginFallback: true,
|
|
4809
|
-
|
|
4810
|
-
allowedOrigins: [
|
|
4811
|
-
"http://localhost:18789",
|
|
4812
|
-
"http://127.0.0.1:18789"
|
|
4813
|
-
]
|
|
4909
|
+
allowedOrigins: ["http://localhost:18789", "http://127.0.0.1:18789"]
|
|
4814
4910
|
}
|
|
4815
4911
|
},
|
|
4816
|
-
// 技能配置
|
|
4912
|
+
// skills: 技能配置
|
|
4817
4913
|
skills: {
|
|
4818
|
-
load: {
|
|
4819
|
-
// 监听技能文件变化
|
|
4820
|
-
watch: true
|
|
4821
|
-
}
|
|
4914
|
+
load: { watch: true }
|
|
4822
4915
|
},
|
|
4823
|
-
// 日志配置
|
|
4916
|
+
// logging: 日志配置
|
|
4824
4917
|
logging: {
|
|
4825
|
-
// 日志级别
|
|
4826
4918
|
level: "info",
|
|
4827
|
-
// 控制台日志级别
|
|
4828
4919
|
consoleLevel: "info",
|
|
4829
|
-
// 控制台样式
|
|
4830
4920
|
consoleStyle: "pretty",
|
|
4831
|
-
// 敏感信息脱敏
|
|
4832
4921
|
redactSensitive: "off",
|
|
4833
|
-
// 日志文件路径(空表示使用系统默认)
|
|
4834
4922
|
file: "",
|
|
4835
|
-
// 脱敏模式
|
|
4836
4923
|
redactPatterns: ["sk-.*"]
|
|
4837
4924
|
},
|
|
4838
|
-
// 插件配置
|
|
4925
|
+
// plugins: 插件配置
|
|
4839
4926
|
plugins: {
|
|
4840
|
-
|
|
4841
|
-
allow: ["openclaw-workclaw"],
|
|
4842
|
-
// 插件安装配置
|
|
4927
|
+
allow: [config.PLUGIN_NAME],
|
|
4843
4928
|
installs: {
|
|
4844
|
-
|
|
4845
|
-
"openclaw-workclaw": {
|
|
4846
|
-
// 插件来源类型:npm 表示从 NPM 安装
|
|
4929
|
+
[config.PLUGIN_NAME]: {
|
|
4847
4930
|
source: "npm",
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
// 插件安装路径
|
|
4851
|
-
installPath: this.getExtensionsPath()
|
|
4931
|
+
spec: PLUGIN_PACKAGE_NAME,
|
|
4932
|
+
installPath: paths.target
|
|
4852
4933
|
}
|
|
4853
4934
|
},
|
|
4854
|
-
// 插件入口配置
|
|
4855
4935
|
entries: {
|
|
4856
|
-
|
|
4857
|
-
"openclaw-workclaw": {
|
|
4858
|
-
// 是否启用此插件
|
|
4859
|
-
enabled: true
|
|
4860
|
-
}
|
|
4936
|
+
[config.PLUGIN_NAME]: { enabled: true }
|
|
4861
4937
|
}
|
|
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: ""
|
|
4880
4938
|
}
|
|
4881
4939
|
};
|
|
4882
|
-
const finalConfig =
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
agentId,
|
|
4891
|
-
success: true
|
|
4892
|
-
}, {
|
|
4893
|
-
headers: {
|
|
4894
|
-
Authorization: token
|
|
4895
|
-
}
|
|
4896
|
-
});
|
|
4897
|
-
logger.success("智小途插件初始化回调成功");
|
|
4898
|
-
} catch {
|
|
4899
|
-
logger.warn("回调通知失败,请手动确认");
|
|
4900
|
-
}
|
|
4901
|
-
}
|
|
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);
|
|
4940
|
+
const finalConfig = deepMerge(originalConfig, newConfig);
|
|
4941
|
+
this.spinner.prefixText = this.prefixText;
|
|
4942
|
+
this.spinner.text = chalk.cyan("正在写入配置...");
|
|
4943
|
+
debugLog("[更新配置] 写入配置文件...");
|
|
4944
|
+
await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
|
|
4945
|
+
this.prefixText += chalk.green(` ✓ 配置更新完成
|
|
4946
|
+
`);
|
|
4947
|
+
debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
|
|
4942
4948
|
}
|
|
4943
4949
|
}
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
async action(...args) {
|
|
4951
|
-
const options = args[args.length - 1];
|
|
4952
|
-
await this.handleInit(options);
|
|
4950
|
+
function checkEnv() {
|
|
4951
|
+
debugLog("[环境检查] 检测 Node.js 版本...");
|
|
4952
|
+
const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
|
|
4953
|
+
debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
|
|
4954
|
+
if (!semver.gte(nodeVersion, "18.0.0")) {
|
|
4955
|
+
throw new AppError2(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
|
|
4953
4956
|
}
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
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;
|
|
4957
|
+
debugLog("[环境检查] Node.js 版本检查通过");
|
|
4958
|
+
debugLog("[环境检查] 检测 npm...");
|
|
4959
|
+
try {
|
|
4960
|
+
const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
|
|
4961
|
+
debugLog(`[环境检查] npm 版本: ${npmVersion}`);
|
|
4962
|
+
debugLog("[环境检查] npm 检测通过");
|
|
4963
|
+
} catch {
|
|
4964
|
+
throw new AppError2(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
|
|
4971
4965
|
}
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4966
|
+
}
|
|
4967
|
+
async function createLocalCommand(options) {
|
|
4968
|
+
setDebug(!!options.debug);
|
|
4969
|
+
debugLog("[初始化] 开始处理...");
|
|
4970
|
+
debugLog(`[初始化] 参数: scenario=${options.scenario}, env=${options.env}, phone=${options.phone}, debug=${options.debug}`);
|
|
4971
|
+
checkEnv();
|
|
4972
|
+
debugLog("[初始化] 环境检查通过");
|
|
4973
|
+
try {
|
|
4974
|
+
let scenario = options.scenario;
|
|
4975
|
+
let env = options.env;
|
|
4976
|
+
let phone = options.phone;
|
|
4977
|
+
let userPass = options.userPass;
|
|
4978
|
+
const questions = [];
|
|
4979
|
+
if (!scenario) {
|
|
4980
|
+
debugLog("[初始化] 需要选择安装场景");
|
|
4981
|
+
questions.push({
|
|
4982
|
+
type: "list",
|
|
4983
|
+
name: "scenario",
|
|
4984
|
+
message: chalk.cyan("请选择安装场景:"),
|
|
4985
|
+
default: "windows-local",
|
|
4986
|
+
choices: [
|
|
4987
|
+
{ name: `${chalk.green("Windows")} ${chalk.dim("本地安装")}`, value: "windows-local" },
|
|
4988
|
+
{ name: `${chalk.green("macOS")} ${chalk.dim("本地安装")}`, value: "mac-local" },
|
|
4989
|
+
{ name: `${chalk.green("Linux")} ${chalk.dim("本地安装")}`, value: "linux-local" }
|
|
4990
|
+
]
|
|
4991
|
+
});
|
|
4992
|
+
} else {
|
|
4993
|
+
debugLog(`[初始化] 使用命令行参数: scenario=${scenario}`);
|
|
4995
4994
|
}
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
4995
|
+
if (!env) {
|
|
4996
|
+
debugLog("[初始化] 需要选择环境");
|
|
4997
|
+
questions.push({
|
|
4998
|
+
type: "list",
|
|
4999
|
+
name: "env",
|
|
5000
|
+
message: chalk.cyan("请选择环境:"),
|
|
5001
|
+
default: "test",
|
|
5002
|
+
choices: [
|
|
5003
|
+
{ name: `${chalk.green("测试环境")} ${chalk.dim("(test)")}`, value: "test" },
|
|
5004
|
+
{ name: `${chalk.red("正式环境")} ${chalk.dim("(prod)")}`, value: "prod" }
|
|
5005
|
+
]
|
|
5006
|
+
});
|
|
5007
|
+
} else {
|
|
5008
|
+
debugLog(`[初始化] 使用命令行参数: env=${env}`);
|
|
5007
5009
|
}
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5010
|
+
if (!phone) {
|
|
5011
|
+
debugLog("[初始化] 需要输入手机号码");
|
|
5012
|
+
questions.push({
|
|
5013
|
+
type: "input",
|
|
5014
|
+
name: "phone",
|
|
5015
|
+
message: chalk.cyan("请输入手机号码:"),
|
|
5016
|
+
validate: (value) => {
|
|
5017
|
+
if (!value || value.trim() === "") {
|
|
5018
|
+
return chalk.red("手机号码不能为空");
|
|
5019
|
+
}
|
|
5020
|
+
if (!/^1[3-9]\d{9}$/.test(value)) {
|
|
5021
|
+
return chalk.yellow("请输入正确的手机号码");
|
|
5022
|
+
}
|
|
5023
|
+
return true;
|
|
5024
|
+
}
|
|
5025
|
+
});
|
|
5026
|
+
} else {
|
|
5027
|
+
debugLog("[初始化] 使用命令行参数: phone");
|
|
5016
5028
|
}
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5029
|
+
if (!userPass) {
|
|
5030
|
+
debugLog("[初始化] 需要输入用户密码");
|
|
5031
|
+
questions.push({
|
|
5032
|
+
type: "input",
|
|
5033
|
+
name: "userPass",
|
|
5034
|
+
message: chalk.cyan("请输入用户密码:"),
|
|
5035
|
+
validate: (value) => {
|
|
5036
|
+
if (!value || value.trim() === "") {
|
|
5037
|
+
return chalk.red("用户密码不能为空");
|
|
5038
|
+
}
|
|
5039
|
+
return true;
|
|
5040
|
+
}
|
|
5041
|
+
});
|
|
5042
|
+
} else {
|
|
5043
|
+
debugLog("[初始化] 使用命令行参数: userPass");
|
|
5044
|
+
}
|
|
5045
|
+
if (questions.length > 0) {
|
|
5046
|
+
debugLog(`[初始化] 开始交互式问答,共 ${questions.length} 个问题`);
|
|
5047
|
+
const answers = await inquirer.prompt(questions);
|
|
5048
|
+
debugLog("[初始化] 交互式问答完成");
|
|
5049
|
+
scenario = scenario || answers.scenario;
|
|
5050
|
+
env = env || answers.env || "test";
|
|
5051
|
+
phone = phone || answers.phone;
|
|
5052
|
+
userPass = userPass || answers.userPass;
|
|
5037
5053
|
}
|
|
5038
|
-
|
|
5054
|
+
debugLog(`[初始化] 最终参数: scenario=${scenario}, env=${env}, phone=${phone}, pluginVersion=${options.pluginVersion}`);
|
|
5055
|
+
debugLog("[初始化] 创建 LocalInstaller 实例...");
|
|
5056
|
+
const installer = new LocalInstaller({
|
|
5057
|
+
phone,
|
|
5058
|
+
userPass,
|
|
5059
|
+
scenario,
|
|
5060
|
+
env,
|
|
5061
|
+
pluginVersion: options.pluginVersion
|
|
5062
|
+
});
|
|
5063
|
+
debugLog("[初始化] 开始安装...");
|
|
5064
|
+
await installer.install();
|
|
5065
|
+
debugLog("[初始化] 安装完成");
|
|
5066
|
+
console.log(installer.getPrefixText() + boxen(
|
|
5067
|
+
`${chalk.green("✦")} 插件初始化成功!`,
|
|
5068
|
+
{
|
|
5069
|
+
title: `${chalk.bold.green("初始化成功")}`,
|
|
5070
|
+
titleAlignment: "center",
|
|
5071
|
+
padding: { top: 1, bottom: 1, left: 5, right: 5 },
|
|
5072
|
+
margin: 1,
|
|
5073
|
+
borderStyle: "round",
|
|
5074
|
+
borderColor: "green",
|
|
5075
|
+
textAlignment: "center"
|
|
5076
|
+
}
|
|
5077
|
+
));
|
|
5078
|
+
} catch (error) {
|
|
5079
|
+
debugLog(`[初始化] 发生错误: ${error.message}`);
|
|
5080
|
+
console.error(boxen(
|
|
5081
|
+
`${chalk.yellow(error.message)}`,
|
|
5082
|
+
{
|
|
5083
|
+
title: `${chalk.bold.red("初始化失败")}`,
|
|
5084
|
+
titleAlignment: "center",
|
|
5085
|
+
padding: { top: 1, bottom: 1, left: 5, right: 5 },
|
|
5086
|
+
margin: 1,
|
|
5087
|
+
borderStyle: "round",
|
|
5088
|
+
borderColor: "red",
|
|
5089
|
+
textAlignment: "center"
|
|
5090
|
+
}
|
|
5091
|
+
));
|
|
5092
|
+
process$1.exit(1);
|
|
5039
5093
|
}
|
|
5040
5094
|
}
|
|
5041
|
-
function
|
|
5042
|
-
|
|
5043
|
-
const cmd = initCommand.getCommand();
|
|
5044
|
-
program2.addCommand(cmd);
|
|
5095
|
+
function registerCommands(program2) {
|
|
5096
|
+
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);
|
|
5045
5097
|
}
|
|
5046
5098
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
5047
5099
|
const __dirname$1 = dirname(__filename$1);
|
|
@@ -5049,7 +5101,8 @@ const pkg = JSON.parse(readFileSync(resolve(__dirname$1, "../package.json"), "ut
|
|
|
5049
5101
|
const program = new Command();
|
|
5050
5102
|
function index() {
|
|
5051
5103
|
program.name(Object.keys(pkg.bin)[0]).version(pkg.version).description(pkg.description);
|
|
5052
|
-
|
|
5104
|
+
registerCommands(program);
|
|
5105
|
+
registerCommands$1(program);
|
|
5053
5106
|
program.parse(process$1.argv);
|
|
5054
5107
|
}
|
|
5055
5108
|
export {
|