claudesync 0.2.4__tar.gz → 0.2.6__tar.gz

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.
Files changed (22) hide show
  1. {claudesync-0.2.4/src/claudesync.egg-info → claudesync-0.2.6}/PKG-INFO +2 -1
  2. {claudesync-0.2.4 → claudesync-0.2.6}/pyproject.toml +3 -2
  3. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/__init__.py +3 -2
  4. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/file_handler.py +22 -10
  5. claudesync-0.2.6/src/claudesync/main.py +156 -0
  6. claudesync-0.2.6/src/claudesync/manual_auth.py +77 -0
  7. {claudesync-0.2.4 → claudesync-0.2.6/src/claudesync.egg-info}/PKG-INFO +2 -1
  8. claudesync-0.2.6/src/claudesync.egg-info/entry_points.txt +2 -0
  9. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/requires.txt +1 -0
  10. claudesync-0.2.4/src/claudesync/main.py +0 -100
  11. claudesync-0.2.4/src/claudesync/manual_auth.py +0 -30
  12. claudesync-0.2.4/src/claudesync.egg-info/entry_points.txt +0 -2
  13. {claudesync-0.2.4 → claudesync-0.2.6}/LICENSE +0 -0
  14. {claudesync-0.2.4 → claudesync-0.2.6}/README.md +0 -0
  15. {claudesync-0.2.4 → claudesync-0.2.6}/setup.cfg +0 -0
  16. {claudesync-0.2.4 → claudesync-0.2.6}/setup.py +0 -0
  17. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/api_utils.py +0 -0
  18. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/debounce.py +0 -0
  19. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/gitignore_utils.py +0 -0
  20. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/SOURCES.txt +0 -0
  21. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/dependency_links.txt +0 -0
  22. {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: claudesync
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A tool to synchronize local files with Claude.ai projects
5
5
  Author-email: Jahziah Wagner <jahziah.wagner+pypi@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/jahwag/claudesync
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: watchdog
15
15
  Requires-Dist: requests
16
+ Requires-Dist: pathspec
16
17
 
17
18
  # ClaudeSync
18
19
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claudesync"
7
- version = "0.2.4"
7
+ version = "0.2.6"
8
8
  authors = [
9
9
  {name = "Jahziah Wagner", email = "jahziah.wagner+pypi@gmail.com"},
10
10
  ]
@@ -19,6 +19,7 @@ classifiers = [
19
19
  dependencies = [
20
20
  "watchdog",
21
21
  "requests",
22
+ "pathspec",
22
23
  ]
23
24
 
24
25
  [project.urls]
@@ -26,7 +27,7 @@ dependencies = [
26
27
  "Bug Tracker" = "https://github.com/jahwag/claudesync/issues"
27
28
 
28
29
  [project.scripts]
29
- claudesync = "claudesync.claudesync:main"
30
+ claudesync = "claudesync.main:main"
30
31
 
31
32
  [tool.setuptools.packages.find]
32
33
  where = ["src"]
@@ -1,6 +1,7 @@
1
1
  from .main import main
2
- from .file_handler import FileUploadHandler
3
2
  from .api_utils import fetch_user_id, fetch_projects, select_project, create_project
3
+ from .debounce import DebounceHandler
4
+ from .file_handler import FileUploadHandler
4
5
  from .manual_auth import get_session_key
5
6
 
6
- __all__ = ['main', 'FileUploadHandler', 'fetch_user_id', 'fetch_projects', 'select_project', 'create_project', 'get_session_key']
7
+ __all__ = ['main', 'FileUploadHandler', 'DebounceHandler', 'fetch_user_id', 'fetch_projects', 'select_project', 'create_project', 'get_session_key']
@@ -1,11 +1,12 @@
1
1
  import os
2
+ import mimetypes
2
3
  from watchdog.events import FileSystemEventHandler
3
- from debounce import DebounceHandler
4
- from gitignore_utils import load_gitignore, should_ignore
4
+ from .debounce import DebounceHandler
5
+ from .gitignore_utils import load_gitignore, should_ignore
5
6
  import requests
6
7
 
7
8
  class FileUploadHandler(FileSystemEventHandler):
8
- def __init__(self, api_endpoint, session_key, base_path, delay=5):
9
+ def __init__(self, api_endpoint, session_key, base_path, delay=5, max_file_size=1024*320): # 320 KB limit or roughly 4k lines
9
10
  self.api_endpoint = api_endpoint
10
11
  self.session_key = session_key
11
12
  self.base_path = os.path.abspath(base_path)
@@ -19,6 +20,7 @@ class FileUploadHandler(FileSystemEventHandler):
19
20
  self.debouncer = DebounceHandler(delay)
20
21
  self.gitignore = load_gitignore(self.base_path)
21
22
  self.log_callback = None
23
+ self.max_file_size = max_file_size
22
24
 
23
25
  def log(self, message):
24
26
  if self.log_callback:
@@ -35,13 +37,24 @@ class FileUploadHandler(FileSystemEventHandler):
35
37
  self.debouncer.debounce(self.upload_file, event.src_path)
36
38
 
37
39
  def should_ignore_file(self, file_path):
38
- # Check if the file is in the .git directory
39
- rel_path = os.path.relpath(file_path, self.base_path)
40
- if rel_path.startswith('.git' + os.path.sep) or rel_path == '.git':
41
- return True
40
+ # Check if the file is in the .git directory
41
+ rel_path = os.path.relpath(file_path, self.base_path)
42
+ if rel_path.startswith('.git' + os.path.sep) or rel_path == '.git':
43
+ return True
44
+
45
+ # Check file size
46
+ if os.path.getsize(file_path) > self.max_file_size:
47
+ self.log(f"Ignoring large file: {file_path}")
48
+ return True
42
49
 
43
- # Check if the file should be ignored based on .gitignore
44
- return should_ignore(self.gitignore, file_path, self.base_path)
50
+ # Check file type
51
+ mime_type, _ = mimetypes.guess_type(file_path)
52
+ if mime_type and not mime_type.startswith('text/'):
53
+ self.log(f"Ignoring non-text file: {file_path}")
54
+ return True
55
+
56
+ # Check if the file should be ignored based on .gitignore
57
+ return should_ignore(self.gitignore, file_path, self.base_path)
45
58
 
46
59
  def api_request(self, method, url, **kwargs):
47
60
  try:
@@ -88,4 +101,3 @@ class FileUploadHandler(FileSystemEventHandler):
88
101
  self.log(f"Uploaded: {file_name}")
89
102
  except Exception as e:
90
103
  print(f"Error processing file {file_path}: {str(e)}")
91
-
@@ -0,0 +1,156 @@
1
+ import blessed
2
+ import time
3
+ import argparse
4
+ import json
5
+ import os
6
+ import sys
7
+ from watchdog.observers import Observer
8
+ from .manual_auth import get_session_key, get_or_update_config_value, get_config, save_config
9
+ from .file_handler import FileUploadHandler
10
+ from .api_utils import fetch_user_id, fetch_projects, select_project
11
+
12
+ class ClaudeSyncTUI:
13
+ def __init__(self, session_key, watch_dir, user_id, project_id, delay):
14
+ self.term = blessed.Terminal()
15
+ self.session_key = session_key
16
+ self.watch_dir = watch_dir
17
+ self.user_id = user_id
18
+ self.project_id = project_id
19
+ self.delay = delay
20
+ self.log_messages = []
21
+ self.observer = None
22
+ self.handler = None
23
+ self.scroll_position = 0
24
+ self.max_log_lines = 1000 # Increased for more history
25
+ self.auto_scroll = True # New flag for automatic scrolling
26
+
27
+ def setup(self):
28
+ if not self.user_id:
29
+ self.user_id = fetch_user_id(self.session_key)
30
+ if not self.project_id:
31
+ projects = fetch_projects(self.user_id, self.session_key)
32
+ self.project_id = select_project(projects, self.user_id, self.session_key)
33
+
34
+ api_endpoint = f"https://claude.ai/api/organizations/{self.user_id}/projects/{self.project_id}/docs"
35
+ self.handler = FileUploadHandler(api_endpoint, self.session_key, self.watch_dir, self.delay)
36
+ self.handler.log_callback = self.add_log_message
37
+
38
+ self.observer = Observer()
39
+ self.observer.schedule(self.handler, self.watch_dir, recursive=True)
40
+
41
+ def initial_sync(self):
42
+ print("Please wait ...")
43
+ self.add_log_message("Performing initial synchronization...")
44
+ for root, dirs, files in os.walk(self.watch_dir):
45
+ for file in files:
46
+ file_path = os.path.join(root, file)
47
+ if not self.handler.should_ignore_file(file_path):
48
+ self.handler.upload_file(file_path)
49
+ self.add_log_message("Initial synchronization completed.")
50
+
51
+ def add_log_message(self, message):
52
+ self.log_messages.append(message)
53
+ if len(self.log_messages) > self.max_log_lines:
54
+ self.log_messages.pop(0)
55
+ if self.auto_scroll:
56
+ self.scroll_to_bottom()
57
+
58
+ def scroll_to_bottom(self):
59
+ self.scroll_position = max(0, len(self.log_messages) - (self.term.height - 11))
60
+
61
+ def draw(self):
62
+ print(self.term.clear())
63
+ print(self.term.move_y(0) + self.term.black_on_skyblue(self.term.center('ClaudeSync TUI')))
64
+
65
+ print(self.term.move_y(2) + f"Watching directory: {self.watch_dir}")
66
+ print(f"Upload delay: {self.delay} seconds")
67
+ print(f"User ID: {self.user_id}")
68
+ print(f"Project ID: {self.project_id}")
69
+ print(f"Auto-scroll: {'ON' if self.auto_scroll else 'OFF'}")
70
+
71
+ print(self.term.move_y(8) + self.term.black_on_skyblue(self.term.center('Recent Activity')))
72
+
73
+ log_height = self.term.height - 12
74
+ visible_logs = self.log_messages[self.scroll_position:self.scroll_position + log_height]
75
+ for i, message in enumerate(visible_logs):
76
+ print(self.term.move_y(10 + i) + message)
77
+
78
+ print(self.term.move_y(self.term.height - 1) + "Press 'q' to quit, 'j'/'k' to scroll, 'a' to toggle auto-scroll")
79
+
80
+ def run(self):
81
+ with self.term.cbreak(), self.term.hidden_cursor():
82
+ self.initial_sync()
83
+ self.observer.start()
84
+ while True:
85
+ self.draw()
86
+ inp = self.term.inkey(timeout=0.1)
87
+ if inp == 'q':
88
+ break
89
+ elif inp == 'j':
90
+ self.auto_scroll = False
91
+ self.scroll_position = min(len(self.log_messages) - 1, self.scroll_position + 1)
92
+ elif inp == 'k':
93
+ self.auto_scroll = False
94
+ self.scroll_position = max(0, self.scroll_position - 1)
95
+ elif inp == 'a':
96
+ self.auto_scroll = not self.auto_scroll
97
+ if self.auto_scroll:
98
+ self.scroll_to_bottom()
99
+
100
+ self.observer.stop()
101
+ self.observer.join()
102
+
103
+
104
+ def main():
105
+ parser = argparse.ArgumentParser(description="Sync local files with Claude.ai projects.")
106
+ parser.add_argument("--session-key", help="Session key for authentication")
107
+ parser.add_argument("--watch-dir", default=".", help="Directory to watch for changes")
108
+ parser.add_argument("--user-id", help="User ID for Claude API")
109
+ parser.add_argument("--project-id", help="Project ID for Claude API")
110
+ parser.add_argument("--delay", type=int, help="Delay in seconds before uploading")
111
+ args = parser.parse_args()
112
+
113
+ config = get_config()
114
+
115
+ # Get or update session key
116
+ session_key = args.session_key or get_session_key()
117
+
118
+ # Get or update watch directory
119
+ watch_dir = args.watch_dir or get_or_update_config_value('watch_dir', "Enter the directory to watch", ".")
120
+
121
+ # Get or fetch user ID
122
+ user_id = args.user_id or config.get('user_id')
123
+ if not user_id:
124
+ print("Fetching user ID...")
125
+ user_id = fetch_user_id(session_key)
126
+ config['user_id'] = user_id
127
+ save_config(config)
128
+ print(f"User ID fetched and stored: {user_id}")
129
+
130
+ # Get or select project ID
131
+ project_id = args.project_id or config.get('project_id')
132
+ if not project_id:
133
+ print("No project ID found. Fetching projects...")
134
+ projects = fetch_projects(user_id, session_key)
135
+ project_id = select_project(projects, user_id, session_key)
136
+ config['project_id'] = project_id
137
+ save_config(config)
138
+ print(f"Project ID selected and stored: {project_id}")
139
+ else:
140
+ use_stored = input(f"Found stored project ID: {project_id}. Use it? (y/n): ").strip().lower()
141
+ if use_stored != 'y':
142
+ projects = fetch_projects(user_id, session_key)
143
+ project_id = select_project(projects, user_id, session_key)
144
+ config['project_id'] = project_id
145
+ save_config(config)
146
+ print(f"New project ID selected and stored: {project_id}")
147
+
148
+ # Get or update delay
149
+ delay = args.delay or int(get_or_update_config_value('delay', "Enter the delay in seconds before uploading", "5"))
150
+
151
+ tui = ClaudeSyncTUI(session_key, watch_dir, user_id, project_id, delay)
152
+ tui.setup()
153
+ tui.run()
154
+
155
+ if __name__ == "__main__":
156
+ main()
@@ -0,0 +1,77 @@
1
+ import webbrowser
2
+ import os
3
+ import json
4
+
5
+ CONFIG_DIR = os.path.expanduser('~/.claudesync')
6
+ CONFIG_FILE = os.path.join(CONFIG_DIR, 'config.json')
7
+
8
+ def get_config():
9
+ if os.path.exists(CONFIG_FILE):
10
+ with open(CONFIG_FILE, 'r') as f:
11
+ return json.load(f)
12
+ return {}
13
+
14
+ def save_config(config):
15
+ os.makedirs(CONFIG_DIR, exist_ok=True)
16
+ with open(CONFIG_FILE, 'w') as f:
17
+ json.dump(config, f, indent=2)
18
+
19
+ def get_session_key():
20
+ config = get_config()
21
+ stored_session_key = config.get('sessionKey')
22
+
23
+ if stored_session_key:
24
+ use_stored = input(f"Found stored sessionKey. Use it? (y/n): ").strip().lower()
25
+ if use_stored == 'y':
26
+ return stored_session_key
27
+
28
+ print("To obtain your session key, please follow these steps:")
29
+ print("1. Open your web browser and go to https://claude.ai")
30
+ print("2. Log in to your Claude account if you haven't already")
31
+ print("3. Once logged in, open your browser's developer tools:")
32
+ print(" - Chrome/Edge: Press F12 or Ctrl+Shift+I (Cmd+Option+I on Mac)")
33
+ print(" - Firefox: Press F12 or Ctrl+Shift+I (Cmd+Option+I on Mac)")
34
+ print(" - Safari: Enable developer tools in Preferences > Advanced, then press Cmd+Option+I")
35
+ print("4. In the developer tools, go to the 'Application' tab (Chrome/Edge) or 'Storage' tab (Firefox)")
36
+ print("5. In the left sidebar, expand 'Cookies' and select 'https://claude.ai'")
37
+ print("6. Find the cookie named 'sessionKey' and copy its value")
38
+
39
+ try:
40
+ webbrowser.open("https://claude.ai")
41
+ except:
42
+ print("Unable to automatically open the browser. Please navigate to https://claude.ai manually.")
43
+
44
+ while True:
45
+ session_key = input("Please enter your sessionKey value: ").strip()
46
+ if session_key:
47
+ config['sessionKey'] = session_key
48
+ save_config(config)
49
+ print(f"SessionKey stored in {CONFIG_FILE}")
50
+ return session_key
51
+ else:
52
+ print("Session key cannot be empty. Please try again.")
53
+
54
+ def get_or_update_config_value(key, prompt, current_value=None):
55
+ config = get_config()
56
+ stored_value = config.get(key, current_value)
57
+
58
+ if stored_value is not None:
59
+ use_stored = input(f"Found stored {key}: {stored_value}. Use it? (y/n): ").strip().lower()
60
+ if use_stored == 'y':
61
+ return stored_value
62
+
63
+ while True:
64
+ new_value = input(f"{prompt}: ").strip()
65
+ if new_value:
66
+ config[key] = new_value
67
+ save_config(config)
68
+ print(f"{key} stored in {CONFIG_FILE}")
69
+ return new_value
70
+ elif current_value is not None:
71
+ return current_value
72
+ else:
73
+ print(f"{key} cannot be empty. Please try again.")
74
+
75
+ if __name__ == "__main__":
76
+ session_key = get_session_key()
77
+ print(f"Session key obtained: {session_key}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: claudesync
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A tool to synchronize local files with Claude.ai projects
5
5
  Author-email: Jahziah Wagner <jahziah.wagner+pypi@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/jahwag/claudesync
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: watchdog
15
15
  Requires-Dist: requests
16
+ Requires-Dist: pathspec
16
17
 
17
18
  # ClaudeSync
18
19
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ claudesync = claudesync.main:main
@@ -1,2 +1,3 @@
1
1
  watchdog
2
2
  requests
3
+ pathspec
@@ -1,100 +0,0 @@
1
- import blessed
2
- import time
3
- import argparse
4
- import json
5
- import os
6
- import sys
7
- from watchdog.observers import Observer
8
- from file_handler import FileUploadHandler
9
- from api_utils import fetch_user_id, fetch_projects, select_project, create_project
10
-
11
- from manual_auth import get_session_key
12
-
13
- class ClaudeSyncTUI:
14
- def __init__(self, session_key, watch_dir, user_id, project_id, delay):
15
- self.term = blessed.Terminal()
16
- self.session_key = session_key
17
- self.watch_dir = watch_dir
18
- self.user_id = user_id
19
- self.project_id = project_id
20
- self.delay = delay
21
- self.log_messages = []
22
- self.observer = None
23
- self.handler = None
24
-
25
- def setup(self):
26
- if not self.user_id:
27
- self.user_id = fetch_user_id(self.session_key)
28
- if not self.project_id:
29
- projects = fetch_projects(self.user_id, self.session_key)
30
- self.project_id = select_project(projects, self.user_id, self.session_key)
31
-
32
- api_endpoint = f"https://claude.ai/api/organizations/{self.user_id}/projects/{self.project_id}/docs"
33
- self.handler = FileUploadHandler(api_endpoint, self.session_key, self.watch_dir, self.delay)
34
- self.handler.log_callback = self.add_log_message
35
-
36
- self.observer = Observer()
37
- self.observer.schedule(self.handler, self.watch_dir, recursive=True)
38
-
39
- def add_log_message(self, message):
40
- self.log_messages.append(message)
41
- if len(self.log_messages) > 10:
42
- self.log_messages.pop(0)
43
-
44
- def draw(self):
45
- print(self.term.clear())
46
- print(self.term.move_y(0) + self.term.black_on_skyblue(self.term.center('ClaudeSync TUI')))
47
-
48
- print(self.term.move_y(2) + f"Watching directory: {self.watch_dir}")
49
- print(f"Upload delay: {self.delay} seconds")
50
- print(f"User ID: {self.user_id}")
51
- print(f"Project ID: {self.project_id}")
52
-
53
- print(self.term.move_y(7) + self.term.black_on_skyblue(self.term.center('Recent Activity')))
54
- for i, message in enumerate(self.log_messages):
55
- print(self.term.move_y(9 + i) + message)
56
-
57
- print(self.term.move_y(20) + "Press 'q' to quit")
58
-
59
- def run(self):
60
- with self.term.cbreak(), self.term.hidden_cursor():
61
- self.observer.start()
62
- while True:
63
- self.draw()
64
- inp = self.term.inkey(timeout=1)
65
- if inp == 'q':
66
- break
67
- self.observer.stop()
68
- self.observer.join()
69
-
70
- def main():
71
- parser = argparse.ArgumentParser(description="Sync local files with Claude.ai projects.")
72
- parser.add_argument("--session-key", help="Session key for authentication")
73
- parser.add_argument("--watch-dir", default=".", help="Directory to watch for changes")
74
- parser.add_argument("--user-id", help="User ID for Claude API (optional, will be fetched if not provided)")
75
- parser.add_argument("--project-id", help="Project ID for Claude API")
76
- parser.add_argument("--delay", type=int, default=5, help="Delay in seconds before uploading (default: 5)")
77
- args = parser.parse_args()
78
-
79
- # Load config from file if it exists
80
- config = {}
81
- if os.path.exists('config.json'):
82
- with open('config.json', 'r') as f:
83
- config = json.load(f)
84
-
85
- # If session key is not provided, use the manual input method
86
- session_key = args.session_key
87
- if not session_key:
88
- session_key = get_session_key()
89
-
90
- watch_dir = args.watch_dir
91
- user_id = args.user_id or config.get('user_id')
92
- project_id = args.project_id or config.get('project_id')
93
- delay = args.delay
94
-
95
- tui = ClaudeSyncTUI(session_key, watch_dir, user_id, project_id, delay)
96
- tui.setup()
97
- tui.run()
98
-
99
- if __name__ == "__main__":
100
- main()
@@ -1,30 +0,0 @@
1
- import webbrowser
2
-
3
- def get_session_key():
4
- print("To obtain your session key, please follow these steps:")
5
- print("1. Open your web browser and go to https://claude.ai")
6
- print("2. Log in to your Claude account if you haven't already")
7
- print("3. Once logged in, open your browser's developer tools:")
8
- print(" - Chrome/Edge: Press F12 or Ctrl+Shift+I (Cmd+Option+I on Mac)")
9
- print(" - Firefox: Press F12 or Ctrl+Shift+I (Cmd+Option+I on Mac)")
10
- print(" - Safari: Enable developer tools in Preferences > Advanced, then press Cmd+Option+I")
11
- print("4. In the developer tools, go to the 'Application' tab (Chrome/Edge) or 'Storage' tab (Firefox)")
12
- print("5. In the left sidebar, expand 'Cookies' and select 'https://claude.ai'")
13
- print("6. Find the cookie named 'sessionKey' and copy its value")
14
-
15
- # Attempt to open the Claude.ai website for the user
16
- try:
17
- webbrowser.open("https://claude.ai")
18
- except:
19
- print("Unable to automatically open the browser. Please navigate to https://claude.ai manually. ")
20
-
21
- while True:
22
- session_key = input("Please enter your sessionKey value: ").strip()
23
- if session_key:
24
- return session_key
25
- else:
26
- print("Session key cannot be empty. Please try again.")
27
-
28
- if __name__ == "__main__":
29
- session_key = get_session_key()
30
- print(f"Session key obtained: {session_key}")
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- claudesync = claudesync.claudesync:main
File without changes
File without changes
File without changes
File without changes