claudesync 0.1.2__tar.gz → 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: claudesync
3
- Version: 0.1.2
3
+ Version: 0.2.0
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
@@ -25,6 +25,7 @@ Key features:
25
25
  - Configurable delay to prevent excessive uploads during active editing
26
26
  - Supports both command-line arguments and config file for flexibility
27
27
  - Handles recursive directory watching
28
+ - User-friendly Terminal User Interface (TUI)
28
29
 
29
30
  Use ClaudeSync if you're working with Claude.ai Projects and want to keep your project's knowledge base updated with your local files without manual uploads.
30
31
 
@@ -47,10 +48,10 @@ You have two options for configuring ClaudeSync:
47
48
 
48
49
  ### Option 1: Command-line Arguments
49
50
 
50
- Provide your user ID and project ID directly in the command:
51
+ Provide your session key and watch directory directly in the command:
51
52
 
52
53
  ```bash
53
- claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --user-id your-user-id --project-id your-project-id
54
+ claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
54
55
  ```
55
56
 
56
57
  ### Option 2: Configuration File
@@ -64,7 +65,7 @@ Create a `config.json` file in your working directory:
64
65
  }
65
66
  ```
66
67
 
67
- Then run ClaudeSync without the `--user-id` and `--project-id` arguments:
68
+ Then run ClaudeSync:
68
69
 
69
70
  ```bash
70
71
  claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
@@ -81,57 +82,38 @@ claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
81
82
  ### Parameters
82
83
 
83
84
  - `--session-key`: Your Claude.ai session key (required)
84
- - `--watch-dir`: Directory to watch for changes (required)
85
+ - `--watch-dir`: Directory to watch for changes (default: current directory)
85
86
  - `--user-id`: User ID for Claude API (optional if in config.json)
86
87
  - `--project-id`: Project ID for Claude API (optional if in config.json)
87
- - `--delete-all`: Delete all project documents
88
88
  - `--delay`: Delay in seconds before uploading (default: 5)
89
89
 
90
90
  ### Examples
91
91
 
92
- Watch a directory and sync changes:
93
- ```bash
94
- claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --user-id your-user-id --project-id your-project-id
95
- ```
96
-
97
- Delete all documents in the project:
92
+ Watch the current directory and sync changes:
98
93
  ```bash
99
- claudesync --session-key YOUR_SESSION_KEY --delete-all --user-id your-user-id --project-id your-project-id
94
+ claudesync --session-key YOUR_SESSION_KEY
100
95
  ```
101
96
 
102
- Set a custom delay for file uploads:
97
+ Watch a specific directory with a custom delay:
103
98
  ```bash
104
99
  claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --delay 10
105
100
  ```
106
101
 
107
- ## Development
108
-
109
- To contribute to ClaudeSync:
110
-
111
- 1. Clone the repository:
112
- ```
113
- git clone https://github.com/yourusername/claudesync.git
114
- ```
115
- 2. Install development dependencies:
116
- ```
117
- pip install -r requirements.txt
118
- ```
119
- 3. Make your changes and test locally:
120
- ```
121
- python -m build
122
- pip install -e .
123
- ```
124
- 4. Run the locally installed version:
125
- ```
126
- claudesync --help
127
- ```
128
- 5. Remember to clean up after testing:
129
- ```
130
- pip uninstall claudesync
131
- rm -rf dist build **/*.egg-info
132
- ```
133
- 6. Submit a pull request with your changes.
102
+ ## Terminal User Interface (TUI)
103
+
104
+ ClaudeSync now features a user-friendly TUI that displays:
105
+
106
+ - Watched directory
107
+ - Upload delay
108
+ - User ID
109
+ - Project ID
110
+ - Recent activity log
111
+
112
+ To exit the TUI, press 'q'.
113
+
114
+ ## Contributing
134
115
 
116
+ We welcome contributions to ClaudeSync! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information on how to get started.
135
117
 
136
118
  ## License
137
119
 
@@ -9,6 +9,7 @@ Key features:
9
9
  - Configurable delay to prevent excessive uploads during active editing
10
10
  - Supports both command-line arguments and config file for flexibility
11
11
  - Handles recursive directory watching
12
+ - User-friendly Terminal User Interface (TUI)
12
13
 
13
14
  Use ClaudeSync if you're working with Claude.ai Projects and want to keep your project's knowledge base updated with your local files without manual uploads.
14
15
 
@@ -31,10 +32,10 @@ You have two options for configuring ClaudeSync:
31
32
 
32
33
  ### Option 1: Command-line Arguments
33
34
 
34
- Provide your user ID and project ID directly in the command:
35
+ Provide your session key and watch directory directly in the command:
35
36
 
36
37
  ```bash
37
- claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --user-id your-user-id --project-id your-project-id
38
+ claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
38
39
  ```
39
40
 
40
41
  ### Option 2: Configuration File
@@ -48,7 +49,7 @@ Create a `config.json` file in your working directory:
48
49
  }
49
50
  ```
50
51
 
51
- Then run ClaudeSync without the `--user-id` and `--project-id` arguments:
52
+ Then run ClaudeSync:
52
53
 
53
54
  ```bash
54
55
  claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
@@ -65,57 +66,38 @@ claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
65
66
  ### Parameters
66
67
 
67
68
  - `--session-key`: Your Claude.ai session key (required)
68
- - `--watch-dir`: Directory to watch for changes (required)
69
+ - `--watch-dir`: Directory to watch for changes (default: current directory)
69
70
  - `--user-id`: User ID for Claude API (optional if in config.json)
70
71
  - `--project-id`: Project ID for Claude API (optional if in config.json)
71
- - `--delete-all`: Delete all project documents
72
72
  - `--delay`: Delay in seconds before uploading (default: 5)
73
73
 
74
74
  ### Examples
75
75
 
76
- Watch a directory and sync changes:
77
- ```bash
78
- claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --user-id your-user-id --project-id your-project-id
79
- ```
80
-
81
- Delete all documents in the project:
76
+ Watch the current directory and sync changes:
82
77
  ```bash
83
- claudesync --session-key YOUR_SESSION_KEY --delete-all --user-id your-user-id --project-id your-project-id
78
+ claudesync --session-key YOUR_SESSION_KEY
84
79
  ```
85
80
 
86
- Set a custom delay for file uploads:
81
+ Watch a specific directory with a custom delay:
87
82
  ```bash
88
83
  claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --delay 10
89
84
  ```
90
85
 
91
- ## Development
92
-
93
- To contribute to ClaudeSync:
94
-
95
- 1. Clone the repository:
96
- ```
97
- git clone https://github.com/yourusername/claudesync.git
98
- ```
99
- 2. Install development dependencies:
100
- ```
101
- pip install -r requirements.txt
102
- ```
103
- 3. Make your changes and test locally:
104
- ```
105
- python -m build
106
- pip install -e .
107
- ```
108
- 4. Run the locally installed version:
109
- ```
110
- claudesync --help
111
- ```
112
- 5. Remember to clean up after testing:
113
- ```
114
- pip uninstall claudesync
115
- rm -rf dist build **/*.egg-info
116
- ```
117
- 6. Submit a pull request with your changes.
86
+ ## Terminal User Interface (TUI)
87
+
88
+ ClaudeSync now features a user-friendly TUI that displays:
89
+
90
+ - Watched directory
91
+ - Upload delay
92
+ - User ID
93
+ - Project ID
94
+ - Recent activity log
95
+
96
+ To exit the TUI, press 'q'.
97
+
98
+ ## Contributing
118
99
 
100
+ We welcome contributions to ClaudeSync! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information on how to get started.
119
101
 
120
102
  ## License
121
103
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claudesync"
7
- version = "0.1.2"
7
+ version = "0.2.0"
8
8
  authors = [
9
9
  {name = "Jahziah Wagner", email = "jahziah.wagner+pypi@gmail.com"},
10
10
  ]
@@ -29,4 +29,4 @@ dependencies = [
29
29
  claudesync = "claudesync.claudesync:main"
30
30
 
31
31
  [tool.setuptools.packages.find]
32
- where = ["src"]
32
+ where = ["src"]
@@ -0,0 +1,6 @@
1
+ from .main import main
2
+ from .file_handler import FileUploadHandler
3
+ from .api_utils import fetch_user_id, fetch_projects, select_project, create_project
4
+ from .manual_auth import get_session_key
5
+
6
+ __all__ = ['main', 'FileUploadHandler', 'fetch_user_id', 'fetch_projects', 'select_project', 'create_project', 'get_session_key']
@@ -0,0 +1,94 @@
1
+ import sys
2
+ import requests
3
+
4
+ def fetch_user_id(session_key):
5
+ url = "https://claude.ai/api/bootstrap"
6
+ headers = {
7
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
8
+ 'Accept': '*/*',
9
+ 'Accept-Language': 'en-US,en;q=0.5',
10
+ 'Referer': 'https://claude.ai/',
11
+ 'Origin': 'https://claude.ai',
12
+ 'Connection': 'keep-alive'
13
+ }
14
+ cookies = {'sessionKey': session_key}
15
+
16
+ try:
17
+ response = requests.get(url, headers=headers, cookies=cookies)
18
+ response.raise_for_status()
19
+ data = response.json()
20
+ return data['account']['memberships'][0]['organization']['uuid']
21
+ except (requests.RequestException, KeyError, IndexError) as e:
22
+ print(f"Error fetching user ID: {str(e)}")
23
+ sys.exit(1)
24
+
25
+ def fetch_projects(user_id, session_key):
26
+ url = f"https://claude.ai/api/organizations/{user_id}/projects"
27
+ headers = {
28
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
29
+ 'Accept': '*/*',
30
+ 'Accept-Language': 'en-US,en;q=0.5',
31
+ 'Referer': 'https://claude.ai/',
32
+ 'Origin': 'https://claude.ai',
33
+ 'Connection': 'keep-alive'
34
+ }
35
+ cookies = {'sessionKey': session_key, 'lastActiveOrg': user_id}
36
+
37
+ try:
38
+ response = requests.get(url, headers=headers, cookies=cookies)
39
+ response.raise_for_status()
40
+ return response.json()
41
+ except requests.RequestException as e:
42
+ print(f"Error fetching projects: {str(e)}")
43
+ sys.exit(1)
44
+
45
+ def select_project(projects, user_id, session_key):
46
+ # Filter out archived projects
47
+ active_projects = [project for project in projects if project.get('archived_at') is None]
48
+
49
+ print("Available projects:")
50
+ for i, project in enumerate(active_projects, 1):
51
+ print(f"{i}. {project['name']} (ID: {project['uuid']})")
52
+ print(f"{len(active_projects) + 1}. Create new project")
53
+
54
+ while True:
55
+ try:
56
+ choice = int(input("Enter the number of the project you want to use (or create new): "))
57
+ if 1 <= choice <= len(active_projects):
58
+ return active_projects[choice - 1]['uuid']
59
+ elif choice == len(active_projects) + 1:
60
+ name = input("Enter the name for the new project: ")
61
+ description = input("Enter a description for the new project (optional): ")
62
+ new_project = create_project(user_id, session_key, name, description)
63
+ print(f"New project created: {new_project['name']} (ID: {new_project['uuid']})")
64
+ return new_project['uuid']
65
+ else:
66
+ print("Invalid choice. Please try again.")
67
+ except ValueError:
68
+ print("Invalid input. Please enter a number.")
69
+
70
+ def create_project(user_id, session_key, name, description):
71
+ url = f"https://claude.ai/api/organizations/{user_id}/projects"
72
+ headers = {
73
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
74
+ 'Accept': '*/*',
75
+ 'Accept-Language': 'en-US,en;q=0.5',
76
+ 'Referer': 'https://claude.ai/',
77
+ 'Origin': 'https://claude.ai',
78
+ 'Connection': 'keep-alive',
79
+ 'Content-Type': 'application/json'
80
+ }
81
+ cookies = {'sessionKey': session_key, 'lastActiveOrg': user_id}
82
+ payload = {
83
+ "name": name,
84
+ "description": description,
85
+ "is_private": True
86
+ }
87
+
88
+ try:
89
+ response = requests.post(url, headers=headers, cookies=cookies, json=payload)
90
+ response.raise_for_status()
91
+ return response.json()
92
+ except requests.RequestException as e:
93
+ print(f"Error creating project: {str(e)}")
94
+ sys.exit(1)
@@ -0,0 +1,21 @@
1
+ from threading import Timer
2
+
3
+ class DebounceHandler:
4
+ def __init__(self, delay):
5
+ self.delay = delay
6
+ self.timer = None
7
+ self.queued_files = set()
8
+
9
+ def debounce(self, func, file_path):
10
+ self.queued_files.add(file_path)
11
+
12
+ def debounced_func():
13
+ files_to_process = self.queued_files.copy()
14
+ self.queued_files.clear()
15
+ for file in files_to_process:
16
+ func(file)
17
+
18
+ if self.timer is not None:
19
+ self.timer.cancel()
20
+ self.timer = Timer(self.delay, debounced_func)
21
+ self.timer.start()
@@ -0,0 +1,92 @@
1
+ import os
2
+ from watchdog.events import FileSystemEventHandler
3
+ from debounce import DebounceHandler
4
+ from gitignore_utils import load_gitignore, should_ignore
5
+ import requests
6
+
7
+ class FileUploadHandler(FileSystemEventHandler):
8
+ def __init__(self, api_endpoint, session_key, base_path, delay=5):
9
+ self.api_endpoint = api_endpoint
10
+ self.session_key = session_key
11
+ self.base_path = os.path.abspath(base_path)
12
+ self.headers = {
13
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
14
+ 'Accept': '*/*', 'Accept-Language': 'en-US,en;q=0.5', 'Content-Type': 'application/json',
15
+ 'Referer': f'https://claude.ai/project/{api_endpoint.split("/")[-2]}',
16
+ 'Origin': 'https://claude.ai', 'Connection': 'keep-alive'
17
+ }
18
+ self.cookies = {'sessionKey': session_key, 'lastActiveOrg': api_endpoint.split("/")[4]}
19
+ self.debouncer = DebounceHandler(delay)
20
+ self.gitignore = load_gitignore(self.base_path)
21
+ self.log_callback = None
22
+
23
+ def log(self, message):
24
+ if self.log_callback:
25
+ self.log_callback(message)
26
+ else:
27
+ print(message)
28
+
29
+ def on_modified(self, event):
30
+ if not event.is_directory and not self.should_ignore_file(event.src_path):
31
+ self.debouncer.debounce(self.upload_file, event.src_path)
32
+
33
+ def on_created(self, event):
34
+ if not event.is_directory and not self.should_ignore_file(event.src_path):
35
+ self.debouncer.debounce(self.upload_file, event.src_path)
36
+
37
+ def should_ignore_file(self, file_path):
38
+ # Check if the file is in a hidden directory
39
+ rel_path = os.path.relpath(file_path, self.base_path)
40
+ path_parts = rel_path.split(os.path.sep)
41
+ if any(part.startswith('.') for part in path_parts[:-1]):
42
+ return True
43
+
44
+ # Check if the file should be ignored based on .gitignore
45
+ return should_ignore(self.gitignore, file_path, self.base_path)
46
+
47
+ def api_request(self, method, url, **kwargs):
48
+ try:
49
+ response = requests.request(method, url, headers=self.headers, cookies=self.cookies, **kwargs)
50
+ response.raise_for_status()
51
+ return response.json() if response.text else None
52
+ except requests.RequestException as e:
53
+ print(f"API request error: {str(e)}")
54
+ return None
55
+
56
+ def get_documents(self):
57
+ return self.api_request('GET', self.api_endpoint) or []
58
+
59
+ def delete_document(self, uuid):
60
+ if self.api_request('DELETE', f"{self.api_endpoint}/{uuid}"):
61
+ print(f"Deleted document: {uuid}")
62
+
63
+ def delete_all_documents(self):
64
+ for doc in self.get_documents():
65
+ self.delete_document(doc['uuid'])
66
+ print("All documents deleted.")
67
+
68
+ def upload_file(self, file_path):
69
+ if not os.path.isfile(file_path) or self.should_ignore_file(file_path):
70
+ return
71
+ if os.path.getsize(file_path) == 0:
72
+ print(f"Skipping empty file: {file_path}")
73
+ return
74
+ try:
75
+ with open(file_path, 'r', encoding='utf-8') as file:
76
+ content = file.read()
77
+ if not content.strip():
78
+ print(f"Skipping file with only whitespace: {file_path}")
79
+ return
80
+
81
+ rel_path = os.path.relpath(file_path, self.base_path)
82
+ file_name = rel_path.replace(os.path.sep, '/')
83
+
84
+ for doc in self.get_documents():
85
+ if doc['file_name'] == file_name:
86
+ self.delete_document(doc['uuid'])
87
+ payload = {"file_name": file_name, "content": content}
88
+ if self.api_request('POST', self.api_endpoint, json=payload):
89
+ self.log(f"Uploaded: {file_name}")
90
+ except Exception as e:
91
+ print(f"Error processing file {file_path}: {str(e)}")
92
+
@@ -0,0 +1,31 @@
1
+ import os
2
+ import pathspec
3
+
4
+ def load_gitignore(base_path):
5
+ patterns = []
6
+ current_dir = base_path
7
+ while True:
8
+ gitignore_path = os.path.join(current_dir, '.gitignore')
9
+ if os.path.exists(gitignore_path):
10
+ with open(gitignore_path, 'r') as f:
11
+ patterns.extend(f.read().splitlines())
12
+
13
+ if os.path.exists(os.path.join(current_dir, '.git')):
14
+ # Stop if we've reached the root of the Git repository
15
+ break
16
+
17
+ parent_dir = os.path.dirname(current_dir)
18
+ if parent_dir == current_dir or parent_dir == base_path:
19
+ # Stop if we've reached the filesystem root or the base watched directory
20
+ break
21
+ current_dir = parent_dir
22
+
23
+ if patterns:
24
+ return pathspec.PathSpec.from_lines('gitwildmatch', patterns)
25
+ return None
26
+
27
+ def should_ignore(gitignore, file_path, base_path):
28
+ if gitignore is None:
29
+ return False
30
+ rel_path = os.path.relpath(file_path, base_path)
31
+ return gitignore.match_file(rel_path)
@@ -0,0 +1,100 @@
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()
@@ -0,0 +1,30 @@
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: claudesync
3
- Version: 0.1.2
3
+ Version: 0.2.0
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
@@ -25,6 +25,7 @@ Key features:
25
25
  - Configurable delay to prevent excessive uploads during active editing
26
26
  - Supports both command-line arguments and config file for flexibility
27
27
  - Handles recursive directory watching
28
+ - User-friendly Terminal User Interface (TUI)
28
29
 
29
30
  Use ClaudeSync if you're working with Claude.ai Projects and want to keep your project's knowledge base updated with your local files without manual uploads.
30
31
 
@@ -47,10 +48,10 @@ You have two options for configuring ClaudeSync:
47
48
 
48
49
  ### Option 1: Command-line Arguments
49
50
 
50
- Provide your user ID and project ID directly in the command:
51
+ Provide your session key and watch directory directly in the command:
51
52
 
52
53
  ```bash
53
- claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --user-id your-user-id --project-id your-project-id
54
+ claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
54
55
  ```
55
56
 
56
57
  ### Option 2: Configuration File
@@ -64,7 +65,7 @@ Create a `config.json` file in your working directory:
64
65
  }
65
66
  ```
66
67
 
67
- Then run ClaudeSync without the `--user-id` and `--project-id` arguments:
68
+ Then run ClaudeSync:
68
69
 
69
70
  ```bash
70
71
  claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
@@ -81,57 +82,38 @@ claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch
81
82
  ### Parameters
82
83
 
83
84
  - `--session-key`: Your Claude.ai session key (required)
84
- - `--watch-dir`: Directory to watch for changes (required)
85
+ - `--watch-dir`: Directory to watch for changes (default: current directory)
85
86
  - `--user-id`: User ID for Claude API (optional if in config.json)
86
87
  - `--project-id`: Project ID for Claude API (optional if in config.json)
87
- - `--delete-all`: Delete all project documents
88
88
  - `--delay`: Delay in seconds before uploading (default: 5)
89
89
 
90
90
  ### Examples
91
91
 
92
- Watch a directory and sync changes:
93
- ```bash
94
- claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --user-id your-user-id --project-id your-project-id
95
- ```
96
-
97
- Delete all documents in the project:
92
+ Watch the current directory and sync changes:
98
93
  ```bash
99
- claudesync --session-key YOUR_SESSION_KEY --delete-all --user-id your-user-id --project-id your-project-id
94
+ claudesync --session-key YOUR_SESSION_KEY
100
95
  ```
101
96
 
102
- Set a custom delay for file uploads:
97
+ Watch a specific directory with a custom delay:
103
98
  ```bash
104
99
  claudesync --session-key YOUR_SESSION_KEY --watch-dir /path/to/watch --delay 10
105
100
  ```
106
101
 
107
- ## Development
108
-
109
- To contribute to ClaudeSync:
110
-
111
- 1. Clone the repository:
112
- ```
113
- git clone https://github.com/yourusername/claudesync.git
114
- ```
115
- 2. Install development dependencies:
116
- ```
117
- pip install -r requirements.txt
118
- ```
119
- 3. Make your changes and test locally:
120
- ```
121
- python -m build
122
- pip install -e .
123
- ```
124
- 4. Run the locally installed version:
125
- ```
126
- claudesync --help
127
- ```
128
- 5. Remember to clean up after testing:
129
- ```
130
- pip uninstall claudesync
131
- rm -rf dist build **/*.egg-info
132
- ```
133
- 6. Submit a pull request with your changes.
102
+ ## Terminal User Interface (TUI)
103
+
104
+ ClaudeSync now features a user-friendly TUI that displays:
105
+
106
+ - Watched directory
107
+ - Upload delay
108
+ - User ID
109
+ - Project ID
110
+ - Recent activity log
111
+
112
+ To exit the TUI, press 'q'.
113
+
114
+ ## Contributing
134
115
 
116
+ We welcome contributions to ClaudeSync! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information on how to get started.
135
117
 
136
118
  ## License
137
119
 
@@ -3,7 +3,12 @@ README.md
3
3
  pyproject.toml
4
4
  setup.py
5
5
  src/claudesync/__init__.py
6
- src/claudesync/claudesync.py
6
+ src/claudesync/api_utils.py
7
+ src/claudesync/debounce.py
8
+ src/claudesync/file_handler.py
9
+ src/claudesync/gitignore_utils.py
10
+ src/claudesync/main.py
11
+ src/claudesync/manual_auth.py
7
12
  src/claudesync.egg-info/PKG-INFO
8
13
  src/claudesync.egg-info/SOURCES.txt
9
14
  src/claudesync.egg-info/dependency_links.txt
@@ -1 +0,0 @@
1
- from .claudesync import main
@@ -1,157 +0,0 @@
1
- import time
2
- import os
3
- import argparse
4
- import sys
5
- import json
6
- from watchdog.observers import Observer
7
- from watchdog.events import FileSystemEventHandler
8
- import requests
9
- from threading import Timer
10
-
11
- class DebounceHandler:
12
- def __init__(self, delay):
13
- self.delay = delay
14
- self.timer = None
15
-
16
- def debounce(self, func, *args):
17
- def debounced_func():
18
- self.timer = None
19
- func(*args)
20
-
21
- if self.timer is not None:
22
- self.timer.cancel()
23
- self.timer = Timer(self.delay, debounced_func)
24
- self.timer.start()
25
-
26
- class FileUploadHandler(FileSystemEventHandler):
27
- def __init__(self, api_endpoint, session_key, base_path, delay=5):
28
- self.api_endpoint = api_endpoint
29
- self.session_key = session_key
30
- self.base_path = base_path
31
- self.headers = {
32
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
33
- 'Accept': '*/*', 'Accept-Language': 'en-US,en;q=0.5', 'Content-Type': 'application/json',
34
- 'Referer': f'https://claude.ai/project/{api_endpoint.split("/")[-2]}',
35
- 'Origin': 'https://claude.ai', 'Connection': 'keep-alive'
36
- }
37
- self.cookies = {'sessionKey': session_key, 'lastActiveOrg': api_endpoint.split("/")[4]}
38
- self.debouncer = DebounceHandler(delay)
39
-
40
- def on_modified(self, event):
41
- if not event.is_directory:
42
- self.debouncer.debounce(self.upload_file, event.src_path)
43
-
44
- def on_created(self, event):
45
- if not event.is_directory:
46
- self.debouncer.debounce(self.upload_file, event.src_path)
47
-
48
- def api_request(self, method, url, **kwargs):
49
- try:
50
- response = requests.request(method, url, headers=self.headers, cookies=self.cookies, **kwargs)
51
- response.raise_for_status()
52
- return response.json() if response.text else None
53
- except requests.RequestException as e:
54
- print(f"API request error: {str(e)}")
55
- return None
56
-
57
- def get_documents(self):
58
- return self.api_request('GET', self.api_endpoint) or []
59
-
60
- def delete_document(self, uuid):
61
- if self.api_request('DELETE', f"{self.api_endpoint}/{uuid}"):
62
- print(f"Deleted document: {uuid}")
63
-
64
- def delete_all_documents(self):
65
- for doc in self.get_documents():
66
- self.delete_document(doc['uuid'])
67
- print("All documents deleted.")
68
-
69
- def upload_file(self, file_path):
70
- if not os.path.isfile(file_path):
71
- return
72
- if os.path.getsize(file_path) == 0:
73
- print(f"Skipping empty file: {file_path}")
74
- return
75
- try:
76
- with open(file_path, 'r', encoding='utf-8') as file:
77
- content = file.read()
78
- if not content.strip():
79
- print(f"Skipping file with only whitespace: {file_path}")
80
- return
81
-
82
- rel_path = os.path.relpath(file_path, self.base_path)
83
- file_name = rel_path.replace(os.path.sep, '/')
84
-
85
- for doc in self.get_documents():
86
- if doc['file_name'] == file_name:
87
- self.delete_document(doc['uuid'])
88
- payload = {"file_name": file_name, "content": content}
89
- if self.api_request('POST', self.api_endpoint, json=payload):
90
- print(f"Uploaded: {file_name}")
91
- except Exception as e:
92
- print(f"Error processing file {file_path}: {str(e)}")
93
-
94
- def watch_directory(path, api_endpoint, session_key, delay):
95
- observer = Observer()
96
- handler = FileUploadHandler(api_endpoint, session_key, path, delay)
97
- observer.schedule(handler, path, recursive=True)
98
- observer.start()
99
- try:
100
- while True:
101
- time.sleep(1)
102
- except KeyboardInterrupt:
103
- observer.stop()
104
- observer.join()
105
-
106
- def load_config():
107
- try:
108
- with open('config.json', 'r') as f:
109
- return json.load(f)
110
- except FileNotFoundError:
111
- return {}
112
- except json.JSONDecodeError:
113
- print("Invalid JSON in config.json. Please check the file format.")
114
- sys.exit(1)
115
-
116
- def main():
117
- config = load_config()
118
-
119
- parser = argparse.ArgumentParser(description="Sync local files with Claude.ai projects.")
120
- parser.add_argument("--session-key", required=True, help="Session key for authentication")
121
- parser.add_argument("--watch-dir", required=True, help="Directory to watch for changes")
122
- parser.add_argument("--user-id", help="User ID for Claude API")
123
- parser.add_argument("--project-id", help="Project ID for Claude API")
124
- parser.add_argument("--delete-all", action="store_true", help="Delete all documents in the project")
125
- parser.add_argument("--delay", type=int, default=5, help="Delay in seconds before uploading (default: 5)")
126
- args = parser.parse_args()
127
-
128
- user_id = args.user_id or config.get('user_id')
129
- project_id = args.project_id or config.get('project_id')
130
-
131
- if not user_id or not project_id:
132
- print("Error: user_id and project_id must be provided either in config.json or as command-line arguments.")
133
- sys.exit(1)
134
-
135
- api_endpoint = f"https://claude.ai/api/organizations/{user_id}/projects/{project_id}/docs"
136
-
137
- handler = FileUploadHandler(api_endpoint, args.session_key, args.watch_dir, args.delay)
138
-
139
- if args.delete_all:
140
- handler.delete_all_documents()
141
- print("All documents deleted.")
142
- sys.exit(0)
143
- else:
144
- print(f"Watching directory: {args.watch_dir}")
145
- print(f"Upload delay: {args.delay} seconds")
146
- observer = Observer()
147
- observer.schedule(handler, args.watch_dir, recursive=True)
148
- observer.start()
149
- try:
150
- while True:
151
- pass
152
- except KeyboardInterrupt:
153
- observer.stop()
154
- observer.join()
155
-
156
- if __name__ == "__main__":
157
- main()
File without changes
File without changes
File without changes