ctxsync 0.8.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.
Files changed (44) hide show
  1. ctxsync-0.8.0/LICENSE +21 -0
  2. ctxsync-0.8.0/PKG-INFO +151 -0
  3. ctxsync-0.8.0/README.md +97 -0
  4. ctxsync-0.8.0/pyproject.toml +74 -0
  5. ctxsync-0.8.0/setup.cfg +4 -0
  6. ctxsync-0.8.0/setup.py +6 -0
  7. ctxsync-0.8.0/src/ctxsync/__init__.py +0 -0
  8. ctxsync-0.8.0/src/ctxsync/chat_sync.py +186 -0
  9. ctxsync-0.8.0/src/ctxsync/cli/__init__.py +3 -0
  10. ctxsync-0.8.0/src/ctxsync/cli/auth.py +77 -0
  11. ctxsync-0.8.0/src/ctxsync/cli/category.py +71 -0
  12. ctxsync-0.8.0/src/ctxsync/cli/chat.py +357 -0
  13. ctxsync-0.8.0/src/ctxsync/cli/config.py +72 -0
  14. ctxsync-0.8.0/src/ctxsync/cli/file.py +29 -0
  15. ctxsync-0.8.0/src/ctxsync/cli/main.py +257 -0
  16. ctxsync-0.8.0/src/ctxsync/cli/organization.py +98 -0
  17. ctxsync-0.8.0/src/ctxsync/cli/project.py +422 -0
  18. ctxsync-0.8.0/src/ctxsync/cli/session.py +626 -0
  19. ctxsync-0.8.0/src/ctxsync/cli/submodule.py +148 -0
  20. ctxsync-0.8.0/src/ctxsync/cli/sync.py +79 -0
  21. ctxsync-0.8.0/src/ctxsync/compression.py +302 -0
  22. ctxsync-0.8.0/src/ctxsync/configmanager/__init__.py +5 -0
  23. ctxsync-0.8.0/src/ctxsync/configmanager/base_config_manager.py +255 -0
  24. ctxsync-0.8.0/src/ctxsync/configmanager/file_config_manager.py +362 -0
  25. ctxsync-0.8.0/src/ctxsync/configmanager/inmemory_config_manager.py +134 -0
  26. ctxsync-0.8.0/src/ctxsync/exceptions.py +22 -0
  27. ctxsync-0.8.0/src/ctxsync/provider_factory.py +38 -0
  28. ctxsync-0.8.0/src/ctxsync/providers/__init__.py +0 -0
  29. ctxsync-0.8.0/src/ctxsync/providers/base_claude_ai.py +537 -0
  30. ctxsync-0.8.0/src/ctxsync/providers/base_provider.py +109 -0
  31. ctxsync-0.8.0/src/ctxsync/providers/claude_ai.py +192 -0
  32. ctxsync-0.8.0/src/ctxsync/session_key_manager.py +129 -0
  33. ctxsync-0.8.0/src/ctxsync/syncmanager.py +328 -0
  34. ctxsync-0.8.0/src/ctxsync/utils.py +416 -0
  35. ctxsync-0.8.0/src/ctxsync.egg-info/PKG-INFO +151 -0
  36. ctxsync-0.8.0/src/ctxsync.egg-info/SOURCES.txt +42 -0
  37. ctxsync-0.8.0/src/ctxsync.egg-info/dependency_links.txt +1 -0
  38. ctxsync-0.8.0/src/ctxsync.egg-info/entry_points.txt +2 -0
  39. ctxsync-0.8.0/src/ctxsync.egg-info/requires.txt +17 -0
  40. ctxsync-0.8.0/src/ctxsync.egg-info/top_level.txt +1 -0
  41. ctxsync-0.8.0/tests/test_chat_happy_path.py +74 -0
  42. ctxsync-0.8.0/tests/test_claude_ai.py +253 -0
  43. ctxsync-0.8.0/tests/test_happy_path.py +77 -0
  44. ctxsync-0.8.0/tests/test_session_key_manager.py +66 -0
ctxsync-0.8.0/LICENSE ADDED
@@ -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.
ctxsync-0.8.0/PKG-INFO ADDED
@@ -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,97 @@
1
+ # ctxsync
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4
+ [![PyPI](https://badge.fury.io/py/ctxsync.svg)](https://pypi.org/project/ctxsync/)
5
+ [![Release](https://img.shields.io/github/release/jahwag/ctxsync.svg)](https://github.com/jahwag/ctxsync/releases)
6
+ [![Build Status](https://github.com/jahwag/ctxsync/actions/workflows/python-package.yml/badge.svg)](https://github.com/jahwag/ctxsync/actions/workflows/python-package.yml)
7
+ [![Issues](https://img.shields.io/github/issues/jahwag/ctxsync)](https://github.com/jahwag/ctxsync/issues)
8
+ [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
9
+ [![Dependencies](https://img.shields.io/librariesio/github/jahwag/ctxsync)](https://github.com/jahwag/ctxsync/network/dependencies)
10
+ [![Last Commit](https://img.shields.io/github/last-commit/jahwag/ctxsync.svg)](https://github.com/jahwag/ctxsync/commits/main)
11
+ [![Sponsor jahwag](https://img.shields.io/badge/Sponsor-♥-red)](https://github.com/sponsors/jahwag)
12
+
13
+
14
+ ctxsync (formerly known as ClaudeSync) bridges your local development environment with Claude.ai projects, enabling seamless synchronization to enhance your AI-powered workflow.
15
+
16
+ > **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.
17
+
18
+ ![ctxsync in Action](ctxsync.gif)
19
+
20
+ ## ⚠️ Disclaimer
21
+
22
+ ctxsync is an independent, open-source project **not affiliated** with Anthropic or Claude.ai. By using ctxsync, you agree to:
23
+
24
+ 1. Use it at your own risk.
25
+ 2. Acknowledge potential violation of Anthropic's Terms of Service.
26
+ 3. Assume responsibility for any consequences.
27
+ 4. Understand that Anthropic does not support this tool.
28
+
29
+ Please review [Anthropic's Terms of Service](https://www.anthropic.com/legal/consumer-terms) before using ctxsync.
30
+
31
+ ## 🌟 Features
32
+
33
+ - **File sync**: Synchronize local files with [Claude.ai projects](https://www.anthropic.com/news/projects).
34
+ - **Cross-Platform**: Compatible with [Windows, macOS, and Linux](https://github.com/jahwag/ctxsync/releases).
35
+ - **Configurable**: Plenty of [configuration options](https://github.com/jahwag/ctxsync/wiki/Quick-reference).
36
+ - **Integrate**: Designed to be easy to integrate into your pipelines.
37
+ - **Secure**: Ensures data privacy and security.
38
+
39
+ ## ⚙️ Prerequisites
40
+
41
+ ### 📄 Supported Claude.ai plans
42
+
43
+ | [Plan](https://www.anthropic.com/pricing) | Supported |
44
+ |--------|-----------|
45
+ | Pro | ✅ |
46
+ | Team | ✅ |
47
+ | Free | ❌ |
48
+
49
+ ### 🔑 SSH Key
50
+
51
+ 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.
52
+
53
+ ### 💻 Software
54
+
55
+ - **Python**: ≥ [3.10](https://www.python.org/downloads/)
56
+ - **pip**: [Python package installer](https://pip.pypa.io/en/stable/installation/)
57
+
58
+ ## 🚀 Quick Start
59
+
60
+ 1. **Install ctxsync**
61
+ ```shell
62
+ pip install ctxsync
63
+ ```
64
+
65
+ 2. **Authenticate**
66
+ ```shell
67
+ ctxsync auth login
68
+ ```
69
+
70
+ 3. **Create a Project**
71
+ ```shell
72
+ ctxsync project create
73
+ ```
74
+
75
+ 4. **Start Syncing***
76
+ ```shell
77
+ ctxsync push
78
+ ```
79
+ **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).*
80
+
81
+ 📚 [Detailed Guides & FAQs](https://github.com/jahwag/ctxsync/wiki)
82
+
83
+ ## 🤝 Support & Contribute
84
+
85
+ Enjoying ctxsync? Support us by:
86
+
87
+ - ⭐ [Starring the Repository](https://github.com/jahwag/ctxsync)
88
+ - 🐛 [Reporting Issues](https://github.com/jahwag/ctxsync/issues)
89
+ - 🌍 [Contributing](CONTRIBUTING.md)
90
+ - 💬 [Join Our Discord](https://discord.gg/pR4qeMH4u4)
91
+ - 💖 [Sponsor Us](https://github.com/sponsors/jahwag)
92
+
93
+ Your contributions help improve ctxsync!
94
+
95
+ ---
96
+
97
+ [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,74 @@
1
+ [project]
2
+ name = "ctxsync"
3
+ version = "0.8.0"
4
+ authors = [
5
+ {name = "Jahziah Wagner", email = "540380+jahwag@users.noreply.github.com"},
6
+ ]
7
+ description = "A tool to synchronize local files with Claude.ai projects"
8
+ license = {file = "LICENSE"}
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Operating System :: OS Independent",
15
+ ]
16
+ dependencies = [
17
+ "click>=8.1.7",
18
+ "click_completion>=0.5.2",
19
+ "pathspec>=0.12.1",
20
+ "pytest>=8.3.2",
21
+ "python_crontab>=3.2.0",
22
+ "setuptools>=73.0.1",
23
+ "sseclient_py>=1.8.0",
24
+ "tqdm>=4.66.5",
25
+ "pytest-cov>=5.0.0",
26
+ "crontab>=1.0.1",
27
+ "python-crontab>=3.2.0",
28
+ "Brotli>=1.1.0",
29
+ "cryptography>=42.0.4",
30
+ ]
31
+ keywords = [
32
+ "sync",
33
+ "files",
34
+ "Claude.ai",
35
+ "automation",
36
+ "synchronization",
37
+ "project management",
38
+ "file management",
39
+ "cloud sync",
40
+ "cli tool",
41
+ "command line",
42
+ "productivity",
43
+ "development tools",
44
+ "file synchronization",
45
+ "continuous integration",
46
+ "devops",
47
+ "version control"
48
+ ]
49
+
50
+ [project.optional-dependencies]
51
+ test = [
52
+ "pytest>=8.2.2",
53
+ "pytest-cov>=5.0.0",
54
+ ]
55
+
56
+ [project.urls]
57
+ "Homepage" = "https://github.com/jahwag/ctxsync"
58
+ "Bug Tracker" = "https://github.com/jahwag/ctxsync/issues"
59
+
60
+ [project.scripts]
61
+ ctxsync = "ctxsync.cli.main:cli"
62
+
63
+ [build-system]
64
+ requires = ["setuptools>=42", "wheel"]
65
+ build-backend = "setuptools.build_meta"
66
+
67
+ [tool.setuptools.packages.find]
68
+ where = ["src"]
69
+ include = ["ctxsync*"]
70
+
71
+ [tool.pytest.ini_options]
72
+ testpaths = ["tests"]
73
+ python_files = "test_*.py"
74
+ addopts = "-v --cov=ctxsync --cov-report=term-missing"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
ctxsync-0.8.0/setup.py ADDED
@@ -0,0 +1,6 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ packages=find_packages(where="src"),
5
+ package_dir={"": "src"},
6
+ )
File without changes
@@ -0,0 +1,186 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import re
5
+
6
+ from tqdm import tqdm
7
+
8
+ from .exceptions import ConfigurationError
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def sync_chats(provider, config, sync_all=False):
14
+ """
15
+ Synchronize chats and their artifacts from the remote source.
16
+
17
+ This function fetches all chats for the active organization, saves their metadata,
18
+ messages, and extracts any artifacts found in the assistant's messages.
19
+
20
+ Args:
21
+ provider: The API provider instance.
22
+ config: The configuration manager instance.
23
+ sync_all (bool): If True, sync all chats regardless of project. If False, only sync chats for the active project.
24
+
25
+ Raises:
26
+ ConfigurationError: If required configuration settings are missing.
27
+ """
28
+ # Get the local_path for chats
29
+ local_path = config.get("local_path")
30
+ if not local_path:
31
+ raise ConfigurationError(
32
+ "Local path not set. Use 'ctxsync project set' or 'ctxsync project create' to set it."
33
+ )
34
+
35
+ # Create chats directory within local_path
36
+ chat_destination = os.path.join(local_path, "claude_chats")
37
+ os.makedirs(chat_destination, exist_ok=True)
38
+
39
+ # Get the active organization ID
40
+ organization_id = config.get("active_organization_id")
41
+ if not organization_id:
42
+ raise ConfigurationError(
43
+ "No active organization set. Please set an organization."
44
+ )
45
+
46
+ # Get the active project ID
47
+ active_project_id = config.get("active_project_id")
48
+ if not active_project_id and not sync_all:
49
+ raise ConfigurationError(
50
+ "No active project set. Please set a project or use the -a flag to sync all chats."
51
+ )
52
+
53
+ # Fetch all chats for the organization
54
+ logger.debug(f"Fetching chats for organization {organization_id}")
55
+ chats = provider.get_chat_conversations(organization_id)
56
+ logger.debug(f"Found {len(chats)} chats")
57
+
58
+ # Process each chat
59
+ for chat in tqdm(chats, desc="Chats"):
60
+ sync_chat(
61
+ active_project_id,
62
+ chat,
63
+ chat_destination,
64
+ organization_id,
65
+ provider,
66
+ sync_all,
67
+ )
68
+
69
+ logger.debug(f"Chats and artifacts synchronized to {chat_destination}")
70
+
71
+
72
+ def sync_chat(
73
+ active_project_id, chat, chat_destination, organization_id, provider, sync_all
74
+ ):
75
+ # Check if the chat belongs to the active project or if we're syncing all chats
76
+ if sync_all or (
77
+ chat.get("project") and chat["project"].get("uuid") == active_project_id
78
+ ):
79
+ logger.debug(f"Processing chat {chat['uuid']}")
80
+ chat_folder = os.path.join(chat_destination, chat["uuid"])
81
+ os.makedirs(chat_folder, exist_ok=True)
82
+
83
+ # Save chat metadata
84
+ metadata_file = os.path.join(chat_folder, "metadata.json")
85
+ if not os.path.exists(metadata_file):
86
+ with open(metadata_file, "w") as f:
87
+ json.dump(chat, f, indent=2)
88
+
89
+ # Fetch full chat conversation
90
+ logger.debug(f"Fetching full conversation for chat {chat['uuid']}")
91
+ full_chat = provider.get_chat_conversation(organization_id, chat["uuid"])
92
+
93
+ # Process each message in the chat
94
+ for message in full_chat["chat_messages"]:
95
+ message_file = os.path.join(chat_folder, f"{message['uuid']}.json")
96
+
97
+ # Skip processing if the message file already exists
98
+ if os.path.exists(message_file):
99
+ logger.debug(f"Skipping existing message {message['uuid']}")
100
+ continue
101
+
102
+ # Save the message
103
+ with open(message_file, "w") as f:
104
+ json.dump(message, f, indent=2)
105
+
106
+ # Handle artifacts in assistant messages
107
+ if message["sender"] == "assistant":
108
+ artifacts = extract_artifacts(message["text"])
109
+ if artifacts:
110
+ save_artifacts(artifacts, chat_folder, message)
111
+ else:
112
+ logger.debug(
113
+ f"Skipping chat {chat['uuid']} as it doesn't belong to the active project"
114
+ )
115
+
116
+
117
+ def save_artifacts(artifacts, chat_folder, message):
118
+ logger.info(f"Found {len(artifacts)} artifacts in message {message['uuid']}")
119
+ artifact_folder = os.path.join(chat_folder, "artifacts")
120
+ os.makedirs(artifact_folder, exist_ok=True)
121
+ for artifact in artifacts:
122
+ # Save each artifact
123
+ artifact_file = os.path.join(
124
+ artifact_folder,
125
+ f"{artifact['identifier']}.{get_file_extension(artifact['type'])}",
126
+ )
127
+ if not os.path.exists(artifact_file):
128
+ with open(artifact_file, "w") as f:
129
+ f.write(artifact["content"])
130
+
131
+
132
+ def get_file_extension(artifact_type):
133
+ """
134
+ Get the appropriate file extension for a given artifact type.
135
+
136
+ Args:
137
+ artifact_type (str): The MIME type of the artifact.
138
+
139
+ Returns:
140
+ str: The corresponding file extension.
141
+ """
142
+ type_to_extension = {
143
+ "text/html": "html",
144
+ "application/vnd.ant.code": "txt",
145
+ "image/svg+xml": "svg",
146
+ "application/vnd.ant.mermaid": "mmd",
147
+ "application/vnd.ant.react": "jsx",
148
+ }
149
+ return type_to_extension.get(artifact_type, "txt")
150
+
151
+
152
+ def extract_artifacts(text):
153
+ """
154
+ Extract artifacts from the given text.
155
+
156
+ This function searches for antArtifact tags in the text and extracts
157
+ the artifact information, including identifier, type, and content.
158
+
159
+ Args:
160
+ text (str): The text to search for artifacts.
161
+
162
+ Returns:
163
+ list: A list of dictionaries containing artifact information.
164
+ """
165
+ artifacts = []
166
+
167
+ # Regular expression to match the <antArtifact> tags and extract their attributes and content
168
+ pattern = re.compile(
169
+ r'<antArtifact\s+identifier="([^"]+)"\s+type="([^"]+)"\s+title="([^"]+)">([\s\S]*?)</antArtifact>',
170
+ re.MULTILINE,
171
+ )
172
+
173
+ # Find all matches in the text
174
+ matches = pattern.findall(text)
175
+
176
+ for match in matches:
177
+ identifier, artifact_type, title, content = match
178
+ artifacts.append(
179
+ {
180
+ "identifier": identifier,
181
+ "type": artifact_type,
182
+ "content": content.strip(),
183
+ }
184
+ )
185
+
186
+ return artifacts
@@ -0,0 +1,3 @@
1
+ from .main import cli
2
+
3
+ __all__ = ["cli"]
@@ -0,0 +1,77 @@
1
+ import click
2
+
3
+ from ctxsync.provider_factory import get_provider
4
+ from ..exceptions import ProviderError
5
+ from ..utils import handle_errors
6
+
7
+
8
+ @click.group()
9
+ def auth():
10
+ """Manage authentication."""
11
+ pass
12
+
13
+
14
+ @auth.command()
15
+ @click.option(
16
+ "--provider",
17
+ prompt="Choose provider",
18
+ type=click.Choice(["claude.ai"], case_sensitive=False),
19
+ default="claude.ai",
20
+ help="The provider to use for this project",
21
+ )
22
+ @click.option(
23
+ "--session-key",
24
+ help="Directly provide the Claude.ai session key",
25
+ envvar="CLAUDE_SESSION_KEY",
26
+ )
27
+ @click.option(
28
+ "--auto-approve",
29
+ is_flag=True,
30
+ help="Automatically approve the suggested expiry time",
31
+ )
32
+ @click.pass_context
33
+ @handle_errors
34
+ def login(ctx, provider, session_key, auto_approve):
35
+ """Authenticate with an AI provider."""
36
+ config = ctx.obj
37
+ provider_instance = get_provider(config, provider)
38
+
39
+ try:
40
+ if session_key:
41
+ # If session key is provided, bypass the interactive prompt
42
+ if not session_key.startswith("sk-ant"):
43
+ raise ProviderError(
44
+ "Invalid sessionKey format. Must start with 'sk-ant'"
45
+ )
46
+ # Set auto_approve to True when session key is provided
47
+ provider_instance._auto_approve_expiry = auto_approve
48
+ provider_instance._provided_session_key = session_key
49
+
50
+ session_key, expiry = provider_instance.login()
51
+ config.set_session_key(provider, session_key, expiry)
52
+ click.echo(
53
+ f"Successfully authenticated with {provider}. Session key stored globally."
54
+ )
55
+ except ProviderError as e:
56
+ click.echo(f"Authentication failed: {str(e)}")
57
+
58
+
59
+ @auth.command()
60
+ @click.pass_obj
61
+ def logout(config):
62
+ """Log out from all AI providers."""
63
+ config.clear_all_session_keys()
64
+ click.echo("Logged out from all providers successfully.")
65
+
66
+
67
+ @auth.command()
68
+ @click.pass_obj
69
+ def ls(config):
70
+ """List all authenticated providers."""
71
+ authenticated_providers = config.get_providers_with_session_keys()
72
+ if authenticated_providers:
73
+ click.echo("Authenticated providers:")
74
+ for provider in authenticated_providers:
75
+ click.echo(f" - {provider}")
76
+ else:
77
+ click.echo("No authenticated providers found.")