@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.
@@ -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 fs from "node:fs/promises";
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 os from "node:os";
11
- import { execSync } from "node:child_process";
12
- class BaseCommand {
13
- program;
14
- options = [];
15
- constructor(program2) {
16
- this.program = program2;
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
- init() {
19
- const cmd = this.program.command(this.command);
20
- cmd.description(this.description);
21
- this.options.forEach(({ name, description }) => {
22
- cmd.option(name, description);
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
- cmd.action(this.action.bind(this));
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
- function createLogger() {
28
- return {
29
- info(message) {
30
- console.log(chalk.blue(""), message);
31
- },
32
- success(message) {
33
- console.log(chalk.green(""), message);
34
- },
35
- warn(message) {
36
- console.log(chalk.yellow("!"), message);
37
- },
38
- error(message) {
39
- console.log(chalk.red("✗"), message);
40
- },
41
- step(message) {
42
- console.log(chalk.cyan("›"), chalk.cyan(message));
43
- },
44
- start(message) {
45
- return ora({
46
- text: message,
47
- color: "cyan"
48
- }).start();
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
- const logger = createLogger();
53
- async function readConfig(configPath) {
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 content = await fs.readFile(configPath, "utf-8");
56
- return JSON.parse(content);
468
+ const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
469
+ debugLog(`[环境检查] npm 版本: ${npmVersion}`);
470
+ debugLog("[环境检查] npm 检测通过");
57
471
  } catch {
58
- logger.warn(`配置文件不存在或读取失败: ${configPath}`);
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 writeConfig(configPath, config) {
63
- const dir = path.dirname(configPath);
64
- await fs.mkdir(dir, { recursive: true });
65
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
66
- }
67
- const defaultConfig = {
68
- modelUrl: {
69
- test: "http://172.168.80.30:30005/v1",
70
- prod: "https://maas-api.workbrain.cn/v1/"
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
- const openApisUrl = {
74
- test: "http://172.168.80.30:32005/open-apis",
75
- prod: "https://workbrain.cn/backend-api/open-apis"
76
- };
77
- const apiBaseUrl = {
78
- test: "https://test-api.workbrain.cn/backend-api/",
79
- prod: "https://workbrain.cn/backend-api/"
80
- };
81
- function getApiBaseUrl(env) {
82
- return env === "test" ? apiBaseUrl.test : apiBaseUrl.prod;
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 getWsUrl(env) {
85
- return env === "test" ? openApisUrl.test : openApisUrl.prod;
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 getOpenApisUrl(env) {
88
- return env === "test" ? openApisUrl.test : openApisUrl.prod;
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 getModelUrl(env) {
91
- return env === "test" ? defaultConfig.modelUrl.test : defaultConfig.modelUrl.prod;
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
- try {
3979
- const encrypt = new JSEncrypt();
3980
- encrypt.setPublicKey(PUBLIC_KEY);
3981
- const encrypted = encrypt.encrypt(txt);
3982
- if (!encrypted) {
3983
- return null;
3984
- }
3985
- return encrypted;
3986
- } catch (error) {
3987
- console.error("RSA 加密失败:", error);
3988
- return null;
3989
- }
4611
+ const encrypt = new JSEncrypt();
4612
+ encrypt.setPublicKey(PUBLIC_KEY);
4613
+ const result = encrypt.encrypt(txt);
4614
+ return result || null;
3990
4615
  }
3991
- const ERROR_CODES = {
3992
- // 环境检查错误 (1000-1999)
3993
- NODE_VERSION_LOW: 1001,
3994
- NODE_NOT_FOUND: 1002,
3995
- NPM_NOT_FOUND: 1003,
3996
- // 配置错误 (2000-2999)
3997
- PHONE_REQUIRED: 2001,
3998
- USER_PASS_REQUIRED: 2002,
3999
- MISSING_AGENT_ID: 2003,
4000
- MISSING_APP_KEY: 2004,
4001
- MISSING_APP_SECRET: 2005,
4002
- // 网络错误 (3000-3999)
4003
- LOGIN_FAILED: 3001,
4004
- GET_BOUND_CONFIG_FAILED: 3002,
4005
- FETCH_CONFIG_FAILED: 3003,
4006
- // 文件系统错误 (4000-4999)
4007
- CONFIG_WRITE_FAILED: 4001,
4008
- FILE_COPY_FAILED: 4002,
4009
- NPM_INSTALL_FAILED: 4003,
4010
- // 加密错误 (5000-5999)
4011
- PASSWORD_ENCRYPT_FAILED: 5001
4012
- };
4013
- class AppError extends Error {
4014
- code;
4015
- details;
4016
- constructor(code, message, details) {
4017
- super(message);
4018
- this.name = "AppError";
4019
- this.code = code;
4020
- this.details = details;
4021
- }
4022
- toJSON() {
4023
- return {
4024
- code: this.code,
4025
- message: this.message,
4026
- details: this.details
4027
- };
4028
- }
4029
- toString() {
4030
- return `[${this.code}] ${this.message}`;
4031
- }
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
- async function httpGet(url, config) {
4088
- const startTime = Date.now();
4089
- printRequestLog({
4090
- method: "GET",
4091
- url,
4092
- headers: config?.headers
4093
- });
4094
- try {
4095
- const client = createHttpClient();
4096
- const response = await client.get(url, config);
4097
- const duration = Date.now() - startTime;
4098
- const result = {
4099
- status: response.status,
4100
- data: response.data
4101
- };
4102
- printResponseLog({
4103
- method: "GET",
4104
- url,
4105
- headers: config?.headers,
4106
- response: result,
4107
- duration
4108
- });
4109
- return result;
4110
- } catch (error) {
4111
- const duration = Date.now() - startTime;
4112
- const errorMessage = error instanceof Error ? error.message : String(error);
4113
- printRequestLog({
4114
- method: "GET",
4115
- url,
4116
- headers: config?.headers,
4117
- error: errorMessage,
4118
- duration
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 detectOS() {
4172
- const platform = os.platform();
4173
- switch (platform) {
4174
- case "win32":
4175
- return "windows-local";
4176
- case "darwin":
4177
- return "mac-local";
4178
- case "linux":
4179
- return "linux-local";
4180
- default:
4181
- return "linux-local";
4182
- }
4183
- }
4184
- async function ensureDir(dirPath) {
4185
- await fs.mkdir(dirPath, { recursive: true });
4186
- }
4187
- class BaseInstaller {
4188
- config;
4189
- logger;
4190
- constructor(config, logger2) {
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
- const PLUGIN_PACKAGE_NAME = "@workclaw/openclaw-workclaw";
4367
- class LocalInstaller extends BaseInstaller {
4644
+ class LocalInstaller {
4368
4645
  constructor(config) {
4369
- super(config, logger);
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 AppError(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4654
+ throw new AppError2(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4374
4655
  }
4375
4656
  if (!this.config.userPass) {
4376
- throw new AppError(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
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
- logger.info("开始安装 WorkClaw 插件(NPM 安装模式)");
4397
- logger.info("=".repeat(40));
4663
+ debugLog("[安装开始]");
4398
4664
  this.validateConfig();
4399
- await this.cleanOldFiles();
4400
- this.checkEnv();
4401
- await this.downloadFromNpm();
4402
- await this.fetchConfigAndUpdate();
4403
- logger.success("安装完成");
4404
- return { success: true, message: "插件安装成功" };
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
- const errorMessage = formatError(error);
4407
- if (error instanceof AppError) {
4408
- logger.error(`安装失败 [${error.code}]: ${errorMessage}`);
4409
- } else {
4410
- logger.error(`安装失败: ${errorMessage}`);
4411
- }
4412
- return { success: false, error: errorMessage };
4688
+ this.spinner.stop();
4689
+ throw error;
4413
4690
  }
4414
4691
  }
4415
- async cleanOldFiles() {
4416
- const spinner = logger.start("步骤 1: 清理旧版本");
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
- checkEnv() {
4428
- const spinner = logger.start("步骤 2: 检查环境");
4429
- try {
4430
- const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
4431
- const majorVersion = Number.parseInt(nodeVersion.replace("v", "").split(".")[0]);
4432
- if (majorVersion < 18) {
4433
- spinner.fail(`Node.js 版本过低(当前: ${nodeVersion},需要: v18.0.0 或更高版本)`);
4434
- throw new AppError(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本过低(当前: ${nodeVersion},需要: v18.0.0 或更高版本)`);
4435
- }
4436
- spinner.text = `Node.js 已就绪(${nodeVersion})`;
4437
- } catch (error) {
4438
- if (error instanceof AppError) {
4439
- throw error;
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 downloadFromNpm() {
4453
- const spinner = logger.start("步骤 3: 从 NPM 下载插件");
4454
- const extensionsDir = this.getExtensionsPath();
4455
- spinner.text = `插件安装目录: ${extensionsDir}`;
4456
- try {
4457
- await fs.mkdir(extensionsDir, { recursive: true });
4458
- } catch {
4459
- }
4460
- spinner.text = `正在从 NPM 下载插件 ${PLUGIN_PACKAGE_NAME}...`;
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
- execSync(`npm install ${PLUGIN_PACKAGE_NAME}`, {
4463
- cwd: extensionsDir,
4464
- stdio: "pipe"
4465
- });
4466
- spinner.succeed("插件下载完成");
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
- spinner.fail("插件下载失败");
4469
- throw new AppError(ERROR_CODES.NPM_INSTALL_FAILED, `插件 ${PLUGIN_PACKAGE_NAME} 下载失败`);
4470
- }
4471
- }
4472
- async fetchConfigAndUpdate() {
4473
- const spinner = logger.start("步骤 5: 获取配置并更新");
4474
- spinner.text = "正在登录...";
4475
- const token = await this.login();
4476
- spinner.text = "登录成功";
4477
- spinner.text = "正在获取绑定配置...";
4478
- const boundConfig = await this.fetchBoundConfig(token);
4479
- if (!boundConfig || !boundConfig.agentId) {
4480
- spinner.fail("获取绑定配置失败:缺少 agentId");
4481
- throw new Error("获取绑定配置失败:缺少 agentId");
4482
- }
4483
- spinner.text = "正在更新配置文件...";
4484
- await this.updateConfig(boundConfig, token);
4485
- spinner.succeed("配置文件更新完成");
4486
- }
4487
- async login() {
4488
- const apiUrl = `${this.getApiBaseUrl()}/user/login/pass`;
4489
- logger.info(`登录接口: ${apiUrl}`);
4490
- const encryptedPassword = rsaEncrypt(this.config.userPass);
4491
- if (!encryptedPassword) {
4492
- throw new Error("密码加密失败");
4493
- }
4494
- const response = await httpPost(apiUrl, {
4495
- phone: this.config.phone,
4496
- userPass: encryptedPassword
4497
- });
4498
- const res = response.data;
4499
- if (res.code === 200 && res.data && res.data.token) {
4500
- return res.data.token;
4736
+ this.prefixText += chalk.green(` ✓ 无旧版本需要清理
4737
+ `);
4738
+ debugLog("[清理旧版本] 未发现旧版本");
4501
4739
  }
4502
- throw new Error(res.message || "登录失败");
4503
4740
  }
4504
- async fetchBoundConfig(token) {
4505
- const apiUrl = `${this.getApiBaseUrl()}/work-bot/local/bound/init`;
4506
- logger.info(`绑定配置接口: ${apiUrl}`);
4507
- const response = await httpGet(apiUrl, {
4508
- headers: {
4509
- Authorization: token
4510
- }
4511
- });
4512
- const res = response.data;
4513
- if (res.code === 200 && res.data) {
4514
- return {
4515
- appKey: res.data.app_key,
4516
- appSecret: res.data.app_secret,
4517
- userId: res.data.user_id,
4518
- agentId: res.data.agent_id,
4519
- modelApiKey: res.data.model_api_key,
4520
- modelApiBaseUrl: res.data.model_api_base_url
4521
- };
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 updateConfig(boundConfig, token) {
4526
- const configPath = this.getConfigPath();
4527
- const workspacePath = this.getWorkspacePath();
4528
- logger.info(`配置文件路径: ${configPath}`);
4529
- logger.info(`工作区路径: ${workspacePath}`);
4530
- logger.info(`Agent ID: ${boundConfig.agentId}`);
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(configPath, "utf-8");
4772
+ const content = await fs.readFile(paths.config, "utf-8");
4534
4773
  originalConfig = JSON.parse(content);
4535
- logger.info("已读取现有配置文件");
4774
+ debugLog("[更新配置] 读取原有配置成功");
4536
4775
  } catch {
4537
- logger.info("创建新配置文件");
4776
+ debugLog("[更新配置] 无原有配置");
4538
4777
  }
4539
- const dynamicConfig = {
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
- // API 基础地址,从绑定配置获取或使用默认值
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
- models: [
4570
- {
4571
- // 模型标识符
4572
- id: "Pro/MiniMaxAI/MiniMax-M2.5",
4573
- // 模型显示名称
4574
- name: "MiniMax-M2.5",
4575
- // 是否支持推理能力
4576
- reasoning: false,
4577
- // 支持的输入类型
4578
- input: [
4579
- "text"
4580
- ],
4581
- // 费用配置(单位:分)
4582
- cost: {
4583
- // 输入费用
4584
- input: 0,
4585
- // 输出费用
4586
- output: 0,
4587
- // 缓存读取费用
4588
- cacheRead: 0,
4589
- // 缓存写入费用
4590
- cacheWrite: 0
4591
- },
4592
- // 上下文窗口大小(token 数)
4593
- contextWindow: 2e5,
4594
- // 最大输出 token 数
4595
- maxTokens: 65536
4596
- }
4597
- ]
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
- // Agent 配置
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
- workspace: workspacePath,
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
- // Agent 配置
4630
- subagents: {
4631
- // 子 Agent 最大并发数
4632
- maxConcurrent: 8
4633
- }
4823
+ subagents: { maxConcurrent: 8 }
4634
4824
  },
4635
- // Agent 列表
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
- deny: [
4649
- "image",
4650
- "web_search",
4651
- "web_fetch"
4652
- ],
4653
- // 媒体工具配置
4654
- media: {
4655
- // 图片处理工具
4656
- image: { enabled: false }
4657
- },
4658
- // 网页工具配置
4659
- web: {
4660
- // 网络搜索
4661
- search: { enabled: false },
4662
- // 网页抓取
4663
- fetch: { enabled: false }
4664
- },
4665
- // 提权工具配置
4666
- elevated: {
4667
- // 启用/禁用提权功能
4668
- enabled: true,
4669
- // 允许使用提权的来源
4670
- allowFrom: {
4671
- // webchat来源允许使用提权
4672
- webchat: ["*"]
4673
- }
4674
- },
4675
- // 执行工具配置
4676
- exec: {
4677
- // 安全模式级别
4678
- security: "full"
4679
- }
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
- // 绑定的智能体ID
4685
- agentId: "main",
4686
- match: {
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
- // API 基础地址
4747
- baseUrl: this.config.baseUrl || this.getDefaultBaseUrl(),
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
- // Gateway 节点配置
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
- // WorkClaw 插件安装信息
4845
- "openclaw-workclaw": {
4846
- // 插件来源类型:npm 表示从 NPM 安装
4929
+ [config.PLUGIN_NAME]: {
4847
4930
  source: "npm",
4848
- // NPM 包名
4849
- npm: PLUGIN_PACKAGE_NAME,
4850
- // 插件安装路径
4851
- installPath: this.getExtensionsPath()
4931
+ spec: PLUGIN_PACKAGE_NAME,
4932
+ installPath: paths.target
4852
4933
  }
4853
4934
  },
4854
- // 插件入口配置
4855
4935
  entries: {
4856
- // WorkClaw 插件入口
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 = this.mergeConfig(originalConfig, dynamicConfig);
4883
- await fs.writeFile(configPath, JSON.stringify(finalConfig, null, 2), "utf-8");
4884
- await this.sendCallback(boundConfig.agentId, token);
4885
- }
4886
- async sendCallback(agentId, token) {
4887
- try {
4888
- const callbackUrl = `${this.getOpenApisUrl()}/instance/agent-created/status`;
4889
- await httpPost(callbackUrl, {
4890
- agentId,
4891
- success: true
4892
- }, {
4893
- headers: {
4894
- Authorization: token
4895
- }
4896
- });
4897
- logger.success("智小途插件初始化回调成功");
4898
- } catch {
4899
- logger.warn("回调通知失败,请手动确认");
4900
- }
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
- class InitCommand extends BaseCommand {
4945
- command = "init";
4946
- description = "初始化 WorkClaw 插件";
4947
- /**
4948
- * 实现抽象的 action 方法
4949
- */
4950
- async action(...args) {
4951
- const options = args[args.length - 1];
4952
- await this.handleInit(options);
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
- * 获取 Commander 命令实例
4956
- */
4957
- getCommand() {
4958
- const cmd = new Command(this.command);
4959
- cmd.description(this.description).option("-s, --scenario <scenario>", "安装场景 (windows-local/mac-local/linux-local/linux-box)").option("-e, --env <env>", "环境 (test/prod)", "test").option("--phone <phone>", "手机号码").option("--user-pass <userPass>", "用户密码").option("--base-url <baseUrl>", "API 基础地址").option("--ws-url <wsUrl>", "WebSocket 地址").option("-f, --force", "强制覆盖", false).action(this.handleInit.bind(this)).addHelpText("after", `
4960
- 示例:
4961
- $ workclaw init --phone 13800138000 --user-pass 123456
4962
- $ workclaw init --scenario windows-local --env test
4963
- $ workclaw init --scenario linux-box --base-url https://api.example.com
4964
-
4965
- 场景说明:
4966
- windows-local - Windows 本地安装
4967
- mac-local - macOS 本地安装
4968
- linux-local - Linux 本地安装
4969
- linux-box - Linux Box 环境安装`);
4970
- return cmd;
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
- * 处理 init 命令
4974
- */
4975
- async handleInit(options) {
4976
- logger.info("WorkClaw CLI 插件初始化工具 v2.0.0");
4977
- logger.info("=".repeat(40));
4978
- const scenario = this.resolveScenario(options.scenario);
4979
- const env = this.resolveEnv(options.env);
4980
- logger.info(`安装场景: ${this.getScenarioLabel(scenario)}`);
4981
- logger.info(`环境: ${env === "test" ? "测试环境" : "正式环境"}`);
4982
- const config = {
4983
- phone: options.phone,
4984
- userPass: options.userPass,
4985
- baseUrl: options.baseUrl,
4986
- wsUrl: options.wsUrl,
4987
- force: options.force,
4988
- env,
4989
- scenario
4990
- };
4991
- const installer = this.createInstaller(config);
4992
- const result = await installer.install();
4993
- if (!result.success) {
4994
- process$1.exit(1);
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
- resolveScenario(scenario) {
5001
- if (scenario) {
5002
- const validScenarios = ["windows-local", "mac-local", "linux-local", "linux-box"];
5003
- if (validScenarios.includes(scenario)) {
5004
- return scenario;
5005
- }
5006
- logger.warn(`无效的场景: ${scenario},将使用自动检测`);
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
- return detectOS();
5009
- }
5010
- /**
5011
- * 解析环境
5012
- */
5013
- resolveEnv(env) {
5014
- if (env === "prod" || env === "test") {
5015
- return env;
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
- return "test";
5018
- }
5019
- /**
5020
- * 获取场景标签
5021
- */
5022
- getScenarioLabel(scenario) {
5023
- const labels = {
5024
- "windows-local": "Windows 本地安装",
5025
- "mac-local": "macOS 本地安装",
5026
- "linux-local": "Linux 本地安装",
5027
- "linux-box": "Linux Box 环境安装"
5028
- };
5029
- return labels[scenario];
5030
- }
5031
- /**
5032
- * 创建安装器实例
5033
- */
5034
- createInstaller(config) {
5035
- if (config.scenario === "linux-box") {
5036
- return new BoxInstaller(config);
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
- return new LocalInstaller(config);
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 createInitCommandInstance(program2) {
5042
- const initCommand = new InitCommand(program2);
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
- createInitCommandInstance(program);
5104
+ registerCommands(program);
5105
+ registerCommands$1(program);
5053
5106
  program.parse(process$1.argv);
5054
5107
  }
5055
5108
  export {