soulsync 1.0.13 → 1.0.14

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/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "soulsync",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "SoulSync plugin for OpenClaw - cross-bot memory synchronization",
5
5
  "main": "index.js",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/alanliuc-a11y/soulsync"
9
9
  },
10
+ "dependencies": {
11
+ "open": "^10.1.0"
12
+ },
10
13
  "openclaw": {
11
14
  "extensions": [
12
15
  "./index.js"
package/src/client.py CHANGED
@@ -153,6 +153,22 @@ class OpenClawClient:
153
153
  else:
154
154
  error = response.json().get('error', 'Unknown error')
155
155
  raise Exception(f"Get profile failed: {error}")
156
+
157
+ def get_user_info(self) -> dict:
158
+ """获取当前登录用户信息
159
+
160
+ Returns:
161
+ 用户信息,包含 email, name, subscription_status
162
+ """
163
+ url = f"{self.cloud_url}/api/auth/user/info"
164
+
165
+ response = self.session.get(url, headers=self._get_headers())
166
+
167
+ if response.status_code == 200:
168
+ return response.json()
169
+ else:
170
+ error = response.json().get('error', 'Unknown error')
171
+ raise Exception(f"Get user info failed: {error}")
156
172
 
157
173
  def connect_websocket(self, on_message_callback):
158
174
  """连接 WebSocket
package/src/config.js ADDED
@@ -0,0 +1,100 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ let config = null;
6
+
7
+ function getPluginDir() {
8
+ return path.dirname(__filename);
9
+ }
10
+
11
+ function loadConfig() {
12
+ if (config) return config;
13
+
14
+ const pluginDir = path.dirname(__filename);
15
+ const configPath = path.join(pluginDir, 'config.json');
16
+
17
+ try {
18
+ if (fs.existsSync(configPath)) {
19
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
20
+ return config;
21
+ }
22
+ } catch (e) {
23
+ console.error('[SoulSync] Error loading config:', e);
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function saveConfig(newConfig) {
29
+ const pluginDir = path.dirname(__filename);
30
+ const configPath = path.join(pluginDir, 'config.json');
31
+
32
+ try {
33
+ let existing = {};
34
+ if (fs.existsSync(configPath)) {
35
+ existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
36
+ }
37
+ config = { ...existing, ...newConfig };
38
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
39
+ return true;
40
+ } catch (e) {
41
+ console.error('[SoulSync] Error saving config:', e);
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function clearConfig() {
47
+ const pluginDir = path.dirname(__filename);
48
+ const configPath = path.join(pluginDir, 'config.json');
49
+
50
+ try {
51
+ if (fs.existsSync(configPath)) {
52
+ const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
53
+ delete existing.token;
54
+ delete existing.email;
55
+ delete existing.device_id;
56
+ delete existing.device_name;
57
+ fs.writeFileSync(configPath, JSON.stringify(existing, null, 2));
58
+ config = existing;
59
+ }
60
+ return true;
61
+ } catch (e) {
62
+ console.error('[SoulSync] Error clearing config:', e);
63
+ return false;
64
+ }
65
+ }
66
+
67
+ function getDeviceName(deviceType = 'local') {
68
+ const hostname = os.hostname();
69
+ const platform = os.platform();
70
+ const username = os.userInfo().username;
71
+
72
+ let type = 'local';
73
+ if (deviceType === 'cloud') type = '云端';
74
+ else if (deviceType === 'ssh') type = 'SSH';
75
+ else {
76
+ if (platform === 'win32') type = 'Windows';
77
+ else if (platform === 'darwin') type = 'Mac';
78
+ else if (platform === 'linux') type = 'Linux';
79
+ }
80
+
81
+ if (deviceType === 'cloud') {
82
+ return `${type} Bot`;
83
+ }
84
+
85
+ return `${username}的${type}`;
86
+ }
87
+
88
+ function isAuthenticated() {
89
+ const cfg = loadConfig();
90
+ return cfg && cfg.token && cfg.email && cfg.email !== 'your-email@example.com';
91
+ }
92
+
93
+ module.exports = {
94
+ loadConfig,
95
+ saveConfig,
96
+ clearConfig,
97
+ getDeviceName,
98
+ isAuthenticated,
99
+ getPluginDir
100
+ };
@@ -0,0 +1,150 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ let pollingInterval = null;
8
+
9
+ function getCloudUrl() {
10
+ const pluginDir = path.dirname(__filename);
11
+ const configPath = path.join(pluginDir, 'config.json');
12
+ try {
13
+ if (fs.existsSync(configPath)) {
14
+ const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
15
+ if (cfg.cloud_url) return cfg.cloud_url;
16
+ }
17
+ } catch (e) {}
18
+ return 'https://soulsync.work';
19
+ }
20
+
21
+ function makeRequest(method, urlPath, body = null) {
22
+ return new Promise((resolve, reject) => {
23
+ const cloudUrl = getCloudUrl();
24
+ const isHttps = cloudUrl.startsWith('https');
25
+ const client = isHttps ? https : http;
26
+
27
+ const urlObj = new URL(cloudUrl + urlPath);
28
+
29
+ const options = {
30
+ hostname: urlObj.hostname,
31
+ port: urlObj.port || (isHttps ? 443 : 80),
32
+ path: urlObj.pathname + urlObj.search,
33
+ method: method,
34
+ headers: { 'Content-Type': 'application/json' }
35
+ };
36
+
37
+ const req = client.request(options, (res) => {
38
+ let data = '';
39
+ res.on('data', chunk => data += chunk);
40
+ res.on('end', () => {
41
+ try {
42
+ const json = JSON.parse(data);
43
+ resolve({ status: res.statusCode, body: json });
44
+ } catch (e) {
45
+ resolve({ status: res.statusCode, body: data });
46
+ }
47
+ });
48
+ });
49
+
50
+ req.on('error', reject);
51
+ if (body) req.write(JSON.stringify(body));
52
+ req.end();
53
+ });
54
+ }
55
+
56
+ function requestDeviceCode() {
57
+ return makeRequest('POST', '/api/auth/device-code');
58
+ }
59
+
60
+ function pollForAuth(deviceCode) {
61
+ return makeRequest('GET', `/api/auth/device/${deviceCode}/status`);
62
+ }
63
+
64
+ function startPolling(deviceCode, onSuccess, onError) {
65
+ pollingInterval = setInterval(async () => {
66
+ try {
67
+ const result = await pollForAuth(deviceCode);
68
+
69
+ if (result.body.status === 'authorized' && result.body.token) {
70
+ stopPolling();
71
+ onSuccess(result.body.token);
72
+ return;
73
+ }
74
+
75
+ if (result.body.status === 'expired') {
76
+ stopPolling();
77
+ onError(new Error('Device code expired'));
78
+ return;
79
+ }
80
+
81
+ if (result.body.status === 'not_found') {
82
+ stopPolling();
83
+ onError(new Error('Device code not found'));
84
+ return;
85
+ }
86
+ } catch (e) {
87
+ stopPolling();
88
+ onError(e);
89
+ }
90
+ }, 3000);
91
+ }
92
+
93
+ function stopPolling() {
94
+ if (pollingInterval) {
95
+ clearInterval(pollingInterval);
96
+ pollingInterval = null;
97
+ }
98
+ }
99
+
100
+ function savePendingAuth(deviceCode, authUrl) {
101
+ const homeDir = os.homedir();
102
+ const pendingDir = path.join(homeDir, '.soulsync');
103
+ const pendingFile = path.join(pendingDir, '.pending_auth');
104
+
105
+ try {
106
+ if (!fs.existsSync(pendingDir)) {
107
+ fs.mkdirSync(pendingDir, { recursive: true });
108
+ }
109
+ fs.writeFileSync(pendingFile, JSON.stringify({ deviceCode, authUrl, timestamp: Date.now() }));
110
+ return true;
111
+ } catch (e) {
112
+ console.error('[SoulSync] Error saving pending auth:', e);
113
+ return false;
114
+ }
115
+ }
116
+
117
+ function loadPendingAuth() {
118
+ const homeDir = os.homedir();
119
+ const pendingFile = path.join(homeDir, '.soulsync', '.pending_auth');
120
+
121
+ try {
122
+ if (fs.existsSync(pendingFile)) {
123
+ const data = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
124
+ if (Date.now() - data.timestamp < 900000) {
125
+ return data;
126
+ }
127
+ fs.unlinkSync(pendingFile);
128
+ }
129
+ } catch (e) {}
130
+ return null;
131
+ }
132
+
133
+ function clearPendingAuth() {
134
+ const homeDir = os.homedir();
135
+ const pendingFile = path.join(homeDir, '.soulsync', '.pending_auth');
136
+ try {
137
+ if (fs.existsSync(pendingFile)) {
138
+ fs.unlinkSync(pendingFile);
139
+ }
140
+ } catch (e) {}
141
+ }
142
+
143
+ module.exports = {
144
+ requestDeviceCode,
145
+ startPolling,
146
+ stopPolling,
147
+ savePendingAuth,
148
+ loadPendingAuth,
149
+ clearPendingAuth
150
+ };
@@ -0,0 +1,48 @@
1
+ const http = require('http');
2
+ const url = require('url');
3
+
4
+ function createOAuthServer() {
5
+ return new Promise((resolve, reject) => {
6
+ const server = http.createServer((req, res) => {
7
+ const parsedUrl = url.parse(req.url, true);
8
+
9
+ if (parsedUrl.pathname === '/callback') {
10
+ const { token, state, error } = parsedUrl.query;
11
+
12
+ if (error) {
13
+ res.writeHead(400, { 'Content-Type': 'text/html' });
14
+ res.end('<html><body><h1>授权失败</h1><p>请关闭此窗口并重试。</p></body></html>');
15
+ server.close();
16
+ reject(new Error(error));
17
+ return;
18
+ }
19
+
20
+ if (token) {
21
+ res.writeHead(200, { 'Content-Type': 'text/html' });
22
+ res.end('<html><body><h1>授权成功!</h1><p>请关闭此窗口并返回终端。</p></body></html>');
23
+ server.close();
24
+ resolve({ token, state });
25
+ return;
26
+ }
27
+
28
+ res.writeHead(400, { 'Content-Type': 'text/html' });
29
+ res.end('<html><body><h1>无效响应</h1></body></html>');
30
+ server.close();
31
+ reject(new Error('No token received'));
32
+ return;
33
+ }
34
+
35
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
36
+ res.end('Not Found');
37
+ });
38
+
39
+ server.listen(0, () => {
40
+ const port = server.address().port;
41
+ resolve({ server, port });
42
+ });
43
+
44
+ server.on('error', reject);
45
+ });
46
+ }
47
+
48
+ module.exports = { createOAuthServer };
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 测试 SSL 修复是否有效
4
+ """
5
+ import ssl
6
+ import sys
7
+
8
+ print(f"Python 版本: {sys.version}")
9
+ print(f"SSL 版本: {ssl.OPENSSL_VERSION}")
10
+ print()
11
+
12
+ # 测试 1: 使用 ssl.create_default_context()
13
+ print("测试 1: 使用 ssl.create_default_context()")
14
+ try:
15
+ import urllib.request
16
+ ctx = ssl.create_default_context()
17
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
18
+ req = urllib.request.Request('https://soulsync.work/api/health')
19
+ response = urllib.request.urlopen(req, context=ctx)
20
+ print(f"✅ 成功: {response.read().decode()}")
21
+ except Exception as e:
22
+ print(f"❌ 失败: {e}")
23
+
24
+ print()
25
+
26
+ # 测试 2: 使用 requests + TLSAdapter
27
+ print("测试 2: 使用 requests + 自定义 TLSAdapter")
28
+ try:
29
+ import requests
30
+ from requests.adapters import HTTPAdapter
31
+
32
+ class TLSAdapter(HTTPAdapter):
33
+ def init_poolmanager(self, *args, **kwargs):
34
+ ctx = ssl.create_default_context()
35
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
36
+ kwargs['ssl_context'] = ctx
37
+ return super().init_poolmanager(*args, **kwargs)
38
+
39
+ session = requests.Session()
40
+ session.mount('https://', TLSAdapter())
41
+ response = session.get('https://soulsync.work/api/health')
42
+ print(f"✅ 成功: {response.text}")
43
+ except Exception as e:
44
+ print(f"❌ 失败: {e}")
45
+
46
+ print()
47
+
48
+ # 测试 3: 直接 requests(无适配器)
49
+ print("测试 3: 直接 requests(无适配器)")
50
+ try:
51
+ import requests
52
+ response = requests.get('https://soulsync.work/api/health')
53
+ print(f"✅ 成功: {response.text}")
54
+ except Exception as e:
55
+ print(f"❌ 失败: {e}")
56
+
57
+ print()
58
+ print("测试完成")