claudesync 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {claudesync-0.3.1/src/claudesync.egg-info → claudesync-0.3.2}/PKG-INFO +1 -1
  2. {claudesync-0.3.1 → claudesync-0.3.2}/pyproject.toml +1 -1
  3. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/cli/api.py +0 -2
  4. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/cli/sync.py +6 -4
  5. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/config_manager.py +2 -2
  6. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/utils.py +61 -47
  7. {claudesync-0.3.1 → claudesync-0.3.2/src/claudesync.egg-info}/PKG-INFO +1 -1
  8. {claudesync-0.3.1 → claudesync-0.3.2}/tests/test_utils.py +4 -2
  9. {claudesync-0.3.1 → claudesync-0.3.2}/LICENSE +0 -0
  10. {claudesync-0.3.1 → claudesync-0.3.2}/README.md +0 -0
  11. {claudesync-0.3.1 → claudesync-0.3.2}/setup.cfg +0 -0
  12. {claudesync-0.3.1 → claudesync-0.3.2}/setup.py +0 -0
  13. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/__init__.py +0 -0
  14. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/cli/__init__.py +0 -0
  15. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/cli/main.py +0 -0
  16. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/cli/organization.py +0 -0
  17. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/cli/project.py +0 -0
  18. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/exceptions.py +0 -0
  19. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/provider_factory.py +0 -0
  20. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/providers/__init__.py +0 -0
  21. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync/providers/claude_ai.py +0 -0
  22. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync.egg-info/SOURCES.txt +0 -0
  23. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync.egg-info/dependency_links.txt +0 -0
  24. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync.egg-info/entry_points.txt +0 -0
  25. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync.egg-info/requires.txt +0 -0
  26. {claudesync-0.3.1 → claudesync-0.3.2}/src/claudesync.egg-info/top_level.txt +0 -0
  27. {claudesync-0.3.1 → claudesync-0.3.2}/tests/test_config_manager.py +0 -0
  28. {claudesync-0.3.1 → claudesync-0.3.2}/tests/test_provider_factory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: claudesync
3
- Version: 0.3.1
3
+ Version: 0.3.2
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
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claudesync"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  authors = [
9
9
  {name = "Jahziah Wagner", email = "jahziah.wagner+pypi@gmail.com"},
10
10
  ]
@@ -64,5 +64,3 @@ def max_filesize(config, size):
64
64
  return
65
65
  config.set("max_file_size", size)
66
66
  click.echo(f"Maximum file size set to {size} bytes.")
67
-
68
-
@@ -55,7 +55,7 @@ def sync(config):
55
55
  local_files = get_local_files(local_path)
56
56
 
57
57
  # Track remote files to delete
58
- remote_files_to_delete = set(rf['file_name'] for rf in remote_files)
58
+ remote_files_to_delete = set(rf["file_name"] for rf in remote_files)
59
59
 
60
60
  for local_file, local_checksum in local_files.items():
61
61
  remote_file = next(
@@ -69,7 +69,7 @@ def sync(config):
69
69
  active_organization_id, active_project_id, remote_file["uuid"]
70
70
  )
71
71
  with open(
72
- os.path.join(local_path, local_file), "r", encoding="utf-8"
72
+ os.path.join(local_path, local_file), "r", encoding="utf-8"
73
73
  ) as file:
74
74
  content = file.read()
75
75
  provider.upload_file(
@@ -80,7 +80,7 @@ def sync(config):
80
80
  else:
81
81
  click.echo(f"Uploading new file {local_file} to remote...")
82
82
  with open(
83
- os.path.join(local_path, local_file), "r", encoding="utf-8"
83
+ os.path.join(local_path, local_file), "r", encoding="utf-8"
84
84
  ) as file:
85
85
  content = file.read()
86
86
  provider.upload_file(
@@ -91,7 +91,9 @@ def sync(config):
91
91
  # Delete remote files that no longer exist locally
92
92
  for file_to_delete in remote_files_to_delete:
93
93
  click.echo(f"Deleting {file_to_delete} from remote...")
94
- remote_file = next(rf for rf in remote_files if rf["file_name"] == file_to_delete)
94
+ remote_file = next(
95
+ rf for rf in remote_files if rf["file_name"] == file_to_delete
96
+ )
95
97
  provider.delete_file(
96
98
  active_organization_id, active_project_id, remote_file["uuid"]
97
99
  )
@@ -14,7 +14,7 @@ class ConfigManager:
14
14
  return {
15
15
  "log_level": "INFO",
16
16
  "upload_delay": 0.5,
17
- "max_file_size": 32 * 1024 # Default 32 KB
17
+ "max_file_size": 32 * 1024, # Default 32 KB
18
18
  }
19
19
  with open(self.config_file, "r") as f:
20
20
  config = json.load(f)
@@ -35,4 +35,4 @@ class ConfigManager:
35
35
 
36
36
  def set(self, key, value):
37
37
  self.config[key] = value
38
- self._save_config()
38
+ self._save_config()
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import hashlib
3
- import mimetypes
4
3
  from functools import wraps
5
4
 
6
5
  import click
@@ -15,64 +14,79 @@ logger = logging.getLogger(__name__)
15
14
 
16
15
  config_manager = ConfigManager()
17
16
 
17
+
18
18
  def calculate_checksum(content):
19
19
  normalized_content = content.replace("\r\n", "\n").replace("\r", "\n").strip()
20
20
  return hashlib.md5(normalized_content.encode("utf-8")).hexdigest()
21
21
 
22
22
 
23
23
  def load_gitignore(base_path):
24
- patterns = []
25
- current_dir = base_path
26
- while True:
27
- gitignore_path = os.path.join(current_dir, ".gitignore")
28
- if os.path.exists(gitignore_path):
29
- with open(gitignore_path, "r") as f:
30
- patterns.extend(f.read().splitlines())
31
-
32
- if os.path.exists(os.path.join(current_dir, ".git")):
33
- break # Stop if we've reached the root of the Git repository
34
-
35
- parent_dir = os.path.dirname(current_dir)
36
- if parent_dir == current_dir or parent_dir == base_path:
37
- break # Stop if we've reached the filesystem root or the base watched directory
38
- current_dir = parent_dir
39
-
40
- return pathspec.PathSpec.from_lines("gitwildmatch", patterns) if patterns else None
41
-
42
- def should_ignore(gitignore, local_path):
43
- # Check file type
44
- mime_type, _ = mimetypes.guess_type(local_path)
45
- if mime_type and not mime_type.startswith("text/"):
46
- return True
47
- # Check if .git dir
48
- if ".git" in local_path.split(os.sep):
49
- return True
50
- # Check if temporary editor file
51
- if local_path.endswith("~"):
52
- return True
53
- # Check if too big
54
- max_file_size = config_manager.get("max_file_size", 32 * 1024) # Default to 32 KB if not set
55
- if os.path.getsize(local_path) > max_file_size:
56
- return True
57
- # Check .gitignore
58
- return gitignore.match_file(local_path) if gitignore else False
24
+ gitignore_path = os.path.join(base_path, ".gitignore")
25
+ if os.path.exists(gitignore_path):
26
+ with open(gitignore_path, "r") as f:
27
+ return pathspec.PathSpec.from_lines("gitwildmatch", f)
28
+ return None
29
+
30
+
31
+ def is_text_file(file_path, sample_size=8192):
32
+ try:
33
+ with open(file_path, "rb") as file:
34
+ return b"\x00" not in file.read(sample_size)
35
+ except IOError:
36
+ return False
37
+
38
+
39
+ def calculate_checksum(content):
40
+ return hashlib.md5(content.encode("utf-8")).hexdigest()
59
41
 
60
42
 
61
43
  def get_local_files(local_path):
62
44
  gitignore = load_gitignore(local_path)
63
45
  files = {}
64
- for root, _, filenames in os.walk(local_path):
46
+
47
+ # List of directories to exclude
48
+ exclude_dirs = {".git", ".svn", ".hg", ".bzr", "_darcs", "CVS"}
49
+
50
+ for root, dirs, filenames in os.walk(local_path):
51
+ # Remove excluded directories
52
+ dirs[:] = [d for d in dirs if d not in exclude_dirs]
53
+
54
+ rel_root = os.path.relpath(root, local_path)
55
+ if rel_root == ".":
56
+ rel_root = ""
57
+
65
58
  for filename in filenames:
66
- file_path = os.path.join(root, filename)
67
- if not should_ignore(gitignore, file_path):
68
- rel_path = os.path.relpath(file_path, local_path)
69
- try:
70
- with open(file_path, "r", encoding="utf-8") as file:
71
- content = file.read()
72
- files[rel_path] = calculate_checksum(content)
73
- except Exception as e:
74
- logger.error(f"Error reading file {file_path}: {str(e)}")
75
- continue
59
+ rel_path = os.path.join(rel_root, filename)
60
+ full_path = os.path.join(root, filename)
61
+
62
+ # Skip files larger than 200KB
63
+ max_file_size = config_manager.get("max_file_size", 32 * 1024)
64
+ if os.path.getsize(full_path) > max_file_size:
65
+ continue
66
+
67
+ # Skip temporary editor files
68
+ if filename.endswith("~"):
69
+ continue
70
+
71
+ # Use gitignore rules if available
72
+ if gitignore and gitignore.match_file(rel_path):
73
+ continue
74
+
75
+ # Check if it's a text file
76
+ if not is_text_file(full_path):
77
+ continue
78
+
79
+ try:
80
+ with open(full_path, "r", encoding="utf-8") as file:
81
+ content = file.read()
82
+ files[rel_path] = calculate_checksum(content)
83
+ except UnicodeDecodeError:
84
+ # If UTF-8 decoding fails, it's likely not a text file we can handle
85
+ logger.debug(f"Unable to read {full_path} as UTF-8 text. Skipping.")
86
+ continue
87
+ except Exception as e:
88
+ logger.error(f"Error reading file {full_path}: {str(e)}")
89
+
76
90
  return files
77
91
 
78
92
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: claudesync
3
- Version: 0.3.1
3
+ Version: 0.3.2
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
  License: MIT License
@@ -40,8 +40,10 @@ class TestUtils(unittest.TestCase):
40
40
  f.write("Content of file3")
41
41
 
42
42
  # Create a .git file
43
- with open(os.path.join(tmpdir, ".git"), "w") as f:
44
- f.write("*.log\n")
43
+ for vcs in {".git", ".svn", ".hg", ".bzr", "_darcs", "CVS"}:
44
+ os.makedirs(os.path.join(tmpdir, vcs), exist_ok=True)
45
+ with open(os.path.join(tmpdir, vcs, ".gitignore"), "w") as f:
46
+ f.write("*.log\n")
45
47
 
46
48
  # Create a test~ file
47
49
  with open(os.path.join(tmpdir, "test~"), "w") as f:
File without changes
File without changes
File without changes
File without changes