soulsync 1.0.4 → 1.0.5

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "cloud_url": "http://your-server:3000",
2
+ "cloud_url": "https://soulsync.work",
3
3
  "email": "your-email@example.com",
4
4
  "password": "your-password",
5
5
  "workspace": "./workspace",
package/index.js CHANGED
@@ -1,10 +1,41 @@
1
1
  const { spawn } = require('child_process');
2
2
  const path = require('path');
3
3
 
4
+ let pythonProcess = null;
5
+
4
6
  // 函数形式导出(推荐)
5
7
  module.exports = function register(api) {
6
8
  console.log('[SoulSync] Registering plugin...');
7
9
 
10
+ // 自动启动 Python 服务
11
+ function startPythonService() {
12
+ const pluginDir = path.dirname(__filename);
13
+ const pythonScript = path.join(pluginDir, 'src', 'main.py');
14
+ const pythonPath = process.env.PYTHON_PATH || 'python3';
15
+
16
+ console.log('[SoulSync] Auto-starting Python service...');
17
+
18
+ pythonProcess = spawn(pythonPath, [pythonScript], {
19
+ cwd: pluginDir,
20
+ env: {
21
+ ...process.env,
22
+ OPENCLAW_PLUGIN: 'true',
23
+ PLUGIN_DIR: pluginDir
24
+ },
25
+ stdio: 'inherit'
26
+ });
27
+
28
+ pythonProcess.on('close', (code) => {
29
+ console.log(`[SoulSync] Python process exited with code ${code}`);
30
+ pythonProcess = null;
31
+ });
32
+
33
+ pythonProcess.on('error', (err) => {
34
+ console.error(`[SoulSync] Failed to start Python process: ${err}`);
35
+ pythonProcess = null;
36
+ });
37
+ }
38
+
8
39
  // 注册CLI命令:启动 SoulSync
9
40
  api.registerCli(
10
41
  ({ program }) => {
@@ -12,25 +43,11 @@ module.exports = function register(api) {
12
43
  .command('soulsync:start')
13
44
  .description('启动 SoulSync 同步服务')
14
45
  .action(() => {
15
- const pluginDir = path.dirname(__filename);
16
- const pythonScript = path.join(pluginDir, 'src', 'main.py');
17
- const pythonPath = process.env.PYTHON_PATH || 'python3';
18
-
19
- console.log('[SoulSync] Starting Python service...');
20
-
21
- const pythonProcess = spawn(pythonPath, [pythonScript], {
22
- cwd: pluginDir,
23
- env: {
24
- ...process.env,
25
- OPENCLAW_PLUGIN: 'true',
26
- PLUGIN_DIR: pluginDir
27
- },
28
- stdio: 'inherit'
29
- });
30
-
31
- pythonProcess.on('close', (code) => {
32
- console.log(`[SoulSync] Python process exited with code ${code}`);
33
- });
46
+ if (pythonProcess) {
47
+ console.log('[SoulSync] Service already running');
48
+ return;
49
+ }
50
+ startPythonService();
34
51
  });
35
52
  },
36
53
  { commands: ['soulsync:start'] }
@@ -43,12 +60,20 @@ module.exports = function register(api) {
43
60
  .command('soulsync:stop')
44
61
  .description('停止 SoulSync 同步服务')
45
62
  .action(() => {
46
- console.log('[SoulSync] Stop command received');
47
- // 这里可以实现停止逻辑
63
+ if (pythonProcess) {
64
+ console.log('[SoulSync] Stopping Python service...');
65
+ pythonProcess.kill();
66
+ pythonProcess = null;
67
+ } else {
68
+ console.log('[SoulSync] Service not running');
69
+ }
48
70
  });
49
71
  },
50
72
  { commands: ['soulsync:stop'] }
51
73
  );
52
74
 
75
+ // 自动启动服务
76
+ startPythonService();
77
+
53
78
  console.log('[SoulSync] Plugin registered successfully');
54
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulsync",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "SoulSync plugin for OpenClaw - cross-bot memory synchronization",
5
5
  "main": "index.js",
6
6
  "repository": {
package/src/client.py CHANGED
@@ -1,8 +1,23 @@
1
1
  import json
2
2
  import os
3
+ import ssl
3
4
  import uuid
4
5
  import requests
5
6
  import websocket
7
+ from requests.adapters import HTTPAdapter
8
+ from urllib3.poolmanager import PoolManager
9
+
10
+
11
+ class TLSAdapter(HTTPAdapter):
12
+ """使用兼容 Python 3.13 的 SSL 适配器"""
13
+ def init_poolmanager(self, *args, **kwargs):
14
+ # 使用 create_default_context() 而不是 SSLContext(PROTOCOL_TLS_CLIENT)
15
+ # 这在 Python 3.13 + Windows 上更稳定
16
+ ctx = ssl.create_default_context()
17
+ # 可选:设置最低 TLS 版本为 1.2
18
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
19
+ kwargs['ssl_context'] = ctx
20
+ return super().init_poolmanager(*args, **kwargs)
6
21
 
7
22
 
8
23
  class OpenClawClient:
@@ -16,6 +31,10 @@ class OpenClawClient:
16
31
  self.device_id = self._load_or_generate_device_id()
17
32
  self.ws = None
18
33
  self.ws_thread = None
34
+
35
+ # 创建使用 TLS 1.2 的 session
36
+ self.session = requests.Session()
37
+ self.session.mount('https://', TLSAdapter())
19
38
 
20
39
  def _load_or_generate_device_id(self) -> str:
21
40
  """加载或生成设备ID"""
@@ -95,7 +114,7 @@ class OpenClawClient:
95
114
  'password': password
96
115
  }
97
116
 
98
- response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
117
+ response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
99
118
 
100
119
  if response.status_code == 200:
101
120
  result = response.json()
@@ -123,7 +142,7 @@ class OpenClawClient:
123
142
  url = f"{self.cloud_url}/api/memories"
124
143
  data = {'content': content}
125
144
 
126
- response = requests.post(url, json=data, headers=self._get_headers())
145
+ response = self.session.post(url, json=data, headers=self._get_headers())
127
146
 
128
147
  if response.status_code == 200:
129
148
  return response.json()
@@ -142,7 +161,7 @@ class OpenClawClient:
142
161
  """
143
162
  url = f"{self.cloud_url}/api/memories"
144
163
 
145
- response = requests.get(url, headers=self._get_headers())
164
+ response = self.session.get(url, headers=self._get_headers())
146
165
 
147
166
  if response.status_code == 200:
148
167
  return response.json()
@@ -160,7 +179,7 @@ class OpenClawClient:
160
179
  """
161
180
  url = f"{self.cloud_url}/api/memories/profile"
162
181
 
163
- response = requests.get(url, headers=self._get_headers())
182
+ response = self.session.get(url, headers=self._get_headers())
164
183
 
165
184
  if response.status_code == 200:
166
185
  return response.json()
@@ -231,7 +250,7 @@ class OpenClawClient:
231
250
  url = f"{self.cloud_url}/api/auth/send-code"
232
251
  data = {'email': email}
233
252
 
234
- response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
253
+ response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
235
254
 
236
255
  if response.status_code == 200:
237
256
  return response.json()
@@ -256,7 +275,7 @@ class OpenClawClient:
256
275
  url = f"{self.cloud_url}/api/auth/register"
257
276
  data = {'email': email, 'password': password, 'code': code}
258
277
 
259
- response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
278
+ response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
260
279
 
261
280
  if response.status_code == 201:
262
281
  result = response.json()
@@ -281,7 +300,7 @@ class OpenClawClient:
281
300
  url = f"{self.cloud_url}/api/auth/login"
282
301
  data = {'email': email, 'password': password}
283
302
 
284
- response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
303
+ response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
285
304
 
286
305
  if response.status_code == 200:
287
306
  result = response.json()
@@ -248,7 +248,7 @@ def prompt_for_missing_config(client):
248
248
 
249
249
  # 如果没有 cloud_url,设置默认值
250
250
  if not cloud_url:
251
- config['cloud_url'] = 'http://47.96.170.74:3000'
251
+ config['cloud_url'] = 'https://soulsync.work'
252
252
  print(f"Using default server / 使用默认服务器: {config['cloud_url']}")
253
253
 
254
254
  # 交互式登录/注册
package/src/main.py CHANGED
@@ -40,11 +40,18 @@ class SoulSyncPlugin:
40
40
  def load_config(self):
41
41
  """加载配置文件"""
42
42
  config_path = os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json'))
43
+ config_example_path = os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json.example'))
43
44
 
44
45
  print(f"Looking for config at: {config_path}")
45
46
 
46
47
  if not os.path.exists(config_path):
47
- raise FileNotFoundError(f"Config file not found: {config_path}")
48
+ if os.path.exists(config_example_path):
49
+ print("Config file not found, copying from config.json.example...")
50
+ import shutil
51
+ shutil.copy(config_example_path, config_path)
52
+ print(f"Created config.json from template")
53
+ else:
54
+ raise FileNotFoundError(f"Config file not found: {config_path}")
48
55
 
49
56
  try:
50
57
  with open(config_path, 'r', encoding='utf-8') as f:
@@ -52,6 +59,24 @@ class SoulSyncPlugin:
52
59
  except json.JSONDecodeError as e:
53
60
  raise ValueError(f"Invalid JSON in config.json: {e}")
54
61
 
62
+ # 检查必要配置
63
+ cloud_url = self.config.get('cloud_url', '').strip()
64
+ email = self.config.get('email', '').strip()
65
+ password = self.config.get('password', '').strip()
66
+
67
+ # 如果 cloud_url 为空,设置为默认值
68
+ if not cloud_url:
69
+ self.config['cloud_url'] = 'https://soulsync.work'
70
+ print("Cloud URL not set, using default: https://soulsync.work")
71
+
72
+ # 如果 email 或 password 为空,需要交互式认证
73
+ if not email or not password:
74
+ print("\nEmail or password not configured, initiating interactive setup...")
75
+ self._interactive_setup()
76
+ # 重新加载配置
77
+ with open(config_path, 'r', encoding='utf-8') as f:
78
+ self.config = json.load(f)
79
+
55
80
  # 处理 workspace 路径
56
81
  workspace = self.config.get('workspace', './workspace')
57
82
  if workspace.startswith('./'):
@@ -68,6 +93,58 @@ class SoulSyncPlugin:
68
93
  print(f" Workspace: {workspace}")
69
94
  print(f" Watch files: {watch_files}")
70
95
 
96
+ def _interactive_setup(self):
97
+ """交互式设置:引导用户登录或注册"""
98
+ from register import Register, Login
99
+ from interactive_auth import interactive_setup
100
+
101
+ print("\n=== First run - Please login or register / 首次运行,请先登录或注册 ===\n")
102
+ print("1. Login / 登录(已有账号)")
103
+ print("2. Register / 注册(新用户)")
104
+
105
+ choice = input("Choose (1/2): ").strip()
106
+
107
+ if choice == '1':
108
+ # 先创建临时 client 用于登录
109
+ temp_client = OpenClawClient(self.config)
110
+ login = Login(temp_client)
111
+ result = login.run()
112
+ if result:
113
+ # 保存认证信息到 config
114
+ self._save_auth_to_config(result)
115
+ elif choice == '2':
116
+ temp_client = OpenClawClient(self.config)
117
+ register = Register(temp_client)
118
+ result = register.run()
119
+ if result:
120
+ self._save_auth_to_config(result)
121
+ else:
122
+ print("Invalid choice / 无效选择")
123
+ sys.exit(1)
124
+
125
+ def _save_auth_to_config(self, auth_result):
126
+ """保存认证结果到 config.json"""
127
+ config_path = os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json'))
128
+
129
+ try:
130
+ with open(config_path, 'r', encoding='utf-8') as f:
131
+ config = json.load(f)
132
+ except:
133
+ config = {}
134
+
135
+ # 保存 email 和 password(如果 auth_result 包含)
136
+ if 'user' in auth_result:
137
+ config['email'] = auth_result['user'].get('email', '')
138
+
139
+ # 保存 token
140
+ if 'token' in auth_result:
141
+ config['token'] = auth_result['token']
142
+
143
+ with open(config_path, 'w', encoding='utf-8') as f:
144
+ json.dump(config, f, indent=2, ensure_ascii=False)
145
+
146
+ print("Auth info saved to config.json")
147
+
71
148
  def initialize(self):
72
149
  """初始化组件"""
73
150
  print("\n=== Initializing SoulSync Plugin ===\n")
@@ -80,11 +157,11 @@ class SoulSyncPlugin:
80
157
  profile = self.client.get_profile()
81
158
  print(f"Using existing token, user: {profile.get('email', 'unknown')}")
82
159
  except Exception as e:
83
- print(f"Token invalid, please login again")
160
+ print(f"Token invalid / 令牌无效, re-authenticating: {e}")
84
161
  token = None
85
162
 
86
163
  if not token:
87
- print("\n=== First run - Please login or register / 首次运行,请先登录或注册 ===\n")
164
+ print("\n=== Token invalid - Please login or register / 令牌无效,请先登录或注册 ===\n")
88
165
  print("1. Login / 登录(已有账号)")
89
166
  print("2. Register / 注册(新用户)")
90
167