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.
- package/config.json.example +1 -1
- package/index.js +46 -21
- package/package.json +1 -1
- package/src/client.py +26 -7
- package/src/interactive_auth.py +1 -1
- package/src/main.py +80 -3
package/config.json.example
CHANGED
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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()
|
package/src/interactive_auth.py
CHANGED
|
@@ -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'] = '
|
|
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
|
-
|
|
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
|
|
160
|
+
print(f"Token invalid / 令牌无效, re-authenticating: {e}")
|
|
84
161
|
token = None
|
|
85
162
|
|
|
86
163
|
if not token:
|
|
87
|
-
print("\n===
|
|
164
|
+
print("\n=== Token invalid - Please login or register / 令牌无效,请先登录或注册 ===\n")
|
|
88
165
|
print("1. Login / 登录(已有账号)")
|
|
89
166
|
print("2. Register / 注册(新用户)")
|
|
90
167
|
|