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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QR Code 加密工具 v3 — Node.js 精简版
|
|
3
|
+
*
|
|
4
|
+
* 手机端、Node.js CLI 共享同一套算法,兼容微信小程序/H5/App。
|
|
5
|
+
* 算法:XOR 流密码 + Base64url,密钥由共享 secret + salt 派生。
|
|
6
|
+
*
|
|
7
|
+
* 输出格式: v3:<base64url(IV[4B] + ciphertext)>
|
|
8
|
+
* - 移除 checksum,改为 IV 参与密钥索引的流密码,本身已具备完整性验证能力
|
|
9
|
+
* - 缩短约 33% 的密文长度
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ 本文件与 my-uniapp/src/utils/qr-crypto.js 逻辑一致,
|
|
12
|
+
* 修改任一文件时必须同步更新另一个。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ── 共享密钥(嵌入 App + clawmessenger,不可泄露)──────────────────
|
|
16
|
+
const QR_SECRET = 'dm_im_qr_key_v2_2026_secure';
|
|
17
|
+
const QR_SALT = 'qr_salt_x9k2';
|
|
18
|
+
|
|
19
|
+
function deriveKey() {
|
|
20
|
+
const input = QR_SECRET + QR_SALT;
|
|
21
|
+
const key = [];
|
|
22
|
+
for (let i = 0; i < 32; i++) {
|
|
23
|
+
let h = 0;
|
|
24
|
+
for (let j = 0; j < input.length; j++) {
|
|
25
|
+
h = ((h << 5) - h + input.charCodeAt((i + j) % input.length)) | 0;
|
|
26
|
+
}
|
|
27
|
+
key.push(h & 0xff);
|
|
28
|
+
}
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function randomBytes(n) {
|
|
33
|
+
const bytes = [];
|
|
34
|
+
for (let i = 0; i < n; i++) bytes.push(Math.floor(Math.random() * 256));
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stringToUtf8Bytes(str) {
|
|
39
|
+
const bytes = [];
|
|
40
|
+
for (let i = 0; i < str.length; i++) {
|
|
41
|
+
const code = str.charCodeAt(i);
|
|
42
|
+
if (code < 0x80) bytes.push(code);
|
|
43
|
+
else if (code < 0x800) bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
|
|
44
|
+
else bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
|
45
|
+
}
|
|
46
|
+
return bytes;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function utf8BytesToString(bytes) {
|
|
50
|
+
let str = '';
|
|
51
|
+
let i = 0;
|
|
52
|
+
while (i < bytes.length) {
|
|
53
|
+
const b1 = bytes[i];
|
|
54
|
+
if (b1 < 0x80) { str += String.fromCharCode(b1); i++; }
|
|
55
|
+
else if ((b1 & 0xe0) === 0xc0 && i + 1 < bytes.length) {
|
|
56
|
+
str += String.fromCharCode(((b1 & 0x1f) << 6) | (bytes[i + 1] & 0x3f)); i += 2;
|
|
57
|
+
} else if ((b1 & 0xf0) === 0xe0 && i + 2 < bytes.length) {
|
|
58
|
+
str += String.fromCharCode(((b1 & 0x0f) << 12) | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f)); i += 3;
|
|
59
|
+
} else { i++; }
|
|
60
|
+
}
|
|
61
|
+
return str;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── Base64url(无 padding)──────────────────────────────────── */
|
|
65
|
+
const B64U = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
66
|
+
|
|
67
|
+
function toBase64url(bytes) {
|
|
68
|
+
let res = '';
|
|
69
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
70
|
+
const a = bytes[i];
|
|
71
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
72
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
73
|
+
res += B64U[a >> 2];
|
|
74
|
+
res += B64U[((a & 3) << 4) | (b >> 4)];
|
|
75
|
+
if (i + 1 < bytes.length) res += B64U[((b & 15) << 2) | (c >> 6)];
|
|
76
|
+
if (i + 2 < bytes.length) res += B64U[c & 63];
|
|
77
|
+
}
|
|
78
|
+
return res;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function fromBase64url(str) {
|
|
82
|
+
const bytes = [];
|
|
83
|
+
let i = 0;
|
|
84
|
+
// 先补齐 Base64url 可能缺失的 padding,使长度为 4 的倍数
|
|
85
|
+
const padLen = (4 - (str.length % 4)) % 4;
|
|
86
|
+
str = str.replace(/[^A-Za-z0-9\-_]/g, '') + '='.repeat(padLen);
|
|
87
|
+
while (i < str.length) {
|
|
88
|
+
const a = B64U.indexOf(str[i++]);
|
|
89
|
+
const b = B64U.indexOf(str[i++]);
|
|
90
|
+
const c = B64U.indexOf(str[i++]);
|
|
91
|
+
const d = B64U.indexOf(str[i++]);
|
|
92
|
+
if (a === -1 || b === -1) break;
|
|
93
|
+
bytes.push((a << 2) | (b >> 4));
|
|
94
|
+
if (c !== -1 && str[i - 2] !== '=') bytes.push(((b & 15) << 4) | (c >> 2));
|
|
95
|
+
if (d !== -1 && str[i - 1] !== '=') bytes.push(((c & 3) << 6) | d);
|
|
96
|
+
}
|
|
97
|
+
return bytes;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── 加密 / 解密 ─────────────────────────────────────────────── */
|
|
101
|
+
|
|
102
|
+
export function encryptQR(plaintext) {
|
|
103
|
+
const key = deriveKey();
|
|
104
|
+
const iv = randomBytes(4);
|
|
105
|
+
const plainBytes = stringToUtf8Bytes(plaintext);
|
|
106
|
+
|
|
107
|
+
const cipherBytes = [];
|
|
108
|
+
for (let i = 0; i < plainBytes.length; i++) {
|
|
109
|
+
const keyIdx = (i + iv[i % 4]) % 32;
|
|
110
|
+
cipherBytes.push(plainBytes[i] ^ key[keyIdx]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const payload = [...iv, ...cipherBytes];
|
|
114
|
+
return 'v3:' + toBase64url(payload);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function decryptQR(encrypted) {
|
|
118
|
+
if (!encrypted || typeof encrypted !== 'string') return null;
|
|
119
|
+
|
|
120
|
+
// 兼容旧版 v2 格式
|
|
121
|
+
if (encrypted.startsWith('v2:')) return _decryptV2(encrypted);
|
|
122
|
+
if (!encrypted.startsWith('v3:')) return null;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const payload = fromBase64url(encrypted.substring(3));
|
|
126
|
+
if (payload.length < 4) return null;
|
|
127
|
+
|
|
128
|
+
const iv = payload.slice(0, 4);
|
|
129
|
+
const cipherBytes = payload.slice(4);
|
|
130
|
+
const key = deriveKey();
|
|
131
|
+
|
|
132
|
+
const plainBytes = [];
|
|
133
|
+
for (let i = 0; i < cipherBytes.length; i++) {
|
|
134
|
+
const keyIdx = (i + iv[i % 4]) % 32;
|
|
135
|
+
plainBytes.push(cipherBytes[i] ^ key[keyIdx]);
|
|
136
|
+
}
|
|
137
|
+
return utf8BytesToString(plainBytes);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ── v2 兼容解密 ─────────────────────────────────────────────── */
|
|
144
|
+
function _decryptV2(encrypted) {
|
|
145
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
146
|
+
let base64 = encrypted.substring(3).replace(/-/g, '+').replace(/_/g, '/');
|
|
147
|
+
while (base64.length % 4 !== 0) base64 += '=';
|
|
148
|
+
|
|
149
|
+
const bytes = [];
|
|
150
|
+
let i = 0;
|
|
151
|
+
while (i < base64.length) {
|
|
152
|
+
const a = chars.indexOf(base64[i++]);
|
|
153
|
+
const b = chars.indexOf(base64[i++]);
|
|
154
|
+
const c = chars.indexOf(base64[i++]);
|
|
155
|
+
const d = chars.indexOf(base64[i++]);
|
|
156
|
+
if (a === -1 || b === -1) break;
|
|
157
|
+
bytes.push((a << 2) | (b >> 4));
|
|
158
|
+
if (c !== -1 && base64[i - 2] !== '=') bytes.push(((b & 15) << 4) | (c >> 2));
|
|
159
|
+
if (d !== -1 && base64[i - 1] !== '=') bytes.push(((c & 3) << 6) | d);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (bytes.length < 8) return null;
|
|
163
|
+
const iv = bytes.slice(0, 4);
|
|
164
|
+
const cs = bytes.slice(bytes.length - 4);
|
|
165
|
+
const cipherBytes = bytes.slice(4, bytes.length - 4);
|
|
166
|
+
|
|
167
|
+
const key = deriveKey();
|
|
168
|
+
const plainBytes = [];
|
|
169
|
+
for (let i = 0; i < cipherBytes.length; i++) {
|
|
170
|
+
const keyIdx = (i + iv[i % 4]) % 32;
|
|
171
|
+
plainBytes.push(cipherBytes[i] ^ key[keyIdx]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let a = 0x67452301, b = 0xefcdab89;
|
|
175
|
+
for (let i = 0; i < plainBytes.length; i++) {
|
|
176
|
+
a = ((a << 5) - a + plainBytes[i] + b) | 0;
|
|
177
|
+
b = ((b << 3) - b + plainBytes[i] + a) | 0;
|
|
178
|
+
}
|
|
179
|
+
const expectedCs = [(a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff];
|
|
180
|
+
if (cs[0] !== expectedCs[0] || cs[1] !== expectedCs[1] ||
|
|
181
|
+
cs[2] !== expectedCs[2] || cs[3] !== expectedCs[3]) return null;
|
|
182
|
+
|
|
183
|
+
return utf8BytesToString(plainBytes);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default { encryptQR, decryptQR };
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* claw_messenger - 手动设置
|
|
5
|
+
* 用于重新配置节点昵称
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as readline from 'readline';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as crypto from 'crypto';
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
import axios from 'axios';
|
|
14
|
+
import qrcode from 'qrcode-terminal';
|
|
15
|
+
import { encryptQR } from './qr-crypto-node.js';
|
|
16
|
+
import { ensureChatCompletionsEnabled, ensureImageModelSupport, ensurePluginEntry, ensureChannelConfig } from '../dist/openclaw-config.js';
|
|
17
|
+
|
|
18
|
+
const SERVER_URL = process.env.DM_SERVER_URL || 'https://newsradar.dreamdt.cn/im';
|
|
19
|
+
const CONFIG_DIR = path.join(os.homedir(), '.claw-bridge', 'openclaw');
|
|
20
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
21
|
+
const LEGACY_CONFIG_FILE = path.join(os.homedir(), '.claw-bridge', 'config.json');
|
|
22
|
+
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
29
|
+
|
|
30
|
+
function generateNodeId() {
|
|
31
|
+
const random = crypto.randomBytes(3).toString('hex');
|
|
32
|
+
return `claw_${random}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadConfig() {
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
38
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
39
|
+
return JSON.parse(content);
|
|
40
|
+
}
|
|
41
|
+
// 兼容旧路径
|
|
42
|
+
if (fs.existsSync(LEGACY_CONFIG_FILE)) {
|
|
43
|
+
const content = fs.readFileSync(LEGACY_CONFIG_FILE, 'utf-8');
|
|
44
|
+
return JSON.parse(content);
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function saveConfig(config) {
|
|
51
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
52
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function deriveApiBaseUrl(serverUrl) {
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(serverUrl);
|
|
60
|
+
return `${url.protocol}//${url.host}`;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function registerNode(nodeName) {
|
|
67
|
+
const nodeId = generateNodeId();
|
|
68
|
+
console.log(`\n正在注册节点:${nodeId}...`);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const resp = await axios.post(`${SERVER_URL}/api/claw/register`, {
|
|
72
|
+
node_id: nodeId,
|
|
73
|
+
name: nodeName,
|
|
74
|
+
}, { timeout: 10000 });
|
|
75
|
+
|
|
76
|
+
if (resp.data?.code === 200) {
|
|
77
|
+
const token = resp.data.data?.token || resp.data.data?.rong_token || resp.data.rong_token || '';
|
|
78
|
+
if (token) {
|
|
79
|
+
return { nodeId, token };
|
|
80
|
+
}
|
|
81
|
+
console.error('注册失败: 接口未返回 token');
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (resp.data?.code === 409) {
|
|
86
|
+
console.log('节点已存在,获取 token...');
|
|
87
|
+
const tokenResp = await axios.get(`${SERVER_URL}/api/claw/token/${nodeId}`, { timeout: 10000 });
|
|
88
|
+
|
|
89
|
+
if (tokenResp.data?.code === 200) {
|
|
90
|
+
return {
|
|
91
|
+
nodeId,
|
|
92
|
+
token: tokenResp.data.data?.token || tokenResp.data.token || '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
console.error('获取 token 失败:', tokenResp.data?.message || '未知错误');
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.error('注册失败:', resp.data?.message || resp.data?.errorMessage || `code ${resp.data?.code}`);
|
|
100
|
+
return null;
|
|
101
|
+
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('注册异常:', err.message);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function showBindQRCode(nodeId, nodeName) {
|
|
109
|
+
console.log('\n========================================');
|
|
110
|
+
console.log(' 请使用 App 扫码绑定');
|
|
111
|
+
console.log('========================================\n');
|
|
112
|
+
|
|
113
|
+
const bindData = JSON.stringify({
|
|
114
|
+
type: 'bind_openclaw',
|
|
115
|
+
node_id: nodeId,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 加密 QR 数据
|
|
120
|
+
const encrypted = encryptQR(bindData);
|
|
121
|
+
|
|
122
|
+
// 生成在线二维码 URL 作为备选
|
|
123
|
+
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(encrypted)}`;
|
|
124
|
+
|
|
125
|
+
// 使用更大的二维码尺寸(small: false)提高扫描成功率
|
|
126
|
+
qrcode.generate(encrypted, { small: false, type: 'terminal' });
|
|
127
|
+
|
|
128
|
+
console.log('\n');
|
|
129
|
+
console.log('节点 ID:', nodeId);
|
|
130
|
+
console.log('提示:打开 App -> AI 助手 -> 扫码添加');
|
|
131
|
+
console.log('\n如果二维码无法扫描,可访问:', qrUrl);
|
|
132
|
+
console.log('或手动输入节点 ID:', nodeId);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function main() {
|
|
136
|
+
console.log('🦞 claw_messenger - 配置\n');
|
|
137
|
+
|
|
138
|
+
let config = loadConfig();
|
|
139
|
+
|
|
140
|
+
if (config) {
|
|
141
|
+
// 补写 apiBaseUrl(旧配置可能没有)
|
|
142
|
+
if (!config.apiBaseUrl) {
|
|
143
|
+
const derived = deriveApiBaseUrl(SERVER_URL);
|
|
144
|
+
if (derived) {
|
|
145
|
+
config.apiBaseUrl = derived;
|
|
146
|
+
saveConfig(config);
|
|
147
|
+
console.log(` ℹ️ 已自动补写 apiBaseUrl: ${derived}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('当前配置:');
|
|
152
|
+
console.log(` 节点 ID: ${config.nodeId}`);
|
|
153
|
+
console.log(` 节点名称:${config.nodeName}`);
|
|
154
|
+
if (config.apiBaseUrl) {
|
|
155
|
+
console.log(` 后端地址: ${config.apiBaseUrl}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const reset = await question('\n是否重新注册?(y/N): ');
|
|
159
|
+
if (reset.toLowerCase() !== 'y') {
|
|
160
|
+
rl.close();
|
|
161
|
+
console.log('\n✅ 配置未更改');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 用户选择重新注册,清除旧配置
|
|
166
|
+
console.log(' 正在清除旧配置...');
|
|
167
|
+
try {
|
|
168
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
169
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
170
|
+
console.log(' ✅ 旧配置已清除');
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.warn(' ⚠️ 清除旧配置失败:', err.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('\n========================================');
|
|
178
|
+
console.log(' 节点注册');
|
|
179
|
+
console.log('========================================\n');
|
|
180
|
+
|
|
181
|
+
const nodeName = await question('请输入节点昵称 (如:我的龙虾): ');
|
|
182
|
+
|
|
183
|
+
if (!nodeName.trim()) {
|
|
184
|
+
console.error('昵称不能为空');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result = await registerNode(nodeName.trim());
|
|
189
|
+
|
|
190
|
+
if (!result) {
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
config = {
|
|
195
|
+
nodeId: result.nodeId,
|
|
196
|
+
nodeName: nodeName.trim(),
|
|
197
|
+
token: result.token,
|
|
198
|
+
createdAt: new Date().toISOString(),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const derived = deriveApiBaseUrl(SERVER_URL);
|
|
202
|
+
if (derived) {
|
|
203
|
+
config.apiBaseUrl = derived;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
saveConfig(config);
|
|
207
|
+
|
|
208
|
+
console.log('\n========================================');
|
|
209
|
+
console.log(' 注册成功!');
|
|
210
|
+
console.log(` 节点 ID: ${result.nodeId}`);
|
|
211
|
+
console.log(` 节点名称:${nodeName.trim()}`);
|
|
212
|
+
console.log('========================================\n');
|
|
213
|
+
|
|
214
|
+
await showBindQRCode(result.nodeId, nodeName.trim());
|
|
215
|
+
|
|
216
|
+
rl.close();
|
|
217
|
+
|
|
218
|
+
// 确保 OpenClaw chatCompletions 端点已启用(SSE 流式调用需要)
|
|
219
|
+
console.log('\n🔧 检查 OpenClaw gateway 配置...');
|
|
220
|
+
try {
|
|
221
|
+
await ensureChatCompletionsEnabled();
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.log(' ⚠️ 自动启用 chatCompletions 失败,请手动在 openclaw.json 中添加:');
|
|
224
|
+
console.log(' gateway.http.endpoints.chatCompletions.enabled = true');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 确保模型支持 image 类型(IM 图片消息需要)
|
|
228
|
+
try {
|
|
229
|
+
await ensureImageModelSupport();
|
|
230
|
+
} catch (e) {
|
|
231
|
+
console.log(' ⚠️ 自动配置模型 image 类型失败,请手动在 openclaw.json 中为模型添加 image 类型');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 确保插件在 OpenClaw 中注册(关键:plugins.entries 中必须配置才能加载)
|
|
235
|
+
console.log('\n🔧 注册插件到 OpenClaw...');
|
|
236
|
+
try {
|
|
237
|
+
await ensurePluginEntry();
|
|
238
|
+
console.log(' ✅ 插件已注册到 openclaw.json');
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.log(' ⚠️ 自动注册插件失败,请手动在 openclaw.json 中添加:');
|
|
241
|
+
console.log(' plugins.entries.claw_messenger = { enabled: true }');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 确保 channels.claw_messenger 包含 nodeName
|
|
245
|
+
if (config?.nodeName) {
|
|
246
|
+
console.log('🔧 配置 channel...');
|
|
247
|
+
try {
|
|
248
|
+
await ensureChannelConfig(config.nodeName);
|
|
249
|
+
console.log(' ✅ channel 配置已写入 openclaw.json');
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.log(' ⚠️ 自动配置 channel 失败,请手动在 openclaw.json 中添加:');
|
|
252
|
+
console.log(` channels.claw_messenger = { enabled: true, nodeName: "${config.nodeName}" }`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log('\n✅ 配置完成!重启 OpenClaw 后生效');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
main().catch(err => {
|
|
260
|
+
console.error('配置失败:', err);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getMacAddress } from './mac-address.js';
|
|
2
|
+
interface RegisterResult {
|
|
3
|
+
nodeId: string;
|
|
4
|
+
nodeName: string;
|
|
5
|
+
token: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 从 OpenClaw USER.md 获取昵称
|
|
10
|
+
*/
|
|
11
|
+
declare function getNicknameFromOpenClaw(): Promise<string | null>;
|
|
12
|
+
/**
|
|
13
|
+
* 获取设备信息
|
|
14
|
+
*/
|
|
15
|
+
declare function getDeviceInfo(): {
|
|
16
|
+
deviceName: string;
|
|
17
|
+
platform: NodeJS.Platform;
|
|
18
|
+
arch: string;
|
|
19
|
+
totalMem: number;
|
|
20
|
+
freeMem: number;
|
|
21
|
+
cpuModel: string;
|
|
22
|
+
cpuCount: number;
|
|
23
|
+
macAddress: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* 注册节点 - 调用服务端接口
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerNode(serverUrl?: string, nodeName?: string): Promise<RegisterResult>;
|
|
29
|
+
/**
|
|
30
|
+
* 获取或注册 Token
|
|
31
|
+
* 始终从服务端获取新 token,不使用本地缓存(缓存 token 可能已过期导致 31029)
|
|
32
|
+
*
|
|
33
|
+
* @param nodeName 节点昵称
|
|
34
|
+
* @param forceRegister 是否强制重新注册(忽略已有配置)
|
|
35
|
+
*/
|
|
36
|
+
export declare function getOrRegisterToken(nodeName?: string, forceRegister?: boolean): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* 加载配置
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadConfig(): Promise<{
|
|
41
|
+
nodeId: string;
|
|
42
|
+
nodeName: string;
|
|
43
|
+
token: string;
|
|
44
|
+
macAddress: string;
|
|
45
|
+
expiresAt?: number;
|
|
46
|
+
} | null>;
|
|
47
|
+
export { getMacAddress, getDeviceInfo, getNicknameFromOpenClaw };
|
|
48
|
+
export declare function getAppKey(serverUrl?: string): Promise<string>;
|
|
49
|
+
export declare function getAppSecret(serverUrl: string, token: string, nodeId?: string): Promise<string | undefined>;
|