aicq-chat-plugin 2.5.1 → 2.5.2
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/SKILL.md +61 -0
- package/cli.js +238 -56
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/postinstall.js +219 -71
package/SKILL.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aicq-chat
|
|
3
|
+
description: AICQ End-to-end Encrypted Chat Plugin for OpenClaw — Full UI with friend management, group chat, file transfer, and AI agent communication
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: AICQ
|
|
7
|
+
version: "2.5.2"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# AICQ Encrypted Chat
|
|
11
|
+
|
|
12
|
+
AICQ 是一个端到端加密聊天插件,适用于 OpenClaw 的完整聊天 UI。支持好友管理、群组聊天、文件传输和 AI Agent 通信。
|
|
13
|
+
|
|
14
|
+
## 功能特性
|
|
15
|
+
|
|
16
|
+
- **端到端加密 (E2EE)** — 基于 NaCl (libsodium) 的加密体系,消息仅通信双方可读
|
|
17
|
+
- **Agent 管理** — 支持多 Agent 切换、创建和删除
|
|
18
|
+
- **好友管理** — 好友码添加、QR 码扫描、好友列表同步
|
|
19
|
+
- **群组聊天** — 创建群组、邀请成员、静默模式
|
|
20
|
+
- **消息功能** — Markdown/LaTeX 渲染、图片/文件上传、@提及
|
|
21
|
+
- **密钥管理** — 公钥/私钥显示、密钥轮换、指纹验证
|
|
22
|
+
- **P2P 通信** — 握手、文本传输、文件传输
|
|
23
|
+
|
|
24
|
+
## 一键启动
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 使用 npx 直接运行(推荐,无需安装)
|
|
28
|
+
npx aicq-chat-plugin
|
|
29
|
+
|
|
30
|
+
# 或全局安装
|
|
31
|
+
npm install -g aicq-chat-plugin
|
|
32
|
+
aicq-plugin
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## OpenClaw 集成
|
|
36
|
+
|
|
37
|
+
插件会自动注册为 OpenClaw sidecar,提供以下工具和网关:
|
|
38
|
+
|
|
39
|
+
### 工具
|
|
40
|
+
- `chat-friend` — 好友管理
|
|
41
|
+
- `chat-send` — 发送消息
|
|
42
|
+
- `chat-export-key` — 导出密钥
|
|
43
|
+
|
|
44
|
+
### 网关方法
|
|
45
|
+
- `aicq.status` — 插件状态
|
|
46
|
+
- `aicq.friends.list/add/remove` — 好友操作
|
|
47
|
+
- `aicq.chat.send/history/delete` — 聊天操作
|
|
48
|
+
- `aicq.groups.list/create/join` — 群组操作
|
|
49
|
+
- `aicq.identity.info` — 身份信息
|
|
50
|
+
|
|
51
|
+
## 配置
|
|
52
|
+
|
|
53
|
+
| 变量 | 默认值 | 说明 |
|
|
54
|
+
|------|--------|------|
|
|
55
|
+
| `AICQ_PORT` | 6109 | 插件服务端口 |
|
|
56
|
+
| `AICQ_SERVER_URL` | http://aicq.online:61018 | AICQ 服务器地址 |
|
|
57
|
+
| `AICQ_DATA_DIR` | ~/.aicq-plugin | 数据存储目录 |
|
|
58
|
+
|
|
59
|
+
## Chat UI
|
|
60
|
+
|
|
61
|
+
启动后访问 http://localhost:6109 即可使用完整的聊天界面。
|
package/cli.js
CHANGED
|
@@ -34,6 +34,70 @@ for (let i = 0; i < args.length; i++) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// ── SKILL.md template ─────────────────────────────────────────────
|
|
38
|
+
const SKILL_MD_TEMPLATE = `---
|
|
39
|
+
name: aicq-chat
|
|
40
|
+
description: AICQ End-to-end Encrypted Chat Plugin for OpenClaw — Full UI with friend management, group chat, file transfer, and AI agent communication
|
|
41
|
+
license: MIT
|
|
42
|
+
metadata:
|
|
43
|
+
author: AICQ
|
|
44
|
+
version: "{VERSION}"
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
# AICQ Encrypted Chat
|
|
48
|
+
|
|
49
|
+
AICQ 是一个端到端加密聊天插件,适用于 OpenClaw 的完整聊天 UI。支持好友管理、群组聊天、文件传输和 AI Agent 通信。
|
|
50
|
+
|
|
51
|
+
## 功能特性
|
|
52
|
+
|
|
53
|
+
- **端到端加密 (E2EE)** — 基于 NaCl (libsodium) 的加密体系,消息仅通信双方可读
|
|
54
|
+
- **Agent 管理** — 支持多 Agent 切换、创建和删除
|
|
55
|
+
- **好友管理** — 好友码添加、QR 码扫描、好友列表同步
|
|
56
|
+
- **群组聊天** — 创建群组、邀请成员、静默模式
|
|
57
|
+
- **消息功能** — Markdown/LaTeX 渲染、图片/文件上传、@提及
|
|
58
|
+
- **密钥管理** — 公钥/私钥显示、密钥轮换、指纹验证
|
|
59
|
+
- **P2P 通信** — 握手、文本传输、文件传输
|
|
60
|
+
|
|
61
|
+
## 一键启动
|
|
62
|
+
|
|
63
|
+
\`\`\`bash
|
|
64
|
+
# 使用 npx 直接运行(推荐,无需安装)
|
|
65
|
+
npx aicq-chat-plugin
|
|
66
|
+
|
|
67
|
+
# 或全局安装
|
|
68
|
+
npm install -g aicq-chat-plugin
|
|
69
|
+
aicq-plugin
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
## OpenClaw 集成
|
|
73
|
+
|
|
74
|
+
插件会自动注册为 OpenClaw sidecar,提供以下工具和网关:
|
|
75
|
+
|
|
76
|
+
### 工具
|
|
77
|
+
- \`chat-friend\` — 好友管理
|
|
78
|
+
- \`chat-send\` — 发送消息
|
|
79
|
+
- \`chat-export-key\` — 导出密钥
|
|
80
|
+
|
|
81
|
+
### 网关方法
|
|
82
|
+
- \`aicq.status\` — 插件状态
|
|
83
|
+
- \`aicq.friends.list/add/remove\` — 好友操作
|
|
84
|
+
- \`aicq.chat.send/history/delete\` — 聊天操作
|
|
85
|
+
- \`aicq.groups.list/create/join\` — 群组操作
|
|
86
|
+
- \`aicq.identity.info\` — 身份信息
|
|
87
|
+
|
|
88
|
+
## 配置
|
|
89
|
+
|
|
90
|
+
| 变量 | 默认值 | 说明 |
|
|
91
|
+
|------|--------|------|
|
|
92
|
+
| \`AICQ_PORT\` | 6109 | 插件服务端口 |
|
|
93
|
+
| \`AICQ_SERVER_URL\` | http://aicq.online:61018 | AICQ 服务器地址 |
|
|
94
|
+
| \`AICQ_DATA_DIR\` | ~/.aicq-plugin | 数据存储目录 |
|
|
95
|
+
|
|
96
|
+
## Chat UI
|
|
97
|
+
|
|
98
|
+
启动后访问 http://localhost:6109 即可使用完整的聊天界面。
|
|
99
|
+
`;
|
|
100
|
+
|
|
37
101
|
// ── Find OpenClaw installation ──────────────────────────────────────
|
|
38
102
|
function findOpenClawDir() {
|
|
39
103
|
const candidates = [
|
|
@@ -50,41 +114,63 @@ function findOpenClawDir() {
|
|
|
50
114
|
return null;
|
|
51
115
|
}
|
|
52
116
|
|
|
53
|
-
// ──
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
console.log('[AICQ] If you have OpenClaw installed, set OPENCLAW_HOME environment variable.');
|
|
59
|
-
return false;
|
|
117
|
+
// ── Find OpenClaw workspace (for skills/ directory) ────────────────
|
|
118
|
+
function findOpenClawWorkspace() {
|
|
119
|
+
// Check OPENCLAW_WORKSPACE env var first
|
|
120
|
+
if (process.env.OPENCLAW_WORKSPACE) {
|
|
121
|
+
return process.env.OPENCLAW_WORKSPACE;
|
|
60
122
|
}
|
|
61
123
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
124
|
+
// Try to find workspace from clawhub or common locations
|
|
125
|
+
const home = os.homedir();
|
|
126
|
+
const candidates = [
|
|
127
|
+
// Current working directory (most common for clawhub)
|
|
128
|
+
process.cwd(),
|
|
129
|
+
// Common workspace locations
|
|
130
|
+
path.join(home, 'my-project'),
|
|
131
|
+
path.join(home, 'openclaw'),
|
|
132
|
+
path.join(home, '.openclaw'),
|
|
133
|
+
];
|
|
66
134
|
|
|
67
|
-
// Check if
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (existing.version === current.version) {
|
|
74
|
-
console.log(`[AICQ] Plugin already installed at ${targetDir} (v${current.version})`);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
} catch (e) {}
|
|
135
|
+
// Check if any candidate has a skills/ directory
|
|
136
|
+
for (const dir of candidates) {
|
|
137
|
+
const skillsDir = path.join(dir, 'skills');
|
|
138
|
+
if (fs.existsSync(skillsDir)) {
|
|
139
|
+
return dir;
|
|
140
|
+
}
|
|
78
141
|
}
|
|
79
142
|
|
|
80
|
-
|
|
81
|
-
|
|
143
|
+
// Also check parent directories of the current working dir
|
|
144
|
+
let current = process.cwd();
|
|
145
|
+
for (let i = 0; i < 3; i++) {
|
|
146
|
+
const skillsDir = path.join(current, 'skills');
|
|
147
|
+
if (fs.existsSync(skillsDir)) {
|
|
148
|
+
return current;
|
|
149
|
+
}
|
|
150
|
+
current = path.dirname(current);
|
|
151
|
+
}
|
|
82
152
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Recursively copy a directory ────────────────────────────────────
|
|
157
|
+
function copyDirRecursive(src, dest) {
|
|
158
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
159
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const srcPath = path.join(src, entry.name);
|
|
162
|
+
const destPath = path.join(dest, entry.name);
|
|
163
|
+
if (entry.isDirectory()) {
|
|
164
|
+
if (entry.name === 'node_modules') continue;
|
|
165
|
+
copyDirRecursive(srcPath, destPath);
|
|
166
|
+
} else {
|
|
167
|
+
fs.copyFileSync(srcPath, destPath);
|
|
168
|
+
}
|
|
86
169
|
}
|
|
170
|
+
}
|
|
87
171
|
|
|
172
|
+
// ── Install plugin files to a target directory ─────────────────────
|
|
173
|
+
function installToDir(sourceDir, targetDir, version) {
|
|
88
174
|
// Files and dirs to copy
|
|
89
175
|
const filesToCopy = [
|
|
90
176
|
'index.js', 'cli.js', 'postinstall.js',
|
|
@@ -92,7 +178,7 @@ function installToOpenClaw() {
|
|
|
92
178
|
];
|
|
93
179
|
const dirsToCopy = ['lib', 'public'];
|
|
94
180
|
|
|
95
|
-
// Create target directory
|
|
181
|
+
// Create target directory if needed
|
|
96
182
|
if (!fs.existsSync(targetDir)) {
|
|
97
183
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
98
184
|
}
|
|
@@ -118,39 +204,134 @@ function installToOpenClaw() {
|
|
|
118
204
|
}
|
|
119
205
|
}
|
|
120
206
|
|
|
121
|
-
//
|
|
122
|
-
|
|
207
|
+
// Generate SKILL.md with current version
|
|
208
|
+
const skillMd = SKILL_MD_TEMPLATE.replace('{VERSION}', version);
|
|
209
|
+
fs.writeFileSync(path.join(targetDir, 'SKILL.md'), skillMd, 'utf8');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Copy plugin files to OpenClaw (skills + plugins) ──────────────
|
|
213
|
+
function installToOpenClaw() {
|
|
214
|
+
const PLUGIN_ID = 'aicq-chat';
|
|
215
|
+
const sourceDir = path.resolve(__dirname);
|
|
216
|
+
let version = '2.5.2';
|
|
217
|
+
|
|
218
|
+
// Read version from package.json
|
|
123
219
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
220
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(sourceDir, 'package.json'), 'utf8'));
|
|
221
|
+
version = pkg.version;
|
|
222
|
+
} catch (e) {}
|
|
223
|
+
|
|
224
|
+
// ── Step 1: Install to OpenClaw skills/ directory ──────────────
|
|
225
|
+
// This is the primary install location — OpenClaw dashboard discovers
|
|
226
|
+
// skills by scanning skills/ directories for SKILL.md marker files.
|
|
227
|
+
let skillsInstalled = false;
|
|
228
|
+
const workspace = findOpenClawWorkspace();
|
|
229
|
+
if (workspace) {
|
|
230
|
+
const skillsDir = path.join(workspace, 'skills');
|
|
231
|
+
const skillTargetDir = path.join(skillsDir, PLUGIN_ID);
|
|
232
|
+
|
|
233
|
+
// Check if already installed and up-to-date
|
|
234
|
+
const targetSkillJson = path.join(skillTargetDir, 'openclaw.plugin.json');
|
|
235
|
+
if (fs.existsSync(targetSkillJson)) {
|
|
236
|
+
try {
|
|
237
|
+
const existing = JSON.parse(fs.readFileSync(targetSkillJson, 'utf8'));
|
|
238
|
+
if (existing.version === version) {
|
|
239
|
+
console.log(`[AICQ] Skill already installed at ${skillTargetDir} (v${version})`);
|
|
240
|
+
skillsInstalled = true;
|
|
241
|
+
}
|
|
242
|
+
} catch (e) {}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!skillsInstalled) {
|
|
246
|
+
console.log(`[AICQ] Found workspace at: ${workspace}`);
|
|
247
|
+
console.log(`[AICQ] Installing skill to ${skillTargetDir}...`);
|
|
248
|
+
|
|
249
|
+
if (!fs.existsSync(skillsDir)) {
|
|
250
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
installToDir(sourceDir, skillTargetDir, version);
|
|
254
|
+
|
|
255
|
+
// Install npm dependencies in target
|
|
256
|
+
console.log('[AICQ] Installing skill dependencies...');
|
|
257
|
+
try {
|
|
258
|
+
execSync('npm install --omit=dev', {
|
|
259
|
+
cwd: skillTargetDir,
|
|
260
|
+
stdio: 'pipe',
|
|
261
|
+
timeout: 120000,
|
|
262
|
+
});
|
|
263
|
+
console.log('[AICQ] Skill dependencies installed.');
|
|
264
|
+
} catch (e) {
|
|
265
|
+
console.log('[AICQ] Warning: npm install failed. You may need to run manually:');
|
|
266
|
+
console.log(` cd ${skillTargetDir} && npm install`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(`[AICQ] Skill installed to: ${skillTargetDir}`);
|
|
270
|
+
skillsInstalled = true;
|
|
271
|
+
}
|
|
133
272
|
}
|
|
134
273
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
274
|
+
// ── Step 2: Install to OpenClaw plugins/ directory ─────────────
|
|
275
|
+
// This is the secondary install location for sidecar startup.
|
|
276
|
+
// OpenClaw reads openclaw.plugin.json from plugins/ to launch sidecar.
|
|
277
|
+
let pluginInstalled = false;
|
|
278
|
+
const openclawDir = findOpenClawDir();
|
|
279
|
+
if (openclawDir) {
|
|
280
|
+
const pluginsDir = path.join(openclawDir, 'plugins');
|
|
281
|
+
const pluginTargetDir = path.join(pluginsDir, PLUGIN_ID);
|
|
139
282
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
} else {
|
|
151
|
-
fs.copyFileSync(srcPath, destPath);
|
|
283
|
+
// Check if already installed and up-to-date
|
|
284
|
+
const targetPluginJson = path.join(pluginTargetDir, 'openclaw.plugin.json');
|
|
285
|
+
if (fs.existsSync(targetPluginJson)) {
|
|
286
|
+
try {
|
|
287
|
+
const existing = JSON.parse(fs.readFileSync(targetPluginJson, 'utf8'));
|
|
288
|
+
if (existing.version === version) {
|
|
289
|
+
console.log(`[AICQ] Plugin already installed at ${pluginTargetDir} (v${version})`);
|
|
290
|
+
pluginInstalled = true;
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {}
|
|
152
293
|
}
|
|
294
|
+
|
|
295
|
+
if (!pluginInstalled) {
|
|
296
|
+
console.log(`[AICQ] Found OpenClaw at: ${openclawDir}`);
|
|
297
|
+
console.log(`[AICQ] Installing plugin to ${pluginTargetDir}...`);
|
|
298
|
+
|
|
299
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
300
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
installToDir(sourceDir, pluginTargetDir, version);
|
|
304
|
+
|
|
305
|
+
// Install npm dependencies in target
|
|
306
|
+
console.log('[AICQ] Installing plugin dependencies...');
|
|
307
|
+
try {
|
|
308
|
+
execSync('npm install --omit=dev', {
|
|
309
|
+
cwd: pluginTargetDir,
|
|
310
|
+
stdio: 'pipe',
|
|
311
|
+
timeout: 120000,
|
|
312
|
+
});
|
|
313
|
+
console.log('[AICQ] Plugin dependencies installed.');
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.log('[AICQ] Warning: npm install failed. You may need to run manually:');
|
|
316
|
+
console.log(` cd ${pluginTargetDir} && npm install`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(`[AICQ] Plugin installed to: ${pluginTargetDir}`);
|
|
320
|
+
pluginInstalled = true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── Summary ────────────────────────────────────────────────────
|
|
325
|
+
if (!skillsInstalled && !pluginInstalled) {
|
|
326
|
+
console.log('[AICQ] OpenClaw not found, skipping auto-install.');
|
|
327
|
+
console.log('[AICQ] If you have OpenClaw installed, set OPENCLAW_HOME or OPENCLAW_WORKSPACE environment variable.');
|
|
328
|
+
console.log('[AICQ] OPENCLAW_HOME=<openclaw-root-dir> (for plugins/ directory)');
|
|
329
|
+
console.log('[AICQ] OPENCLAW_WORKSPACE=<workspace-dir> (for skills/ directory)');
|
|
330
|
+
return false;
|
|
153
331
|
}
|
|
332
|
+
|
|
333
|
+
console.log('[AICQ] Restart OpenClaw to activate the plugin.');
|
|
334
|
+
return true;
|
|
154
335
|
}
|
|
155
336
|
|
|
156
337
|
// ── Help ────────────────────────────────────────────────────────────
|
|
@@ -176,7 +357,8 @@ Environment Variables:
|
|
|
176
357
|
AICQ_PORT Plugin server port
|
|
177
358
|
AICQ_SERVER_URL AICQ server URL
|
|
178
359
|
AICQ_DATA_DIR Data directory (default: ~/.aicq-plugin)
|
|
179
|
-
OPENCLAW_HOME OpenClaw installation directory
|
|
360
|
+
OPENCLAW_HOME OpenClaw installation directory (for plugins/)
|
|
361
|
+
OPENCLAW_WORKSPACE OpenClaw workspace directory (for skills/)
|
|
180
362
|
|
|
181
363
|
Examples:
|
|
182
364
|
npx aicq-chat-plugin # Install + start
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicq-chat-plugin",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "AICQ End-to-end Encrypted Chat Plugin for OpenClaw — Full UI with friend management, group chat, file transfer, and AI agent communication",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"lib/",
|
|
14
14
|
"public/",
|
|
15
15
|
"openclaw.plugin.json",
|
|
16
|
+
"SKILL.md",
|
|
16
17
|
"README.md"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
package/postinstall.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* AICQ Chat Plugin — Post-install script
|
|
4
4
|
*
|
|
5
|
-
* Automatically installs the plugin into OpenClaw's plugins
|
|
6
|
-
* OpenClaw
|
|
7
|
-
*
|
|
5
|
+
* Automatically installs the plugin into OpenClaw's skills/ and plugins/ directories.
|
|
6
|
+
* OpenClaw discovers skills by scanning skills/ directories for SKILL.md marker files.
|
|
7
|
+
* OpenClaw reads openclaw.plugin.json from plugins/ to launch sidecar processes.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
@@ -15,6 +15,65 @@ const { execSync } = require('child_process');
|
|
|
15
15
|
const PLUGIN_ID = 'aicq-chat';
|
|
16
16
|
const PLUGIN_DIR = path.resolve(__dirname);
|
|
17
17
|
|
|
18
|
+
// ── SKILL.md template ─────────────────────────────────────────────
|
|
19
|
+
const SKILL_MD_TEMPLATE = `---
|
|
20
|
+
name: aicq-chat
|
|
21
|
+
description: AICQ End-to-end Encrypted Chat Plugin for OpenClaw — Full UI with friend management, group chat, file transfer, and AI agent communication
|
|
22
|
+
license: MIT
|
|
23
|
+
metadata:
|
|
24
|
+
author: AICQ
|
|
25
|
+
version: "{VERSION}"
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# AICQ Encrypted Chat
|
|
29
|
+
|
|
30
|
+
AICQ 是一个端到端加密聊天插件,适用于 OpenClaw 的完整聊天 UI。支持好友管理、群组聊天、文件传输和 AI Agent 通信。
|
|
31
|
+
|
|
32
|
+
## 功能特性
|
|
33
|
+
|
|
34
|
+
- **端到端加密 (E2EE)** — 基于 NaCl (libsodium) 的加密体系,消息仅通信双方可读
|
|
35
|
+
- **Agent 管理** — 支持多 Agent 切换、创建和删除
|
|
36
|
+
- **好友管理** — 好友码添加、QR 码扫描、好友列表同步
|
|
37
|
+
- **群组聊天** — 创建群组、邀请成员、静默模式
|
|
38
|
+
- **消息功能** — Markdown/LaTeX 渲染、图片/文件上传、@提及
|
|
39
|
+
- **密钥管理** — 公钥/私钥显示、密钥轮换、指纹验证
|
|
40
|
+
- **P2P 通信** — 握手、文本传输、文件传输
|
|
41
|
+
|
|
42
|
+
## 一键启动
|
|
43
|
+
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
npx aicq-chat-plugin
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
## OpenClaw 集成
|
|
49
|
+
|
|
50
|
+
插件会自动注册为 OpenClaw sidecar,提供以下工具和网关:
|
|
51
|
+
|
|
52
|
+
### 工具
|
|
53
|
+
- \`chat-friend\` — 好友管理
|
|
54
|
+
- \`chat-send\` — 发送消息
|
|
55
|
+
- \`chat-export-key\` — 导出密钥
|
|
56
|
+
|
|
57
|
+
### 网关方法
|
|
58
|
+
- \`aicq.status\` — 插件状态
|
|
59
|
+
- \`aicq.friends.list/add/remove\` — 好友操作
|
|
60
|
+
- \`aicq.chat.send/history/delete\` — 聊天操作
|
|
61
|
+
- \`aicq.groups.list/create/join\` — 群组操作
|
|
62
|
+
- \`aicq.identity.info\` — 身份信息
|
|
63
|
+
|
|
64
|
+
## 配置
|
|
65
|
+
|
|
66
|
+
| 变量 | 默认值 | 说明 |
|
|
67
|
+
|------|--------|------|
|
|
68
|
+
| \`AICQ_PORT\` | 6109 | 插件服务端口 |
|
|
69
|
+
| \`AICQ_SERVER_URL\` | http://aicq.online:61018 | AICQ 服务器地址 |
|
|
70
|
+
| \`AICQ_DATA_DIR\` | ~/.aicq-plugin | 数据存储目录 |
|
|
71
|
+
|
|
72
|
+
## Chat UI
|
|
73
|
+
|
|
74
|
+
启动后访问 http://localhost:6109 即可使用完整的聊天界面。
|
|
75
|
+
`;
|
|
76
|
+
|
|
18
77
|
// ── Find OpenClaw installation ──────────────────────────────────────
|
|
19
78
|
function findOpenClawDir() {
|
|
20
79
|
const candidates = [
|
|
@@ -23,7 +82,6 @@ function findOpenClawDir() {
|
|
|
23
82
|
path.join(os.homedir(), '.config', 'openclaw'),
|
|
24
83
|
];
|
|
25
84
|
|
|
26
|
-
// Check environment variable
|
|
27
85
|
if (process.env.OPENCLAW_HOME) {
|
|
28
86
|
candidates.unshift(process.env.OPENCLAW_HOME);
|
|
29
87
|
}
|
|
@@ -36,17 +94,60 @@ function findOpenClawDir() {
|
|
|
36
94
|
return null;
|
|
37
95
|
}
|
|
38
96
|
|
|
39
|
-
// ──
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
97
|
+
// ── Find OpenClaw workspace (for skills/ directory) ────────────────
|
|
98
|
+
function findOpenClawWorkspace() {
|
|
99
|
+
if (process.env.OPENCLAW_WORKSPACE) {
|
|
100
|
+
return process.env.OPENCLAW_WORKSPACE;
|
|
101
|
+
}
|
|
43
102
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
103
|
+
const home = os.homedir();
|
|
104
|
+
const candidates = [
|
|
105
|
+
process.cwd(),
|
|
106
|
+
path.join(home, 'my-project'),
|
|
107
|
+
path.join(home, 'openclaw'),
|
|
108
|
+
path.join(home, '.openclaw'),
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (const dir of candidates) {
|
|
112
|
+
const skillsDir = path.join(dir, 'skills');
|
|
113
|
+
if (fs.existsSync(skillsDir)) {
|
|
114
|
+
return dir;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check parent directories
|
|
119
|
+
let current = process.cwd();
|
|
120
|
+
for (let i = 0; i < 3; i++) {
|
|
121
|
+
const skillsDir = path.join(current, 'skills');
|
|
122
|
+
if (fs.existsSync(skillsDir)) {
|
|
123
|
+
return current;
|
|
124
|
+
}
|
|
125
|
+
current = path.dirname(current);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Recursively copy a directory ────────────────────────────────────
|
|
132
|
+
function copyDirRecursive(src, dest) {
|
|
133
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
134
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
135
|
+
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const srcPath = path.join(src, entry.name);
|
|
138
|
+
const destPath = path.join(dest, entry.name);
|
|
139
|
+
|
|
140
|
+
if (entry.isDirectory()) {
|
|
141
|
+
if (entry.name === 'node_modules') continue;
|
|
142
|
+
copyDirRecursive(srcPath, destPath);
|
|
143
|
+
} else {
|
|
144
|
+
fs.copyFileSync(srcPath, destPath);
|
|
145
|
+
}
|
|
47
146
|
}
|
|
147
|
+
}
|
|
48
148
|
|
|
49
|
-
|
|
149
|
+
// ── Install plugin files to a target directory ─────────────────────
|
|
150
|
+
function installToDir(sourceDir, targetDir, version) {
|
|
50
151
|
const filesToCopy = [
|
|
51
152
|
'index.js',
|
|
52
153
|
'cli.js',
|
|
@@ -61,18 +162,14 @@ function installToOpenClaw(openclawDir) {
|
|
|
61
162
|
'public',
|
|
62
163
|
];
|
|
63
164
|
|
|
64
|
-
//
|
|
65
|
-
if (fs.existsSync(targetDir)) {
|
|
66
|
-
console.log(`[AICQ] Updating existing plugin at ${targetDir}`);
|
|
67
|
-
// Don't remove the entire dir — preserve node_modules and data
|
|
68
|
-
} else {
|
|
69
|
-
console.log(`[AICQ] Installing plugin to ${targetDir}`);
|
|
165
|
+
// Create target directory if needed
|
|
166
|
+
if (!fs.existsSync(targetDir)) {
|
|
70
167
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
71
168
|
}
|
|
72
169
|
|
|
73
170
|
// Copy individual files
|
|
74
171
|
for (const file of filesToCopy) {
|
|
75
|
-
const src = path.join(
|
|
172
|
+
const src = path.join(sourceDir, file);
|
|
76
173
|
const dest = path.join(targetDir, file);
|
|
77
174
|
if (fs.existsSync(src)) {
|
|
78
175
|
fs.copyFileSync(src, dest);
|
|
@@ -81,10 +178,9 @@ function installToOpenClaw(openclawDir) {
|
|
|
81
178
|
|
|
82
179
|
// Copy directories
|
|
83
180
|
for (const dir of dirsToCopy) {
|
|
84
|
-
const src = path.join(
|
|
181
|
+
const src = path.join(sourceDir, dir);
|
|
85
182
|
const dest = path.join(targetDir, dir);
|
|
86
183
|
if (fs.existsSync(src)) {
|
|
87
|
-
// Remove old and copy fresh
|
|
88
184
|
if (fs.existsSync(dest)) {
|
|
89
185
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
90
186
|
}
|
|
@@ -92,15 +188,33 @@ function installToOpenClaw(openclawDir) {
|
|
|
92
188
|
}
|
|
93
189
|
}
|
|
94
190
|
|
|
95
|
-
//
|
|
96
|
-
|
|
191
|
+
// Generate SKILL.md with current version
|
|
192
|
+
const skillMd = SKILL_MD_TEMPLATE.replace('{VERSION}', version);
|
|
193
|
+
fs.writeFileSync(path.join(targetDir, 'SKILL.md'), skillMd, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Install to skills/ directory ────────────────────────────────────
|
|
197
|
+
function installToSkillsDir(workspace, version) {
|
|
198
|
+
const skillsDir = path.join(workspace, 'skills');
|
|
199
|
+
const targetDir = path.join(skillsDir, PLUGIN_ID);
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(skillsDir)) {
|
|
202
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(`[AICQ] Installing skill to ${targetDir}`);
|
|
206
|
+
|
|
207
|
+
installToDir(PLUGIN_DIR, targetDir, version);
|
|
208
|
+
|
|
209
|
+
// Install npm dependencies
|
|
210
|
+
console.log('[AICQ] Installing skill dependencies...');
|
|
97
211
|
try {
|
|
98
|
-
execSync('npm install --
|
|
212
|
+
execSync('npm install --omit=dev', {
|
|
99
213
|
cwd: targetDir,
|
|
100
214
|
stdio: 'pipe',
|
|
101
215
|
timeout: 120000,
|
|
102
216
|
});
|
|
103
|
-
console.log('[AICQ]
|
|
217
|
+
console.log('[AICQ] Skill dependencies installed.');
|
|
104
218
|
} catch (e) {
|
|
105
219
|
console.log('[AICQ] Warning: npm install failed. You may need to run it manually:');
|
|
106
220
|
console.log(` cd ${targetDir} && npm install`);
|
|
@@ -109,23 +223,34 @@ function installToOpenClaw(openclawDir) {
|
|
|
109
223
|
return targetDir;
|
|
110
224
|
}
|
|
111
225
|
|
|
112
|
-
// ──
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
const
|
|
226
|
+
// ── Install to plugins/ directory ───────────────────────────────────
|
|
227
|
+
function installToPluginsDir(openclawDir, version) {
|
|
228
|
+
const pluginsDir = path.join(openclawDir, 'plugins');
|
|
229
|
+
const targetDir = path.join(pluginsDir, PLUGIN_ID);
|
|
116
230
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
231
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
232
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
233
|
+
}
|
|
120
234
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
235
|
+
console.log(`[AICQ] Installing plugin to ${targetDir}`);
|
|
236
|
+
|
|
237
|
+
installToDir(PLUGIN_DIR, targetDir, version);
|
|
238
|
+
|
|
239
|
+
// Install npm dependencies
|
|
240
|
+
console.log('[AICQ] Installing plugin dependencies...');
|
|
241
|
+
try {
|
|
242
|
+
execSync('npm install --omit=dev', {
|
|
243
|
+
cwd: targetDir,
|
|
244
|
+
stdio: 'pipe',
|
|
245
|
+
timeout: 120000,
|
|
246
|
+
});
|
|
247
|
+
console.log('[AICQ] Plugin dependencies installed.');
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.log('[AICQ] Warning: npm install failed. You may need to run it manually:');
|
|
250
|
+
console.log(` cd ${targetDir} && npm install`);
|
|
128
251
|
}
|
|
252
|
+
|
|
253
|
+
return targetDir;
|
|
129
254
|
}
|
|
130
255
|
|
|
131
256
|
// ── Main ────────────────────────────────────────────────────────────
|
|
@@ -135,54 +260,77 @@ console.log(' ║ AICQ Chat Plugin — Installing... ║');
|
|
|
135
260
|
console.log(' ╚══════════════════════════════════════════════╝');
|
|
136
261
|
console.log('');
|
|
137
262
|
|
|
138
|
-
//
|
|
139
|
-
|
|
263
|
+
// Read version from package.json
|
|
264
|
+
let version = '2.5.2';
|
|
265
|
+
try {
|
|
266
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PLUGIN_DIR, 'package.json'), 'utf8'));
|
|
267
|
+
version = pkg.version;
|
|
268
|
+
} catch (e) {}
|
|
269
|
+
|
|
270
|
+
let skillInstalled = false;
|
|
271
|
+
let pluginInstalled = false;
|
|
140
272
|
|
|
273
|
+
// Step 1: Install to skills/ directory (for dashboard discovery)
|
|
274
|
+
const workspace = findOpenClawWorkspace();
|
|
275
|
+
if (workspace) {
|
|
276
|
+
console.log(`[AICQ] Found workspace at: ${workspace}`);
|
|
277
|
+
try {
|
|
278
|
+
const skillDir = installToSkillsDir(workspace, version);
|
|
279
|
+
console.log(`[AICQ] Skill installed to: ${skillDir}`);
|
|
280
|
+
skillInstalled = true;
|
|
281
|
+
} catch (e) {
|
|
282
|
+
console.error('[AICQ] Skill install failed:', e.message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Step 2: Install to plugins/ directory (for sidecar startup)
|
|
287
|
+
const openclawDir = findOpenClawDir();
|
|
141
288
|
if (openclawDir) {
|
|
142
289
|
console.log(`[AICQ] Found OpenClaw at: ${openclawDir}`);
|
|
143
290
|
try {
|
|
144
|
-
const
|
|
145
|
-
console.log(
|
|
146
|
-
|
|
147
|
-
console.log(' ║ AICQ Plugin Installed Successfully! ║');
|
|
148
|
-
console.log(' ╠══════════════════════════════════════════════╣');
|
|
149
|
-
console.log(' ║ ║');
|
|
150
|
-
console.log(' ║ Plugin installed to: ║');
|
|
151
|
-
console.log(` ║ ${installedDir}`);
|
|
152
|
-
console.log(' ║ ║');
|
|
153
|
-
console.log(' ║ Restart OpenClaw to activate the plugin. ║');
|
|
154
|
-
console.log(' ║ ║');
|
|
155
|
-
console.log(' ║ Chat UI: http://localhost:6109 ║');
|
|
156
|
-
console.log(' ║ Docs: https://aicq.online ║');
|
|
157
|
-
console.log(' ╚══════════════════════════════════════════════╝');
|
|
158
|
-
console.log('');
|
|
291
|
+
const pluginDir = installToPluginsDir(openclawDir, version);
|
|
292
|
+
console.log(`[AICQ] Plugin installed to: ${pluginDir}`);
|
|
293
|
+
pluginInstalled = true;
|
|
159
294
|
} catch (e) {
|
|
160
|
-
console.error('[AICQ]
|
|
161
|
-
console.log('');
|
|
162
|
-
printManualInstructions();
|
|
295
|
+
console.error('[AICQ] Plugin install failed:', e.message);
|
|
163
296
|
}
|
|
164
|
-
} else {
|
|
165
|
-
console.log('[AICQ] OpenClaw not found — skipping auto-install.');
|
|
166
|
-
console.log('');
|
|
167
|
-
printManualInstructions();
|
|
168
297
|
}
|
|
169
298
|
|
|
170
|
-
|
|
299
|
+
// Summary
|
|
300
|
+
console.log('');
|
|
301
|
+
if (skillInstalled || pluginInstalled) {
|
|
302
|
+
console.log(' ╔══════════════════════════════════════════════╗');
|
|
303
|
+
console.log(' ║ AICQ Plugin Installed Successfully! ║');
|
|
304
|
+
console.log(' ╠══════════════════════════════════════════════╣');
|
|
305
|
+
console.log(' ║ ║');
|
|
306
|
+
if (skillInstalled) {
|
|
307
|
+
console.log(' ║ ✅ Skill installed (dashboard visible) ║');
|
|
308
|
+
}
|
|
309
|
+
if (pluginInstalled) {
|
|
310
|
+
console.log(' ║ ✅ Plugin installed (sidecar ready) ║');
|
|
311
|
+
}
|
|
312
|
+
console.log(' ║ ║');
|
|
313
|
+
console.log(' ║ Restart OpenClaw to activate the plugin. ║');
|
|
314
|
+
console.log(' ║ ║');
|
|
315
|
+
console.log(' ║ Chat UI: http://localhost:6109 ║');
|
|
316
|
+
console.log(' ║ Docs: https://aicq.online ║');
|
|
317
|
+
console.log(' ╚══════════════════════════════════════════════╝');
|
|
318
|
+
} else {
|
|
171
319
|
console.log(' ╔══════════════════════════════════════════════╗');
|
|
172
320
|
console.log(' ║ AICQ Chat Plugin Installed! ║');
|
|
173
321
|
console.log(' ╠══════════════════════════════════════════════╣');
|
|
174
322
|
console.log(' ║ ║');
|
|
175
|
-
console.log(' ║
|
|
176
|
-
console.log(' ║
|
|
177
|
-
console.log(' ║
|
|
178
|
-
console.log(' ║
|
|
179
|
-
console.log(' ║
|
|
323
|
+
console.log(' ║ OpenClaw not found — auto-install skipped ║');
|
|
324
|
+
console.log(' ║ ║');
|
|
325
|
+
console.log(' ║ Set environment variables: ║');
|
|
326
|
+
console.log(' ║ OPENCLAW_HOME=<openclaw-root> ║');
|
|
327
|
+
console.log(' ║ OPENCLAW_WORKSPACE=<workspace-dir> ║');
|
|
180
328
|
console.log(' ║ ║');
|
|
181
329
|
console.log(' ║ Or start standalone: ║');
|
|
182
|
-
console.log(' ║ npx aicq-plugin
|
|
330
|
+
console.log(' ║ npx aicq-chat-plugin ║');
|
|
183
331
|
console.log(' ║ ║');
|
|
184
332
|
console.log(' ║ Chat UI: http://localhost:6109 ║');
|
|
185
333
|
console.log(' ║ Docs: https://aicq.online ║');
|
|
186
334
|
console.log(' ╚══════════════════════════════════════════════╝');
|
|
187
|
-
console.log('');
|
|
188
335
|
}
|
|
336
|
+
console.log('');
|