ctxsync 0.8.0__py3-none-any.whl

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.
ctxsync/utils.py ADDED
@@ -0,0 +1,416 @@
1
+ import os
2
+ import hashlib
3
+ from functools import wraps
4
+ from pathlib import Path
5
+
6
+ import click
7
+ import pathspec
8
+ import logging
9
+
10
+ from ctxsync.exceptions import ConfigurationError, ProviderError
11
+ from ctxsync.provider_factory import get_provider
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def normalize_and_calculate_md5(content):
17
+ """
18
+ Calculate the MD5 checksum of the given content after normalizing line endings.
19
+
20
+ This function normalizes the line endings of the input content to Unix-style (\n),
21
+ strips leading and trailing whitespace, and then calculates the MD5 checksum of the
22
+ normalized content. This is useful for ensuring consistent checksums across different
23
+ operating systems and environments where line ending styles may vary.
24
+
25
+ Args:
26
+ content (str): The content for which to calculate the checksum.
27
+
28
+ Returns:
29
+ str: The hexadecimal MD5 checksum of the normalized content.
30
+ """
31
+ normalized_content = content.replace("\r\n", "\n").replace("\r", "\n").strip()
32
+ return hashlib.md5(normalized_content.encode("utf-8")).hexdigest()
33
+
34
+
35
+ def load_gitignore(base_path):
36
+ """
37
+ Loads and parses the .gitignore file from the specified base path.
38
+
39
+ This function attempts to find a .gitignore file in the given base path. If found,
40
+ it reads the file and creates a PathSpec object that can be used to match paths
41
+ against the patterns defined in the .gitignore file. This is useful for filtering
42
+ out files that should be ignored based on the project's .gitignore settings.
43
+
44
+ Args:
45
+ base_path (str): The base directory path where the .gitignore file is located.
46
+
47
+ Returns:
48
+ pathspec.PathSpec or None: A PathSpec object containing the patterns from the .gitignore file
49
+ if the file exists; otherwise, None.
50
+ """
51
+ gitignore_path = os.path.join(base_path, ".gitignore")
52
+ if os.path.exists(gitignore_path):
53
+ with open(gitignore_path, "r") as f:
54
+ return pathspec.PathSpec.from_lines("gitwildmatch", f)
55
+ return None
56
+
57
+
58
+ def is_text_file(file_path, sample_size=8192):
59
+ """
60
+ Determines if a file is a text file by checking for the absence of null bytes.
61
+
62
+ This function reads a sample of the file (default 8192 bytes) and checks if it contains
63
+ any null byte (\x00). The presence of a null byte is often indicative of a binary file.
64
+ This is a heuristic method and may not be 100% accurate for all file types.
65
+
66
+ Args:
67
+ file_path (str): The path to the file to be checked.
68
+ sample_size (int, optional): The number of bytes to read from the file for checking.
69
+ Defaults to 8192.
70
+
71
+ Returns:
72
+ bool: True if the file is likely a text file, False if it is likely binary or an error occurred.
73
+ """
74
+ try:
75
+ with open(file_path, "rb") as file:
76
+ return b"\x00" not in file.read(sample_size)
77
+ except IOError:
78
+ return False
79
+
80
+
81
+ def compute_md5_hash(content):
82
+ """
83
+ Computes the MD5 hash of the given content.
84
+
85
+ This function takes a string as input, encodes it into UTF-8, and then computes the MD5 hash of the encoded string.
86
+ The result is a hexadecimal representation of the hash, which is commonly used for creating a quick and simple
87
+ fingerprint of a piece of data.
88
+
89
+ Args:
90
+ content (str): The content for which to compute the MD5 hash.
91
+
92
+ Returns:
93
+ str: The hexadecimal MD5 hash of the input content.
94
+ """
95
+ return hashlib.md5(content.encode("utf-8")).hexdigest()
96
+
97
+
98
+ def should_process_file(
99
+ config_manager, file_path, filename, gitignore, base_path, claudeignore
100
+ ):
101
+ """
102
+ Determines whether a file should be processed based on various criteria.
103
+
104
+ This function checks if a file should be included in the synchronization process by applying
105
+ several filters:
106
+ - Checks if the file size is within the configured maximum limit.
107
+ - Skips temporary editor files (ending with '~').
108
+ - Applies .gitignore rules if a gitignore PathSpec is provided.
109
+ - Verifies if the file is a text file.
110
+
111
+ Args:
112
+ file_path (str): The full path to the file.
113
+ filename (str): The name of the file.
114
+ gitignore (pathspec.PathSpec or None): A PathSpec object containing .gitignore patterns, if available.
115
+ base_path (str): The base directory path of the project.
116
+ claudeignore (pathspec.PathSpec or None): A PathSpec object containing .claudeignore patterns, if available.
117
+
118
+ Returns:
119
+ bool: True if the file should be processed, False otherwise.
120
+ """
121
+ # Check file size
122
+ max_file_size = config_manager.get("max_file_size", 32 * 1024)
123
+ if os.path.getsize(file_path) > max_file_size:
124
+ return False
125
+
126
+ # Skip temporary editor files
127
+ if filename.endswith("~"):
128
+ return False
129
+
130
+ rel_path = os.path.relpath(file_path, base_path)
131
+
132
+ # Use gitignore rules if available
133
+ if gitignore and gitignore.match_file(rel_path):
134
+ return False
135
+
136
+ # Use .claudeignore rules if available
137
+ if claudeignore and claudeignore.match_file(rel_path):
138
+ return False
139
+
140
+ # Check if it's a text file
141
+ return is_text_file(file_path)
142
+
143
+
144
+ def process_file(file_path):
145
+ """
146
+ Reads the content of a file and computes its MD5 hash.
147
+
148
+ This function attempts to read the file as UTF-8 text and compute its MD5 hash.
149
+ If the file cannot be read as UTF-8 or any other error occurs, it logs the issue
150
+ and returns None.
151
+
152
+ Args:
153
+ file_path (str): The path to the file to be processed.
154
+
155
+ Returns:
156
+ str or None: The MD5 hash of the file's content if successful, None otherwise.
157
+ """
158
+ try:
159
+ with open(file_path, "r", encoding="utf-8") as file:
160
+ content = file.read()
161
+ return compute_md5_hash(content)
162
+ except UnicodeDecodeError:
163
+ logger.debug(f"Unable to read {file_path} as UTF-8 text. Skipping.")
164
+ except Exception as e:
165
+ logger.error(f"Error reading file {file_path}: {str(e)}")
166
+ return None
167
+
168
+
169
+ def get_local_files(config, local_path, category=None, include_submodules=False):
170
+ """
171
+ Retrieves a dictionary of local files within a specified path, applying various filters.
172
+
173
+ Args:
174
+ config: config manager to use
175
+ local_path (str): The base directory path to search for files.
176
+ category (str, optional): The file category to filter by.
177
+ include_submodules (bool, optional): Whether to include files from submodules.
178
+
179
+ Returns:
180
+ dict: A dictionary where keys are relative file paths, and values are MD5 hashes of the file contents.
181
+ """
182
+ gitignore = load_gitignore(local_path)
183
+ claudeignore = load_claudeignore(local_path)
184
+ files = {}
185
+ exclude_dirs = {
186
+ ".git",
187
+ ".svn",
188
+ ".hg",
189
+ ".bzr",
190
+ "_darcs",
191
+ "CVS",
192
+ "claude_chats",
193
+ ".ctxsync",
194
+ ".claudesync", # legacy config dir from before the rename
195
+ }
196
+
197
+ categories = config.get("file_categories", {})
198
+ if category and category not in categories:
199
+ raise ValueError(f"Invalid category: {category}")
200
+
201
+ patterns = ["*"] # Default to all files
202
+ if category:
203
+ patterns = categories[category]["patterns"]
204
+
205
+ spec = pathspec.PathSpec.from_lines("gitwildmatch", patterns)
206
+
207
+ submodules = config.get("submodules", [])
208
+ submodule_paths = [sm["relative_path"] for sm in submodules]
209
+
210
+ for root, dirs, filenames in os.walk(local_path, topdown=True):
211
+ rel_root = os.path.relpath(root, local_path)
212
+ rel_root = "" if rel_root == "." else rel_root
213
+
214
+ # Skip submodule directories if not including submodules
215
+ if not include_submodules:
216
+ dirs[:] = [
217
+ d for d in dirs if os.path.join(rel_root, d) not in submodule_paths
218
+ ]
219
+
220
+ dirs[:] = [
221
+ d
222
+ for d in dirs
223
+ if d not in exclude_dirs
224
+ and not (gitignore and gitignore.match_file(os.path.join(rel_root, d)))
225
+ and not (
226
+ claudeignore and claudeignore.match_file(os.path.join(rel_root, d))
227
+ )
228
+ ]
229
+
230
+ for filename in filenames:
231
+ rel_path = os.path.join(rel_root, filename)
232
+ full_path = os.path.join(root, filename)
233
+
234
+ if spec.match_file(rel_path) and should_process_file(
235
+ config, full_path, filename, gitignore, local_path, claudeignore
236
+ ):
237
+ file_hash = process_file(full_path)
238
+ if file_hash:
239
+ files[rel_path] = file_hash
240
+
241
+ return files
242
+
243
+
244
+ def handle_errors(func):
245
+ """
246
+ A decorator that wraps a function to catch and handle specific exceptions.
247
+
248
+ This decorator catches exceptions of type ConfigurationError and ProviderError
249
+ that are raised within the decorated function. When such an exception is caught,
250
+ it prints an error message to the console using click's echo function. This is
251
+ useful for CLI applications where a friendly error message is preferred over a
252
+ full traceback for known error conditions.
253
+
254
+ Args:
255
+ func (Callable): The function to be decorated.
256
+
257
+ Returns:
258
+ Callable: The wrapper function that includes exception handling.
259
+ """
260
+
261
+ @wraps(func)
262
+ def wrapper(*args, **kwargs):
263
+ try:
264
+ return func(*args, **kwargs)
265
+ except (ConfigurationError, ProviderError) as e:
266
+ click.echo(f"Error: {str(e)}")
267
+
268
+ return wrapper
269
+
270
+
271
+ def validate_and_get_provider(config, require_org=True, require_project=False):
272
+ """
273
+ Validates the configuration for the presence of an active provider and session key,
274
+ and optionally checks for an active organization ID and project ID. If validation passes,
275
+ it retrieves the provider instance based on the active provider name.
276
+
277
+ Args:
278
+ config (ConfigManager): The configuration manager instance containing settings.
279
+ require_org (bool, optional): Flag to indicate whether an active organization ID
280
+ is required. Defaults to True.
281
+ require_project (bool, optional): Flag to indicate whether an active project ID
282
+ is required. Defaults to False.
283
+
284
+ Returns:
285
+ object: An instance of the provider specified in the configuration.
286
+
287
+ Raises:
288
+ ConfigurationError: If the active provider or session key is missing, or if
289
+ require_org is True and no active organization ID is set,
290
+ or if require_project is True and no active project ID is set.
291
+ ProviderError: If the session key has expired.
292
+ """
293
+ if require_org and not config.get("active_organization_id"):
294
+ raise ConfigurationError(
295
+ "No active organization set. Please select an organization (ctxsync organization set)."
296
+ )
297
+
298
+ if require_project and not config.get("active_project_id"):
299
+ raise ConfigurationError(
300
+ "No active project set. Please select or create a project (ctxsync project set)."
301
+ )
302
+
303
+ active_provider = config.get_active_provider()
304
+ if not active_provider:
305
+ raise ConfigurationError(
306
+ "No active provider set. Please select a provider for this project."
307
+ )
308
+
309
+ session_key, session_key_expiry = config.get_session_key(active_provider)
310
+ if not session_key:
311
+ raise ConfigurationError(
312
+ f"No valid session key found for {active_provider}. Please log in again."
313
+ )
314
+
315
+ return get_provider(config, active_provider)
316
+
317
+
318
+ def validate_and_store_local_path(config):
319
+ """
320
+ Prompts the user for the absolute path to their local project directory and stores it in the configuration.
321
+
322
+ This function repeatedly prompts the user to enter the absolute path to their local project directory until
323
+ a valid absolute path is provided. The path is validated to ensure it exists, is a directory, and is an absolute path.
324
+ Once a valid path is provided, it is stored in the configuration using the `set` method of the `ConfigManager` object.
325
+
326
+ Args:
327
+ config (ConfigManager): The configuration manager instance to store the local path setting.
328
+
329
+ Note:
330
+ This function uses `click.prompt` to interact with the user, providing a default path (the current working directory)
331
+ and validating the user's input to ensure it meets the criteria for an absolute path to a directory.
332
+ """
333
+
334
+ def get_default_path():
335
+ return os.getcwd()
336
+
337
+ while True:
338
+ default_path = get_default_path()
339
+ local_path = click.prompt(
340
+ "Enter the absolute path to your local project directory",
341
+ type=click.Path(
342
+ exists=True, file_okay=False, dir_okay=True, resolve_path=True
343
+ ),
344
+ default=default_path,
345
+ show_default=True,
346
+ )
347
+
348
+ if os.path.isabs(local_path):
349
+ config.set("local_path", local_path)
350
+ click.echo(f"Local path set to: {local_path}")
351
+ break
352
+ else:
353
+ click.echo("Please enter an absolute path.")
354
+
355
+
356
+ def load_claudeignore(base_path):
357
+ """
358
+ Loads and parses the .claudeignore file from the specified base path.
359
+
360
+ Args:
361
+ base_path (str): The base directory path where the .claudeignore file is located.
362
+
363
+ Returns:
364
+ pathspec.PathSpec or None: A PathSpec object containing the patterns from the .claudeignore file
365
+ if the file exists; otherwise, None.
366
+ """
367
+ claudeignore_path = os.path.join(base_path, ".claudeignore")
368
+ if os.path.exists(claudeignore_path):
369
+ with open(claudeignore_path, "r") as f:
370
+ return pathspec.PathSpec.from_lines("gitwildmatch", f)
371
+ return None
372
+
373
+
374
+ def detect_submodules(base_path, submodule_detect_filenames):
375
+ """
376
+ Detects submodules within a project based on specific filenames, respecting .gitignore and .claudeignore.
377
+
378
+ Args:
379
+ base_path (str): The base directory path to start the search from.
380
+ submodule_detect_filenames (list): List of filenames that indicate a submodule.
381
+
382
+ Returns:
383
+ list: A list of tuples (relative_path, detected_filename) for detected submodules,
384
+ excluding the root directory and respecting ignore files.
385
+ """
386
+ submodules = []
387
+ base_path = Path(base_path)
388
+ gitignore = load_gitignore(base_path)
389
+ claudeignore = load_claudeignore(base_path)
390
+
391
+ for root, dirs, files in os.walk(base_path):
392
+ rel_root = Path(root).relative_to(base_path)
393
+
394
+ # Check if the current directory should be ignored
395
+ if gitignore and gitignore.match_file(str(rel_root)):
396
+ dirs[:] = [] # Don't descend into this directory
397
+ continue
398
+ if claudeignore and claudeignore.match_file(str(rel_root)):
399
+ dirs[:] = [] # Don't descend into this directory
400
+ continue
401
+
402
+ for filename in submodule_detect_filenames:
403
+ if filename in files:
404
+ relative_path = str(rel_root)
405
+ # Exclude the root directory (represented by an empty string or '.')
406
+ if relative_path not in ("", "."):
407
+ # Check if the file itself should be ignored
408
+ file_path = rel_root / filename
409
+ if (gitignore and gitignore.match_file(str(file_path))) or (
410
+ claudeignore and claudeignore.match_file(str(file_path))
411
+ ):
412
+ continue
413
+ submodules.append((relative_path, filename))
414
+ break # Stop searching this directory once a submodule is found
415
+
416
+ return submodules
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctxsync
3
+ Version: 0.8.0
4
+ Summary: A tool to synchronize local files with Claude.ai projects
5
+ Author-email: Jahziah Wagner <540380+jahwag@users.noreply.github.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Jahziah Wagner
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/jahwag/ctxsync
29
+ Project-URL: Bug Tracker, https://github.com/jahwag/ctxsync/issues
30
+ Keywords: sync,files,Claude.ai,automation,synchronization,project management,file management,cloud sync,cli tool,command line,productivity,development tools,file synchronization,continuous integration,devops,version control
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Requires-Python: >=3.10
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: click>=8.1.7
38
+ Requires-Dist: click_completion>=0.5.2
39
+ Requires-Dist: pathspec>=0.12.1
40
+ Requires-Dist: pytest>=8.3.2
41
+ Requires-Dist: python_crontab>=3.2.0
42
+ Requires-Dist: setuptools>=73.0.1
43
+ Requires-Dist: sseclient_py>=1.8.0
44
+ Requires-Dist: tqdm>=4.66.5
45
+ Requires-Dist: pytest-cov>=5.0.0
46
+ Requires-Dist: crontab>=1.0.1
47
+ Requires-Dist: python-crontab>=3.2.0
48
+ Requires-Dist: Brotli>=1.1.0
49
+ Requires-Dist: cryptography>=42.0.4
50
+ Provides-Extra: test
51
+ Requires-Dist: pytest>=8.2.2; extra == "test"
52
+ Requires-Dist: pytest-cov>=5.0.0; extra == "test"
53
+ Dynamic: license-file
54
+
55
+ # ctxsync
56
+
57
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
58
+ [![PyPI](https://badge.fury.io/py/ctxsync.svg)](https://pypi.org/project/ctxsync/)
59
+ [![Release](https://img.shields.io/github/release/jahwag/ctxsync.svg)](https://github.com/jahwag/ctxsync/releases)
60
+ [![Build Status](https://github.com/jahwag/ctxsync/actions/workflows/python-package.yml/badge.svg)](https://github.com/jahwag/ctxsync/actions/workflows/python-package.yml)
61
+ [![Issues](https://img.shields.io/github/issues/jahwag/ctxsync)](https://github.com/jahwag/ctxsync/issues)
62
+ [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
63
+ [![Dependencies](https://img.shields.io/librariesio/github/jahwag/ctxsync)](https://github.com/jahwag/ctxsync/network/dependencies)
64
+ [![Last Commit](https://img.shields.io/github/last-commit/jahwag/ctxsync.svg)](https://github.com/jahwag/ctxsync/commits/main)
65
+ [![Sponsor jahwag](https://img.shields.io/badge/Sponsor-♥-red)](https://github.com/sponsors/jahwag)
66
+
67
+
68
+ ctxsync (formerly known as ClaudeSync) bridges your local development environment with Claude.ai projects, enabling seamless synchronization to enhance your AI-powered workflow.
69
+
70
+ > **Renamed from ClaudeSync**: the `claudesync` PyPI package is deprecated — install `ctxsync` instead. Your existing configuration is picked up automatically: `~/.claudesync` is migrated on first run and project-local `.claudesync` directories keep working.
71
+
72
+ ![ctxsync in Action](ctxsync.gif)
73
+
74
+ ## ⚠️ Disclaimer
75
+
76
+ ctxsync is an independent, open-source project **not affiliated** with Anthropic or Claude.ai. By using ctxsync, you agree to:
77
+
78
+ 1. Use it at your own risk.
79
+ 2. Acknowledge potential violation of Anthropic's Terms of Service.
80
+ 3. Assume responsibility for any consequences.
81
+ 4. Understand that Anthropic does not support this tool.
82
+
83
+ Please review [Anthropic's Terms of Service](https://www.anthropic.com/legal/consumer-terms) before using ctxsync.
84
+
85
+ ## 🌟 Features
86
+
87
+ - **File sync**: Synchronize local files with [Claude.ai projects](https://www.anthropic.com/news/projects).
88
+ - **Cross-Platform**: Compatible with [Windows, macOS, and Linux](https://github.com/jahwag/ctxsync/releases).
89
+ - **Configurable**: Plenty of [configuration options](https://github.com/jahwag/ctxsync/wiki/Quick-reference).
90
+ - **Integrate**: Designed to be easy to integrate into your pipelines.
91
+ - **Secure**: Ensures data privacy and security.
92
+
93
+ ## ⚙️ Prerequisites
94
+
95
+ ### 📄 Supported Claude.ai plans
96
+
97
+ | [Plan](https://www.anthropic.com/pricing) | Supported |
98
+ |--------|-----------|
99
+ | Pro | ✅ |
100
+ | Team | ✅ |
101
+ | Free | ❌ |
102
+
103
+ ### 🔑 SSH Key
104
+
105
+ Ensure you have an SSH key for secure credential storage. Follow [GitHub's guide](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to generate and add your SSH key.
106
+
107
+ ### 💻 Software
108
+
109
+ - **Python**: ≥ [3.10](https://www.python.org/downloads/)
110
+ - **pip**: [Python package installer](https://pip.pypa.io/en/stable/installation/)
111
+
112
+ ## 🚀 Quick Start
113
+
114
+ 1. **Install ctxsync**
115
+ ```shell
116
+ pip install ctxsync
117
+ ```
118
+
119
+ 2. **Authenticate**
120
+ ```shell
121
+ ctxsync auth login
122
+ ```
123
+
124
+ 3. **Create a Project**
125
+ ```shell
126
+ ctxsync project create
127
+ ```
128
+
129
+ 4. **Start Syncing***
130
+ ```shell
131
+ ctxsync push
132
+ ```
133
+ **This is a one-way sync. Files not present locally will be removed from the Claude.ai project unless pruning is [disabled](https://github.com/jahwag/ctxsync/wiki/Quick-reference#pruning-remote).*
134
+
135
+ 📚 [Detailed Guides & FAQs](https://github.com/jahwag/ctxsync/wiki)
136
+
137
+ ## 🤝 Support & Contribute
138
+
139
+ Enjoying ctxsync? Support us by:
140
+
141
+ - ⭐ [Starring the Repository](https://github.com/jahwag/ctxsync)
142
+ - 🐛 [Reporting Issues](https://github.com/jahwag/ctxsync/issues)
143
+ - 🌍 [Contributing](CONTRIBUTING.md)
144
+ - 💬 [Join Our Discord](https://discord.gg/pR4qeMH4u4)
145
+ - 💖 [Sponsor Us](https://github.com/sponsors/jahwag)
146
+
147
+ Your contributions help improve ctxsync!
148
+
149
+ ---
150
+
151
+ [Contributors](https://github.com/jahwag/ctxsync/graphs/contributors) • [License](https://github.com/jahwag/ctxsync/blob/master/LICENSE) • [Report Bug](https://github.com/jahwag/ctxsync/issues) • [Request Feature](https://github.com/jahwag/ctxsync/issues/new?labels=enhancement&template=feature_request.md)• [Sponsor](https://github.com/sponsors/jahwag)
@@ -0,0 +1,34 @@
1
+ ctxsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ ctxsync/chat_sync.py,sha256=cvCQkVMtlsRt7fkq-oqT0lJxu6B9hv4ZRUfxYWQOuA8,6215
3
+ ctxsync/compression.py,sha256=ryof8H14v3xytNfnY7FcqDWkxqFtLCRoj0tEfvBVHgs,8585
4
+ ctxsync/exceptions.py,sha256=agZD9I-njaD5N-UCjUds2MFF8QIp2kXcnj2H0RezgJw,831
5
+ ctxsync/provider_factory.py,sha256=mV4y7VO1ywgswhpc8R_Bys7o5NHOv-Mi7jYlWF4UsAA,1363
6
+ ctxsync/session_key_manager.py,sha256=dxP01kT6Wmbi1LmYFel--av0k8BgoJdLBZSZjRXvh5g,5112
7
+ ctxsync/syncmanager.py,sha256=qV_X9DpbQoJZtXbB9lBElZp6CYaEb1FT0RAzfQHah2k,13252
8
+ ctxsync/utils.py,sha256=UhKBN6TdhC_kZ0s8X7KAMp2Jea40k5kMKXxIMdsQV-c,15668
9
+ ctxsync/cli/__init__.py,sha256=78xQXAVUXVZWoJ_aEwZM8TtTrOWtH8e1q2umnxV6hoA,41
10
+ ctxsync/cli/auth.py,sha256=1QpxvKBEPDhdDbVTuZgQzW3Q1mmpfKlCX4ZSZYnAyHs,2281
11
+ ctxsync/cli/category.py,sha256=pBndaBqE-XeVVOovRipXrRiID9CDyyu1RRgoN55U0tw,2116
12
+ ctxsync/cli/chat.py,sha256=bDMJjIHoHxCguOVVMvQTVjguSP06K5oSSHwFYy6Atys,12145
13
+ ctxsync/cli/config.py,sha256=sGgpCcIu6s-m8tRvngAJKg-R3MYvBKwTaaLN1Z6_gns,1791
14
+ ctxsync/cli/file.py,sha256=BhrB4q31LKX25frKTls79LZa7-qkWfKoBmd6wMBnUag,910
15
+ ctxsync/cli/main.py,sha256=altXKR9pgJ32L3u3tQeQyuCryv4_lIUBUDYWBajRB70,8580
16
+ ctxsync/cli/organization.py,sha256=yO2AiakwRrSJYKHTaG0r5Ia6EhDb3W3W4q6Xa7nJBFk,3473
17
+ ctxsync/cli/project.py,sha256=ITIa67LV2FaRBQIEKl0mBPgC10tMpO-2AYgZnrkHUBo,14849
18
+ ctxsync/cli/session.py,sha256=-gng9o_mKoJ-kozKp-OQwAMbcfWrn-Wxp3hMW22VBKA,21907
19
+ ctxsync/cli/submodule.py,sha256=ztQYNKUYAK9DA-TD3PsY9-o-SYTTji3JSEQajezJOB0,5442
20
+ ctxsync/cli/sync.py,sha256=tG0OZScx0v9QfuypZYb-SuV7AjVGZLIWcXpL3kQxHw0,2636
21
+ ctxsync/configmanager/__init__.py,sha256=FUYyc4O4HbhredgmepDdQLvIZFEgjyBkDrOgqfuoi4Y,240
22
+ ctxsync/configmanager/base_config_manager.py,sha256=kBL9WOhWlUMyaeRhivriLwP5dhC1I3tPHDIHHn7-Ge0,8751
23
+ ctxsync/configmanager/file_config_manager.py,sha256=svRDAeD9m4Q6-RcNKVP_NSDKSVF9IwrNJ8gGMlSOftQ,13923
24
+ ctxsync/configmanager/inmemory_config_manager.py,sha256=sYXVLHmM1Yj0lqnMPiJ_CqKlX6bLOpHAEINZfrtZyJY,4756
25
+ ctxsync/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ ctxsync/providers/base_claude_ai.py,sha256=yhW348BP6FK_TxdChpayKKFE0u2xttmVQg0XR5TCtcU,20478
27
+ ctxsync/providers/base_provider.py,sha256=_lexXePxs8Zxt-XVQRC6Wmn7RL2c0M_6rz8kieznkk8,3703
28
+ ctxsync/providers/claude_ai.py,sha256=uus0pV50hLyx6UowzcmM8Gszp2w4N1xoKgAUmMD39ZI,7628
29
+ ctxsync-0.8.0.dist-info/licenses/LICENSE,sha256=CkW6EcjzggHkYIRN8cq-IlkbJ8QNHWtav12eOYVFm_Y,1071
30
+ ctxsync-0.8.0.dist-info/METADATA,sha256=OyquyJ24O4XyZKgyMDpFRsSjiOzGmyhsM76yIThBrVo,6977
31
+ ctxsync-0.8.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
32
+ ctxsync-0.8.0.dist-info/entry_points.txt,sha256=cuVaKmAdQvFu66NP_FoQnZUok9Yzh_ltaEoXNNZR2R8,49
33
+ ctxsync-0.8.0.dist-info/top_level.txt,sha256=cQ6EoHkORJlapSx7EHzuod8w95cftGfaAbO4zUKS3Ak,8
34
+ ctxsync-0.8.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ctxsync = ctxsync.cli.main:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jahziah Wagner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ctxsync