aicq-chat-plugin 2.4.1 → 2.5.1

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/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,137 @@ for (let i = 0; i < args.length; i++) {
30
34
  }
31
35
  }
32
36
 
37
+ // ── Find OpenClaw installation ──────────────────────────────────────
38
+ function findOpenClawDir() {
39
+ const candidates = [
40
+ path.join(os.homedir(), '.openclaw'),
41
+ path.join(os.homedir(), 'openclaw'),
42
+ path.join(os.homedir(), '.config', 'openclaw'),
43
+ ];
44
+ if (process.env.OPENCLAW_HOME) {
45
+ candidates.unshift(process.env.OPENCLAW_HOME);
46
+ }
47
+ for (const dir of candidates) {
48
+ if (fs.existsSync(dir)) return dir;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // ── Copy plugin files to OpenClaw plugins directory ─────────────────
54
+ function installToOpenClaw() {
55
+ const openclawDir = findOpenClawDir();
56
+ if (!openclawDir) {
57
+ console.log('[AICQ] OpenClaw not found, skipping auto-install.');
58
+ console.log('[AICQ] If you have OpenClaw installed, set OPENCLAW_HOME environment variable.');
59
+ return false;
60
+ }
61
+
62
+ const PLUGIN_ID = 'aicq-chat';
63
+ const pluginsDir = path.join(openclawDir, 'plugins');
64
+ const targetDir = path.join(pluginsDir, PLUGIN_ID);
65
+ const sourceDir = path.resolve(__dirname);
66
+
67
+ // Check if already installed and up-to-date
68
+ const targetPluginJson = path.join(targetDir, 'openclaw.plugin.json');
69
+ if (fs.existsSync(targetPluginJson)) {
70
+ try {
71
+ const existing = JSON.parse(fs.readFileSync(targetPluginJson, 'utf8'));
72
+ const current = JSON.parse(fs.readFileSync(path.join(sourceDir, 'openclaw.plugin.json'), 'utf8'));
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) {}
78
+ }
79
+
80
+ console.log(`[AICQ] Found OpenClaw at: ${openclawDir}`);
81
+ console.log(`[AICQ] Installing plugin to ${targetDir}...`);
82
+
83
+ // Create plugins directory if needed
84
+ if (!fs.existsSync(pluginsDir)) {
85
+ fs.mkdirSync(pluginsDir, { recursive: true });
86
+ }
87
+
88
+ // Files and dirs to copy
89
+ const filesToCopy = [
90
+ 'index.js', 'cli.js', 'postinstall.js',
91
+ 'openclaw.plugin.json', 'package.json', 'README.md',
92
+ ];
93
+ const dirsToCopy = ['lib', 'public'];
94
+
95
+ // Create target directory
96
+ if (!fs.existsSync(targetDir)) {
97
+ fs.mkdirSync(targetDir, { recursive: true });
98
+ }
99
+
100
+ // Copy files
101
+ for (const file of filesToCopy) {
102
+ const src = path.join(sourceDir, file);
103
+ const dest = path.join(targetDir, file);
104
+ if (fs.existsSync(src)) {
105
+ fs.copyFileSync(src, dest);
106
+ }
107
+ }
108
+
109
+ // Copy directories
110
+ for (const dir of dirsToCopy) {
111
+ const src = path.join(sourceDir, dir);
112
+ const dest = path.join(targetDir, dir);
113
+ if (fs.existsSync(src)) {
114
+ if (fs.existsSync(dest)) {
115
+ fs.rmSync(dest, { recursive: true, force: true });
116
+ }
117
+ copyDirRecursive(src, dest);
118
+ }
119
+ }
120
+
121
+ // Install npm dependencies in target
122
+ console.log('[AICQ] Installing plugin dependencies...');
123
+ try {
124
+ execSync('npm install --production', {
125
+ cwd: targetDir,
126
+ stdio: 'pipe',
127
+ timeout: 120000,
128
+ });
129
+ console.log('[AICQ] Dependencies installed.');
130
+ } catch (e) {
131
+ console.log('[AICQ] Warning: npm install failed. You may need to run manually:');
132
+ console.log(` cd ${targetDir} && npm install`);
133
+ }
134
+
135
+ console.log(`[AICQ] Plugin installed to: ${targetDir}`);
136
+ console.log('[AICQ] Restart OpenClaw to activate the plugin.');
137
+ return true;
138
+ }
139
+
140
+ // ── Recursively copy a directory ────────────────────────────────────
141
+ function copyDirRecursive(src, dest) {
142
+ fs.mkdirSync(dest, { recursive: true });
143
+ const entries = fs.readdirSync(src, { withFileTypes: true });
144
+ for (const entry of entries) {
145
+ const srcPath = path.join(src, entry.name);
146
+ const destPath = path.join(dest, entry.name);
147
+ if (entry.isDirectory()) {
148
+ if (entry.name === 'node_modules') continue;
149
+ copyDirRecursive(srcPath, destPath);
150
+ } else {
151
+ fs.copyFileSync(srcPath, destPath);
152
+ }
153
+ }
154
+ }
155
+
156
+ // ── Help ────────────────────────────────────────────────────────────
33
157
  if (command === '--help' || command === '-h') {
34
158
  console.log(`
35
159
  AICQ Chat Plugin — End-to-End Encrypted Chat for OpenClaw
36
160
 
37
161
  Usage:
162
+ npx aicq-chat-plugin Start plugin + auto-install to OpenClaw
38
163
  aicq-plugin [command] [options]
39
164
 
40
165
  Commands:
41
- start Start the plugin server (default)
166
+ start Install to OpenClaw (if needed) and start plugin server (default)
167
+ install Install plugin to OpenClaw only (don't start server)
42
168
  status Check if the plugin is running
43
169
 
44
170
  Options:
@@ -50,15 +176,19 @@ Environment Variables:
50
176
  AICQ_PORT Plugin server port
51
177
  AICQ_SERVER_URL AICQ server URL
52
178
  AICQ_DATA_DIR Data directory (default: ~/.aicq-plugin)
179
+ OPENCLAW_HOME OpenClaw installation directory
53
180
 
54
181
  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
182
+ npx aicq-chat-plugin # Install + start
183
+ aicq-plugin # Start on default port
184
+ aicq-plugin install # Install to OpenClaw only
185
+ aicq-plugin --port 8080 # Start on port 8080
186
+ aicq-plugin -s http://localhost # Connect to local server
58
187
  `);
59
188
  process.exit(0);
60
189
  }
61
190
 
191
+ // ── Status ──────────────────────────────────────────────────────────
62
192
  if (command === 'status') {
63
193
  const http = require('http');
64
194
  const req = http.get(`http://localhost:${port}/api/status`, (res) => {
@@ -80,7 +210,7 @@ if (command === 'status') {
80
210
  });
81
211
  req.on('error', () => {
82
212
  console.log(`AICQ Plugin is not running on port ${port}.`);
83
- console.log(`Start it with: aicq-plugin --port ${port}`);
213
+ console.log(`Start it with: npx aicq-chat-plugin`);
84
214
  });
85
215
  req.setTimeout(3000, () => {
86
216
  req.destroy();
@@ -89,7 +219,15 @@ if (command === 'status') {
89
219
  process.exit(0);
90
220
  }
91
221
 
92
- // Start the plugin server
222
+ // ── Install only ────────────────────────────────────────────────────
223
+ if (command === 'install') {
224
+ installToOpenClaw();
225
+ process.exit(0);
226
+ }
227
+
228
+ // ── Start (default) — auto-install then run ─────────────────────────
229
+ installToOpenClaw();
230
+
93
231
  console.log(`[AICQ] Starting plugin on port ${port}`);
94
232
  console.log(`[AICQ] Server: ${serverUrl}`);
95
233
 
package/lib/crypto.js CHANGED
@@ -31,7 +31,13 @@ function generateExchangeKeypair() {
31
31
 
32
32
  function signMessage(message, secretKeyHex) {
33
33
  const secretKey = Buffer.from(secretKeyHex, 'hex');
34
- const messageBytes = naclUtil.decodeUTF8(message);
34
+ // If message looks like hex (64 chars), treat as raw bytes to match server's bytes.fromhex()
35
+ let messageBytes;
36
+ if (/^[0-9a-fA-F]{64}$/.test(message)) {
37
+ messageBytes = Buffer.from(message, 'hex');
38
+ } else {
39
+ messageBytes = naclUtil.decodeUTF8(message);
40
+ }
35
41
  const signature = nacl.sign.detached(messageBytes, secretKey);
36
42
  return Buffer.from(signature).toString('hex');
37
43
  }
@@ -39,7 +45,13 @@ function signMessage(message, secretKeyHex) {
39
45
  function verifySignature(message, signatureHex, publicKeyHex) {
40
46
  try {
41
47
  const publicKey = Buffer.from(publicKeyHex, 'hex');
42
- const messageBytes = naclUtil.decodeUTF8(message);
48
+ // If message looks like hex (64 chars), treat as raw bytes to match server
49
+ let messageBytes;
50
+ if (/^[0-9a-fA-F]{64}$/.test(message)) {
51
+ messageBytes = Buffer.from(message, 'hex');
52
+ } else {
53
+ messageBytes = naclUtil.decodeUTF8(message);
54
+ }
43
55
  const signature = Buffer.from(signatureHex, 'hex');
44
56
  return nacl.sign.detached.verify(messageBytes, signature, publicKey);
45
57
  } catch (e) {
@@ -53,9 +53,13 @@ class ServerClient {
53
53
  public_key: identity.signing_public_key,
54
54
  agent_name: identity.nickname || agentId,
55
55
  });
56
- if (data.accessToken) {
57
- this.jwtToken = data.accessToken;
56
+ if (data.access_token || data.accessToken) {
57
+ this.jwtToken = data.access_token || data.accessToken;
58
58
  this.currentAgentId = agentId;
59
+ // Store server-side account ID for WS auth (nodeId must match JWT sub)
60
+ if (data.account && data.account.id) {
61
+ this.serverAccountId = data.account.id;
62
+ }
59
63
  }
60
64
  return data;
61
65
  }
@@ -83,9 +87,13 @@ class ServerClient {
83
87
  challenge,
84
88
  });
85
89
 
86
- if (loginData.accessToken) {
87
- this.jwtToken = loginData.accessToken;
90
+ if (loginData.access_token || loginData.accessToken) {
91
+ this.jwtToken = loginData.access_token || loginData.accessToken;
88
92
  this.currentAgentId = agentId;
93
+ // Store server-side account ID for WS auth (nodeId must match JWT sub)
94
+ if (loginData.account && loginData.account.id) {
95
+ this.serverAccountId = loginData.account.id;
96
+ }
89
97
  }
90
98
  return loginData;
91
99
  }
@@ -198,7 +206,7 @@ class ServerClient {
198
206
  console.log('[WS] Connected, sending auth...');
199
207
  this.ws.send(JSON.stringify({
200
208
  type: 'online',
201
- nodeId: this.currentAgentId,
209
+ nodeId: this.serverAccountId || this.currentAgentId,
202
210
  token: this.jwtToken,
203
211
  }));
204
212
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "aicq-chat",
3
3
  "name": "AICQ Encrypted Chat",
4
- "version": "2.2.0",
4
+ "version": "2.5.1",
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.4.1",
3
+ "version": "2.5.1",
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": {
package/postinstall.js CHANGED
@@ -2,26 +2,187 @@
2
2
  /**
3
3
  * AICQ Chat Plugin — Post-install script
4
4
  *
5
- * Displays setup information after npm install.
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.
6
8
  */
7
9
 
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+ const { execSync } = require('child_process');
14
+
15
+ const PLUGIN_ID = 'aicq-chat';
16
+ const PLUGIN_DIR = path.resolve(__dirname);
17
+
18
+ // ── Find OpenClaw installation ──────────────────────────────────────
19
+ function findOpenClawDir() {
20
+ const candidates = [
21
+ path.join(os.homedir(), '.openclaw'),
22
+ path.join(os.homedir(), 'openclaw'),
23
+ path.join(os.homedir(), '.config', 'openclaw'),
24
+ ];
25
+
26
+ // Check environment variable
27
+ if (process.env.OPENCLAW_HOME) {
28
+ candidates.unshift(process.env.OPENCLAW_HOME);
29
+ }
30
+
31
+ for (const dir of candidates) {
32
+ if (fs.existsSync(dir)) {
33
+ return dir;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
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);
43
+
44
+ // Create plugins directory if needed
45
+ if (!fs.existsSync(pluginsDir)) {
46
+ fs.mkdirSync(pluginsDir, { recursive: true });
47
+ }
48
+
49
+ // Files to copy (from package.json "files" field)
50
+ const filesToCopy = [
51
+ 'index.js',
52
+ 'cli.js',
53
+ 'postinstall.js',
54
+ 'openclaw.plugin.json',
55
+ 'package.json',
56
+ 'README.md',
57
+ ];
58
+
59
+ const dirsToCopy = [
60
+ 'lib',
61
+ 'public',
62
+ ];
63
+
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}`);
70
+ fs.mkdirSync(targetDir, { recursive: true });
71
+ }
72
+
73
+ // Copy individual files
74
+ for (const file of filesToCopy) {
75
+ const src = path.join(PLUGIN_DIR, file);
76
+ const dest = path.join(targetDir, file);
77
+ if (fs.existsSync(src)) {
78
+ fs.copyFileSync(src, dest);
79
+ }
80
+ }
81
+
82
+ // Copy directories
83
+ for (const dir of dirsToCopy) {
84
+ const src = path.join(PLUGIN_DIR, dir);
85
+ const dest = path.join(targetDir, dir);
86
+ if (fs.existsSync(src)) {
87
+ // Remove old and copy fresh
88
+ if (fs.existsSync(dest)) {
89
+ fs.rmSync(dest, { recursive: true, force: true });
90
+ }
91
+ copyDirRecursive(src, dest);
92
+ }
93
+ }
94
+
95
+ // Install npm dependencies in the target directory
96
+ console.log('[AICQ] Installing plugin dependencies...');
97
+ try {
98
+ execSync('npm install --production', {
99
+ cwd: targetDir,
100
+ stdio: 'pipe',
101
+ timeout: 120000,
102
+ });
103
+ console.log('[AICQ] Dependencies installed.');
104
+ } catch (e) {
105
+ console.log('[AICQ] Warning: npm install failed. You may need to run it manually:');
106
+ console.log(` cd ${targetDir} && npm install`);
107
+ }
108
+
109
+ return targetDir;
110
+ }
111
+
112
+ // ── Recursively copy a directory ────────────────────────────────────
113
+ function copyDirRecursive(src, dest) {
114
+ fs.mkdirSync(dest, { recursive: true });
115
+ const entries = fs.readdirSync(src, { withFileTypes: true });
116
+
117
+ for (const entry of entries) {
118
+ const srcPath = path.join(src, entry.name);
119
+ const destPath = path.join(dest, entry.name);
120
+
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
+ }
128
+ }
129
+ }
130
+
131
+ // ── Main ────────────────────────────────────────────────────────────
8
132
  console.log('');
9
133
  console.log(' ╔══════════════════════════════════════════════╗');
10
- console.log(' ║ AICQ Chat Plugin Installed! ║');
11
- console.log(' ╠══════════════════════════════════════════════╣');
12
- console.log(' ║ ║');
13
- console.log(' ║ Start the plugin: ║');
14
- console.log(' ║ npx aicq-plugin ║');
15
- console.log(' ║ ║');
16
- console.log(' ║ Or install globally: ║');
17
- console.log(' ║ npm install -g aicq-chat-plugin ║');
18
- console.log(' ║ aicq-plugin ║');
19
- console.log(' ║ ║');
20
- console.log(' ║ Options: ║');
21
- console.log(' ║ --port <port> Server port (6109) ║');
22
- console.log(' ║ --server <url> AICQ server URL ║');
23
- console.log(' ║ ║');
24
- console.log(' ║ Chat UI: http://localhost:6109 ║');
25
- console.log(' ║ Docs: https://aicq.online ║');
134
+ console.log(' ║ AICQ Chat Plugin — Installing... ║');
26
135
  console.log(' ╚══════════════════════════════════════════════╝');
27
136
  console.log('');
137
+
138
+ // Try to auto-install into OpenClaw
139
+ const openclawDir = findOpenClawDir();
140
+
141
+ if (openclawDir) {
142
+ console.log(`[AICQ] Found OpenClaw at: ${openclawDir}`);
143
+ 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('');
159
+ } catch (e) {
160
+ console.error('[AICQ] Auto-install failed:', e.message);
161
+ console.log('');
162
+ printManualInstructions();
163
+ }
164
+ } else {
165
+ console.log('[AICQ] OpenClaw not found — skipping auto-install.');
166
+ console.log('');
167
+ printManualInstructions();
168
+ }
169
+
170
+ function printManualInstructions() {
171
+ console.log(' ╔══════════════════════════════════════════════╗');
172
+ console.log(' ║ AICQ Chat Plugin Installed! ║');
173
+ console.log(' ╠══════════════════════════════════════════════╣');
174
+ 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 ║');
180
+ console.log(' ║ ║');
181
+ console.log(' ║ Or start standalone: ║');
182
+ console.log(' ║ npx aicq-plugin ║');
183
+ console.log(' ║ ║');
184
+ console.log(' ║ Chat UI: http://localhost:6109 ║');
185
+ console.log(' ║ Docs: https://aicq.online ║');
186
+ console.log(' ╚══════════════════════════════════════════════╝');
187
+ console.log('');
188
+ }