@aiyiran/myclaw 1.0.28 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -236,6 +236,16 @@ function runNew() {
236
236
  }
237
237
  }
238
238
 
239
+ // ============================================================================
240
+ // 微信接入向导
241
+ // ============================================================================
242
+
243
+ function runWeixin() {
244
+ const { WizardRunner } = require('./wizards/wizard-runner');
245
+ const config = require('./wizards/weixin.config.json');
246
+ new WizardRunner(config).run();
247
+ }
248
+
239
249
  // ============================================================================
240
250
  // WSL2 安装 (独立模块)
241
251
  // ============================================================================
@@ -519,6 +529,9 @@ function showHelp() {
519
529
  console.log('用法:');
520
530
  console.log(' myclaw <command>');
521
531
  console.log('');
532
+ console.log('向导 (交互式):');
533
+ console.log(' weixin 微信接入向导(步骤引导 + 教学说明)');
534
+ console.log('');
522
535
  console.log('命令:');
523
536
  console.log(' install 安装 OpenClaw 服务');
524
537
  console.log(' status 简化版状态查看(学生友好)');
@@ -532,11 +545,11 @@ function showHelp() {
532
545
  console.log(' help 显示帮助信息');
533
546
  console.log('');
534
547
  console.log('示例:');
548
+ console.log(' myclaw weixin # 打开微信接入向导');
535
549
  console.log(' myclaw install # 安装 OpenClaw');
536
550
  console.log(' myclaw status # 查看状态');
537
551
  console.log(' myclaw new helper # 创建名为 helper 的 Agent');
538
552
  console.log(' myclaw open # 打开默认控制台');
539
- console.log(' myclaw open http://... # 打开指定 URL');
540
553
  console.log(' myclaw patch # 注入 UI 扩展');
541
554
  console.log(' myclaw restart # 重启 Gateway');
542
555
  console.log('');
@@ -552,6 +565,8 @@ console.log(colors.blue + 'MyClaw v' + version + colors.nc);
552
565
 
553
566
  if (!command || command === 'help' || command === '--help' || command === '-h') {
554
567
  showHelp();
568
+ } else if (command === 'weixin') {
569
+ runWeixin();
555
570
  } else if (command === 'install') {
556
571
  runInstall();
557
572
  } else if (command === 'status') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -13,5 +13,9 @@
13
13
  },
14
14
  "keywords": [],
15
15
  "author": "",
16
- "license": "ISC"
16
+ "license": "ISC",
17
+ "dependencies": {
18
+ "chalk": "^4.1.2",
19
+ "ora": "^5.4.1"
20
+ }
17
21
  }
@@ -0,0 +1,120 @@
1
+ {
2
+ "id": "weixin",
3
+ "title": "微信接入向导",
4
+ "subtitle": "引导你一步步完成微信通道的接入配置",
5
+
6
+ "steps": [
7
+ {
8
+ "id": "check-plugins",
9
+ "icon": "🔍",
10
+ "label": "查看微信插件状态",
11
+ "type": "check",
12
+ "command": {
13
+ "exec": "openclaw plugins list",
14
+ "display": "openclaw plugins list"
15
+ },
16
+ "teach": {
17
+ "title": "查看微信插件状态",
18
+ "description": "列出系统中当前已安装的所有 OpenClaw 插件。\n\nOpenClaw 通过「插件」来支持不同平台(微信、飞书、钉钉等),\n每个插件负责一个平台的通信协议。",
19
+ "look_for": "在输出中查找 openclaw-weixin,有就说明插件已安装",
20
+ "tip": "🔒 只读操作,不修改任何配置,可以随时查看。"
21
+ }
22
+ },
23
+ {
24
+ "id": "check-channels",
25
+ "icon": "🔍",
26
+ "label": "查看渠道连接状态",
27
+ "type": "check",
28
+ "command": {
29
+ "exec": "openclaw channels list",
30
+ "display": "openclaw channels list"
31
+ },
32
+ "teach": {
33
+ "title": "查看所有渠道的连接状态",
34
+ "description": "列出系统中所有已配置的「渠道(Channel)」。\n\n渠道是平台账号与 OpenClaw 的实际连接通道:\n - 插件 = 能力(知道怎么对接微信)\n - 渠道 = 连接(你的微信账号已登录并在线)\n\n两者都需要就绪,微信功能才能正常运行。",
35
+ "look_for": "查找 openclaw-weixin,状态是否为 connected",
36
+ "tip": "🔒 只读操作,不修改任何配置,可以随时查看。"
37
+ }
38
+ },
39
+ {
40
+ "id": "install-plugin",
41
+ "icon": "📦",
42
+ "label": "安装微信插件",
43
+ "type": "action",
44
+ "command": {
45
+ "exec": "openclaw plugins install \"@tencent-weixin/openclaw-weixin\"",
46
+ "display": "openclaw plugins install \"@tencent-weixin/openclaw-weixin\""
47
+ },
48
+ "teach": {
49
+ "title": "安装微信插件",
50
+ "description": "从官方源下载并安装微信通道插件。\n\n这条命令做了三件事:\n 1. 找到 @tencent-weixin/openclaw-weixin 插件包\n 2. 从官方源下载插件代码\n 3. 安装到 OpenClaw 插件目录\n\n整个接入流程只需安装一次,重新登录时不需要重复执行。",
51
+ "look_for": "看到 installed successfully 或类似成功提示",
52
+ "tip": "⚠️ 需要网络连接,首次下载可能需要一点时间,请耐心等待。"
53
+ }
54
+ },
55
+ {
56
+ "id": "enable-plugin",
57
+ "icon": "⚙️",
58
+ "label": "启用微信插件",
59
+ "type": "action",
60
+ "command": {
61
+ "exec": "openclaw config set plugins.entries.openclaw-weixin.enabled true",
62
+ "display": "openclaw config set plugins.entries.openclaw-weixin.enabled true"
63
+ },
64
+ "teach": {
65
+ "title": "启用微信插件",
66
+ "description": "将微信插件的「启用状态」写入 OpenClaw 配置文件。\n\n安装插件 ≠ 启用插件。这类似于:\n - 安装 = 把软件下载到电脑\n - 启用 = 打开这个软件的开关\n\n这条命令修改了配置文件中的一个开关:\n plugins.entries.openclaw-weixin.enabled = true",
67
+ "look_for": "命令无报错即为成功,配置通常立即生效",
68
+ "tip": "💡 安装和启用是两个独立步骤,缺一不可。"
69
+ }
70
+ },
71
+ {
72
+ "id": "login-weixin",
73
+ "icon": "📱",
74
+ "label": "扫码登录微信",
75
+ "type": "interactive",
76
+ "command": {
77
+ "exec": "openclaw channels login --channel openclaw-weixin",
78
+ "display": "openclaw channels login --channel openclaw-weixin"
79
+ },
80
+ "teach": {
81
+ "title": "扫码登录微信账号",
82
+ "description": "将你的微信账号与 OpenClaw 渠道绑定。\n\n执行后终端会显示一个二维码,操作步骤:\n 1. 打开手机微信\n 2. 点击右上角「+」→「扫一扫」\n 3. 扫描终端里的二维码\n 4. 手机上点击「确认授权」\n\n重新连接时,只需重复这一步,不需要重装插件。",
83
+ "look_for": "终端显示「登录成功」或类似提示",
84
+ "tip": "📱 准备好手机,扫码后在手机上点击确认才算完成。"
85
+ }
86
+ },
87
+ {
88
+ "id": "restart-gateway",
89
+ "icon": "🔄",
90
+ "label": "重启 Gateway",
91
+ "type": "action",
92
+ "command": {
93
+ "exec": "openclaw gateway restart",
94
+ "display": "openclaw gateway restart"
95
+ },
96
+ "teach": {
97
+ "title": "重启 Gateway 使配置生效",
98
+ "description": "重启 OpenClaw 核心服务,让之前的所有配置变更生效。\n\nGateway 是 OpenClaw 的「大脑」,它在启动时读取配置。\n我们刚才修改了配置(安装并启用了插件),\n所以需要重启让 Gateway 加载新配置。\n\n类比:改了系统设置后,有时候需要重启电脑才生效。",
99
+ "look_for": "重启后 Gateway 运行中,控制台地址 http://127.0.0.1:18789",
100
+ "tip": "⏳ 重启期间短暂不可用,请等待几秒钟再继续。"
101
+ }
102
+ },
103
+ {
104
+ "id": "verify",
105
+ "icon": "✅",
106
+ "label": "验证微信接入是否成功",
107
+ "type": "check",
108
+ "command": {
109
+ "exec": "openclaw channels list",
110
+ "display": "openclaw channels list"
111
+ },
112
+ "teach": {
113
+ "title": "验证微信是否成功接入",
114
+ "description": "再次查看渠道列表,确认微信已正常连接。\n\n这和第 2 步用的是同一条命令,但现在应该能看到:\n - openclaw-weixin 出现在列表中\n - 状态显示为 connected(已连接)\n\n如果状态还是 disconnected,可能需要重新扫码(步骤 5)。",
115
+ "look_for": "openclaw-weixin 的状态为 connected ✅",
116
+ "tip": "🎉 如果看到 connected,恭喜完成微信接入!"
117
+ }
118
+ }
119
+ ]
120
+ }
@@ -0,0 +1,353 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * wizard-runner.js
5
+ * ─────────────────────────────────────────────────────────────
6
+ * 通用 TUI 向导引擎
7
+ *
8
+ * 读取一个 wizard config 对象,渲染交互式分屏界面:
9
+ * - 上半部分:步骤列表(↑↓ 移动光标)
10
+ * - 下半部分:当前步骤的教学说明 + 要执行的命令
11
+ * - Enter:进入执行流程(先展示说明,确认后再跑命令)
12
+ *
13
+ * 步骤类型(type):
14
+ * check 只读查询,直接 execSync + 显示输出
15
+ * action 有副作用的操作,execSync + 显示输出
16
+ * interactive 需要终端交互(如扫码),spawnSync + inherit stdio
17
+ *
18
+ * 使用方式:
19
+ * const { WizardRunner } = require('./wizard-runner');
20
+ * const config = require('./weixin.config.json');
21
+ * new WizardRunner(config).run();
22
+ */
23
+
24
+ const chalk = require('chalk');
25
+ const { execSync, spawnSync } = require('child_process');
26
+
27
+ // ─────────────────────────────────────────────────────────────
28
+ // 常量 & 工具
29
+ // ─────────────────────────────────────────────────────────────
30
+
31
+ const W = 62; // 界面宽度(字符数)
32
+
33
+ function hr(char = '─', w = W) {
34
+ return char.repeat(w);
35
+ }
36
+
37
+ function cls() {
38
+ process.stdout.write('\x1b[2J\x1b[H');
39
+ }
40
+
41
+ // 把多行字符串每行加前缀
42
+ function indent(text, prefix = ' ') {
43
+ return text.split('\n').map(l => prefix + l).join('\n');
44
+ }
45
+
46
+ // ─────────────────────────────────────────────────────────────
47
+ // 状态标签
48
+ // ─────────────────────────────────────────────────────────────
49
+
50
+ const BADGE = {
51
+ check: () => chalk.bgCyan.black(' 查 看 '),
52
+ pending: () => chalk.bgGray.white(' 未执行 '),
53
+ done: () => chalk.bgGreen.black(' ✓ 完成 '),
54
+ error: () => chalk.bgRed.white(' ✗ 失败 '),
55
+ };
56
+
57
+ // ─────────────────────────────────────────────────────────────
58
+ // WizardRunner
59
+ // ─────────────────────────────────────────────────────────────
60
+
61
+ class WizardRunner {
62
+ constructor(config) {
63
+ this.config = config;
64
+ this.steps = config.steps;
65
+ this.cursor = 0;
66
+ this._busy = false; // 执行期间屏蔽主键盘
67
+
68
+ // 初始化每步状态
69
+ this.statuses = {};
70
+ this.steps.forEach(s => {
71
+ this.statuses[s.id] = s.type === 'check' ? 'check' : 'pending';
72
+ });
73
+ }
74
+
75
+ // ───────────────────────────────────────────────────────────
76
+ // 渲染:主界面(上:步骤列表 / 下:当前步骤说明)
77
+ // ───────────────────────────────────────────────────────────
78
+
79
+ render() {
80
+ cls();
81
+ const { config, steps, cursor, statuses } = this;
82
+
83
+ // ── Header ──
84
+ console.log('');
85
+ console.log(' ' + chalk.bold.cyan('⚡ ' + config.title));
86
+ console.log(' ' + chalk.gray(config.subtitle));
87
+ console.log('');
88
+
89
+ // ── 步骤列表 ──
90
+ console.log(
91
+ ' ' + chalk.bold('步骤列表') +
92
+ chalk.dim(' ( ↑↓ 移动 Enter 执行 q 退出 )')
93
+ );
94
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
95
+
96
+ steps.forEach((step, i) => {
97
+ const selected = i === cursor;
98
+ const arrow = selected ? chalk.cyan.bold('▶') : ' ';
99
+ const numStr = chalk.dim(String(i + 1).padStart(2) + '.');
100
+ const label = selected
101
+ ? chalk.bold.white(step.label)
102
+ : chalk.white(step.label);
103
+ const badge = (BADGE[statuses[step.id]] || BADGE.pending)();
104
+
105
+ console.log(` ${arrow} ${numStr} ${step.icon} ${label} ${badge}`);
106
+ });
107
+
108
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
109
+ console.log('');
110
+
111
+ // ── 当前步骤说明 ──
112
+ const step = steps[cursor];
113
+ const teach = step.teach;
114
+
115
+ console.log(' ' + chalk.bold.yellow('📖 步骤说明'));
116
+ console.log('');
117
+ console.log(' ' + chalk.bold.white('【' + teach.title + '】'));
118
+ console.log('');
119
+ console.log(indent(teach.description, ' '));
120
+ console.log('');
121
+ console.log(' ' + chalk.dim('执行命令:'));
122
+ this._renderCommand(step.command.display);
123
+ console.log('');
124
+
125
+ if (teach.look_for) {
126
+ console.log(' ' + chalk.dim('👀 执行后观察:') + chalk.yellow(teach.look_for));
127
+ console.log('');
128
+ }
129
+
130
+ console.log(' ' + chalk.dim('💡 ') + chalk.gray(teach.tip));
131
+ console.log('');
132
+ }
133
+
134
+ // ───────────────────────────────────────────────────────────
135
+ // 渲染:执行前说明页(按 Enter 之后先展示,再确认执行)
136
+ // ───────────────────────────────────────────────────────────
137
+
138
+ renderPreExec(step) {
139
+ cls();
140
+ const teach = step.teach;
141
+
142
+ console.log('');
143
+ console.log(' ' + chalk.bold.cyan('▶ ' + step.icon + ' ' + step.label));
144
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
145
+ console.log('');
146
+
147
+ // 教学说明
148
+ console.log(' ' + chalk.bold.yellow('📋 执行前说明'));
149
+ console.log('');
150
+ console.log(indent(teach.description, ' '));
151
+ console.log('');
152
+
153
+ // 将要执行的命令(教学核心:让学生看清命令)
154
+ console.log(' ' + chalk.bold('将要执行的命令:'));
155
+ console.log('');
156
+ this._renderCommand(step.command.display);
157
+ console.log('');
158
+
159
+ if (step.type === 'interactive') {
160
+ console.log(' ' + chalk.yellow('⚠️ 此步骤是交互式操作,将接管终端(如显示二维码)'));
161
+ console.log('');
162
+ }
163
+
164
+ if (teach.tip) {
165
+ console.log(' ' + chalk.dim('💡 ') + chalk.gray(teach.tip));
166
+ console.log('');
167
+ }
168
+
169
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
170
+ console.log(
171
+ ' ' + chalk.dim('按 ') + chalk.bold('Enter') + chalk.dim(' 开始执行') +
172
+ chalk.dim(' | 按 ') + chalk.bold('q') + chalk.dim(' 取消')
173
+ );
174
+ }
175
+
176
+ // ───────────────────────────────────────────────────────────
177
+ // 渲染:执行中 header
178
+ // ───────────────────────────────────────────────────────────
179
+
180
+ renderExecHeader(step) {
181
+ cls();
182
+ console.log('');
183
+ console.log(' ' + chalk.bold.cyan('⚡ 正在执行:' + step.icon + ' ' + step.label));
184
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
185
+ console.log(' ' + chalk.dim('$ ' + step.command.display));
186
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
187
+ console.log('');
188
+ console.log(' ' + chalk.dim('↓ 命令输出 ↓'));
189
+ console.log('');
190
+ }
191
+
192
+ // ───────────────────────────────────────────────────────────
193
+ // 执行步骤(完整流程)
194
+ // ───────────────────────────────────────────────────────────
195
+
196
+ async executeStep(idx) {
197
+ const step = this.steps[idx];
198
+
199
+ // 1. 展示执行前说明页
200
+ this.renderPreExec(step);
201
+
202
+ // 2. 等待用户确认(Enter 继续,q 取消)
203
+ const confirmed = await this._waitForConfirm();
204
+ if (!confirmed) {
205
+ this.render();
206
+ return;
207
+ }
208
+
209
+ // 3. 展示执行中头部
210
+ this.renderExecHeader(step);
211
+
212
+ // 4. 关闭 raw mode,让命令输出正常渲染
213
+ process.stdin.setRawMode(false);
214
+
215
+ // 5. 执行命令
216
+ let success = false;
217
+ try {
218
+ if (step.type === 'interactive') {
219
+ // interactive:透传 stdin/stdout(扫码、确认等)
220
+ const result = spawnSync(step.command.exec, {
221
+ shell: true,
222
+ stdio: 'inherit',
223
+ });
224
+ success = result.status === 0;
225
+ } else {
226
+ // check / action:普通执行
227
+ execSync(step.command.exec, {
228
+ shell: true,
229
+ stdio: 'inherit',
230
+ });
231
+ success = true;
232
+ }
233
+ } catch (e) {
234
+ success = false;
235
+ }
236
+
237
+ // 6. 更新状态
238
+ this.statuses[step.id] = success ? 'done' : 'error';
239
+
240
+ // 7. 显示结果
241
+ console.log('');
242
+ console.log(' ' + chalk.gray(hr('─', W - 4)));
243
+
244
+ if (success) {
245
+ console.log(' ' + chalk.bold.green('✓ 执行完成!'));
246
+ if (step.teach.look_for) {
247
+ console.log(' ' + chalk.yellow('👆 观察上方输出:' + step.teach.look_for));
248
+ }
249
+ } else {
250
+ console.log(' ' + chalk.bold.red('✗ 执行遇到问题,请查看上方错误信息'));
251
+ }
252
+
253
+ console.log('');
254
+ console.log(' ' + chalk.dim('按任意键返回步骤列表...'));
255
+
256
+ // 8. 重启 raw mode,等待任意键
257
+ process.stdin.setRawMode(true);
258
+ await this._waitForAnyKey();
259
+
260
+ this.render();
261
+ }
262
+
263
+ // ───────────────────────────────────────────────────────────
264
+ // 键盘处理
265
+ // ───────────────────────────────────────────────────────────
266
+
267
+ _onData(buf) {
268
+ // 执行期间主键盘屏蔽(由 _busy 控制)
269
+ if (this._busy) return;
270
+
271
+ const key = buf.toString();
272
+
273
+ if (key === '\x1b[A') { // ↑
274
+ this.cursor = Math.max(0, this.cursor - 1);
275
+ this.render();
276
+ } else if (key === '\x1b[B') { // ↓
277
+ this.cursor = Math.min(this.steps.length - 1, this.cursor + 1);
278
+ this.render();
279
+ } else if (key === '\r' || key === '\n') { // Enter
280
+ this._busy = true;
281
+ this.executeStep(this.cursor).finally(() => {
282
+ this._busy = false;
283
+ });
284
+ } else if (key === 'q' || key === 'Q' || key === '\x03') { // q / Ctrl+C
285
+ this.exit();
286
+ }
287
+ }
288
+
289
+ // 等待 Enter(确认)或 q(取消)
290
+ // 返回 true = 确认,false = 取消
291
+ _waitForConfirm() {
292
+ return new Promise(resolve => {
293
+ process.stdin.once('data', buf => {
294
+ const key = buf.toString();
295
+ if (key === 'q' || key === 'Q' || key === '\x03') {
296
+ resolve(false);
297
+ } else {
298
+ resolve(true); // 任意其他键(包括 Enter)= 确认
299
+ }
300
+ });
301
+ });
302
+ }
303
+
304
+ // 等待任意键
305
+ _waitForAnyKey() {
306
+ return new Promise(resolve => {
307
+ process.stdin.once('data', resolve);
308
+ });
309
+ }
310
+
311
+ // ───────────────────────────────────────────────────────────
312
+ // 辅助:渲染命令行样式
313
+ // ───────────────────────────────────────────────────────────
314
+
315
+ _renderCommand(display) {
316
+ // 命令太长时拆行显示(>50 字符)
317
+ const MAX = 54;
318
+ if (display.length <= MAX) {
319
+ console.log(' ' + chalk.bgBlack.cyan(' $ ') + chalk.bgBlack.white(' ' + display + ' '));
320
+ } else {
321
+ // 简单的命令折行
322
+ const parts = display.match(/.{1,MAX}/g) || [display];
323
+ parts.forEach((part, i) => {
324
+ const prefix = i === 0
325
+ ? chalk.bgBlack.cyan(' $ ')
326
+ : chalk.bgBlack.cyan(' ');
327
+ console.log(' ' + prefix + chalk.bgBlack.white(' ' + part + ' '));
328
+ });
329
+ }
330
+ }
331
+
332
+ // ───────────────────────────────────────────────────────────
333
+ // 入口 & 退出
334
+ // ───────────────────────────────────────────────────────────
335
+
336
+ run() {
337
+ process.stdin.setRawMode(true);
338
+ process.stdin.resume();
339
+ process.stdin.on('data', buf => this._onData(buf));
340
+ this.render();
341
+ }
342
+
343
+ exit() {
344
+ process.stdin.setRawMode(false);
345
+ process.stdin.pause();
346
+ console.log('');
347
+ console.log(' ' + chalk.cyan('再见!👋'));
348
+ console.log('');
349
+ process.exit(0);
350
+ }
351
+ }
352
+
353
+ module.exports = { WizardRunner };