soulsync 1.0.22 → 1.2.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/index.js CHANGED
@@ -7,15 +7,11 @@ const crypto = require('crypto');
7
7
  const os = require('os');
8
8
  const { createRequire } = require('module');
9
9
 
10
- const { getDeviceName, loadConfig, saveConfig, isAuthenticated } = require('./src/config');
10
+ const { getDeviceName, loadConfig, saveConfig, isAuthenticated, getPluginDir } = require('./src/config');
11
11
 
12
- let pythonProcess = null;
12
+ let nodeProcess = null;
13
13
  let deviceCodePolling = null;
14
14
 
15
- function getPluginDir() {
16
- return path.dirname(__filename);
17
- }
18
-
19
15
  function getCloudUrl() {
20
16
  const cfg = loadConfig();
21
17
  return (cfg && cfg.cloud_url) || 'https://soulsync.work';
@@ -120,7 +116,7 @@ async function startOAuthLocal() {
120
116
  });
121
117
 
122
118
  await registerDevice(deviceId, deviceName, 'local', token);
123
- startPythonService('--start');
119
+ startNodeService('--start');
124
120
 
125
121
  const greeting = await getUserGreeting(token, deviceName);
126
122
  return { success: true, message: greeting };
@@ -169,7 +165,7 @@ async function startDeviceCodeCLI() {
169
165
  });
170
166
 
171
167
  registerDevice(deviceId, deviceName, 'ssh', token);
172
- startPythonService('--start');
168
+ startNodeService('--start');
173
169
 
174
170
  const greeting = await getUserGreeting(token, deviceName);
175
171
  return { success: true, message: greeting };
@@ -230,40 +226,42 @@ async function registerDevice(deviceId, deviceName, deviceType, token) {
230
226
  }
231
227
  }
232
228
 
233
- function startPythonService(mode = '--start') {
229
+ function startNodeService(mode = '--start') {
234
230
  const pluginDir = getPluginDir();
235
- const pythonScript = path.join(pluginDir, 'src', 'main.py');
236
- const pythonPath = process.env.PYTHON_PATH || 'python3';
237
-
238
- console.log(`[SoulSync] Starting Python service (${mode})...`);
239
-
240
- if (pythonProcess) {
241
- pythonProcess.kill();
242
- pythonProcess = null;
231
+ const daemonScript = path.join(pluginDir, 'src', 'daemon.js');
232
+
233
+ console.log(`[SoulSync] Starting Node.js sync service (${mode})...`);
234
+
235
+ if (nodeProcess) {
236
+ nodeProcess.kill();
237
+ nodeProcess = null;
243
238
  }
244
-
245
- pythonProcess = spawn(pythonPath, [pythonScript, mode], {
239
+
240
+ nodeProcess = spawn(process.execPath, [daemonScript, mode], {
246
241
  cwd: pluginDir,
247
242
  env: { ...process.env, OPENCLAW_PLUGIN: 'true', PLUGIN_DIR: pluginDir },
248
- stdio: 'inherit'
243
+ stdio: 'ignore',
244
+ detached: true
249
245
  });
250
-
251
- pythonProcess.on('close', (code) => {
252
- console.log(`[SoulSync] Python process exited with code ${code}`);
253
- pythonProcess = null;
246
+
247
+ nodeProcess.on('close', (code) => {
248
+ console.log(`[SoulSync] Node.js process exited with code ${code}`);
249
+ nodeProcess = null;
254
250
  });
255
-
256
- pythonProcess.on('error', (err) => {
257
- console.error(`[SoulSync] Failed to start Python process: ${err}`);
258
- pythonProcess = null;
251
+
252
+ nodeProcess.on('error', (err) => {
253
+ console.error(`[SoulSync] Failed to start Node.js process: ${err}`);
254
+ nodeProcess = null;
259
255
  });
256
+
257
+ nodeProcess.unref();
260
258
  }
261
259
 
262
- function stopPythonService() {
263
- if (pythonProcess) {
264
- console.log('[SoulSync] Stopping Python service...');
265
- pythonProcess.kill();
266
- pythonProcess = null;
260
+ function stopNodeService() {
261
+ if (nodeProcess) {
262
+ console.log('[SoulSync] Stopping Node.js service...');
263
+ nodeProcess.kill();
264
+ nodeProcess = null;
267
265
  }
268
266
  }
269
267
 
@@ -343,7 +341,7 @@ async function startDeviceCodeFlow() {
343
341
  });
344
342
 
345
343
  registerDevice(deviceId, deviceName, 'cloud', token);
346
- startPythonService('--start');
344
+ startNodeService('--start');
347
345
 
348
346
  const greeting = await getUserGreeting(token, deviceName);
349
347
  return { success: true, message: greeting };
@@ -385,12 +383,12 @@ module.exports = function register(api) {
385
383
  return;
386
384
  }
387
385
 
388
- if (pythonProcess) {
386
+ if (nodeProcess) {
389
387
  console.log('[SoulSync] Service already running');
390
388
  return;
391
389
  }
392
390
 
393
- startPythonService('--start');
391
+ startNodeService('--start');
394
392
  });
395
393
  },
396
394
  { commands: ['soulsync:start'] }
@@ -440,8 +438,8 @@ module.exports = function register(api) {
440
438
  return 'SoulSync is not configured. Please connect first.';
441
439
  }
442
440
 
443
- if (!pythonProcess) {
444
- startPythonService('--sync');
441
+ if (!nodeProcess) {
442
+ startNodeService('--sync');
445
443
  return 'Sync triggered.';
446
444
  }
447
445
  return 'Sync already in progress.';
@@ -452,7 +450,7 @@ module.exports = function register(api) {
452
450
  description: 'Logout and unbind device from SoulSync. Call when user says "退出soulsync", "解绑设备", "断开soulsync连接".',
453
451
  input_schema: { type: 'object', properties: {}, required: [] }
454
452
  }, async () => {
455
- stopPythonService();
453
+ stopNodeService();
456
454
 
457
455
  const cfg = loadConfig();
458
456
  if (cfg && cfg.device_id && cfg.token) {
@@ -567,12 +565,12 @@ module.exports = function register(api) {
567
565
  return;
568
566
  }
569
567
 
570
- if (pythonProcess) {
568
+ if (nodeProcess) {
571
569
  console.log('[SoulSync] Service already running');
572
570
  return;
573
571
  }
574
572
 
575
- startPythonService('--start');
573
+ startNodeService('--start');
576
574
  });
577
575
  },
578
576
  { commands: ['soulsync:start'] }
@@ -584,7 +582,7 @@ module.exports = function register(api) {
584
582
  .command('soulsync:stop')
585
583
  .description('停止 SoulSync 同步服务')
586
584
  .action(() => {
587
- stopPythonService();
585
+ stopNodeService();
588
586
  console.log('[SoulSync] Service stopped');
589
587
  });
590
588
  },
@@ -593,7 +591,7 @@ module.exports = function register(api) {
593
591
 
594
592
  if (isAuthenticated()) {
595
593
  console.log('[SoulSync] Auto-starting sync service...');
596
- startPythonService('--start');
594
+ startNodeService('--start');
597
595
  }
598
596
 
599
597
  console.log('[SoulSync] Plugin loaded. Run "openclaw soulsync:start" to begin.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulsync",
3
- "version": "1.0.22",
3
+ "version": "1.2.0",
4
4
  "description": "SoulSync plugin for OpenClaw - cross-bot memory synchronization",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -8,11 +8,13 @@
8
8
  "url": "https://github.com/alanliuc-a11y/soulsync"
9
9
  },
10
10
  "dependencies": {
11
- "open": "^10.1.0"
11
+ "chokidar": "^3.5.3",
12
+ "open": "^10.1.0",
13
+ "ws": "^8.14.2"
12
14
  },
13
15
  "openclaw": {
14
16
  "extensions": [
15
17
  "./index.js"
16
18
  ]
17
19
  }
18
- }
20
+ }
@@ -0,0 +1,84 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+ const { URL } = require('url');
4
+
5
+ class APIClient {
6
+ constructor(baseUrl, token) {
7
+ this.baseUrl = baseUrl || 'https://soulsync.work';
8
+ this.token = token;
9
+ }
10
+
11
+ async request(method, path, body = null) {
12
+ return new Promise((resolve, reject) => {
13
+ const urlStr = this.baseUrl + path;
14
+ const url = new URL(urlStr);
15
+ const isHttps = url.protocol === 'https:';
16
+ const lib = isHttps ? https : http;
17
+
18
+ const options = {
19
+ hostname: url.hostname,
20
+ port: url.port || (isHttps ? 443 : 80),
21
+ path: url.pathname + url.search,
22
+ method: method,
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ 'Authorization': `Bearer ${this.token}`
26
+ }
27
+ };
28
+
29
+ const req = lib.request(options, (res) => {
30
+ let data = '';
31
+ res.on('data', chunk => data += chunk);
32
+ res.on('end', () => {
33
+ try {
34
+ const parsed = data ? JSON.parse(data) : {};
35
+ resolve({ status: res.statusCode, body: parsed });
36
+ } catch (e) {
37
+ resolve({ status: res.statusCode, body: data });
38
+ }
39
+ });
40
+ });
41
+
42
+ req.on('error', reject);
43
+
44
+ if (body) {
45
+ req.write(JSON.stringify(body));
46
+ }
47
+ req.end();
48
+ });
49
+ }
50
+
51
+ async getProfiles() {
52
+ try {
53
+ console.log('[SoulSync] API getProfiles called');
54
+ const result = await this.request('GET', '/api/profiles');
55
+ console.log(`[SoulSync] API raw response:`, JSON.stringify(result).substring(0, 300));
56
+ return result;
57
+ } catch (e) {
58
+ console.error('[SoulSync] API getProfiles error:', e.message);
59
+ return { status: 500, body: null };
60
+ }
61
+ }
62
+
63
+ async updateProfiles(content, version = 0) {
64
+ return this.request('PUT', '/api/profiles', { content, version });
65
+ }
66
+
67
+ async getUserInfo() {
68
+ return this.request('GET', '/api/auth/user/info');
69
+ }
70
+
71
+ async registerDevice(deviceId, deviceName, deviceType) {
72
+ return this.request('POST', '/api/devices', {
73
+ device_id: deviceId,
74
+ device_name: deviceName,
75
+ device_type: deviceType
76
+ });
77
+ }
78
+
79
+ async deleteDevice(deviceId) {
80
+ return this.request('DELETE', `/api/devices/${deviceId}`);
81
+ }
82
+ }
83
+
84
+ module.exports = { APIClient };
package/src/daemon.js ADDED
@@ -0,0 +1,94 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { SyncEngine } = require('./sync-engine');
5
+ const { loadConfig, getPluginDir } = require('./config');
6
+
7
+ function getLockFile() {
8
+ return path.join(os.tmpdir(), 'soulsync.lock');
9
+ }
10
+
11
+ function checkAlreadyRunning() {
12
+ const lockFile = getLockFile();
13
+ if (fs.existsSync(lockFile)) {
14
+ try {
15
+ const pid = parseInt(fs.readFileSync(lockFile, 'utf-8'));
16
+ try {
17
+ process.kill(pid, 0);
18
+ console.log('[SoulSync] Sync service already running (PID:', pid, ')');
19
+ return true;
20
+ } catch (e) {
21
+ fs.unlinkSync(lockFile);
22
+ }
23
+ } catch (e) {
24
+ fs.unlinkSync(lockFile);
25
+ }
26
+ }
27
+ return false;
28
+ }
29
+
30
+ function writeLockFile() {
31
+ const lockFile = getLockFile();
32
+ fs.writeFileSync(lockFile, process.pid.toString(), 'utf-8');
33
+ }
34
+
35
+ function removeLockFile() {
36
+ const lockFile = getLockFile();
37
+ if (fs.existsSync(lockFile)) {
38
+ fs.unlinkSync(lockFile);
39
+ }
40
+ }
41
+
42
+ async function main() {
43
+ const config = loadConfig();
44
+ if (!config || !config.token) {
45
+ console.error('[SoulSync] Not configured. Run "openclaw soulsync:start" first.');
46
+ process.exit(1);
47
+ }
48
+
49
+ if (checkAlreadyRunning()) {
50
+ process.exit(0);
51
+ }
52
+
53
+ writeLockFile();
54
+
55
+ const engine = new SyncEngine(config);
56
+
57
+ process.on('SIGTERM', () => {
58
+ console.log('[SoulSync] Received SIGTERM, shutting down...');
59
+ engine.disconnect();
60
+ removeLockFile();
61
+ process.exit(0);
62
+ });
63
+
64
+ process.on('SIGINT', () => {
65
+ console.log('[SoulSync] Received SIGINT, shutting down...');
66
+ engine.disconnect();
67
+ removeLockFile();
68
+ process.exit(0);
69
+ });
70
+
71
+ process.on('uncaughtException', (e) => {
72
+ console.error('[SoulSync] Uncaught exception:', e);
73
+ engine.disconnect();
74
+ removeLockFile();
75
+ process.exit(1);
76
+ });
77
+
78
+ try {
79
+ await engine.initialize();
80
+ engine.startWatching();
81
+ console.log('[SoulSync] Sync service started');
82
+ } catch (e) {
83
+ console.error('[SoulSync] Failed to start sync service:', e.message);
84
+ engine.disconnect();
85
+ removeLockFile();
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ if (require.main === module) {
91
+ main();
92
+ }
93
+
94
+ module.exports = { SyncEngine, checkAlreadyRunning };