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.
- {claudesync-0.1.2/src/claudesync.egg-info → claudesync-0.2.0}/PKG-INFO +23 -41
- {claudesync-0.1.2 → claudesync-0.2.0}/README.md +22 -40
- {claudesync-0.1.2 → claudesync-0.2.0}/pyproject.toml +2 -2
- claudesync-0.2.0/src/claudesync/__init__.py +6 -0
- claudesync-0.2.0/src/claudesync/api_utils.py +94 -0
- claudesync-0.2.0/src/claudesync/debounce.py +21 -0
- claudesync-0.2.0/src/claudesync/file_handler.py +92 -0
- claudesync-0.2.0/src/claudesync/gitignore_utils.py +31 -0
- claudesync-0.2.0/src/claudesync/main.py +100 -0
- claudesync-0.2.0/src/claudesync/manual_auth.py +30 -0
- {claudesync-0.1.2 → claudesync-0.2.0/src/claudesync.egg-info}/PKG-INFO +23 -41
- {claudesync-0.1.2 → claudesync-0.2.0}/src/claudesync.egg-info/SOURCES.txt +6 -1
- claudesync-0.1.2/src/claudesync/__init__.py +0 -1
- claudesync-0.1.2/src/claudesync/claudesync.py +0 -157
- {claudesync-0.1.2 → claudesync-0.2.0}/LICENSE +0 -0
- {claudesync-0.1.2 → claudesync-0.2.0}/setup.cfg +0 -0
- {claudesync-0.1.2 → claudesync-0.2.0}/setup.py +0 -0
- {claudesync-0.1.2 → claudesync-0.2.0}/src/claudesync.egg-info/dependency_links.txt +0 -0
- {claudesync-0.1.2 → claudesync-0.2.0}/src/claudesync.egg-info/entry_points.txt +0 -0
- {claudesync-0.1.2 → claudesync-0.2.0}/src/claudesync.egg-info/requires.txt +0 -0
- {claudesync-0.1.2 → claudesync-0.2.0}/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.
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
94
|
+
claudesync --session-key YOUR_SESSION_KEY
|
|
100
95
|
```
|
|
101
96
|
|
|
102
|
-
|
|
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
|
-
##
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
78
|
+
claudesync --session-key YOUR_SESSION_KEY
|
|
84
79
|
```
|
|
85
80
|
|
|
86
|
-
|
|
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
|
-
##
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
94
|
+
claudesync --session-key YOUR_SESSION_KEY
|
|
100
95
|
```
|
|
101
96
|
|
|
102
|
-
|
|
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
|
-
##
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|