aicq-chat-plugin 2.5.0 → 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 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
@@ -3,14 +3,18 @@
3
3
  * AICQ Chat Plugin — CLI Entry Point
4
4
  *
5
5
  * Usage:
6
- * aicq-plugin Start the plugin server (default port 6109)
7
- * aicq-plugin start Start the plugin server
8
- * aicq-plugin status Check plugin status
9
- * aicq-plugin --port Specify port (default 6109)
10
- * aicq-plugin --help Show help
6
+ * npx aicq-chat-plugin Start plugin + auto-install to OpenClaw
7
+ * aicq-plugin Same as above
8
+ * aicq-plugin start Start the plugin server
9
+ * aicq-plugin install Install plugin to OpenClaw only
10
+ * aicq-plugin status Check plugin status
11
+ * aicq-plugin --port <port> Specify port (default 6109)
12
+ * aicq-plugin --help Show help
11
13
  */
12
- const { spawn } = require('child_process');
14
+ const { spawn, execSync } = require('child_process');
15
+ const fs = require('fs');
13
16
  const path = require('path');
17
+ const os = require('os');
14
18
 
15
19
  const args = process.argv.slice(2);
16
20
  const command = args[0] || 'start';
@@ -30,15 +34,318 @@ for (let i = 0; i < args.length; i++) {
30
34
  }
31
35
  }
32
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
+
101
+ // ── Find OpenClaw installation ──────────────────────────────────────
102
+ function findOpenClawDir() {
103
+ const candidates = [
104
+ path.join(os.homedir(), '.openclaw'),
105
+ path.join(os.homedir(), 'openclaw'),
106
+ path.join(os.homedir(), '.config', 'openclaw'),
107
+ ];
108
+ if (process.env.OPENCLAW_HOME) {
109
+ candidates.unshift(process.env.OPENCLAW_HOME);
110
+ }
111
+ for (const dir of candidates) {
112
+ if (fs.existsSync(dir)) return dir;
113
+ }
114
+ return null;
115
+ }
116
+
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;
122
+ }
123
+
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
+ ];
134
+
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
+ }
141
+ }
142
+
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
+ }
152
+
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
+ }
169
+ }
170
+ }
171
+
172
+ // ── Install plugin files to a target directory ─────────────────────
173
+ function installToDir(sourceDir, targetDir, version) {
174
+ // Files and dirs to copy
175
+ const filesToCopy = [
176
+ 'index.js', 'cli.js', 'postinstall.js',
177
+ 'openclaw.plugin.json', 'package.json', 'README.md',
178
+ ];
179
+ const dirsToCopy = ['lib', 'public'];
180
+
181
+ // Create target directory if needed
182
+ if (!fs.existsSync(targetDir)) {
183
+ fs.mkdirSync(targetDir, { recursive: true });
184
+ }
185
+
186
+ // Copy files
187
+ for (const file of filesToCopy) {
188
+ const src = path.join(sourceDir, file);
189
+ const dest = path.join(targetDir, file);
190
+ if (fs.existsSync(src)) {
191
+ fs.copyFileSync(src, dest);
192
+ }
193
+ }
194
+
195
+ // Copy directories
196
+ for (const dir of dirsToCopy) {
197
+ const src = path.join(sourceDir, dir);
198
+ const dest = path.join(targetDir, dir);
199
+ if (fs.existsSync(src)) {
200
+ if (fs.existsSync(dest)) {
201
+ fs.rmSync(dest, { recursive: true, force: true });
202
+ }
203
+ copyDirRecursive(src, dest);
204
+ }
205
+ }
206
+
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
219
+ try {
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
+ }
272
+ }
273
+
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);
282
+
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) {}
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;
331
+ }
332
+
333
+ console.log('[AICQ] Restart OpenClaw to activate the plugin.');
334
+ return true;
335
+ }
336
+
337
+ // ── Help ────────────────────────────────────────────────────────────
33
338
  if (command === '--help' || command === '-h') {
34
339
  console.log(`
35
340
  AICQ Chat Plugin — End-to-End Encrypted Chat for OpenClaw
36
341
 
37
342
  Usage:
343
+ npx aicq-chat-plugin Start plugin + auto-install to OpenClaw
38
344
  aicq-plugin [command] [options]
39
345
 
40
346
  Commands:
41
- start Start the plugin server (default)
347
+ start Install to OpenClaw (if needed) and start plugin server (default)
348
+ install Install plugin to OpenClaw only (don't start server)
42
349
  status Check if the plugin is running
43
350
 
44
351
  Options:
@@ -50,15 +357,20 @@ Environment Variables:
50
357
  AICQ_PORT Plugin server port
51
358
  AICQ_SERVER_URL AICQ server URL
52
359
  AICQ_DATA_DIR Data directory (default: ~/.aicq-plugin)
360
+ OPENCLAW_HOME OpenClaw installation directory (for plugins/)
361
+ OPENCLAW_WORKSPACE OpenClaw workspace directory (for skills/)
53
362
 
54
363
  Examples:
55
- aicq-plugin # Start on default port
56
- aicq-plugin --port 8080 # Start on port 8080
57
- aicq-plugin -s http://localhost # Connect to local server
364
+ npx aicq-chat-plugin # Install + start
365
+ aicq-plugin # Start on default port
366
+ aicq-plugin install # Install to OpenClaw only
367
+ aicq-plugin --port 8080 # Start on port 8080
368
+ aicq-plugin -s http://localhost # Connect to local server
58
369
  `);
59
370
  process.exit(0);
60
371
  }
61
372
 
373
+ // ── Status ──────────────────────────────────────────────────────────
62
374
  if (command === 'status') {
63
375
  const http = require('http');
64
376
  const req = http.get(`http://localhost:${port}/api/status`, (res) => {
@@ -80,7 +392,7 @@ if (command === 'status') {
80
392
  });
81
393
  req.on('error', () => {
82
394
  console.log(`AICQ Plugin is not running on port ${port}.`);
83
- console.log(`Start it with: aicq-plugin --port ${port}`);
395
+ console.log(`Start it with: npx aicq-chat-plugin`);
84
396
  });
85
397
  req.setTimeout(3000, () => {
86
398
  req.destroy();
@@ -89,7 +401,15 @@ if (command === 'status') {
89
401
  process.exit(0);
90
402
  }
91
403
 
92
- // Start the plugin server
404
+ // ── Install only ────────────────────────────────────────────────────
405
+ if (command === 'install') {
406
+ installToOpenClaw();
407
+ process.exit(0);
408
+ }
409
+
410
+ // ── Start (default) — auto-install then run ─────────────────────────
411
+ installToOpenClaw();
412
+
93
413
  console.log(`[AICQ] Starting plugin on port ${port}`);
94
414
  console.log(`[AICQ] Server: ${serverUrl}`);
95
415
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "aicq-chat",
3
3
  "name": "AICQ Encrypted Chat",
4
- "version": "2.5.0",
4
+ "version": "2.5.2",
5
5
  "description": "End-to-end encrypted chat plugin for OpenClaw agents — Node.js implementation with full UI",
6
6
  "entry": "index.js",
7
7
  "activation": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicq-chat-plugin",
3
- "version": "2.5.0",
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 directory.
6
- * OpenClaw requires plugins to be in ~/.openclaw/plugins/<plugin-id>/
7
- * with an openclaw.plugin.json file to recognize them.
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
- // ── Copy plugin files to OpenClaw plugins directory ─────────────────
40
- function installToOpenClaw(openclawDir) {
41
- const pluginsDir = path.join(openclawDir, 'plugins');
42
- const targetDir = path.join(pluginsDir, PLUGIN_ID);
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
- // Create plugins directory if needed
45
- if (!fs.existsSync(pluginsDir)) {
46
- fs.mkdirSync(pluginsDir, { recursive: true });
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
- // Files to copy (from package.json "files" field)
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
- // Remove old installation if exists
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(PLUGIN_DIR, file);
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(PLUGIN_DIR, dir);
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
- // Install npm dependencies in the target directory
96
- console.log('[AICQ] Installing plugin dependencies...');
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 --production', {
212
+ execSync('npm install --omit=dev', {
99
213
  cwd: targetDir,
100
214
  stdio: 'pipe',
101
215
  timeout: 120000,
102
216
  });
103
- console.log('[AICQ] Dependencies installed.');
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
- // ── Recursively copy a directory ────────────────────────────────────
113
- function copyDirRecursive(src, dest) {
114
- fs.mkdirSync(dest, { recursive: true });
115
- const entries = fs.readdirSync(src, { withFileTypes: true });
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
- for (const entry of entries) {
118
- const srcPath = path.join(src, entry.name);
119
- const destPath = path.join(dest, entry.name);
231
+ if (!fs.existsSync(pluginsDir)) {
232
+ fs.mkdirSync(pluginsDir, { recursive: true });
233
+ }
120
234
 
121
- if (entry.isDirectory()) {
122
- // Skip node_modules — will be installed fresh
123
- if (entry.name === 'node_modules') continue;
124
- copyDirRecursive(srcPath, destPath);
125
- } else {
126
- fs.copyFileSync(srcPath, destPath);
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
- // Try to auto-install into OpenClaw
139
- const openclawDir = findOpenClawDir();
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 installedDir = installToOpenClaw(openclawDir);
145
- console.log('');
146
- console.log(' ╔══════════════════════════════════════════════╗');
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] Auto-install failed:', e.message);
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
- function printManualInstructions() {
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(' ║ Manual install to OpenClaw: ║');
176
- console.log(' ║ mkdir -p ~/.openclaw/plugins/aicq-chat ║');
177
- console.log(' ║ cp -r ' + PLUGIN_DIR + '/* ~/.openclaw/plugins/aicq-chat/ ║');
178
- console.log(' ║ cd ~/.openclaw/plugins/aicq-chat ║');
179
- console.log(' ║ npm install ║');
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('');