claw_messenger 1.0.0
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/LICENSE +21 -0
- package/README.md +577 -0
- package/bin/auto-init.js +104 -0
- package/bin/cli.js +5 -0
- package/bin/diagnose-plugin.js +174 -0
- package/bin/dm-bridge.cjs +12 -0
- package/bin/install.js +452 -0
- package/bin/postinstall.js +23 -0
- package/bin/qr-crypto-node.js +186 -0
- package/bin/setup.js +262 -0
- package/dist/auto-register.d.ts +49 -0
- package/dist/auto-register.js +328 -0
- package/dist/bridge-runner.d.ts +1 -0
- package/dist/bridge-runner.js +107 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +164 -0
- package/dist/device-status.d.ts +30 -0
- package/dist/device-status.js +109 -0
- package/dist/env-polyfill.d.ts +3 -0
- package/dist/env-polyfill.js +166 -0
- package/dist/group-config-manager.d.ts +22 -0
- package/dist/group-config-manager.js +130 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +36 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +103 -0
- package/dist/mac-address.d.ts +1 -0
- package/dist/mac-address.js +46 -0
- package/dist/openclaw-client.d.ts +41 -0
- package/dist/openclaw-client.js +530 -0
- package/dist/openclaw-config.d.ts +41 -0
- package/dist/openclaw-config.js +359 -0
- package/dist/openclaw.plugin.json +40 -0
- package/dist/package.json +112 -0
- package/dist/plugin-entry.d.ts +54 -0
- package/dist/plugin-entry.js +772 -0
- package/dist/postinstall.js +23 -0
- package/dist/rongcloud-client.d.ts +16 -0
- package/dist/rongcloud-client.js +274 -0
- package/dist/rongcloud-server-api.d.ts +53 -0
- package/dist/rongcloud-server-api.js +221 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +97 -0
- package/openclaw.plugin.json +40 -0
- package/package.json +112 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 插件诊断工具
|
|
4
|
+
* 检查 OpenClaw 插件配置和加载问题
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
|
|
11
|
+
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
12
|
+
const EXTENSIONS_DIR = path.join(OPENCLAW_DIR, 'extensions');
|
|
13
|
+
const PLUGIN_DIR = path.join(EXTENSIONS_DIR, 'claw_messenger');
|
|
14
|
+
|
|
15
|
+
console.log('🔍 OpenClaw 插件诊断\n');
|
|
16
|
+
|
|
17
|
+
// 1. 检查 OpenClaw 配置
|
|
18
|
+
console.log('1. 检查 OpenClaw 配置...');
|
|
19
|
+
const configPath = path.join(OPENCLAW_DIR, 'openclaw.json');
|
|
20
|
+
if (!fs.existsSync(configPath)) {
|
|
21
|
+
console.log(' ❌ openclaw.json 不存在');
|
|
22
|
+
} else {
|
|
23
|
+
try {
|
|
24
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
25
|
+
console.log(' ✅ openclaw.json 存在');
|
|
26
|
+
|
|
27
|
+
const plugins = config.plugins || {};
|
|
28
|
+
console.log(' plugins.allow:', plugins.allow || []);
|
|
29
|
+
console.log(' plugins.entries:', JSON.stringify(plugins.entries || {}, null, 2).split('\n').map(l => ' ' + l).join('\n'));
|
|
30
|
+
|
|
31
|
+
if (plugins.allow?.includes('claw_messenger')) {
|
|
32
|
+
console.log(' ✅ claw_messenger 在 allow 列表中');
|
|
33
|
+
} else {
|
|
34
|
+
console.log(' ❌ claw_messenger 不在 allow 列表中');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (plugins.entries?.claw_messenger?.enabled === true) {
|
|
38
|
+
console.log(' ✅ claw_messenger entries 已启用');
|
|
39
|
+
} else {
|
|
40
|
+
console.log(' ❌ claw_messenger entries 未启用');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const loadPaths = plugins.load?.paths || [];
|
|
44
|
+
const expectedPath = path.join(OPENCLAW_DIR, 'extensions', 'claw_messenger');
|
|
45
|
+
if (loadPaths.includes(expectedPath)) {
|
|
46
|
+
console.log(' ✅ plugins.load.paths 已包含扩展目录');
|
|
47
|
+
} else {
|
|
48
|
+
console.log(' ⚠️ plugins.load.paths 未包含扩展目录(非必须):', expectedPath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const channelConfig = config.channels?.claw_messenger;
|
|
52
|
+
const hasNodeName = channelConfig && typeof channelConfig.nodeName === 'string' && channelConfig.nodeName.trim();
|
|
53
|
+
if (hasNodeName) {
|
|
54
|
+
console.log(' ✅ channels.claw_messenger.nodeName 已配置:', channelConfig.nodeName);
|
|
55
|
+
} else {
|
|
56
|
+
console.log(' ❌ channels.claw_messenger 缺少 nodeName,OpenClaw 不会认为该 channel 已配置');
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.log(' ❌ 解析 openclaw.json 失败:', e.message);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. 检查插件目录
|
|
64
|
+
console.log('\n2. 检查插件目录...');
|
|
65
|
+
if (!fs.existsSync(PLUGIN_DIR)) {
|
|
66
|
+
console.log(' ❌ 插件目录不存在:', PLUGIN_DIR);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(' ✅ 插件目录存在');
|
|
69
|
+
|
|
70
|
+
const files = fs.readdirSync(PLUGIN_DIR);
|
|
71
|
+
console.log(' 文件列表:', files.join(', '));
|
|
72
|
+
|
|
73
|
+
// 检查关键文件
|
|
74
|
+
const requiredFiles = ['package.json', 'openclaw.plugin.json'];
|
|
75
|
+
for (const file of requiredFiles) {
|
|
76
|
+
if (files.includes(file)) {
|
|
77
|
+
console.log(` ✅ ${file} 存在`);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(` ❌ ${file} 不存在`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 3. 检查 package.json
|
|
85
|
+
console.log('\n3. 检查 package.json...');
|
|
86
|
+
const packagePath = path.join(PLUGIN_DIR, 'package.json');
|
|
87
|
+
if (fs.existsSync(packagePath)) {
|
|
88
|
+
try {
|
|
89
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
90
|
+
console.log(' 名称:', pkg.name);
|
|
91
|
+
console.log(' 版本:', pkg.version);
|
|
92
|
+
console.log(' main:', pkg.main);
|
|
93
|
+
console.log(' type:', pkg.type);
|
|
94
|
+
|
|
95
|
+
if (pkg.openclaw) {
|
|
96
|
+
console.log(' ✅ openclaw 字段存在');
|
|
97
|
+
console.log(' extensions:', JSON.stringify(pkg.openclaw.extensions || [], null, 2).split('\n').map(l => ' ' + l).join('\n'));
|
|
98
|
+
} else {
|
|
99
|
+
console.log(' ❌ openclaw 字段不存在');
|
|
100
|
+
}
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.log(' ❌ 解析失败:', e.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4. 检查 openclaw.plugin.json
|
|
107
|
+
console.log('\n4. 检查 openclaw.plugin.json...');
|
|
108
|
+
const manifestPath = path.join(PLUGIN_DIR, 'openclaw.plugin.json');
|
|
109
|
+
if (fs.existsSync(manifestPath)) {
|
|
110
|
+
try {
|
|
111
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
112
|
+
console.log(' id:', manifest.id);
|
|
113
|
+
console.log(' entry:', manifest.entry);
|
|
114
|
+
console.log(' channels:', manifest.channels || []);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.log(' ❌ 解析失败:', e.message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 5. 检查 dist 目录
|
|
121
|
+
console.log('\n5. 检查 dist 目录...');
|
|
122
|
+
const distDir = path.join(PLUGIN_DIR, 'dist');
|
|
123
|
+
if (fs.existsSync(distDir)) {
|
|
124
|
+
console.log(' ✅ dist 目录存在');
|
|
125
|
+
const distFiles = fs.readdirSync(distDir);
|
|
126
|
+
console.log(' 文件数:', distFiles.length);
|
|
127
|
+
|
|
128
|
+
const entryFile = path.join(distDir, 'plugin-entry.js');
|
|
129
|
+
if (fs.existsSync(entryFile)) {
|
|
130
|
+
console.log(' ✅ plugin-entry.js 存在');
|
|
131
|
+
} else {
|
|
132
|
+
console.log(' ❌ plugin-entry.js 不存在');
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
console.log(' ❌ dist 目录不存在');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 6. 检查 node_modules
|
|
139
|
+
console.log('\n6. 检查依赖...');
|
|
140
|
+
const nodeModulesDir = path.join(PLUGIN_DIR, 'node_modules');
|
|
141
|
+
if (fs.existsSync(nodeModulesDir)) {
|
|
142
|
+
const modules = fs.readdirSync(nodeModulesDir).filter(d => !d.startsWith('.'));
|
|
143
|
+
console.log(' ✅ node_modules 存在,依赖数:', modules.length);
|
|
144
|
+
|
|
145
|
+
const criticalDeps = ['@rongcloud/imlib-next', 'axios', 'jsdom', 'ws'];
|
|
146
|
+
for (const dep of criticalDeps) {
|
|
147
|
+
if (fs.existsSync(path.join(nodeModulesDir, dep))) {
|
|
148
|
+
console.log(` ✅ ${dep} 已安装`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log(` ⚠️ ${dep} 未安装`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
console.log(' ❌ node_modules 不存在');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 7. 检查权限
|
|
158
|
+
console.log('\n7. 检查权限...');
|
|
159
|
+
try {
|
|
160
|
+
const stats = fs.statSync(PLUGIN_DIR);
|
|
161
|
+
console.log(' 所有者:', stats.uid);
|
|
162
|
+
console.log(' 组:', stats.gid);
|
|
163
|
+
console.log(' 模式:', stats.mode.toString(8));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.log(' ⚠️ 无法获取权限信息');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log('\n📋 诊断完成');
|
|
169
|
+
console.log('\n💡 建议:');
|
|
170
|
+
console.log(' 如果所有检查都通过,但插件仍未加载,请尝试:');
|
|
171
|
+
console.log(' 1. 重启 OpenClaw: openclaw gateway stop && openclaw gateway');
|
|
172
|
+
console.log(' 2. 检查 OpenClaw 日志: Get-Content $env:LOCALAPPDATA\Temp\openclaw\openclaw-$(Get-Date -Format yyyy-MM-dd).log');
|
|
173
|
+
console.log(' 3. 确保 openclaw.json 中 channels.claw_messenger 包含 nodeName:');
|
|
174
|
+
console.log(' "channels": { "claw_messenger": { "enabled": true, "nodeName": "你的节点昵称" } }');
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* claw_messenger - OpenClaw 插件安装脚本
|
|
5
|
+
* 简化版: 只保留节点注册和插件复制,让用户自由选择其他组件
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync, spawn } from 'node:child_process';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readFileSync, statSync, readdirSync, lstatSync, copyFileSync, mkdirSync as mkdirSyncFs } from 'node:fs';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import * as readline from 'readline';
|
|
14
|
+
import * as crypto from 'crypto';
|
|
15
|
+
import axios from 'axios';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as os from 'os';
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import { ensureChatCompletionsEnabled, ensureImageModelSupport, ensurePluginEntry, ensureChannelConfig } from '../dist/openclaw-config.js';
|
|
20
|
+
import { encryptQR } from './qr-crypto-node.js';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
const PACKAGE_ROOT = join(__dirname, '..');
|
|
25
|
+
const isWindows = process.platform === 'win32';
|
|
26
|
+
|
|
27
|
+
// 检测是否为本地源码运行(非npx/npm安装)
|
|
28
|
+
const isLocalRun = !__filename.includes('node_modules');
|
|
29
|
+
|
|
30
|
+
// 检查 dist 目录是否存在
|
|
31
|
+
const distDir = join(PACKAGE_ROOT, 'dist');
|
|
32
|
+
if (!existsSync(distDir)) {
|
|
33
|
+
console.error('❌ 错误: dist/ 目录不存在');
|
|
34
|
+
console.error(' 请先运行构建命令:');
|
|
35
|
+
if (isLocalRun) {
|
|
36
|
+
console.error(' npm run build');
|
|
37
|
+
console.error(' 或一键安装:');
|
|
38
|
+
console.error(' npm run setup');
|
|
39
|
+
} else {
|
|
40
|
+
console.error(' npm run build');
|
|
41
|
+
}
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SERVER_URL = process.env.DM_SERVER_URL || 'https://newsradar.dreamdt.cn/im';
|
|
46
|
+
const CONFIG_DIR = path.join(os.homedir(), '.claw-bridge', 'openclaw');
|
|
47
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
48
|
+
const LEGACY_CONFIG_FILE = path.join(os.homedir(), '.claw-bridge', 'config.json');
|
|
49
|
+
|
|
50
|
+
// 获取 OpenClaw extensions 目录
|
|
51
|
+
function getExtensionsDir() {
|
|
52
|
+
const profile = process.env.OPENCLAW_PROFILE || '';
|
|
53
|
+
const baseDir = profile
|
|
54
|
+
? join(homedir(), `.openclaw-${profile}`)
|
|
55
|
+
: join(homedir(), '.openclaw');
|
|
56
|
+
|
|
57
|
+
const extensionsDir = join(baseDir, 'extensions', 'claw_messenger');
|
|
58
|
+
|
|
59
|
+
if (existsSync(extensionsDir)) {
|
|
60
|
+
try {
|
|
61
|
+
rmSync(extensionsDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.warn(` ⚠️ 无法清理旧目录: ${e.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!existsSync(extensionsDir)) {
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.error(` ❌ 无法创建目录: ${extensionsDir}`);
|
|
72
|
+
console.error(` 错误: ${e.message}`);
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return extensionsDir;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 安全执行命令(兼容中文路径)
|
|
81
|
+
function safeExecSync(cmd, options = {}) {
|
|
82
|
+
if (isWindows) {
|
|
83
|
+
const wrappedCmd = `chcp 65001 >nul && ${cmd}`;
|
|
84
|
+
return execSync(wrappedCmd, {
|
|
85
|
+
...options,
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
env: { ...process.env, PYTHONIOENCODING: 'utf-8' }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return execSync(cmd, { ...options, encoding: 'utf-8' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 手动递归复制目录(使用 xcopy 避免中文路径问题)
|
|
94
|
+
function copyDirSync(src, dest, showProgress = false) {
|
|
95
|
+
if (isWindows) {
|
|
96
|
+
const srcQuoted = `"${src}"`;
|
|
97
|
+
const destQuoted = `"${dest}"`;
|
|
98
|
+
const cmd = `xcopy ${srcQuoted} ${destQuoted} /E /I /Y /Q`;
|
|
99
|
+
try {
|
|
100
|
+
execSync(cmd, {
|
|
101
|
+
encoding: 'utf-8',
|
|
102
|
+
windowsHide: true,
|
|
103
|
+
env: { ...process.env, LANG: 'zh_CN.UTF-8' }
|
|
104
|
+
});
|
|
105
|
+
} catch (e) {
|
|
106
|
+
const robocmd = `robocopy "${src}" "${dest}" /E /NJH /NJS /NDL /NC /NS`;
|
|
107
|
+
try {
|
|
108
|
+
execSync(robocmd, { encoding: 'utf-8', windowsHide: true });
|
|
109
|
+
} catch (e2) {
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
if (!existsSync(dest)) {
|
|
115
|
+
mkdirSync(dest, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
119
|
+
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
const srcPath = join(src, entry.name);
|
|
122
|
+
const destPath = join(dest, entry.name);
|
|
123
|
+
|
|
124
|
+
if (entry.isDirectory()) {
|
|
125
|
+
copyDirSync(srcPath, destPath, showProgress);
|
|
126
|
+
} else if (entry.isFile()) {
|
|
127
|
+
copyFileSync(srcPath, destPath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function generateNodeId() {
|
|
134
|
+
const random = crypto.randomBytes(3).toString('hex');
|
|
135
|
+
return `claw_${random}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function loadConfig() {
|
|
139
|
+
try {
|
|
140
|
+
if (existsSync(CONFIG_FILE)) {
|
|
141
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
142
|
+
return JSON.parse(content);
|
|
143
|
+
}
|
|
144
|
+
// 兼容旧路径
|
|
145
|
+
if (existsSync(LEGACY_CONFIG_FILE)) {
|
|
146
|
+
const content = readFileSync(LEGACY_CONFIG_FILE, 'utf-8');
|
|
147
|
+
return JSON.parse(content);
|
|
148
|
+
}
|
|
149
|
+
} catch { }
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function deriveApiBaseUrl(serverUrl) {
|
|
154
|
+
try {
|
|
155
|
+
const url = new URL(serverUrl);
|
|
156
|
+
return `${url.protocol}//${url.host}`;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function saveConfig(config) {
|
|
163
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
164
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
165
|
+
}
|
|
166
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function registerNode(nodeName) {
|
|
170
|
+
const nodeId = generateNodeId();
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const resp = await axios.post(`${SERVER_URL}/api/claw/register`, {
|
|
174
|
+
node_id: nodeId,
|
|
175
|
+
name: nodeName,
|
|
176
|
+
}, { timeout: 10000 });
|
|
177
|
+
|
|
178
|
+
if (resp.data?.code === 200) {
|
|
179
|
+
const token = resp.data.data?.token || resp.data.data?.rong_token || resp.data.rong_token || '';
|
|
180
|
+
if (token) {
|
|
181
|
+
return { nodeId, token };
|
|
182
|
+
}
|
|
183
|
+
console.error(' 注册接口未返回 token');
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (resp.data?.code === 409) {
|
|
188
|
+
console.log(' 节点已存在,获取 token...');
|
|
189
|
+
const tokenResp = await axios.get(`${SERVER_URL}/api/claw/token/${nodeId}`, { timeout: 10000 });
|
|
190
|
+
|
|
191
|
+
if (tokenResp.data?.code === 200) {
|
|
192
|
+
return {
|
|
193
|
+
nodeId,
|
|
194
|
+
token: tokenResp.data.data?.token || tokenResp.data.token || '',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
console.error(' 获取 token 失败:', tokenResp.data?.message || '未知错误');
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.error(' 注册接口返回错误:', resp.data?.message || resp.data?.errorMessage || `code ${resp.data?.code}`);
|
|
202
|
+
return null;
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(' 注册请求异常:', err.message);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function showQRCode(nodeId, nodeName = '') {
|
|
210
|
+
console.log(`\n📱 节点二维码:`);
|
|
211
|
+
console.log(` 节点 ID: ${nodeId}`);
|
|
212
|
+
console.log(` 请使用虾说应用扫码添加,或复制节点 ID 手动输入`);
|
|
213
|
+
console.log();
|
|
214
|
+
|
|
215
|
+
const bindData = JSON.stringify({
|
|
216
|
+
type: 'bind_openclaw',
|
|
217
|
+
node_id: nodeId,
|
|
218
|
+
timestamp: Date.now(),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const encrypted = encryptQR(bindData);
|
|
222
|
+
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(encrypted)}`;
|
|
223
|
+
|
|
224
|
+
if (isWindows) {
|
|
225
|
+
try {
|
|
226
|
+
execSync('chcp 65001', { windowsHide: true, stdio: 'pipe' });
|
|
227
|
+
} catch { }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const qr = await import('qrcode-terminal');
|
|
232
|
+
console.log('\n');
|
|
233
|
+
qr.default.generate(encrypted, { small: false, type: 'terminal' }, (code) => {
|
|
234
|
+
console.log(code);
|
|
235
|
+
console.log();
|
|
236
|
+
});
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.log(` 手机扫码添加: ${qrUrl}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(` 如果二维码无法扫描,可访问: ${qrUrl}`);
|
|
242
|
+
console.log(` 或在应用中手动输入节点 ID: ${nodeId}`);
|
|
243
|
+
console.log();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function main() {
|
|
247
|
+
const rl = readline.createInterface({
|
|
248
|
+
input: process.stdin,
|
|
249
|
+
output: process.stdout
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
253
|
+
|
|
254
|
+
console.log('🦞 claw_messenger - 安装\n');
|
|
255
|
+
|
|
256
|
+
// 检查已有配置
|
|
257
|
+
const existingConfig = loadConfig();
|
|
258
|
+
|
|
259
|
+
let nodeName = null;
|
|
260
|
+
let config = existingConfig;
|
|
261
|
+
|
|
262
|
+
if (existingConfig) {
|
|
263
|
+
if (!existingConfig.apiBaseUrl) {
|
|
264
|
+
const derived = deriveApiBaseUrl(SERVER_URL);
|
|
265
|
+
if (derived) {
|
|
266
|
+
existingConfig.apiBaseUrl = derived;
|
|
267
|
+
saveConfig(existingConfig);
|
|
268
|
+
console.log(` ℹ️ 已自动补写 apiBaseUrl: ${derived}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(`✅ 检测到已有配置:`);
|
|
273
|
+
console.log(` 昵称:${existingConfig.nodeName}`);
|
|
274
|
+
|
|
275
|
+
const update = await question('\n是否重新注册新节点?(y/N): ');
|
|
276
|
+
if (update.toLowerCase() === 'y') {
|
|
277
|
+
nodeName = await question('请输入新节点昵称 (如:小助手): ');
|
|
278
|
+
config = null;
|
|
279
|
+
|
|
280
|
+
console.log(' 正在清除旧配置...');
|
|
281
|
+
try {
|
|
282
|
+
if (existsSync(CONFIG_FILE)) {
|
|
283
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
284
|
+
console.log(' ✅ 旧配置已清除');
|
|
285
|
+
}
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.warn(' ⚠️ 清除旧配置失败:', err.message);
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
console.log('\n使用已有配置');
|
|
291
|
+
await showQRCode(existingConfig.nodeId, existingConfig.nodeName);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!config) {
|
|
296
|
+
if (!nodeName) {
|
|
297
|
+
nodeName = await question('请输入节点昵称 (如:小助手): ');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!nodeName || !nodeName.trim()) {
|
|
301
|
+
rl.close();
|
|
302
|
+
console.error('❌ 昵称不能为空');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
rl.close();
|
|
308
|
+
|
|
309
|
+
const extensionsDir = getExtensionsDir();
|
|
310
|
+
console.log(`\n📦 安装位置:${extensionsDir}`);
|
|
311
|
+
|
|
312
|
+
// 复制插件文件
|
|
313
|
+
console.log('📦 复制文件...');
|
|
314
|
+
const filesToCopy = ['bin', 'dist', 'openclaw.plugin.json', 'package.json', 'README.md'];
|
|
315
|
+
|
|
316
|
+
for (const file of filesToCopy) {
|
|
317
|
+
const src = join(PACKAGE_ROOT, file);
|
|
318
|
+
const dest = join(extensionsDir, file);
|
|
319
|
+
try {
|
|
320
|
+
if (existsSync(src)) {
|
|
321
|
+
if (isWindows) {
|
|
322
|
+
if (fs.statSync(src).isDirectory()) {
|
|
323
|
+
copyDirSync(src, dest);
|
|
324
|
+
} else {
|
|
325
|
+
execSync(`copy /Y "${src}" "${dest}"`, { encoding: 'utf-8', windowsHide: true });
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
const srcStat = lstatSync(src);
|
|
329
|
+
if (srcStat.isDirectory()) {
|
|
330
|
+
copyDirSync(src, dest);
|
|
331
|
+
} else {
|
|
332
|
+
copyFileSync(src, dest);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log(` ✓ ${file}`);
|
|
336
|
+
} else {
|
|
337
|
+
console.log(` ⏭️ ${file} (不存在)`);
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
console.error(` ❌ 复制失败: ${file}`);
|
|
341
|
+
console.error(` 错误: ${err.message}`);
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
console.log(' ✅ 文件复制完成');
|
|
346
|
+
|
|
347
|
+
// 安装依赖
|
|
348
|
+
console.log('📦 安装依赖...');
|
|
349
|
+
console.log(' 这可能需要几分钟,请耐心等待...');
|
|
350
|
+
try {
|
|
351
|
+
safeExecSync('npm install --ignore-scripts --registry=https://registry.npmmirror.com --prefer-offline --no-audit --no-fund --progress=false', {
|
|
352
|
+
cwd: extensionsDir,
|
|
353
|
+
stdio: 'inherit',
|
|
354
|
+
timeout: 300000,
|
|
355
|
+
});
|
|
356
|
+
console.log(' ✅ 依赖安装完成');
|
|
357
|
+
} catch (e) {
|
|
358
|
+
console.log(' ⚠️ 依赖安装失败或超时,请手动运行:');
|
|
359
|
+
console.log(` cd "${extensionsDir}" && npm install`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 插件已安装到 extensions 目录,OpenClaw 会自动发现并加载
|
|
363
|
+
console.log('🔌 插件已就绪,将由 OpenClaw 自动加载');
|
|
364
|
+
|
|
365
|
+
// 注册节点(仅首次或用户选择重新注册)
|
|
366
|
+
if (!existingConfig || !config) {
|
|
367
|
+
console.log('🚀 注册节点...');
|
|
368
|
+
const result = await registerNode(nodeName.trim());
|
|
369
|
+
|
|
370
|
+
if (!result) {
|
|
371
|
+
console.log(' ⚠️ 注册失败,稍后可手动运行:npx claw-bridge-setup');
|
|
372
|
+
} else {
|
|
373
|
+
config = {
|
|
374
|
+
nodeId: result.nodeId,
|
|
375
|
+
nodeName: nodeName.trim(),
|
|
376
|
+
token: result.token,
|
|
377
|
+
createdAt: new Date().toISOString(),
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const derived = deriveApiBaseUrl(SERVER_URL);
|
|
381
|
+
if (derived) {
|
|
382
|
+
config.apiBaseUrl = derived;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
saveConfig(config);
|
|
386
|
+
|
|
387
|
+
console.log(' ✅ 节点已注册');
|
|
388
|
+
console.log(` 节点 ID: ${result.nodeId}`);
|
|
389
|
+
console.log(` 昵称:${nodeName.trim()}`);
|
|
390
|
+
|
|
391
|
+
await showQRCode(result.nodeId, nodeName.trim());
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 确保 OpenClaw chatCompletions 端点已启用
|
|
396
|
+
console.log('🔧 检查 OpenClaw gateway 配置...');
|
|
397
|
+
try {
|
|
398
|
+
await ensureChatCompletionsEnabled();
|
|
399
|
+
} catch (e) {
|
|
400
|
+
console.log(' ⚠️ 自动启用 chatCompletions 失败,请手动在 openclaw.json 中添加:');
|
|
401
|
+
console.log(' gateway.http.endpoints.chatCompletions.enabled = true');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 确保模型支持 image 类型
|
|
405
|
+
try {
|
|
406
|
+
await ensureImageModelSupport();
|
|
407
|
+
} catch (e) {
|
|
408
|
+
console.log(' ⚠️ 自动配置模型 image 类型失败');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 确保插件在 OpenClaw 中注册(关键:plugins.entries 中必须配置才能加载)
|
|
412
|
+
console.log('🔧 注册插件到 OpenClaw...');
|
|
413
|
+
try {
|
|
414
|
+
await ensurePluginEntry();
|
|
415
|
+
console.log(' ✅ 插件已注册到 openclaw.json');
|
|
416
|
+
} catch (e) {
|
|
417
|
+
console.log(' ⚠️ 自动注册插件失败,请手动在 openclaw.json 中添加:');
|
|
418
|
+
console.log(' plugins.entries.claw_messenger = { enabled: true }');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 确保 channels.claw_messenger 包含 nodeName(OpenClaw 凭此判断 channel 已配置)
|
|
422
|
+
const finalNodeName = config?.nodeName || existingConfig?.nodeName;
|
|
423
|
+
if (finalNodeName) {
|
|
424
|
+
console.log('🔧 配置 channel...');
|
|
425
|
+
try {
|
|
426
|
+
await ensureChannelConfig(finalNodeName);
|
|
427
|
+
console.log(' ✅ channel 配置已写入 openclaw.json');
|
|
428
|
+
} catch (e) {
|
|
429
|
+
console.log(' ⚠️ 自动配置 channel 失败,请手动在 openclaw.json 中添加:');
|
|
430
|
+
console.log(` channels.claw_messenger = { enabled: true, nodeName: "${finalNodeName}" }`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
console.log('\n✅ 安装完成!');
|
|
435
|
+
console.log('\n核心功能:');
|
|
436
|
+
console.log(' - 融云 IM 消息收发');
|
|
437
|
+
console.log(' - OpenClaw 私聊/群聊');
|
|
438
|
+
console.log(' - SSE 流式消息');
|
|
439
|
+
console.log('\n下一步:');
|
|
440
|
+
console.log(' 1. 重启 OpenClaw: openclaw gateway');
|
|
441
|
+
console.log(' 2. 在应用内发送消息测试');
|
|
442
|
+
console.log('\n配置:');
|
|
443
|
+
console.log(' 如需更改节点昵称:npx claw-bridge-setup');
|
|
444
|
+
|
|
445
|
+
process.exit(0);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
main().catch(err => {
|
|
449
|
+
console.error('❌ 安装失败:', err.message);
|
|
450
|
+
console.error('详细错误:', err.stack || err);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'fs'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { dirname, join } from 'path'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
// 自动识别当前在 bin/ 还是 dist/ 目录
|
|
9
|
+
const distDir = existsSync(join(__dirname, 'openclaw-config.js'))
|
|
10
|
+
? __dirname
|
|
11
|
+
: join(__dirname, '..', 'dist')
|
|
12
|
+
|
|
13
|
+
const distFile = join(distDir, 'openclaw-config.js')
|
|
14
|
+
|
|
15
|
+
// 开发模式(dist 不存在)跳过
|
|
16
|
+
if (!existsSync(distFile)) {
|
|
17
|
+
console.log('[clawmessenger] 开发模式,跳过 postinstall')
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('[clawmessenger] 插件已安装')
|
|
22
|
+
console.log('[clawmessenger] 核心功能: 融云 IM 桥接、OpenClaw 对话、流式消息')
|
|
23
|
+
console.log('[clawmessenger] 运行 npx claw_messenger 完成节点注册和配置')
|