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.
- {claudesync-0.2.4/src/claudesync.egg-info → claudesync-0.2.6}/PKG-INFO +2 -1
- {claudesync-0.2.4 → claudesync-0.2.6}/pyproject.toml +3 -2
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/__init__.py +3 -2
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/file_handler.py +22 -10
- claudesync-0.2.6/src/claudesync/main.py +156 -0
- claudesync-0.2.6/src/claudesync/manual_auth.py +77 -0
- {claudesync-0.2.4 → claudesync-0.2.6/src/claudesync.egg-info}/PKG-INFO +2 -1
- claudesync-0.2.6/src/claudesync.egg-info/entry_points.txt +2 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/requires.txt +1 -0
- claudesync-0.2.4/src/claudesync/main.py +0 -100
- claudesync-0.2.4/src/claudesync/manual_auth.py +0 -30
- claudesync-0.2.4/src/claudesync.egg-info/entry_points.txt +0 -2
- {claudesync-0.2.4 → claudesync-0.2.6}/LICENSE +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/README.md +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/setup.cfg +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/setup.py +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/api_utils.py +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/debounce.py +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync/gitignore_utils.py +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/SOURCES.txt +0 -0
- {claudesync-0.2.4 → claudesync-0.2.6}/src/claudesync.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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.
|
|
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
|
|
|
@@ -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}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|