@workclaw/cli 1.0.2 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,54 +1,165 @@
1
- # WorkClaw CLI
2
-
3
- WorkClaw CLI 是一个用于初始化和配置 WorkClaw 插件的命令行工具。
4
-
5
- ## 安装
6
-
7
- ```bash
8
- npm install -g @workclaw/cli
9
- ```
10
-
11
- ## 使用
12
-
13
- ### 交互式初始化
14
-
15
- ```bash
16
- workclaw init
17
- ```
18
-
19
- ### 命令行参数
20
-
21
- | 参数 | 说明 |
22
- | ----------- | -------------- |
23
- | --scenario | 安装场景 |
24
- | --env | 环境 (test/prod) |
25
- | --phone | 手机号码 |
26
- | --user-pass | 用户密码 |
27
- | --debug | 开启调试日志 |
28
-
29
- ### 示例
30
-
31
- ```bash
32
- # 交互式安装
33
- workclaw init
34
-
35
- # 指定参数安装
36
- workclaw init --phone 13800138000 --user-pass your_password
37
-
38
- # 开启调试日志
39
- workclaw init --debug
40
- ```
41
-
42
- ## 开发
43
-
44
- ```bash
45
- # 构建
46
- npm run build
47
-
48
- # 本地测试
49
- node dist/index.js init --debug
50
- ```
51
-
52
- ## 许可证
53
-
54
- MIT License
1
+ # WorkClaw CLI
2
+
3
+ WorkClaw CLI 是一个用于初始化和配置 WorkClaw 插件的命令行工具。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ # 使用 npm 安装
9
+ npm install -g @workclaw/cli
10
+
11
+ # 使用 npx 直接运行(无需安装)
12
+ npx @workclaw/cli local
13
+ ```
14
+
15
+ ## 使用
16
+
17
+ ### 命令
18
+
19
+ CLI 提供两个命令:
20
+
21
+ | 命令 | 说明 |
22
+ |------|------|
23
+ | `workclaw local` | 本地账户安装(需要登录) |
24
+ | `workclaw box` | 盒子设备安装(无需登录) |
25
+
26
+ ### 交互式初始化
27
+
28
+ ```bash
29
+ # 本地安装(需要手机号密码)
30
+ workclaw local
31
+ # 或使用 npx
32
+ npx @workclaw/cli local
33
+
34
+ # 盒子安装(需要 AppKey 和 AppSecret)
35
+ workclaw box
36
+ # 或使用 npx
37
+ npx @workclaw/cli box
38
+ ```
39
+
40
+ ### 命令行参数
41
+
42
+ #### local 命令参数
43
+
44
+ | 参数 | 缩写 | 说明 |
45
+ | ---------------- | -- | -------------- |
46
+ | --scenario | -s | 安装场景 |
47
+ | --env | -e | 环境 (test/prod) |
48
+ | --phone | 无 | 手机号码 |
49
+ | --user-pass | 无 | 用户密码 |
50
+ | --plugin-version | 无 | 插件版本号 |
51
+ | --debug | 无 | 调试模式 |
52
+
53
+ #### box 命令参数
54
+
55
+ | 参数 | 缩写 | 说明 |
56
+ | ---------------- | -- | -------------- |
57
+ | --scenario | -s | 安装场景 |
58
+ | --env | -e | 环境 (test/prod) |
59
+ | --app-key | 无 | App Key |
60
+ | --app-secret | 无 | App Secret |
61
+ | --plugin-version | 无 | 插件版本号 |
62
+ | --debug | 无 | 调试模式 |
63
+
64
+ ### 安装场景
65
+
66
+ | 场景 | 说明 |
67
+ | ------------- | ------------ |
68
+ | windows-local | Windows 本地安装 |
69
+ | mac-local | macOS 本地安装 |
70
+ | linux-local | Linux 本地安装 |
71
+
72
+ ### 环境
73
+
74
+ | 环境 | 说明 |
75
+ | ---- | ---- |
76
+ | test | 测试环境 |
77
+ | prod | 正式环境 |
78
+
79
+ ## 示例
80
+
81
+ ### 本地安装
82
+
83
+ #### 交互式安装(推荐新手使用)
84
+
85
+ ```bash
86
+ # 全局安装后运行
87
+ workclaw local
88
+
89
+ # 或使用 npx 直接运行
90
+ npx @workclaw/cli local
91
+ ```
92
+
93
+ #### 仅指定手机号和密码
94
+
95
+ ```bash
96
+ workclaw local --phone 13800138000 --user-pass your_password
97
+ ```
98
+
99
+ #### 完整参数指定
100
+
101
+ ```bash
102
+ workclaw local --scenario windows-local --env test --phone 13800138000 --user-pass your_password
103
+ ```
104
+
105
+ #### 使用正式环境
106
+
107
+ ```bash
108
+ workclaw local --env prod --phone 13800138000 --user-pass your_password
109
+ ```
110
+
111
+ #### macOS 安装
112
+
113
+ ```bash
114
+ workclaw local --scenario mac-local --phone 13800138000 --user-pass your_password
115
+ ```
116
+
117
+ #### Linux 安装
118
+
119
+ ```bash
120
+ workclaw local --scenario linux-local --phone 13800138000 --user-pass your_password
121
+ ```
122
+
123
+ ### 盒子安装
124
+
125
+ 盒子安装适用于不需要用户登录的场景,直接使用 AppKey 和 AppSecret 进行安装:
126
+
127
+ ```bash
128
+ # 基本用法
129
+ workclaw box --app-key your_app_key --app-secret your_app_secret
130
+
131
+ # 指定环境
132
+ workclaw box --app-key your_app_key --app-secret your_app_secret --env prod
133
+
134
+ # 指定场景
135
+ workclaw box --app-key your_app_key --app-secret your_app_secret --scenario linux-local
136
+ ```
137
+
138
+ ### 指定插件版本
139
+
140
+ 可以使用 `--plugin-version` 参数安装指定版本的插件(默认安装最新版):
141
+
142
+ ```bash
143
+ # 安装指定版本
144
+ workclaw local --phone 13800138000 --user-pass your_password --plugin-version 1.0.0
145
+ workclaw box --app-key your_app_key --app-secret your_app_secret --plugin-version 1.0.0
146
+ ```
147
+
148
+ ### 调试模式
149
+
150
+ 当安装失败时,可以使用 `--debug` 参数查看详细日志:
151
+
152
+ ```bash
153
+ # 本地安装 + 调试模式
154
+ workclaw local --phone 13800138000 --user-pass your_password --debug
155
+
156
+ # 盒子安装 + 调试模式
157
+ workclaw box --app-key your_app_key --app-secret your_app_secret --debug
158
+
159
+ # 完整参数 + 调试模式
160
+ workclaw local --scenario windows-local --env test --phone 13800138000 --user-pass your_password --debug
161
+ ```
162
+
163
+ ## 许可证
164
+
165
+ MIT License
@@ -14,36 +14,31 @@ import ora from "ora";
14
14
  import * as tar from "tar";
15
15
  import axios from "axios";
16
16
  import "node:os";
17
- class BaseCommand {
18
- constructor(program2) {
19
- this.program = program2;
20
- this.options = [];
21
- }
22
- options = [];
23
- init() {
24
- const cmd = this.program.command(this.command);
25
- cmd.description(this.description);
26
- this.options.forEach(({ name, description }) => {
27
- cmd.option(name, description);
28
- });
29
- cmd.action(this.action.bind(this));
17
+ let isDebug = false;
18
+ function setDebug(debug) {
19
+ isDebug = debug;
20
+ }
21
+ function debugLog(...args) {
22
+ if (isDebug) {
23
+ console.log(chalk.gray(`[DEBUG]`), ...args);
30
24
  }
31
25
  }
32
- const ERROR_CODES = {
26
+ const ERROR_CODES$1 = {
33
27
  PHONE_REQUIRED: "PHONE_REQUIRED",
34
28
  USER_PASS_REQUIRED: "USER_PASS_REQUIRED",
35
29
  LOGIN_FAILED: "LOGIN_FAILED",
30
+ GET_BOUND_CONFIG_FAILED: "GET_BOUND_CONFIG_FAILED",
36
31
  NODE_VERSION_LOW: "NODE_VERSION_LOW",
37
32
  NPM_NOT_FOUND: "NPM_NOT_FOUND",
38
33
  NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED"
39
34
  };
40
- class AppError extends Error {
35
+ let AppError$1 = class AppError extends Error {
41
36
  constructor(code, message) {
42
37
  super(message);
43
38
  this.code = code;
44
39
  this.name = "AppError";
45
40
  }
46
- }
41
+ };
47
42
  const CONFIG = {
48
43
  PLUGIN_NAME: "openclaw-workclaw",
49
44
  DEFAULT_BASE_URL: "https://workbrain.cn/backend-api",
@@ -95,7 +90,12 @@ function createHttpClient() {
95
90
  }
96
91
  async function httpPost(url, data, config) {
97
92
  const client = createHttpClient();
93
+ debugLog(`[HTTP POST] 请求地址: ${url}`);
94
+ debugLog(`[HTTP POST] 请求参数: ${JSON.stringify(data)}`);
95
+ debugLog(`[HTTP POST] 请求头: ${JSON.stringify(config?.headers || {})}`);
98
96
  const response = await client.post(url, data, config);
97
+ debugLog(`[HTTP POST] 响应状态: ${response.status}`);
98
+ debugLog(`[HTTP POST] 响应内容: ${JSON.stringify(response.data)}`);
99
99
  return {
100
100
  status: response.status,
101
101
  data: response.data
@@ -104,19 +104,29 @@ async function httpPost(url, data, config) {
104
104
  async function login(phone, password, env) {
105
105
  const config = getConfig(env);
106
106
  const url = `${config.API.TUZAI_BASE_URL}/user/login/pass`;
107
+ debugLog("[登录] 开始登录...");
108
+ debugLog(`[登录] 请求地址: ${url}`);
109
+ debugLog(`[登录] 请求参数: phone=${phone}, password=***`);
107
110
  const response = await httpPost(url, {
108
111
  phone,
109
112
  userPass: password
110
113
  });
111
114
  const data = response.data;
115
+ debugLog(`[登录] 响应数据: ${JSON.stringify(data)}`);
112
116
  if (data.code === 200 && data.data?.token) {
117
+ debugLog("[登录] 登录成功,获取到 token");
113
118
  return data.data.token;
114
119
  }
115
- throw new Error(data.message || "登录失败");
120
+ debugLog("[登录] 登录失败");
121
+ throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, data.message || "登录失败");
116
122
  }
117
123
  async function fetchBoundConfig(token, phone, env) {
118
124
  const config = getConfig(env);
119
125
  const url = `${config.API.TUZAI_BASE_URL}/work-bot/local/bound/init`;
126
+ debugLog("[获取绑定配置] 开始获取绑定配置...");
127
+ debugLog(`[获取绑定配置] 请求地址: ${url}`);
128
+ debugLog(`[获取绑定配置] 请求参数: phone=${phone}, localCode=001`);
129
+ debugLog(`[获取绑定配置] 请求头: Authorization=${token.substring(0, 20)}...`);
120
130
  const response = await httpPost(url, {
121
131
  phone,
122
132
  localCode: "001"
@@ -126,6 +136,7 @@ async function fetchBoundConfig(token, phone, env) {
126
136
  }
127
137
  });
128
138
  const data = response.data;
139
+ debugLog(`[获取绑定配置] 响应数据: ${JSON.stringify(data)}`);
129
140
  if (data.code === 200 && data.data) {
130
141
  const boundConfig = {
131
142
  appKey: data.data.app_key,
@@ -135,12 +146,21 @@ async function fetchBoundConfig(token, phone, env) {
135
146
  modelApiKey: data.data.model_api_key,
136
147
  modelApiBaseUrl: data.data.model_api_base_url
137
148
  };
149
+ debugLog("[获取绑定配置] 获取绑定配置成功");
150
+ debugLog(`[获取绑定配置] appKey: ${boundConfig.appKey}`);
151
+ debugLog(`[获取绑定配置] appSecret: ${boundConfig.appSecret ? "***" : "undefined"}`);
152
+ debugLog(`[获取绑定配置] userId: ${boundConfig.userId}`);
153
+ debugLog(`[获取绑定配置] agentId: ${boundConfig.agentId}`);
154
+ debugLog(`[获取绑定配置] modelApiKey: ${boundConfig.modelApiKey || "undefined"}`);
155
+ debugLog(`[获取绑定配置] modelApiBaseUrl: ${boundConfig.modelApiBaseUrl || "undefined"}`);
138
156
  if (!boundConfig.appKey || !boundConfig.appSecret || !boundConfig.agentId) {
139
- throw new Error("获取绑定配置失败:缺少必要字段");
157
+ debugLog("[获取绑定配置] 缺少必要字段");
158
+ throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, "获取绑定配置失败:缺少必要字段");
140
159
  }
141
160
  return boundConfig;
142
161
  }
143
- throw new Error(data.message || data.msg || "获取绑定配置失败");
162
+ debugLog("[获取绑定配置] 获取绑定配置失败");
163
+ throw new AppError$1(ERROR_CODES$1.GET_BOUND_CONFIG_FAILED, data.message || data.msg || "获取绑定配置失败");
144
164
  }
145
165
  var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
146
166
  function int2char(n) {
@@ -4032,19 +4052,10 @@ function rsaEncrypt(txt) {
4032
4052
  const result = encrypt.encrypt(txt);
4033
4053
  return result || null;
4034
4054
  }
4035
- let isDebug = false;
4036
- function setDebug(debug) {
4037
- isDebug = debug;
4038
- }
4039
- function debugLog(...args) {
4040
- if (isDebug) {
4041
- console.log(chalk.gray(`[DEBUG]`), ...args);
4042
- }
4043
- }
4044
- function getHomeDir() {
4055
+ function getHomeDir$1() {
4045
4056
  return process$1.env.HOME || process$1.env.USERPROFILE || "";
4046
4057
  }
4047
- const PLUGIN_PACKAGE_NAME = "@workclaw/openclaw-workclaw";
4058
+ const PLUGIN_PACKAGE_NAME$1 = "@workclaw/openclaw-workclaw";
4048
4059
  class LocalInstaller {
4049
4060
  constructor(config) {
4050
4061
  this.config = config;
@@ -4052,13 +4063,19 @@ class LocalInstaller {
4052
4063
  }
4053
4064
  spinner;
4054
4065
  prefixText = "";
4066
+ getPackageName() {
4067
+ if (this.config.pluginVersion) {
4068
+ return `${PLUGIN_PACKAGE_NAME$1}@${this.config.pluginVersion}`;
4069
+ }
4070
+ return PLUGIN_PACKAGE_NAME$1;
4071
+ }
4055
4072
  validateConfig() {
4056
4073
  debugLog("[验证配置] 检查手机号码和密码...");
4057
4074
  if (!this.config.phone) {
4058
- throw new AppError(ERROR_CODES.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4075
+ throw new AppError$1(ERROR_CODES$1.PHONE_REQUIRED, "手机号码不能为空,请使用 --phone 参数");
4059
4076
  }
4060
4077
  if (!this.config.userPass) {
4061
- throw new AppError(ERROR_CODES.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
4078
+ throw new AppError$1(ERROR_CODES$1.USER_PASS_REQUIRED, "用户密码不能为空,请使用 --user-pass 参数");
4062
4079
  }
4063
4080
  debugLog("[验证配置] 配置验证通过");
4064
4081
  }
@@ -4068,7 +4085,7 @@ class LocalInstaller {
4068
4085
  this.validateConfig();
4069
4086
  const env = this.config.env || "test";
4070
4087
  const config = getConfig(env);
4071
- const homeDir = getHomeDir();
4088
+ const homeDir = getHomeDir$1();
4072
4089
  const paths = {
4073
4090
  home: path.join(homeDir, config.DIRS.OPENCLAW),
4074
4091
  extensions: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS),
@@ -4103,7 +4120,7 @@ class LocalInstaller {
4103
4120
  debugLog("[用户登录] RSA 加密密码...");
4104
4121
  const encryptedPassword = rsaEncrypt(this.config.userPass);
4105
4122
  if (!encryptedPassword) {
4106
- throw new AppError(ERROR_CODES.LOGIN_FAILED, "密码加密失败");
4123
+ throw new AppError$1(ERROR_CODES$1.LOGIN_FAILED, "密码加密失败");
4107
4124
  }
4108
4125
  debugLog("[用户登录] 密码加密成功");
4109
4126
  debugLog("[用户登录] 调用登录接口...");
@@ -4163,8 +4180,10 @@ class LocalInstaller {
4163
4180
  for (let i = 1; i <= maxRetries; i++) {
4164
4181
  debugLog(`[下载插件] 第 ${i} 次尝试下载 tarball...`);
4165
4182
  try {
4183
+ const packageName = this.getPackageName();
4184
+ debugLog(`[下载插件] 下载包名: ${packageName}`);
4166
4185
  const output = execSync(
4167
- `npm pack ${PLUGIN_PACKAGE_NAME} --registry=https://mirrors.tencent.com/npm/ --ignore-scripts`,
4186
+ `npm pack ${packageName} --registry=https://mirrors.tencent.com/npm/ --ignore-scripts`,
4168
4187
  { cwd: tempDir, encoding: "utf-8", timeout: 6e4 }
4169
4188
  );
4170
4189
  const filename = output.trim().split("\n").pop() || "";
@@ -4181,7 +4200,7 @@ class LocalInstaller {
4181
4200
  }
4182
4201
  }
4183
4202
  if (!tarballPath || !await fs.access(tarballPath).then(() => true).catch(() => false)) {
4184
- throw new AppError(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
4203
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
4185
4204
  }
4186
4205
  this.spinner.prefixText = this.prefixText;
4187
4206
  this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("解压 tarball")}`;
@@ -4196,12 +4215,12 @@ class LocalInstaller {
4196
4215
  });
4197
4216
  debugLog("[下载插件] tarball 解压成功");
4198
4217
  } catch (error) {
4199
- throw new AppError(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
4218
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
4200
4219
  }
4201
4220
  const pluginPath = path.join(paths.target, "package.json");
4202
4221
  const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
4203
4222
  if (!pluginExists) {
4204
- throw new AppError(ERROR_CODES.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
4223
+ throw new AppError$1(ERROR_CODES$1.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
4205
4224
  }
4206
4225
  debugLog("[下载插件] 插件解压成功");
4207
4226
  this.prefixText += chalk.green(`✓ 插件下载完成
@@ -4360,7 +4379,7 @@ class LocalInstaller {
4360
4379
  installs: {
4361
4380
  [config.PLUGIN_NAME]: {
4362
4381
  source: "npm",
4363
- npm: PLUGIN_PACKAGE_NAME,
4382
+ npm: PLUGIN_PACKAGE_NAME$1,
4364
4383
  installPath: paths.target
4365
4384
  }
4366
4385
  },
@@ -4386,12 +4405,12 @@ class LocalInstaller {
4386
4405
  debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
4387
4406
  }
4388
4407
  }
4389
- function checkEnv() {
4408
+ function checkEnv$1() {
4390
4409
  debugLog("[环境检查] 检测 Node.js 版本...");
4391
4410
  const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
4392
4411
  debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
4393
4412
  if (!semver.gte(nodeVersion, "18.0.0")) {
4394
- throw new AppError(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
4413
+ throw new AppError$1(ERROR_CODES$1.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
4395
4414
  }
4396
4415
  debugLog("[环境检查] Node.js 版本检查通过");
4397
4416
  debugLog("[环境检查] 检测 npm...");
@@ -4400,156 +4419,628 @@ function checkEnv() {
4400
4419
  debugLog(`[环境检查] npm 版本: ${npmVersion}`);
4401
4420
  debugLog("[环境检查] npm 检测通过");
4402
4421
  } catch {
4403
- throw new AppError(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
4422
+ throw new AppError$1(ERROR_CODES$1.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
4404
4423
  }
4405
4424
  }
4406
- class InitCommand extends BaseCommand {
4407
- command = "init";
4408
- description = "初始化 WorkClaw 插件";
4409
- options = [
4410
- { name: "-s, --scenario <scenario>", description: "安装场景" },
4411
- { name: "-e, --env <env>", description: "环境 (test/prod)" },
4412
- { name: "--phone <phone>", description: "手机号码" },
4413
- { name: "--user-pass <userPass>", description: "用户密码" },
4414
- { name: "--debug", description: "开启调试日志" }
4415
- ];
4416
- constructor(program2) {
4417
- super(program2);
4418
- this.init();
4425
+ async function createLocalCommand(options) {
4426
+ setDebug(!!options.debug);
4427
+ debugLog("[初始化] 开始处理...");
4428
+ debugLog(`[初始化] 参数: scenario=${options.scenario}, env=${options.env}, phone=${options.phone}, debug=${options.debug}`);
4429
+ checkEnv$1();
4430
+ debugLog("[初始化] 环境检查通过");
4431
+ try {
4432
+ let scenario = options.scenario;
4433
+ let env = options.env;
4434
+ let phone = options.phone;
4435
+ let userPass = options.userPass;
4436
+ const questions = [];
4437
+ if (!scenario) {
4438
+ debugLog("[初始化] 需要选择安装场景");
4439
+ questions.push({
4440
+ type: "list",
4441
+ name: "scenario",
4442
+ message: `${nodeEmoji.get("desktop_computer")} 请选择安装场景:`,
4443
+ default: "windows-local",
4444
+ choices: [
4445
+ { name: `${chalk.green("Windows")} ${chalk.gray("本地安装")}`, value: "windows-local" },
4446
+ { name: `${chalk.green("macOS")} ${chalk.gray("本地安装")}`, value: "mac-local" },
4447
+ { name: `${chalk.green("Linux")} ${chalk.gray("本地安装")}`, value: "linux-local" }
4448
+ ]
4449
+ });
4450
+ } else {
4451
+ debugLog(`[初始化] 使用命令行参数: scenario=${scenario}`);
4452
+ }
4453
+ if (!env) {
4454
+ debugLog("[初始化] 需要选择环境");
4455
+ questions.push({
4456
+ type: "list",
4457
+ name: "env",
4458
+ message: `${nodeEmoji.get("globe_with_meridians")} 请选择环境:`,
4459
+ default: "test",
4460
+ choices: [
4461
+ { name: `${chalk.green("测试环境")} ${chalk.gray("(test)")}`, value: "test" },
4462
+ { name: `${chalk.red("正式环境")} ${chalk.gray("(prod)")}`, value: "prod" }
4463
+ ]
4464
+ });
4465
+ } else {
4466
+ debugLog(`[初始化] 使用命令行参数: env=${env}`);
4467
+ }
4468
+ if (!phone) {
4469
+ debugLog("[初始化] 需要输入手机号码");
4470
+ questions.push({
4471
+ type: "input",
4472
+ name: "phone",
4473
+ message: `${nodeEmoji.get("phone")} 请输入手机号码:`,
4474
+ validate: (value) => {
4475
+ if (!value || value.trim() === "") {
4476
+ return `${nodeEmoji.get("x")} 手机号码不能为空`;
4477
+ }
4478
+ if (!/^1[3-9]\d{9}$/.test(value)) {
4479
+ return `${nodeEmoji.get("warning")} 请输入正确的手机号码`;
4480
+ }
4481
+ return true;
4482
+ }
4483
+ });
4484
+ } else {
4485
+ debugLog("[初始化] 使用命令行参数: phone");
4486
+ }
4487
+ if (!userPass) {
4488
+ debugLog("[初始化] 需要输入用户密码");
4489
+ questions.push({
4490
+ type: "input",
4491
+ name: "userPass",
4492
+ message: `${nodeEmoji.get("key")} 请输入用户密码:`,
4493
+ validate: (value) => {
4494
+ if (!value || value.trim() === "") {
4495
+ return `${nodeEmoji.get("x")} 用户密码不能为空`;
4496
+ }
4497
+ return true;
4498
+ }
4499
+ });
4500
+ } else {
4501
+ debugLog("[初始化] 使用命令行参数: userPass");
4502
+ }
4503
+ if (questions.length > 0) {
4504
+ debugLog(`[初始化] 开始交互式问答,共 ${questions.length} 个问题`);
4505
+ const answers = await inquirer.prompt(questions);
4506
+ debugLog("[初始化] 交互式问答完成");
4507
+ scenario = scenario || answers.scenario;
4508
+ env = env || answers.env || "test";
4509
+ phone = phone || answers.phone;
4510
+ userPass = userPass || answers.userPass;
4511
+ }
4512
+ debugLog(`[初始化] 最终参数: scenario=${scenario}, env=${env}, phone=${phone}, pluginVersion=${options.pluginVersion}`);
4513
+ debugLog("[初始化] 创建 LocalInstaller 实例...");
4514
+ const installer = new LocalInstaller({
4515
+ phone,
4516
+ userPass,
4517
+ scenario,
4518
+ env,
4519
+ pluginVersion: options.pluginVersion
4520
+ });
4521
+ debugLog("[初始化] 开始安装...");
4522
+ await installer.install();
4523
+ debugLog("[初始化] 安装完成");
4524
+ console.log(installer.getPrefixText() + boxen(
4525
+ `${nodeEmoji.get("tada")} 插件初始化成功!`,
4526
+ {
4527
+ title: `${chalk.bold.green("初始化成功")}`,
4528
+ titleAlignment: "center",
4529
+ padding: { top: 1, bottom: 1, left: 5, right: 5 },
4530
+ margin: 1,
4531
+ borderStyle: "round",
4532
+ borderColor: "green",
4533
+ textAlignment: "center"
4534
+ }
4535
+ ));
4536
+ } catch (error) {
4537
+ debugLog(`[初始化] 发生错误: ${error.message}`);
4538
+ console.error(boxen(
4539
+ `${chalk.yellow(error.message)}`,
4540
+ {
4541
+ title: `${chalk.bold.red("初始化失败")}`,
4542
+ titleAlignment: "center",
4543
+ padding: { top: 1, bottom: 1, left: 5, right: 5 },
4544
+ margin: 1,
4545
+ borderStyle: "round",
4546
+ borderColor: "red",
4547
+ textAlignment: "center"
4548
+ }
4549
+ ));
4550
+ process$1.exit(1);
4419
4551
  }
4420
- async action(options) {
4421
- await this.handleInit(options);
4552
+ }
4553
+ function registerCommands$1(program2) {
4554
+ 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);
4555
+ }
4556
+ const ERROR_CODES = {
4557
+ APP_KEY_REQUIRED: "APP_KEY_REQUIRED",
4558
+ APP_SECRET_REQUIRED: "APP_SECRET_REQUIRED",
4559
+ NODE_VERSION_LOW: "NODE_VERSION_LOW",
4560
+ NPM_NOT_FOUND: "NPM_NOT_FOUND",
4561
+ NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED"
4562
+ };
4563
+ class AppError2 extends Error {
4564
+ constructor(code, message) {
4565
+ super(message);
4566
+ this.code = code;
4567
+ this.name = "AppError";
4568
+ }
4569
+ }
4570
+ function getHomeDir() {
4571
+ return process$1.env.HOME || process$1.env.USERPROFILE || "";
4572
+ }
4573
+ const PLUGIN_PACKAGE_NAME = "@workclaw/openclaw-workclaw";
4574
+ class BoxInstaller {
4575
+ constructor(config) {
4576
+ this.config = config;
4577
+ this.spinner = ora({ color: "cyan" }).start();
4578
+ }
4579
+ spinner;
4580
+ prefixText = "";
4581
+ getPackageName() {
4582
+ if (this.config.pluginVersion) {
4583
+ return `${PLUGIN_PACKAGE_NAME}@${this.config.pluginVersion}`;
4584
+ }
4585
+ return PLUGIN_PACKAGE_NAME;
4586
+ }
4587
+ validateConfig() {
4588
+ debugLog("[验证配置] 检查 appKey 和 appSecret...");
4589
+ if (!this.config.appKey) {
4590
+ throw new AppError2(ERROR_CODES.APP_KEY_REQUIRED, "AppKey 不能为空,请使用 --app-key 参数");
4591
+ }
4592
+ if (!this.config.appSecret) {
4593
+ throw new AppError2(ERROR_CODES.APP_SECRET_REQUIRED, "AppSecret 不能为空,请使用 --app-secret 参数");
4594
+ }
4595
+ debugLog("[验证配置] 配置验证通过");
4422
4596
  }
4423
- async handleInit(options) {
4424
- setDebug(!!options.debug);
4425
- debugLog("[初始化] 开始处理...");
4426
- debugLog(`[初始化] 参数: scenario=${options.scenario}, env=${options.env}, phone=${options.phone}, debug=${options.debug}`);
4427
- checkEnv();
4428
- debugLog("[初始化] 环境检查通过");
4597
+ async install() {
4429
4598
  try {
4430
- let scenario = options.scenario;
4431
- let env = options.env;
4432
- let phone = options.phone;
4433
- let userPass = options.userPass;
4434
- const questions = [];
4435
- if (!scenario) {
4436
- debugLog("[初始化] 需要选择安装场景");
4437
- questions.push({
4438
- type: "list",
4439
- name: "scenario",
4440
- message: `${nodeEmoji.get("desktop_computer")} 请选择安装场景:`,
4441
- default: "windows-local",
4442
- choices: [
4443
- { name: `${chalk.green("Windows")} ${chalk.gray("本地安装")}`, value: "windows-local" },
4444
- { name: `${chalk.green("macOS")} ${chalk.gray("本地安装")}`, value: "mac-local" },
4445
- { name: `${chalk.green("Linux")} ${chalk.gray("本地安装")}`, value: "linux-local" }
4446
- ]
4447
- });
4448
- } else {
4449
- debugLog(`[初始化] 使用命令行参数: scenario=${scenario}`);
4450
- }
4451
- if (!env) {
4452
- debugLog("[初始化] 需要选择环境");
4453
- questions.push({
4454
- type: "list",
4455
- name: "env",
4456
- message: `${nodeEmoji.get("globe_with_meridians")} 请选择环境:`,
4457
- default: "test",
4458
- choices: [
4459
- { name: `${chalk.green("测试环境")} ${chalk.gray("(test)")}`, value: "test" },
4460
- { name: `${chalk.red("正式环境")} ${chalk.gray("(prod)")}`, value: "prod" }
4461
- ]
4462
- });
4463
- } else {
4464
- debugLog(`[初始化] 使用命令行参数: env=${env}`);
4465
- }
4466
- if (!phone) {
4467
- debugLog("[初始化] 需要输入手机号码");
4468
- questions.push({
4469
- type: "input",
4470
- name: "phone",
4471
- message: `${nodeEmoji.get("phone")} 请输入手机号码:`,
4472
- validate: (value) => {
4473
- if (!value || value.trim() === "") {
4474
- return `${nodeEmoji.get("x")} 手机号码不能为空`;
4475
- }
4476
- if (!/^1[3-9]\d{9}$/.test(value)) {
4477
- return `${nodeEmoji.get("warning")} 请输入正确的手机号码`;
4478
- }
4479
- return true;
4599
+ debugLog("[安装开始]");
4600
+ this.validateConfig();
4601
+ const env = this.config.env || "test";
4602
+ const config = getConfig(env);
4603
+ const homeDir = getHomeDir();
4604
+ const paths = {
4605
+ home: path.join(homeDir, config.DIRS.OPENCLAW),
4606
+ extensions: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS),
4607
+ target: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.EXTENSIONS, config.PLUGIN_NAME),
4608
+ config: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.CONFIG_FILE),
4609
+ workspace: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.WORKSPACE),
4610
+ temp: path.join(homeDir, config.DIRS.OPENCLAW, config.DIRS.TEMP)
4611
+ };
4612
+ debugLog(`[路径配置] home=${paths.home}`);
4613
+ debugLog(`[路径配置] extensions=${paths.extensions}`);
4614
+ debugLog(`[路径配置] target=${paths.target}`);
4615
+ debugLog(`[路径配置] config=${paths.config}`);
4616
+ debugLog(`[路径配置] workspace=${paths.workspace}`);
4617
+ await this.doCleanOldFiles(paths.target);
4618
+ await this.doDownloadFromNpm(paths);
4619
+ await this.doUpdateConfig(paths, env);
4620
+ this.spinner.stop();
4621
+ } catch (error) {
4622
+ this.spinner.stop();
4623
+ throw error;
4624
+ }
4625
+ }
4626
+ getPrefixText() {
4627
+ return this.prefixText;
4628
+ }
4629
+ async doCleanOldFiles(targetPath) {
4630
+ this.spinner.prefixText = this.prefixText;
4631
+ this.spinner.text = `${chalk.cyan("清理旧版本")} ${chalk.dim("→")} ${chalk.yellow("检查目录...")}`;
4632
+ debugLog(`[清理旧版本] 检查目录: ${targetPath}`);
4633
+ try {
4634
+ await fs.access(targetPath);
4635
+ debugLog("[清理旧版本] 发现旧版本,开始清理...");
4636
+ this.spinner.prefixText = this.prefixText;
4637
+ this.spinner.text = `${chalk.cyan("清理旧版本")} ${chalk.dim("→")} ${chalk.yellow("正在清理...")}`;
4638
+ await fs.rm(targetPath, { recursive: true, force: true });
4639
+ this.prefixText += chalk.green(`✓ 旧版本清理完成
4640
+ `);
4641
+ debugLog("[清理旧版本] 清理完成");
4642
+ } catch {
4643
+ this.prefixText += chalk.green(`✓ 未发现旧版本
4644
+ `);
4645
+ debugLog("[清理旧版本] 未发现旧版本");
4646
+ }
4647
+ }
4648
+ async doDownloadFromNpm(paths) {
4649
+ this.spinner.prefixText = this.prefixText;
4650
+ this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("准备目录")}`;
4651
+ debugLog("[下载插件] 创建扩展目录...");
4652
+ await fs.mkdir(paths.extensions, { recursive: true });
4653
+ debugLog(`[下载插件] 扩展目录: ${paths.extensions}`);
4654
+ const tempDir = path.join(paths.temp, `install-${Date.now()}`);
4655
+ debugLog(`[下载插件] 创建临时目录: ${tempDir}`);
4656
+ await fs.mkdir(tempDir, { recursive: true });
4657
+ this.spinner.prefixText = this.prefixText;
4658
+ this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("下载 tarball")}`;
4659
+ debugLog("[下载插件] 执行 npm pack 下载源码包");
4660
+ debugLog(`[下载插件] 工作目录: ${tempDir}`);
4661
+ const tarballPath = await this.downloadTarball(tempDir);
4662
+ await this.extractTarball(tarballPath, paths, tempDir);
4663
+ }
4664
+ async downloadTarball(tempDir) {
4665
+ const maxRetries = 3;
4666
+ let lastError = "";
4667
+ let tarballPath = "";
4668
+ for (let i = 1; i <= maxRetries; i++) {
4669
+ debugLog(`[下载插件] 第 ${i} 次尝试下载 tarball...`);
4670
+ try {
4671
+ const packageName = this.getPackageName();
4672
+ debugLog(`[下载插件] 下载包名: ${packageName}`);
4673
+ const { execSync: execSync2 } = await import("node:child_process");
4674
+ const output = execSync2(
4675
+ `npm pack ${packageName} --registry=https://mirrors.tencent.com/npm/ --ignore-scripts`,
4676
+ { cwd: tempDir, encoding: "utf-8", timeout: 6e4 }
4677
+ );
4678
+ const filename = output.trim().split("\n").pop() || "";
4679
+ tarballPath = path.join(tempDir, filename);
4680
+ debugLog(`[下载插件] tarball 下载成功: ${tarballPath}`);
4681
+ break;
4682
+ } catch (error) {
4683
+ lastError = error.stderr?.toString() || error.message || "未知错误";
4684
+ debugLog(`[下载插件] 第 ${i} 次尝试失败: ${lastError}`);
4685
+ if (i < maxRetries) {
4686
+ debugLog("[下载插件] 等待 3 秒后重试...");
4687
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
4688
+ }
4689
+ }
4690
+ }
4691
+ if (!tarballPath || !await fs.access(tarballPath).then(() => true).catch(() => false)) {
4692
+ throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 下载失败: ${lastError}`);
4693
+ }
4694
+ return tarballPath;
4695
+ }
4696
+ async extractTarball(tarballPath, paths, tempDir) {
4697
+ this.spinner.prefixText = this.prefixText;
4698
+ this.spinner.text = `${chalk.cyan("下载插件")} ${chalk.dim("→")} ${chalk.yellow("解压 tarball")}`;
4699
+ debugLog(`[下载插件] 创建插件目录: ${paths.target}`);
4700
+ await fs.mkdir(paths.target, { recursive: true });
4701
+ debugLog(`[下载插件] 解压 tarball 到: ${paths.target}`);
4702
+ try {
4703
+ await tar.extract({
4704
+ file: tarballPath,
4705
+ cwd: paths.target,
4706
+ strip: 1
4707
+ });
4708
+ debugLog("[下载插件] tarball 解压成功");
4709
+ } catch (error) {
4710
+ throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, `tarball 解压失败: ${error.message}`);
4711
+ }
4712
+ const pluginPath = path.join(paths.target, "package.json");
4713
+ const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
4714
+ if (!pluginExists) {
4715
+ throw new AppError2(ERROR_CODES.NPM_INSTALL_FAILED, "插件解压后未找到 package.json");
4716
+ }
4717
+ debugLog("[下载插件] 插件解压成功");
4718
+ this.prefixText += chalk.green(`✓ 插件下载完成
4719
+ `);
4720
+ try {
4721
+ await fs.rm(tempDir, { recursive: true, force: true });
4722
+ debugLog("[下载插件] 清理临时目录完成");
4723
+ } catch {
4724
+ debugLog("[下载插件] 清理临时目录失败(忽略)");
4725
+ }
4726
+ }
4727
+ async doUpdateConfig(paths, env) {
4728
+ this.spinner.prefixText = this.prefixText;
4729
+ this.spinner.text = `${chalk.cyan("更新配置")} ${chalk.dim("→")} ${chalk.yellow("写入配置文件")}`;
4730
+ debugLog("[更新配置] 创建目录...");
4731
+ await fs.mkdir(paths.home, { recursive: true });
4732
+ await fs.mkdir(paths.workspace, { recursive: true });
4733
+ debugLog("[更新配置] 目录创建完成");
4734
+ debugLog("[更新配置] 读取原有配置...");
4735
+ let originalConfig = {};
4736
+ try {
4737
+ const content = await fs.readFile(paths.config, "utf-8");
4738
+ originalConfig = JSON.parse(content);
4739
+ debugLog("[更新配置] 读取原有配置成功");
4740
+ } catch {
4741
+ debugLog("[更新配置] 无原有配置");
4742
+ }
4743
+ const config = getConfig(env);
4744
+ const finalConfig = {
4745
+ ...originalConfig,
4746
+ diagnostics: {
4747
+ enabled: true,
4748
+ flags: ["*"]
4749
+ },
4750
+ browser: {
4751
+ enabled: false
4752
+ },
4753
+ models: {
4754
+ mode: "merge",
4755
+ providers: {
4756
+ "siliconflow-minimax": {
4757
+ baseUrl: config.MODEL_BASE_URL,
4758
+ apiKey: "",
4759
+ api: "openai-completions",
4760
+ authHeader: true,
4761
+ models: [{
4762
+ id: "Pro/MiniMaxAI/MiniMax-M2.5",
4763
+ name: "MiniMax-M2.5",
4764
+ reasoning: false,
4765
+ input: ["text"],
4766
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
4767
+ contextWindow: 2e5,
4768
+ maxTokens: 65536
4769
+ }]
4480
4770
  }
4481
- });
4482
- } else {
4483
- debugLog("[初始化] 使用命令行参数: phone");
4484
- }
4485
- if (!userPass) {
4486
- debugLog("[初始化] 需要输入用户密码");
4487
- questions.push({
4488
- type: "input",
4489
- name: "userPass",
4490
- message: `${nodeEmoji.get("key")} 请输入用户密码:`,
4491
- validate: (value) => {
4492
- if (!value || value.trim() === "") {
4493
- return `${nodeEmoji.get("x")} 用户密码不能为空`;
4771
+ }
4772
+ },
4773
+ agents: {
4774
+ defaults: {
4775
+ model: { primary: "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5" },
4776
+ models: {
4777
+ "siliconflow-minimax/Pro/MiniMaxAI/MiniMax-M2.5": {
4778
+ alias: "Pro/MiniMaxAI/MiniMax-M2.5"
4494
4779
  }
4495
- return true;
4780
+ },
4781
+ workspace: paths.workspace,
4782
+ compaction: { mode: "safeguard" },
4783
+ verboseDefault: "full",
4784
+ maxConcurrent: 4,
4785
+ subagents: { maxConcurrent: 8 }
4786
+ },
4787
+ list: [{ id: "main", workspace: paths.workspace }]
4788
+ },
4789
+ tools: {
4790
+ deny: ["image", "web_search", "web_fetch"],
4791
+ media: { image: { enabled: false } },
4792
+ web: { search: { enabled: false }, fetch: { enabled: false } },
4793
+ elevated: { enabled: true, allowFrom: { webchat: ["*"] } },
4794
+ exec: { security: "full" }
4795
+ },
4796
+ bindings: [{
4797
+ agentId: "main",
4798
+ match: { channel: "openclaw-workclaw", accountId: "default" }
4799
+ }],
4800
+ messages: { ackReactionScope: "group-mentions" },
4801
+ commands: {
4802
+ native: "auto",
4803
+ nativeSkills: "auto",
4804
+ restart: true,
4805
+ ownerDisplay: "raw"
4806
+ },
4807
+ session: { dmScope: "per-account-channel-peer" },
4808
+ hooks: {
4809
+ internal: {
4810
+ enabled: true,
4811
+ entries: {
4812
+ "boot-md": { enabled: true },
4813
+ "bootstrap-extra-files": { enabled: true },
4814
+ "command-logger": { enabled: true },
4815
+ "session-memory": { enabled: true }
4496
4816
  }
4497
- });
4498
- } else {
4499
- debugLog("[初始化] 使用命令行参数: userPass");
4500
- }
4501
- if (questions.length > 0) {
4502
- debugLog(`[初始化] 开始交互式问答,共 ${questions.length} 个问题`);
4503
- const answers = await inquirer.prompt(questions);
4504
- debugLog("[初始化] 交互式问答完成");
4505
- scenario = scenario || answers.scenario;
4506
- env = env || answers.env || "test";
4507
- phone = phone || answers.phone;
4508
- userPass = userPass || answers.userPass;
4509
- }
4510
- debugLog(`[初始化] 最终参数: scenario=${scenario}, env=${env}, phone=${phone}`);
4511
- debugLog("[初始化] 创建 LocalInstaller 实例...");
4512
- const installer = new LocalInstaller({
4513
- phone,
4514
- userPass,
4515
- scenario,
4516
- env
4817
+ }
4818
+ },
4819
+ channels: {
4820
+ "openclaw-workclaw": {
4821
+ enabled: true,
4822
+ connectionMode: "websocket",
4823
+ wsConnectionStrategy: "per-appKey",
4824
+ appKey: this.config.appKey,
4825
+ appSecret: this.config.appSecret,
4826
+ baseUrl: this.config.baseUrl || config.DEFAULT_BASE_URL,
4827
+ websocketUrl: this.config.wsUrl || config.DEFAULT_WS_URL,
4828
+ allowInsecureTls: true,
4829
+ allowRawJsonPayload: true,
4830
+ accounts: {
4831
+ default: { enabled: true, agentId: "" }
4832
+ }
4833
+ }
4834
+ },
4835
+ gateway: {
4836
+ mode: "local",
4837
+ port: 18789,
4838
+ bind: "loopback",
4839
+ auth: { mode: "token", token: "" },
4840
+ tailscale: { mode: "off", resetOnExit: false },
4841
+ nodes: {
4842
+ denyCommands: [
4843
+ "camera.snap",
4844
+ "camera.clip",
4845
+ "screen.record",
4846
+ "contacts.add",
4847
+ "calendar.add",
4848
+ "reminders.add",
4849
+ "sms.send",
4850
+ "sms.search"
4851
+ ]
4852
+ },
4853
+ controlUi: {
4854
+ allowInsecureAuth: true,
4855
+ dangerouslyDisableDeviceAuth: true,
4856
+ dangerouslyAllowHostHeaderOriginFallback: true,
4857
+ allowedOrigins: ["http://localhost:18789", "http://127.0.0.1:18789"]
4858
+ }
4859
+ },
4860
+ skills: { load: { watch: true } },
4861
+ logging: {
4862
+ level: "info",
4863
+ consoleLevel: "info",
4864
+ consoleStyle: "pretty",
4865
+ redactSensitive: "off",
4866
+ file: "",
4867
+ redactPatterns: ["sk-.*"]
4868
+ },
4869
+ plugins: {
4870
+ allow: [config.PLUGIN_NAME],
4871
+ installs: {
4872
+ [config.PLUGIN_NAME]: {
4873
+ source: "npm",
4874
+ npm: "@workclaw/openclaw-workclaw",
4875
+ installPath: paths.target
4876
+ }
4877
+ },
4878
+ entries: {
4879
+ [config.PLUGIN_NAME]: { enabled: true }
4880
+ }
4881
+ },
4882
+ wizard: {
4883
+ lastRunAt: "",
4884
+ lastRunVersion: "",
4885
+ lastRunCommand: "",
4886
+ lastRunMode: ""
4887
+ },
4888
+ meta: {
4889
+ lastTouchedVersion: "",
4890
+ lastTouchedAt: ""
4891
+ }
4892
+ };
4893
+ debugLog("[更新配置] 写入配置文件...");
4894
+ await fs.writeFile(paths.config, JSON.stringify(finalConfig, null, 2), "utf-8");
4895
+ this.prefixText += chalk.green(`✓ 配置文件更新成功
4896
+ `);
4897
+ debugLog(`[更新配置] 配置文件写入成功: ${paths.config}`);
4898
+ }
4899
+ }
4900
+ function checkEnv() {
4901
+ debugLog("[环境检查] 检测 Node.js 版本...");
4902
+ const nodeVersion = execSync("node --version", { stdio: "pipe" }).toString().trim();
4903
+ debugLog(`[环境检查] Node.js 版本: ${nodeVersion}`);
4904
+ if (!semver.gte(nodeVersion, "18.0.0")) {
4905
+ throw new AppError2(ERROR_CODES.NODE_VERSION_LOW, `Node.js 版本需要 >= 18.0.0,当前版本: ${nodeVersion}`);
4906
+ }
4907
+ debugLog("[环境检查] Node.js 版本检查通过");
4908
+ debugLog("[环境检查] 检测 npm...");
4909
+ try {
4910
+ const npmVersion = execSync("npm --version", { stdio: "pipe" }).toString().trim();
4911
+ debugLog(`[环境检查] npm 版本: ${npmVersion}`);
4912
+ debugLog("[环境检查] npm 检测通过");
4913
+ } catch {
4914
+ throw new AppError2(ERROR_CODES.NPM_NOT_FOUND, "未检测到 npm,请先安装 Node.js 和 npm");
4915
+ }
4916
+ }
4917
+ async function createBoxCommand(options) {
4918
+ setDebug(!!options.debug);
4919
+ debugLog("[初始化] 开始处理...");
4920
+ debugLog(`[初始化] 参数: scenario=${options.scenario}, env=${options.env}, appKey=${options.appKey}, debug=${options.debug}`);
4921
+ checkEnv();
4922
+ debugLog("[初始化] 环境检查通过");
4923
+ try {
4924
+ let scenario = options.scenario;
4925
+ let env = options.env;
4926
+ let appKey = options.appKey;
4927
+ let appSecret = options.appSecret;
4928
+ const questions = [];
4929
+ if (!scenario) {
4930
+ debugLog("[初始化] 需要选择安装场景");
4931
+ questions.push({
4932
+ type: "list",
4933
+ name: "scenario",
4934
+ message: `${nodeEmoji.get("desktop_computer")} 请选择安装场景:`,
4935
+ default: "windows-local",
4936
+ choices: [
4937
+ { name: `${chalk.green("Windows")} ${chalk.gray("本地安装")}`, value: "windows-local" },
4938
+ { name: `${chalk.green("macOS")} ${chalk.gray("本地安装")}`, value: "mac-local" },
4939
+ { name: `${chalk.green("Linux")} ${chalk.gray("本地安装")}`, value: "linux-local" }
4940
+ ]
4941
+ });
4942
+ } else {
4943
+ debugLog(`[初始化] 使用命令行参数: scenario=${scenario}`);
4944
+ }
4945
+ if (!env) {
4946
+ debugLog("[初始化] 需要选择环境");
4947
+ questions.push({
4948
+ type: "list",
4949
+ name: "env",
4950
+ message: `${nodeEmoji.get("globe_with_meridians")} 请选择环境:`,
4951
+ default: "test",
4952
+ choices: [
4953
+ { name: `${chalk.green("测试环境")} ${chalk.gray("(test)")}`, value: "test" },
4954
+ { name: `${chalk.red("正式环境")} ${chalk.gray("(prod)")}`, value: "prod" }
4955
+ ]
4517
4956
  });
4518
- debugLog("[初始化] 开始安装...");
4519
- await installer.install();
4520
- debugLog("[初始化] 安装完成");
4521
- console.log(installer.getPrefixText() + boxen(
4522
- `${nodeEmoji.get("tada")} 插件初始化成功!`,
4523
- {
4524
- title: `${chalk.bold.green("初始化成功")}`,
4525
- titleAlignment: "center",
4526
- padding: { top: 1, bottom: 1, left: 5, right: 5 },
4527
- margin: 1,
4528
- borderStyle: "round",
4529
- borderColor: "green",
4530
- textAlignment: "center"
4957
+ } else {
4958
+ debugLog(`[初始化] 使用命令行参数: env=${env}`);
4959
+ }
4960
+ if (!appKey) {
4961
+ debugLog("[初始化] 需要输入 AppKey");
4962
+ questions.push({
4963
+ type: "input",
4964
+ name: "appKey",
4965
+ message: `${nodeEmoji.get("key")} 请输入 AppKey:`,
4966
+ validate: (value) => {
4967
+ if (!value || value.trim() === "") {
4968
+ return `${nodeEmoji.get("x")} AppKey 不能为空`;
4969
+ }
4970
+ return true;
4531
4971
  }
4532
- ));
4533
- } catch (error) {
4534
- debugLog(`[初始化] 发生错误: ${error.message}`);
4535
- console.error(boxen(
4536
- `${chalk.yellow(error.message)}`,
4537
- {
4538
- title: `${chalk.bold.red("初始化失败")}`,
4539
- titleAlignment: "center",
4540
- padding: { top: 1, bottom: 1, left: 5, right: 5 },
4541
- margin: 1,
4542
- borderStyle: "round",
4543
- borderColor: "red",
4544
- textAlignment: "center"
4972
+ });
4973
+ } else {
4974
+ debugLog("[初始化] 使用命令行参数: appKey");
4975
+ }
4976
+ if (!appSecret) {
4977
+ debugLog("[初始化] 需要输入 AppSecret");
4978
+ questions.push({
4979
+ type: "input",
4980
+ name: "appSecret",
4981
+ message: `${nodeEmoji.get("key")} 请输入 AppSecret:`,
4982
+ validate: (value) => {
4983
+ if (!value || value.trim() === "") {
4984
+ return `${nodeEmoji.get("x")} AppSecret 不能为空`;
4985
+ }
4986
+ return true;
4545
4987
  }
4546
- ));
4547
- process$1.exit(1);
4988
+ });
4989
+ } else {
4990
+ debugLog("[初始化] 使用命令行参数: appSecret");
4991
+ }
4992
+ if (questions.length > 0) {
4993
+ debugLog(`[初始化] 开始交互式问答,共 ${questions.length} 个问题`);
4994
+ const answers = await inquirer.prompt(questions);
4995
+ debugLog("[初始化] 交互式问答完成");
4996
+ scenario = scenario || answers.scenario;
4997
+ env = env || answers.env || "test";
4998
+ appKey = appKey || answers.appKey;
4999
+ appSecret = appSecret || answers.appSecret;
4548
5000
  }
5001
+ debugLog(`[初始化] 最终参数: scenario=${scenario}, env=${env}, pluginVersion=${options.pluginVersion}`);
5002
+ debugLog("[初始化] 创建 BoxInstaller 实例...");
5003
+ const installer = new BoxInstaller({
5004
+ appKey,
5005
+ appSecret,
5006
+ scenario,
5007
+ env,
5008
+ pluginVersion: options.pluginVersion
5009
+ });
5010
+ debugLog("[初始化] 开始安装...");
5011
+ await installer.install();
5012
+ debugLog("[初始化] 安装完成");
5013
+ console.log(installer.getPrefixText() + boxen(
5014
+ `${nodeEmoji.get("tada")} 插件初始化成功!`,
5015
+ {
5016
+ title: `${chalk.bold.green("初始化成功")}`,
5017
+ titleAlignment: "center",
5018
+ padding: { top: 1, bottom: 1, left: 5, right: 5 },
5019
+ margin: 1,
5020
+ borderStyle: "round",
5021
+ borderColor: "green",
5022
+ textAlignment: "center"
5023
+ }
5024
+ ));
5025
+ } catch (error) {
5026
+ debugLog(`[初始化] 发生错误: ${error.message}`);
5027
+ console.error(boxen(
5028
+ `${chalk.yellow(error.message)}`,
5029
+ {
5030
+ title: `${chalk.bold.red("初始化失败")}`,
5031
+ titleAlignment: "center",
5032
+ padding: { top: 1, bottom: 1, left: 5, right: 5 },
5033
+ margin: 1,
5034
+ borderStyle: "round",
5035
+ borderColor: "red",
5036
+ textAlignment: "center"
5037
+ }
5038
+ ));
5039
+ process$1.exit(1);
4549
5040
  }
4550
5041
  }
4551
- function createInitCommandInstance(program2) {
4552
- return new InitCommand(program2);
5042
+ function registerCommands(program2) {
5043
+ program2.command("box").description("盒子设备安装(无需登录)").option("-s, --scenario <scenario>", "安装场景").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);
4553
5044
  }
4554
5045
  const __filename$1 = fileURLToPath(import.meta.url);
4555
5046
  const __dirname$1 = dirname(__filename$1);
@@ -4557,7 +5048,8 @@ const pkg = JSON.parse(readFileSync(resolve(__dirname$1, "../package.json"), "ut
4557
5048
  const program = new Command();
4558
5049
  function index() {
4559
5050
  program.name(Object.keys(pkg.bin)[0]).version(pkg.version).description(pkg.description);
4560
- createInitCommandInstance(program);
5051
+ registerCommands$1(program);
5052
+ registerCommands(program);
4561
5053
  program.parse(process$1.argv);
4562
5054
  }
4563
5055
  export {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import("./index-DdLG7OEj.js").then((cli) => {
2
+ import("./index-39c6v1yS.js").then((cli) => {
3
3
  cli.default();
4
4
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@workclaw/cli",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "1.0.17",
5
5
  "description": "WorkClaw CLI 工具 - 用于初始化和配置 WorkClaw 插件",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -38,4 +38,4 @@
38
38
  "semver": "^7.7.1",
39
39
  "tar": "^7.4.0"
40
40
  }
41
- }
41
+ }