soulsync 1.0.7 → 1.0.9
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 +80 -35
- package/package.json +1 -1
- package/src/main.py +178 -2
package/index.js
CHANGED
|
@@ -1,59 +1,106 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
|
|
4
5
|
let pythonProcess = null;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
function getPluginDir() {
|
|
8
|
+
return path.dirname(__filename);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function checkConfigExists(pluginDir) {
|
|
12
|
+
const configPath = path.join(pluginDir, 'config.json');
|
|
13
|
+
if (!fs.existsSync(configPath)) {
|
|
14
|
+
const examplePath = path.join(pluginDir, 'config.json.example');
|
|
15
|
+
if (fs.existsSync(examplePath)) {
|
|
16
|
+
fs.copyFileSync(examplePath, configPath);
|
|
17
|
+
console.log('[SoulSync] Created config.json from template');
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
24
|
+
const email = (config.email || '').trim();
|
|
25
|
+
const password = (config.password || '').trim();
|
|
26
|
+
return email && password && email !== 'your-email@example.com' && password !== 'your-password';
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function startPythonService(mode = '--start') {
|
|
33
|
+
const pluginDir = getPluginDir();
|
|
34
|
+
const pythonScript = path.join(pluginDir, 'src', 'main.py');
|
|
35
|
+
const pythonPath = process.env.PYTHON_PATH || 'python3';
|
|
36
|
+
|
|
37
|
+
console.log(`[SoulSync] Starting Python service (${mode})...`);
|
|
38
|
+
|
|
39
|
+
pythonProcess = spawn(pythonPath, [pythonScript, mode], {
|
|
40
|
+
cwd: pluginDir,
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
OPENCLAW_PLUGIN: 'true',
|
|
44
|
+
PLUGIN_DIR: pluginDir
|
|
45
|
+
},
|
|
46
|
+
stdio: 'inherit'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
pythonProcess.on('close', (code) => {
|
|
50
|
+
console.log(`[SoulSync] Python process exited with code ${code}`);
|
|
51
|
+
pythonProcess = null;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
pythonProcess.on('error', (err) => {
|
|
55
|
+
console.error(`[SoulSync] Failed to start Python process: ${err}`);
|
|
56
|
+
pythonProcess = null;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return pythonProcess;
|
|
60
|
+
}
|
|
61
|
+
|
|
7
62
|
module.exports = function register(api) {
|
|
8
63
|
console.log('[SoulSync] Registering plugin...');
|
|
9
64
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
}
|
|
65
|
+
api.registerCli(
|
|
66
|
+
({ program }) => {
|
|
67
|
+
program
|
|
68
|
+
.command('soulsync:setup')
|
|
69
|
+
.description('首次配置:注册或登录 SoulSync 账号')
|
|
70
|
+
.action(() => {
|
|
71
|
+
console.log('[SoulSync] Starting setup...');
|
|
72
|
+
startPythonService('--setup');
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
{ commands: ['soulsync:setup'] }
|
|
76
|
+
);
|
|
38
77
|
|
|
39
|
-
// 注册CLI命令:启动 SoulSync
|
|
40
78
|
api.registerCli(
|
|
41
79
|
({ program }) => {
|
|
42
80
|
program
|
|
43
81
|
.command('soulsync:start')
|
|
44
82
|
.description('启动 SoulSync 同步服务')
|
|
45
83
|
.action(() => {
|
|
84
|
+
const pluginDir = getPluginDir();
|
|
85
|
+
const configPath = path.join(pluginDir, 'config.json');
|
|
86
|
+
|
|
87
|
+
if (!checkConfigExists(pluginDir)) {
|
|
88
|
+
console.log('[SoulSync] Not configured. Starting setup...');
|
|
89
|
+
startPythonService('--setup');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
46
93
|
if (pythonProcess) {
|
|
47
94
|
console.log('[SoulSync] Service already running');
|
|
48
95
|
return;
|
|
49
96
|
}
|
|
50
|
-
|
|
97
|
+
|
|
98
|
+
startPythonService('--start');
|
|
51
99
|
});
|
|
52
100
|
},
|
|
53
101
|
{ commands: ['soulsync:start'] }
|
|
54
102
|
);
|
|
55
103
|
|
|
56
|
-
// 注册CLI命令:停止 SoulSync
|
|
57
104
|
api.registerCli(
|
|
58
105
|
({ program }) => {
|
|
59
106
|
program
|
|
@@ -72,8 +119,6 @@ module.exports = function register(api) {
|
|
|
72
119
|
{ commands: ['soulsync:stop'] }
|
|
73
120
|
);
|
|
74
121
|
|
|
75
|
-
|
|
76
|
-
startPythonService();
|
|
77
|
-
|
|
122
|
+
console.log('[SoulSync] Plugin loaded. Run "openclaw soulsync:start" to begin.');
|
|
78
123
|
console.log('[SoulSync] Plugin registered successfully');
|
|
79
124
|
};
|
package/package.json
CHANGED
package/src/main.py
CHANGED
|
@@ -7,6 +7,8 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import sys
|
|
9
9
|
import time
|
|
10
|
+
import getpass
|
|
11
|
+
import argparse
|
|
10
12
|
|
|
11
13
|
# 获取插件根目录
|
|
12
14
|
PLUGIN_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
@@ -111,8 +113,155 @@ class SoulSyncPlugin:
|
|
|
111
113
|
with open(config_path, 'w', encoding='utf-8') as f:
|
|
112
114
|
json.dump(config, f, indent=2, ensure_ascii=False)
|
|
113
115
|
|
|
114
|
-
print("Auth info saved to config.json")
|
|
115
|
-
|
|
116
|
+
print("[SoulSync] Auth info saved to config.json")
|
|
117
|
+
|
|
118
|
+
def run_setup(self):
|
|
119
|
+
"""交互式设置:注册或登录"""
|
|
120
|
+
print("\n[SoulSync] ========================================")
|
|
121
|
+
print("[SoulSync] Welcome to SoulSync! / 欢迎使用 SoulSync!")
|
|
122
|
+
print("[SoulSync] ========================================")
|
|
123
|
+
print("[SoulSync] 1. Register / 注册")
|
|
124
|
+
print("[SoulSync] 2. Login / 登录")
|
|
125
|
+
print("[SoulSync] ========================================")
|
|
126
|
+
|
|
127
|
+
choice = input("[SoulSync] Choose / 选择 (1/2): ").strip()
|
|
128
|
+
|
|
129
|
+
if choice == '1':
|
|
130
|
+
return self._interactive_register()
|
|
131
|
+
elif choice == '2':
|
|
132
|
+
return self._interactive_login()
|
|
133
|
+
else:
|
|
134
|
+
print("[SoulSync] Invalid choice / 无效选择")
|
|
135
|
+
sys.exit(0)
|
|
136
|
+
|
|
137
|
+
def _interactive_login(self):
|
|
138
|
+
"""交互式登录(带重试)"""
|
|
139
|
+
from client import OpenClawClient
|
|
140
|
+
|
|
141
|
+
max_retries = 5
|
|
142
|
+
retry_count = 0
|
|
143
|
+
|
|
144
|
+
while retry_count < max_retries:
|
|
145
|
+
print("\n--- Login / 登录 ---")
|
|
146
|
+
email = input("[SoulSync] Email / 邮箱: ").strip()
|
|
147
|
+
if not email:
|
|
148
|
+
print("[SoulSync] Email cannot be empty / 邮箱不能为空")
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
password = getpass.getpass("[SoulSync] Password / 密码: ")
|
|
152
|
+
if not password:
|
|
153
|
+
print("[SoulSync] Password cannot be empty / 密码不能为空")
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
temp_client = OpenClawClient(self.config)
|
|
158
|
+
result = temp_client.authenticate(email, password)
|
|
159
|
+
if result:
|
|
160
|
+
print("\n[SoulSync] ✓ Login successful! / 登录成功!")
|
|
161
|
+
self._save_auth_to_config(result)
|
|
162
|
+
return True
|
|
163
|
+
except Exception as e:
|
|
164
|
+
retry_count += 1
|
|
165
|
+
remaining = max_retries - retry_count
|
|
166
|
+
error_msg = str(e)
|
|
167
|
+
|
|
168
|
+
if "429" in error_msg or "too many" in error_msg.lower():
|
|
169
|
+
print(f"\n[SoulSync] ❌ {e}")
|
|
170
|
+
print("\n[SoulSync] Too many failed attempts / 登录失败次数过多")
|
|
171
|
+
print("[SoulSync] Exiting... / 退出...")
|
|
172
|
+
sys.exit(0)
|
|
173
|
+
|
|
174
|
+
if remaining > 0:
|
|
175
|
+
print(f"\n[SoulSync] ❌ Login failed: {e} / 登录失败: {e}")
|
|
176
|
+
print(f"[SoulSync] Remaining attempts / 剩余尝试次数: {remaining}")
|
|
177
|
+
else:
|
|
178
|
+
print(f"\n[SoulSync] ❌ Login failed: {e} / 登录失败: {e}")
|
|
179
|
+
|
|
180
|
+
print("\n[SoulSync] ❌ Too many failed attempts. Please try again in 15 minutes. / 登录失败次数过多,请15分钟后再试")
|
|
181
|
+
print("[SoulSync] Exiting... / 退出...")
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
|
|
184
|
+
def _interactive_register(self):
|
|
185
|
+
"""交互式注册(带重试)"""
|
|
186
|
+
from client import OpenClawClient
|
|
187
|
+
|
|
188
|
+
max_retries = 5
|
|
189
|
+
retry_count = 0
|
|
190
|
+
|
|
191
|
+
while retry_count < max_retries:
|
|
192
|
+
print("\n--- Register / 注册 ---")
|
|
193
|
+
email = input("[SoulSync] Email / 邮箱: ").strip()
|
|
194
|
+
if not email or '@' not in email:
|
|
195
|
+
print("[SoulSync] Invalid email / 无效邮箱")
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
password = getpass.getpass("[SoulSync] Password / 密码: ")
|
|
199
|
+
if len(password) < 6:
|
|
200
|
+
print("[SoulSync] Password must be at least 6 characters / 密码至少6位")
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
password2 = getpass.getpass("[SoulSync] Confirm password / 确认密码: ")
|
|
204
|
+
if password != password2:
|
|
205
|
+
print("[SoulSync] Passwords do not match / 两次密码不一致")
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
print(f"\n[SoulSync] Sending verification code to {email}...")
|
|
209
|
+
try:
|
|
210
|
+
temp_client = OpenClawClient(self.config)
|
|
211
|
+
temp_client.send_verification_code(email)
|
|
212
|
+
print("[SoulSync] ✓ Verification code sent! / 验证码已发送!")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
print(f"[SoulSync] ❌ Failed to send code: {e}")
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
code_retry = 0
|
|
218
|
+
while code_retry < max_retries:
|
|
219
|
+
code = input(f"[SoulSync] Enter code / 输入验证码 ({max_retries - code_retry} attempts left): ").strip()
|
|
220
|
+
if len(code) != 6 or not code.isdigit():
|
|
221
|
+
code_retry += 1
|
|
222
|
+
print("[SoulSync] Invalid code format / 验证码格式错误")
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
result = temp_client.register(email, password, code)
|
|
227
|
+
print("\n[SoulSync] ✓ Registration successful! / 注册成功!")
|
|
228
|
+
self._save_auth_to_config(result)
|
|
229
|
+
return True
|
|
230
|
+
except Exception as e:
|
|
231
|
+
code_retry += 1
|
|
232
|
+
remaining_code = max_retries - code_retry
|
|
233
|
+
if "invalid" in str(e).lower() or "expired" in str(e).lower():
|
|
234
|
+
if remaining_code > 0:
|
|
235
|
+
print(f"[SoulSync] ❌ Invalid or expired code: {e}")
|
|
236
|
+
print(f"[SoulSync] Remaining attempts / 剩余尝试: {remaining_code}")
|
|
237
|
+
else:
|
|
238
|
+
print("[SoulSync] ❌ Too many code attempts / 验证码错误次数过多")
|
|
239
|
+
break
|
|
240
|
+
else:
|
|
241
|
+
print(f"[SoulSync] ❌ Registration failed: {e}")
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
if code_retry >= max_retries:
|
|
245
|
+
print("\n[SoulSync] Too many code verification failures.")
|
|
246
|
+
print("[SoulSync] 1. Resend code / 重新发送验证码")
|
|
247
|
+
print("[SoulSync] 2. Start over / 重新开始")
|
|
248
|
+
print("[SoulSync] 3. Exit / 退出")
|
|
249
|
+
|
|
250
|
+
sub_choice = input("[SoulSync] Choose / 选择 (1/2/3): ").strip()
|
|
251
|
+
if sub_choice == '1':
|
|
252
|
+
retry_count = 0
|
|
253
|
+
continue
|
|
254
|
+
elif sub_choice == '2':
|
|
255
|
+
retry_count = 0
|
|
256
|
+
break
|
|
257
|
+
else:
|
|
258
|
+
print("[SoulSync] Exiting... / 退出...")
|
|
259
|
+
sys.exit(0)
|
|
260
|
+
|
|
261
|
+
print("\n[SoulSync] ❌ Too many registration attempts / 注册尝试次数过多")
|
|
262
|
+
print("[SoulSync] Exiting... / 退出...")
|
|
263
|
+
sys.exit(0)
|
|
264
|
+
|
|
116
265
|
def initialize(self):
|
|
117
266
|
"""初始化组件"""
|
|
118
267
|
print("\n[SoulSync] ========================================")
|
|
@@ -297,7 +446,34 @@ class SoulSyncPlugin:
|
|
|
297
446
|
print("Plugin shutdown complete")
|
|
298
447
|
def main():
|
|
299
448
|
"""主函数"""
|
|
449
|
+
parser = argparse.ArgumentParser(description='SoulSync Plugin')
|
|
450
|
+
parser.add_argument('--setup', action='store_true', help='Run interactive setup (register/login)')
|
|
451
|
+
parser.add_argument('--start', action='store_true', help='Start sync service (auto-login from config)')
|
|
452
|
+
|
|
453
|
+
args = parser.parse_args()
|
|
454
|
+
|
|
300
455
|
plugin = SoulSyncPlugin()
|
|
456
|
+
|
|
457
|
+
if args.setup:
|
|
458
|
+
plugin.load_config()
|
|
459
|
+
plugin.run_setup()
|
|
460
|
+
print("\n[SoulSync] ✓ Setup complete! Run 'openclaw soulsync:start' to begin syncing.")
|
|
461
|
+
print("[SoulSync] 设置完成!运行 'openclaw soulsync:start' 开始同步。")
|
|
462
|
+
sys.exit(0)
|
|
463
|
+
|
|
464
|
+
plugin.load_config()
|
|
465
|
+
|
|
466
|
+
email = plugin.config.get('email', '').strip()
|
|
467
|
+
password = plugin.config.get('password', '').strip()
|
|
468
|
+
|
|
469
|
+
if not email or not password:
|
|
470
|
+
print("\n[SoulSync] ========================================")
|
|
471
|
+
print("[SoulSync] Not configured. Run 'openclaw soulsync:setup' first.")
|
|
472
|
+
print("[SoulSync] 尚未配置,请先运行 'openclaw soulsync:setup'")
|
|
473
|
+
print("[SoulSync] ========================================\n")
|
|
474
|
+
sys.exit(0)
|
|
475
|
+
|
|
476
|
+
plugin.initialize()
|
|
301
477
|
plugin.run()
|
|
302
478
|
if __name__ == '__main__':
|
|
303
479
|
main()
|