soulsync 1.0.0 → 1.0.4

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/src/main.py CHANGED
@@ -1,20 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SoulSync OpenClaw 插件主类
4
+ """
5
+
1
6
  import json
2
7
  import os
3
8
  import sys
4
9
  import time
5
10
 
6
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'base'))
11
+ # 获取插件根目录
12
+ PLUGIN_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13
+ SRC_DIR = os.path.join(PLUGIN_DIR, 'src')
14
+
15
+ # 添加 src 到路径
16
+ if SRC_DIR not in sys.path:
17
+ sys.path.insert(0, SRC_DIR)
7
18
 
8
- from src.client import OpenClawClient
9
- from src.watcher import OpenClawMultiWatcher
10
- from src.version_manager import VersionManager
11
- from src.profiles import ProfilesClient
12
- from src.sync import ProfileSync
19
+ from client import OpenClawClient
20
+ from watcher import OpenClawMultiWatcher
21
+ from version_manager import VersionManager
22
+ from profiles import ProfilesClient
23
+ from sync import ProfileSync
24
+ from register import Register, Login
25
+ from interactive_auth import prompt_for_missing_config, interactive_setup, check_existing_config
13
26
 
14
27
 
15
28
  class SoulSyncPlugin:
16
29
  """SoulSync OpenClaw 插件主类"""
17
-
30
+
18
31
  def __init__(self):
19
32
  self.config = None
20
33
  self.client = None
@@ -23,72 +36,102 @@ class SoulSyncPlugin:
23
36
  self.version_manager = None
24
37
  self.profile_sync = None
25
38
  self.running = False
26
-
39
+
27
40
  def load_config(self):
28
41
  """加载配置文件"""
29
- config_path = os.path.join(os.path.dirname(__file__), '..', 'config.json')
30
-
42
+ config_path = os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json'))
43
+
44
+ print(f"Looking for config at: {config_path}")
45
+
31
46
  if not os.path.exists(config_path):
32
47
  raise FileNotFoundError(f"Config file not found: {config_path}")
33
-
34
- with open(config_path, 'r', encoding='utf-8') as f:
35
- self.config = json.load(f)
36
-
48
+
49
+ try:
50
+ with open(config_path, 'r', encoding='utf-8') as f:
51
+ self.config = json.load(f)
52
+ except json.JSONDecodeError as e:
53
+ raise ValueError(f"Invalid JSON in config.json: {e}")
54
+
55
+ # 处理 workspace 路径
37
56
  workspace = self.config.get('workspace', './workspace')
38
- workspace = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', workspace))
39
-
40
- watch_files = self.config.get('watch_files', ['MEMORY.md', 'memory/'])
41
-
57
+ if workspace.startswith('./'):
58
+ workspace = workspace[2:]
59
+ workspace = os.path.normpath(os.path.join(PLUGIN_DIR, workspace))
60
+
61
+ watch_files = self.config.get('watch_files', [])
62
+
42
63
  self.config['workspace'] = workspace
43
64
  self.config['watch_files'] = watch_files
44
-
65
+
45
66
  print(f"Config loaded:")
46
67
  print(f" Cloud URL: {self.config.get('cloud_url')}")
47
68
  print(f" Workspace: {workspace}")
48
69
  print(f" Watch files: {watch_files}")
49
-
70
+
50
71
  def initialize(self):
51
72
  """初始化组件"""
52
73
  print("\n=== Initializing SoulSync Plugin ===\n")
53
-
74
+
54
75
  self.client = OpenClawClient(self.config)
55
-
76
+
77
+ token = self.client._load_token()
78
+ if token:
79
+ try:
80
+ profile = self.client.get_profile()
81
+ print(f"Using existing token, user: {profile.get('email', 'unknown')}")
82
+ except Exception as e:
83
+ print(f"Token invalid, please login again")
84
+ token = None
85
+
86
+ if not token:
87
+ print("\n=== First run - Please login or register / 首次运行,请先登录或注册 ===\n")
88
+ print("1. Login / 登录(已有账号)")
89
+ print("2. Register / 注册(新用户)")
90
+
91
+ choice = input("Choose (1/2): ").strip()
92
+
93
+ if choice == '1':
94
+ login = Login(self.client)
95
+ result = login.run()
96
+ elif choice == '2':
97
+ register = Register(self.client)
98
+ result = register.run()
99
+ else:
100
+ print("Invalid choice / 无效选择")
101
+ sys.exit(1)
102
+
56
103
  email = self.config.get('email')
57
104
  password = self.config.get('password')
58
-
59
- if not email or not password:
60
- print("WARNING: Email and password not configured!")
61
- print("Please edit config.json and add your email and password\n")
62
-
105
+
63
106
  try:
64
- self.client.authenticate(email, password)
107
+ profile = self.client.get_profile()
108
+ print(f"\nLogged in as: {profile.get('email')}")
109
+ subscription = profile.get('subscription', {})
110
+ print(f"Subscription: {subscription.get('status')} (days remaining: {subscription.get('daysRemaining', 0)})\n")
65
111
  except Exception as e:
66
- print(f"Authentication error: {e}")
67
- print("Please check your config.json and try again\n")
68
- raise
69
-
70
- profile = self.client.get_profile()
71
- print(f"\nLogged in as: {profile.get('email')}")
72
- subscription = profile.get('subscription', {})
73
- print(f"Subscription: {subscription.get('status')} (days remaining: {subscription.get('daysRemaining', 0)})\n")
74
-
75
- versions_file = os.path.join(os.path.dirname(__file__), '..', 'versions.json')
112
+ print(f"Warning: Could not get profile: {e}")
113
+
114
+ # 版本管理器
115
+ versions_file = os.path.normpath(os.path.join(PLUGIN_DIR, 'versions.json'))
76
116
  self.version_manager = VersionManager(versions_file)
77
-
117
+
78
118
  self.profiles_client = ProfilesClient(
79
119
  self.config.get('cloud_url'),
80
120
  self.client.token
81
121
  )
82
-
122
+
83
123
  self.profile_sync = ProfileSync(
84
124
  self.profiles_client,
85
125
  self.version_manager,
86
126
  self.config.get('workspace')
87
127
  )
88
-
128
+
89
129
  print("Pulling all profiles from cloud...")
90
- self.profile_sync.pull_all()
91
-
130
+ try:
131
+ self.profile_sync.pull_all()
132
+ except Exception as e:
133
+ print(f"Warning: Could not pull profiles: {e}")
134
+
92
135
  print("\nStarting file watcher...")
93
136
  watch_files = self.config.get('watch_files', [])
94
137
  self.watcher = OpenClawMultiWatcher(
@@ -97,32 +140,35 @@ class SoulSyncPlugin:
97
140
  self.on_file_change
98
141
  )
99
142
  self.watcher.start()
100
-
143
+
101
144
  print("\nConnecting to WebSocket...")
102
- self.client.connect_websocket(self.on_websocket_message)
103
-
145
+ try:
146
+ self.client.connect_websocket(self.on_websocket_message)
147
+ except Exception as e:
148
+ print(f"Warning: Could not connect WebSocket: {e}")
149
+
104
150
  self.running = True
105
-
151
+
106
152
  def on_file_change(self, event_type: str, relative_path: str, absolute_path: str = None):
107
153
  """文件变化回调"""
108
154
  print(f"\n[File {event_type}] {relative_path}")
109
-
155
+
110
156
  if event_type in ['modified', 'created']:
111
157
  time.sleep(0.5)
112
-
158
+
113
159
  try:
114
160
  self.profile_sync.push_file(relative_path)
115
161
  print(f"Upload completed: {relative_path}")
116
162
  except Exception as e:
117
163
  print(f"Upload error: {e}")
118
-
164
+
119
165
  elif event_type == 'deleted':
120
166
  print(f"File deleted (not synced to cloud): {relative_path}")
121
-
167
+
122
168
  def on_websocket_message(self, data: dict):
123
169
  """WebSocket 消息回调"""
124
170
  event = data.get('event')
125
-
171
+
126
172
  if event == 'file_updated':
127
173
  file_path = data.get('file_path')
128
174
  version = data.get('version')
@@ -131,7 +177,7 @@ class SoulSyncPlugin:
131
177
  self.profile_sync.on_remote_change(file_path, version)
132
178
  except Exception as e:
133
179
  print(f"Sync error: {e}")
134
-
180
+
135
181
  elif event == 'new_memory':
136
182
  print(f"\n[WebSocket] New memory available!")
137
183
  try:
@@ -139,60 +185,61 @@ class SoulSyncPlugin:
139
185
  print("Memory synced from remote")
140
186
  except Exception as e:
141
187
  print(f"Sync error: {e}")
142
-
188
+
143
189
  elif data.get('type') == 'authenticated':
144
190
  print(f"[WebSocket] Authenticated, socket_id: {data.get('socket_id')}")
145
191
  elif data.get('type') == 'error':
146
192
  print(f"[WebSocket] Error: {data.get('message')}")
147
-
193
+
148
194
  def run(self):
149
195
  """运行插件"""
150
196
  print("\n" + "=" * 50)
151
197
  print("SoulSync OpenClaw Plugin (Multi-File Sync)")
152
198
  print("=" * 50 + "\n")
153
-
199
+
154
200
  try:
155
201
  self.load_config()
156
202
  self.initialize()
157
-
203
+
158
204
  print("\n=== Plugin Running ===")
159
205
  print("Press Ctrl+C to stop\n")
160
-
206
+
161
207
  while self.running:
162
208
  time.sleep(1)
163
-
164
- if hasattr(self.client, 'ws') and self.client.ws:
165
- if not self.client.ws.sock or not self.client.ws.sock.connected:
166
- print("WebSocket disconnected, reconnecting...")
167
- try:
168
- self.client.connect_websocket(self.on_websocket_message)
169
- except Exception as e:
170
- print(f"Reconnect error: {e}")
171
-
209
+
172
210
  except KeyboardInterrupt:
173
211
  print("\n\nShutting down...")
212
+ self.shutdown()
174
213
  except Exception as e:
175
- print(f"Error: {e}")
176
- finally:
177
- self.stop()
178
-
179
- def stop(self):
180
- """停止插件"""
214
+ print(f"\nError: {e}")
215
+ import traceback
216
+ traceback.print_exc()
217
+ self.shutdown()
218
+ raise
219
+
220
+ def shutdown(self):
221
+ """关闭插件"""
222
+ print("Shutting down SoulSync plugin...")
181
223
  self.running = False
182
-
224
+
183
225
  if self.watcher:
184
- self.watcher.stop()
185
-
226
+ try:
227
+ self.watcher.stop()
228
+ print("File watcher stopped")
229
+ except Exception as e:
230
+ print(f"Error stopping watcher: {e}")
231
+
186
232
  if self.client:
187
- self.client.disconnect_websocket()
188
-
189
- print("Plugin stopped")
190
-
191
-
233
+ try:
234
+ self.client.close()
235
+ print("Client connection closed")
236
+ except Exception as e:
237
+ print(f"Error closing client: {e}")
238
+
239
+ print("Plugin shutdown complete")
192
240
  def main():
241
+ """主函数"""
193
242
  plugin = SoulSyncPlugin()
194
243
  plugin.run()
195
-
196
-
197
244
  if __name__ == '__main__':
198
245
  main()