soulsync 1.0.11 → 1.0.13

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
@@ -61,7 +61,7 @@ class SoulSyncPlugin:
61
61
 
62
62
  cloud_url = self.config.get('cloud_url', '').strip()
63
63
  email = self.config.get('email', '').strip()
64
- password = self.config.get('password', '').strip()
64
+ token = self.config.get('token', '').strip()
65
65
 
66
66
  if not cloud_url:
67
67
  self.config['cloud_url'] = 'https://soulsync.work'
@@ -92,9 +92,11 @@ class SoulSyncPlugin:
92
92
  except:
93
93
  config = {}
94
94
 
95
- # 保存 email password(如果 auth_result 包含)
96
- if 'user' in auth_result:
95
+ # 保存 email(支持两种返回格式:authenticate user 对象,register 只有一个字段)
96
+ if 'user' in auth_result and isinstance(auth_result.get('user'), dict):
97
97
  config['email'] = auth_result['user'].get('email', '')
98
+ elif 'email' in auth_result:
99
+ config['email'] = auth_result.get('email', '')
98
100
 
99
101
  # 保存 token
100
102
  if 'token' in auth_result:
@@ -132,7 +134,7 @@ class SoulSyncPlugin:
132
134
  return result
133
135
  elif choice == '0':
134
136
  print("[SoulSync] Goodbye! / 再见!")
135
- sys.exit(0)
137
+ sys.exit(2)
136
138
  else:
137
139
  print("[SoulSync] Invalid choice / 无效选择")
138
140
 
@@ -164,6 +166,8 @@ class SoulSyncPlugin:
164
166
  temp_client = OpenClawClient(self.config)
165
167
  result = temp_client.authenticate(email, password)
166
168
  if result:
169
+ result['email'] = email
170
+ result['password'] = password
167
171
  print("\n[SoulSync] ✓ Login successful! / 登录成功!")
168
172
  self._save_auth_to_config(result)
169
173
  return True
@@ -176,7 +180,7 @@ class SoulSyncPlugin:
176
180
  print("[SoulSync] Exiting... / 退出...")
177
181
  sys.exit(0)
178
182
 
179
- print(f"\n[SoulSync] ❌ Login failed: {e} / 登录失败: {e}")
183
+ print(f"\n[SoulSync] ❌ {e}")
180
184
  print("[SoulSync] Returning to menu... / 返回菜单...")
181
185
  return None
182
186
 
@@ -281,46 +285,32 @@ class SoulSyncPlugin:
281
285
  profile = self.client.get_profile()
282
286
  print(f"[SoulSync] Using existing token, user: {profile.get('email', 'unknown')}")
283
287
  except Exception as e:
288
+ error_str = str(e)
289
+ if '401' in error_str or 'Unauthorized' in error_str or 'token' in error_str.lower():
290
+ print("[SoulSync] Token expired or invalid. Please re-login via chat: say 'login SoulSync'")
291
+ print("[SoulSync] Token 已过期,请在聊天中说 'login SoulSync' 重新登录")
292
+ return False
284
293
  print(f"[SoulSync] Token invalid, re-authenticating: {e}")
285
294
  token = None
286
295
 
287
296
  if not token:
288
- email = self.config.get('email', '').strip()
289
- password = self.config.get('password', '').strip()
290
-
291
- print("[SoulSync] No valid token, attempting auto-login...")
292
- try:
293
- result = self.client.authenticate(email, password)
294
- if result:
295
- print("[SoulSync] Login successful! / 登录成功!")
296
- self._save_auth_to_config(result)
297
- token = self.client.token
298
- except Exception as e:
299
- error_msg = str(e)
300
- print(f"[SoulSync] Login failed: {e}")
301
-
302
- if "429" in error_msg or "too many" in error_msg.lower():
303
- print("\n[SoulSync] ========================================")
304
- print("[SoulSync] Too many failed attempts. Please try again later / 登录失败次数过多,请稍后再试")
305
- print("[SoulSync] ========================================\n")
306
- else:
307
- print("\n[SoulSync] ========================================")
308
- print("[SoulSync] Login failed: invalid email or password / 登录失败:邮箱或密码错误")
309
- print("[SoulSync] Please check your config file / 请检查配置文件:")
310
- print(f" {os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json'))}")
311
- print("[SoulSync] ========================================\n")
312
-
313
- sys.exit(0)
314
-
297
+ print("[SoulSync] Token expired or invalid. Please re-configure using 'openclaw soulsync:setup'")
298
+ print("[SoulSync] Token 已过期,请重新运行 'openclaw soulsync:setup' 配置")
299
+ return False
300
+
315
301
  email = self.config.get('email')
316
- password = self.config.get('password')
317
-
302
+
318
303
  try:
319
304
  profile = self.client.get_profile()
320
305
  print(f"\n[SoulSync] Logged in as: {profile.get('email')}")
321
306
  subscription = profile.get('subscription', {})
322
307
  print(f"[SoulSync] Subscription: {subscription.get('status')} (days remaining: {subscription.get('daysRemaining', 0)})\n")
323
308
  except Exception as e:
309
+ error_str = str(e)
310
+ if '401' in error_str or 'Unauthorized' in error_str or 'token' in error_str.lower():
311
+ print("[SoulSync] Token expired or invalid. Please re-login via chat: say 'login SoulSync'")
312
+ print("[SoulSync] Token 已过期,请在聊天中说 'login SoulSync' 重新登录")
313
+ return False
324
314
  print(f"[SoulSync] Warning: Could not get profile: {e}")
325
315
 
326
316
  # 版本管理器
@@ -338,13 +328,13 @@ class SoulSyncPlugin:
338
328
  self.config.get('workspace')
339
329
  )
340
330
 
341
- print("Pulling all profiles from cloud...")
331
+ print("[SoulSync] Pulling all profiles from cloud...")
342
332
  try:
343
333
  self.profile_sync.pull_all()
344
334
  except Exception as e:
345
- print(f"Warning: Could not pull profiles: {e}")
335
+ print(f"[SoulSync] Warning: Could not pull profiles: {e}")
346
336
 
347
- print("\nStarting file watcher...")
337
+ print("\n[SoulSync] Starting file watcher...")
348
338
  watch_files = self.config.get('watch_files', [])
349
339
  self.watcher = OpenClawMultiWatcher(
350
340
  self.config.get('workspace'),
@@ -353,55 +343,47 @@ class SoulSyncPlugin:
353
343
  )
354
344
  self.watcher.start()
355
345
 
356
- print("\nConnecting to WebSocket...")
346
+ print("\n[SoulSync] Connecting to WebSocket...")
357
347
  try:
358
348
  self.client.connect_websocket(self.on_websocket_message)
359
349
  except Exception as e:
360
- print(f"Warning: Could not connect WebSocket: {e}")
350
+ print(f"[SoulSync] Warning: Could not connect WebSocket: {e}")
361
351
 
362
352
  self.running = True
363
353
 
364
354
  def on_file_change(self, event_type: str, relative_path: str, absolute_path: str = None):
365
355
  """文件变化回调"""
366
- print(f"\n[File {event_type}] {relative_path}")
356
+ print(f"\n[SoulSync] [File {event_type}] {relative_path}")
367
357
 
368
358
  if event_type in ['modified', 'created']:
369
359
  time.sleep(0.5)
370
360
 
371
361
  try:
372
362
  self.profile_sync.push_file(relative_path)
373
- print(f"Upload completed: {relative_path}")
363
+ print(f"[SoulSync] Upload completed: {relative_path}")
374
364
  except Exception as e:
375
- print(f"Upload error: {e}")
365
+ print(f"[SoulSync] Upload error: {e}")
376
366
 
377
367
  elif event_type == 'deleted':
378
- print(f"File deleted (not synced to cloud): {relative_path}")
368
+ print(f"[SoulSync] File deleted (not synced to cloud): {relative_path}")
379
369
 
380
370
  def on_websocket_message(self, data: dict):
381
371
  """WebSocket 消息回调"""
382
372
  event = data.get('event')
383
373
 
384
- if event == 'file_updated':
385
- file_path = data.get('file_path')
374
+ if event == 'profile:updated':
375
+ user_id = data.get('user_id')
386
376
  version = data.get('version')
387
- print(f"\n[WebSocket] File updated: {file_path} (v{version})")
388
- try:
389
- self.profile_sync.on_remote_change(file_path, version)
390
- except Exception as e:
391
- print(f"Sync error: {e}")
392
-
393
- elif event == 'new_memory':
394
- print(f"\n[WebSocket] New memory available!")
377
+ print(f"\n[SoulSync] [WebSocket] Profile updated (v{version})")
395
378
  try:
396
379
  self.profile_sync.pull_all()
397
- print("Memory synced from remote")
398
380
  except Exception as e:
399
- print(f"Sync error: {e}")
381
+ print(f"[SoulSync] Sync error: {e}")
400
382
 
401
383
  elif data.get('type') == 'authenticated':
402
- print(f"[WebSocket] Authenticated, socket_id: {data.get('socket_id')}")
384
+ print(f"[SoulSync] [WebSocket] Authenticated, socket_id: {data.get('socket_id')}")
403
385
  elif data.get('type') == 'error':
404
- print(f"[WebSocket] Error: {data.get('message')}")
386
+ print(f"[SoulSync] [WebSocket] Error: {data.get('message')}")
405
387
 
406
388
  def run(self):
407
389
  """运行插件"""
@@ -413,17 +395,17 @@ class SoulSyncPlugin:
413
395
  self.load_config()
414
396
  self.initialize()
415
397
 
416
- print("\n=== Plugin Running ===")
417
- print("Press Ctrl+C to stop\n")
398
+ print("\n[SoulSync] === Plugin Running ===")
399
+ print("[SoulSync] Press Ctrl+C to stop\n")
418
400
 
419
401
  while self.running:
420
402
  time.sleep(1)
421
403
 
422
404
  except KeyboardInterrupt:
423
- print("\n\nShutting down...")
405
+ print("\n[SoulSync] Shutting down...")
424
406
  self.shutdown()
425
407
  except Exception as e:
426
- print(f"\nError: {e}")
408
+ print(f"\n[SoulSync] Error: {e}")
427
409
  import traceback
428
410
  traceback.print_exc()
429
411
  self.shutdown()
@@ -431,24 +413,24 @@ class SoulSyncPlugin:
431
413
 
432
414
  def shutdown(self):
433
415
  """关闭插件"""
434
- print("Shutting down SoulSync plugin...")
416
+ print("[SoulSync] Shutting down SoulSync plugin...")
435
417
  self.running = False
436
418
 
437
419
  if self.watcher:
438
420
  try:
439
421
  self.watcher.stop()
440
- print("File watcher stopped")
422
+ print("[SoulSync] File watcher stopped")
441
423
  except Exception as e:
442
- print(f"Error stopping watcher: {e}")
424
+ print(f"[SoulSync] Error stopping watcher: {e}")
443
425
 
444
426
  if self.client:
445
427
  try:
446
428
  self.client.close()
447
- print("Client connection closed")
429
+ print("[SoulSync] Client connection closed")
448
430
  except Exception as e:
449
- print(f"Error closing client: {e}")
431
+ print(f"[SoulSync] Error closing client: {e}")
450
432
 
451
- print("Plugin shutdown complete")
433
+ print("[SoulSync] Plugin shutdown complete")
452
434
  def main():
453
435
  """主函数"""
454
436
  try:
@@ -463,19 +445,25 @@ def main():
463
445
  if args.setup:
464
446
  try:
465
447
  plugin.load_config()
466
- plugin.run_setup()
467
- print("\n[SoulSync] ✓ Setup complete! Run 'openclaw soulsync:start' to begin syncing.")
468
- print("[SoulSync] 设置完成!运行 'openclaw soulsync:start' 开始同步。")
448
+ result = plugin.run_setup()
449
+ if result:
450
+ print("\n[SoulSync] Setup complete! Starting sync... / 设置完成!正在启动同步...")
451
+ print("\n[SoulSync] ========================================")
452
+ print("[SoulSync] Starting SoulSync sync service...")
453
+ print("[SoulSync] ========================================\n")
454
+ plugin.initialize()
455
+ plugin.run()
456
+ return
469
457
  except KeyboardInterrupt:
470
458
  print("\n[SoulSync] Cancelled by user. Goodbye! / 已取消,再见!")
471
- sys.exit(0)
459
+ sys.exit(2)
472
460
 
473
461
  plugin.load_config()
474
462
 
475
463
  email = plugin.config.get('email', '').strip()
476
- password = plugin.config.get('password', '').strip()
464
+ token = plugin.config.get('token', '').strip()
477
465
 
478
- if not email or not password:
466
+ if not email or not token:
479
467
  print("\n[SoulSync] ========================================")
480
468
  print("[SoulSync] Not configured. Run 'openclaw soulsync:setup' first.")
481
469
  print("[SoulSync] 尚未配置,请先运行 'openclaw soulsync:setup'")
package/src/profiles.py CHANGED
@@ -1,12 +1,19 @@
1
1
  import requests
2
+ import sys
3
+ import os
4
+
5
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
6
+ from src.client import TLSAdapter
2
7
 
3
8
 
4
9
  class ProfilesClient:
5
- """Profiles API 客户端"""
10
+ """Profiles API 客户端 - 统一同步接口"""
6
11
 
7
12
  def __init__(self, cloud_url: str, token: str = None):
8
13
  self.cloud_url = cloud_url.rstrip('/')
9
14
  self.token = token
15
+ self.session = requests.Session()
16
+ self.session.mount('https://', TLSAdapter())
10
17
 
11
18
  def _get_headers(self) -> dict:
12
19
  """获取请求头"""
@@ -19,57 +26,50 @@ class ProfilesClient:
19
26
  """设置 token"""
20
27
  self.token = token
21
28
 
22
- def get_profiles(self, path: str = None) -> dict:
23
- """获取 profiles
29
+ def get_profiles(self) -> dict:
30
+ """获取用户的完整 profiles
24
31
 
25
- Args:
26
- path: 可选的文件路径
27
-
28
32
  Returns:
29
- 包含 files 列表的字典
33
+ 包含 content (dict), version, updated_at 的字典
34
+ 示例: {"content": {"SOUL.md": "...", "USER.md": "..."}, "version": 3, "updated_at": "..."}
30
35
  """
31
36
  url = f"{self.cloud_url}/api/profiles"
32
- if path:
33
- url += f"?path={path}"
34
37
 
35
- response = requests.get(url, headers=self._get_headers())
38
+ response = self.session.get(url, headers=self._get_headers())
36
39
 
37
40
  if response.status_code == 200:
38
41
  return response.json()
39
- elif response.status_code == 404:
40
- return {'files': []}
41
42
  else:
42
43
  error = response.json().get('error', 'Unknown error')
43
44
  raise Exception(f"Get profiles failed: {error}")
44
45
 
45
- def upload_profile(self, file_path: str, content: str, version: int) -> dict:
46
- """上传 profile
46
+ def upload_profiles(self, content: dict, version: int) -> dict:
47
+ """整体替换用户的 profiles
47
48
 
48
49
  Args:
49
- file_path: 文件路径
50
- content: 文件内容
51
- version: 当前版本号
50
+ content: 包含所有文件的 dict,key 是文件名,value 是内容
51
+ 示例: {"SOUL.md": "...", "USER.md": "...", "MEMORY.md": "..."}
52
+ version: 客户端当前持有的版本号
52
53
 
53
54
  Returns:
54
- 成功时返回 {file_path, version, updated_at}
55
- 冲突时抛出异常
55
+ 成功时返回 {"content": {...}, "version": N, "updated_at": "..."}
56
+ 冲突时抛出 ConflictError
56
57
  """
57
58
  url = f"{self.cloud_url}/api/profiles"
58
59
  data = {
59
- 'file_path': file_path,
60
60
  'content': content,
61
61
  'version': version
62
62
  }
63
63
 
64
- response = requests.post(url, json=data, headers=self._get_headers())
64
+ response = self.session.put(url, json=data, headers=self._get_headers())
65
65
 
66
66
  if response.status_code == 200:
67
67
  return response.json()
68
68
  elif response.status_code == 409:
69
69
  result = response.json()
70
70
  raise ConflictError(
71
- result.get('latest_content', ''),
72
- result.get('latest_version', 0)
71
+ server_content=result.get('server_content', {}),
72
+ server_version=result.get('server_version', 0)
73
73
  )
74
74
  elif response.status_code == 403:
75
75
  error = response.json().get('error', 'Subscription required')
@@ -77,34 +77,12 @@ class ProfilesClient:
77
77
  else:
78
78
  error = response.json().get('error', 'Unknown error')
79
79
  raise Exception(f"Upload failed: {error}")
80
-
81
- def sync_profiles(self, since: str = '0') -> dict:
82
- """增量同步 profiles
83
-
84
- Args:
85
- since: 时间戳
86
-
87
- Returns:
88
- 包含 files 列表和 server_time 的字典
89
- """
90
- url = f"{self.cloud_url}/api/profiles/sync?since={since}"
91
-
92
- response = requests.get(url, headers=self._get_headers())
93
-
94
- if response.status_code == 200:
95
- return response.json()
96
- elif response.status_code == 403:
97
- error = response.json().get('error', 'Subscription required')
98
- raise Exception(f"Sync failed: {error}")
99
- else:
100
- error = response.json().get('error', 'Unknown error')
101
- raise Exception(f"Sync failed: {error}")
102
80
 
103
81
 
104
82
  class ConflictError(Exception):
105
83
  """版本冲突异常"""
106
84
 
107
- def __init__(self, latest_content: str, latest_version: int):
108
- self.latest_content = latest_content
109
- self.latest_version = latest_version
110
- super().__init__(f"Version conflict: latest version is {latest_version}")
85
+ def __init__(self, server_content: dict, server_version: int):
86
+ self.server_content = server_content
87
+ self.server_version = server_version
88
+ super().__init__(f"Version conflict: server version is {server_version}")
package/src/sync.py CHANGED
@@ -42,7 +42,7 @@ class ProfileSync:
42
42
  with open(absolute_path, 'r', encoding='utf-8') as f:
43
43
  return f.read()
44
44
  except Exception as e:
45
- print(f"[Sync] Error reading local file {file_path}: {e}")
45
+ print(f"[SoulSync] Error reading local file {file_path}: {e}")
46
46
  return None
47
47
  return None
48
48
 
@@ -57,7 +57,7 @@ class ProfileSync:
57
57
  with open(absolute_path, 'w', encoding='utf-8') as f:
58
58
  f.write(content)
59
59
  except Exception as e:
60
- print(f"[Sync] Error writing local file {file_path}: {e}")
60
+ print(f"[SoulSync] Error writing local file {file_path}: {e}")
61
61
 
62
62
  def _create_conflict_backup(self, file_path: str, local_content: str, server_content: str):
63
63
  """创建冲突备份文件"""
@@ -71,64 +71,51 @@ class ProfileSync:
71
71
  f.write(local_content or "(empty)")
72
72
  f.write("\n\n========== SERVER VERSION ==========\n")
73
73
  f.write(server_content or "(empty)")
74
- print(f"[Sync] Conflict backup created: {conflict_path}")
74
+ print(f"[SoulSync] Conflict backup created: {conflict_path}")
75
75
  except Exception as e:
76
- print(f"[Sync] Error creating conflict backup: {e}")
76
+ print(f"[SoulSync] Error creating conflict backup: {e}")
77
77
 
78
78
  def pull_all(self):
79
79
  """Pull all profiles from cloud"""
80
- print("[Sync] Pulling all profiles from cloud...")
80
+ print("[SoulSync] Pulling all profiles from cloud...")
81
81
 
82
82
  try:
83
83
  result = self.client.get_profiles()
84
- cloud_files = result.get('files', [])
84
+ cloud_content = result.get('content', {})
85
+ cloud_version = result.get('version', 0)
85
86
  except Exception as e:
86
- print(f"[Sync] Error fetching cloud profiles: {e}")
87
+ print(f"[SoulSync] Error fetching cloud profiles: {e}")
87
88
  return
88
89
 
89
- if not cloud_files:
90
- print("[Sync] No files on cloud")
90
+ if not cloud_content:
91
+ print("[SoulSync] No profiles on cloud")
91
92
  return
92
93
 
94
+ local_files = ['SOUL.md', 'USER.md', 'MEMORY.md']
93
95
  pulled_count = 0
94
- pushed_count = 0
95
96
  skipped_count = 0
96
97
 
97
- for cloud_file in cloud_files:
98
- file_path = cloud_file.get('file_path')
99
- cloud_version = cloud_file.get('version', 0)
100
- cloud_content = cloud_file.get('content', '')
101
-
102
- if not file_path:
103
- continue
104
-
105
- local_content = self._read_local_file(file_path)
106
- local_version = self.version_manager.get_version(file_path)
98
+ for file_name in local_files:
99
+ cloud_file_content = cloud_content.get(file_name, '')
100
+ local_content = self._read_local_file(file_name)
101
+ local_version = self.version_manager.get_version(file_name)
107
102
 
108
103
  if cloud_version > local_version:
109
- self._mark_syncing(file_path)
104
+ self._mark_syncing(file_name)
110
105
  try:
111
- self._write_local_file(file_path, cloud_content)
112
- self.version_manager.set_version(file_path, cloud_version)
113
- pulled_count += 1
114
- print(f"[Sync] Pulled: {file_path} (v{cloud_version})")
106
+ if cloud_file_content:
107
+ self._write_local_file(file_name, cloud_file_content)
108
+ self.version_manager.set_version(file_name, cloud_version)
109
+ pulled_count += 1
110
+ print(f"[SoulSync] Pulled: {file_name} (v{cloud_version})")
115
111
  finally:
116
- self._unmark_syncing(file_path)
117
- elif local_version > cloud_version and local_content is not None:
118
- try:
119
- result = self.client.upload_profile(file_path, local_content, local_version)
120
- self.version_manager.set_version(file_path, result.get('version', local_version))
121
- pushed_count += 1
122
- print(f"[Sync] Pushed: {file_path} (v{local_version})")
123
- except ConflictError as e:
124
- self._handle_conflict(file_path, local_content, e.server_content, e.server_version)
125
- pushed_count += 1
126
- except Exception as e:
127
- print(f"[Sync] Error pushing {file_path}: {e}")
112
+ self._unmark_syncing(file_name)
128
113
  else:
129
114
  skipped_count += 1
130
115
 
131
- print(f"[Sync] Sync complete: {pulled_count} pulled, {pushed_count} pushed, {skipped_count} skipped")
116
+ self.version_manager.set_version('__profiles__', cloud_version)
117
+
118
+ print(f"[SoulSync] Sync complete: {pulled_count} pulled, {skipped_count} skipped")
132
119
 
133
120
  def push_file(self, file_path: str):
134
121
  """Push a file to cloud"""
@@ -140,64 +127,84 @@ class ProfileSync:
140
127
  try:
141
128
  local_content = self._read_local_file(file_path)
142
129
  if local_content is None:
143
- print(f"[Sync] File not found locally: {file_path}")
130
+ print(f"[SoulSync] File not found locally: {file_path}")
144
131
  return
145
132
 
146
133
  local_version = self.version_manager.get_version(file_path)
147
134
 
135
+ profiles_version = self.version_manager.get_version('__profiles__')
136
+
137
+ current_profiles = {}
148
138
  try:
149
- result = self.client.upload_profile(file_path, local_content, local_version)
150
- new_version = result.get('version', local_version + 1)
139
+ result = self.client.get_profiles()
140
+ current_profiles = result.get('content', {})
141
+ except Exception:
142
+ pass
143
+
144
+ current_profiles[file_path] = local_content
145
+
146
+ try:
147
+ result = self.client.upload_profiles(current_profiles, profiles_version)
148
+ new_version = result.get('version', profiles_version + 1)
149
+ self.version_manager.set_version('__profiles__', new_version)
151
150
  self.version_manager.set_version(file_path, new_version)
152
- print(f"[Sync] Pushed: {file_path} (v{new_version})")
151
+ print(f"[SoulSync] Pushed: {file_path} (v{new_version})")
153
152
  except ConflictError as e:
154
153
  self._handle_conflict(file_path, local_content, e.server_content, e.server_version)
155
154
  except Exception as e:
156
- print(f"[Sync] Error pushing {file_path}: {e}")
155
+ print(f"[SoulSync] Error pushing {file_path}: {e}")
157
156
  finally:
158
157
  self._unmark_syncing(file_path)
159
158
 
160
159
  def on_remote_change(self, file_path: str, version: int):
161
160
  """Handle remote file change"""
162
- local_version = self.version_manager.get_version(file_path)
161
+ local_version = self.version_manager.get_version('__profiles__')
163
162
 
164
163
  if version <= local_version:
165
- print(f"[Sync] Remote version not newer, skipping: {file_path} (local: v{local_version}, remote: v{version})")
164
+ print(f"[SoulSync] Remote version not newer, skipping: {file_path} (local: v{local_version}, remote: v{version})")
166
165
  return
167
166
 
168
167
  self._mark_syncing(file_path)
169
168
 
170
169
  try:
171
- result = self.client.get_profiles(file_path)
172
- files = result.get('files', [])
173
- if not files:
174
- print(f"[Sync] File not found on cloud: {file_path}")
170
+ result = self.client.get_profiles()
171
+ cloud_content = result.get('content', {})
172
+ cloud_version = result.get('version', 0)
173
+
174
+ if file_path not in cloud_content:
175
+ print(f"[SoulSync] File not found on cloud: {file_path}")
175
176
  return
176
177
 
177
- cloud_file = files[0]
178
- cloud_content = cloud_file.get('content', '')
178
+ cloud_file_content = cloud_content.get(file_path, '')
179
179
 
180
180
  local_content = self._read_local_file(file_path)
181
181
 
182
- if local_content is not None and local_content != cloud_content:
183
- self._create_conflict_backup(file_path, local_content, cloud_content)
182
+ if local_content is not None and local_content != cloud_file_content:
183
+ self._create_conflict_backup(file_path, local_content, cloud_file_content)
184
184
 
185
- self._write_local_file(file_path, cloud_content)
185
+ self._write_local_file(file_path, cloud_file_content)
186
+ self.version_manager.set_version('__profiles__', cloud_version)
186
187
  self.version_manager.set_version(file_path, version)
187
- print(f"[Sync] Pulled remote change: {file_path} (v{version})")
188
+ print(f"[SoulSync] Pulled remote change: {file_path} (v{version})")
188
189
  except Exception as e:
189
- print(f"[Sync] Error handling remote change for {file_path}: {e}")
190
+ print(f"[SoulSync] Error handling remote change for {file_path}: {e}")
190
191
  finally:
191
192
  self._unmark_syncing(file_path)
192
193
 
193
194
  def _handle_conflict(self, file_path: str, local_content: str, server_content: str, server_version: int):
194
195
  """Handle conflict when pushing file"""
195
- print(f"[Sync] CONFLICT detected for {file_path}")
196
+ print(f"[SoulSync] CONFLICT detected for {file_path}")
196
197
 
197
198
  self._create_conflict_backup(file_path, local_content, server_content)
198
199
 
199
- self._write_local_file(file_path, server_content)
200
+ if isinstance(server_content, dict):
201
+ server_file_content = server_content.get(file_path, '')
202
+ else:
203
+ server_file_content = server_content
204
+
205
+ self._write_local_file(file_path, server_file_content)
206
+ self.version_manager.set_version('__profiles__', server_version)
200
207
  self.version_manager.set_version(file_path, server_version)
201
208
 
202
- print(f"[Sync] Conflict resolved: server version used for {file_path}")
203
- print(f"[Sync] Please manually merge the conflict backup file")
209
+ print(f"[SoulSync] Conflict resolved: server version used for {file_path}")
210
+ print(f"[SoulSync] Please manually merge the conflict backup file")