claudesync 0.2.6__tar.gz → 0.2.8__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 (34) hide show
  1. {claudesync-0.2.6 → claudesync-0.2.8}/LICENSE +1 -1
  2. claudesync-0.2.8/PKG-INFO +148 -0
  3. claudesync-0.2.8/README.md +109 -0
  4. {claudesync-0.2.6 → claudesync-0.2.8}/pyproject.toml +7 -5
  5. claudesync-0.2.8/src/claudesync/__init__.py +0 -0
  6. claudesync-0.2.8/src/claudesync/cli.py +357 -0
  7. claudesync-0.2.8/src/claudesync/config_manager.py +30 -0
  8. claudesync-0.2.8/src/claudesync/exceptions.py +9 -0
  9. claudesync-0.2.8/src/claudesync/provider_factory.py +17 -0
  10. claudesync-0.2.8/src/claudesync/providers/__init__.py +0 -0
  11. claudesync-0.2.8/src/claudesync/providers/claude_ai.py +154 -0
  12. claudesync-0.2.8/src/claudesync/utils.py +57 -0
  13. claudesync-0.2.8/src/claudesync.egg-info/PKG-INFO +148 -0
  14. claudesync-0.2.8/src/claudesync.egg-info/SOURCES.txt +20 -0
  15. claudesync-0.2.8/src/claudesync.egg-info/entry_points.txt +2 -0
  16. {claudesync-0.2.6 → claudesync-0.2.8}/src/claudesync.egg-info/requires.txt +2 -2
  17. claudesync-0.2.8/tests/test_cli.py +56 -0
  18. claudesync-0.2.8/tests/test_utils.py +54 -0
  19. claudesync-0.2.6/PKG-INFO +0 -85
  20. claudesync-0.2.6/README.md +0 -68
  21. claudesync-0.2.6/src/claudesync/__init__.py +0 -7
  22. claudesync-0.2.6/src/claudesync/api_utils.py +0 -94
  23. claudesync-0.2.6/src/claudesync/debounce.py +0 -21
  24. claudesync-0.2.6/src/claudesync/file_handler.py +0 -103
  25. claudesync-0.2.6/src/claudesync/gitignore_utils.py +0 -31
  26. claudesync-0.2.6/src/claudesync/main.py +0 -156
  27. claudesync-0.2.6/src/claudesync/manual_auth.py +0 -77
  28. claudesync-0.2.6/src/claudesync.egg-info/PKG-INFO +0 -85
  29. claudesync-0.2.6/src/claudesync.egg-info/SOURCES.txt +0 -17
  30. claudesync-0.2.6/src/claudesync.egg-info/entry_points.txt +0 -2
  31. {claudesync-0.2.6 → claudesync-0.2.8}/setup.cfg +0 -0
  32. {claudesync-0.2.6 → claudesync-0.2.8}/setup.py +0 -0
  33. {claudesync-0.2.6 → claudesync-0.2.8}/src/claudesync.egg-info/dependency_links.txt +0 -0
  34. {claudesync-0.2.6 → claudesync-0.2.8}/src/claudesync.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 jahwag
3
+ Copyright (c) 2024 Jahziah Wagner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.1
2
+ Name: claudesync
3
+ Version: 0.2.8
4
+ Summary: A tool to synchronize local files with Claude.ai projects
5
+ Author-email: Jahziah Wagner <jahziah.wagner+pypi@gmail.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/claudesync
29
+ Project-URL: Bug Tracker, https://github.com/jahwag/claudesync/issues
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Requires-Python: >=3.7
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: Click
37
+ Requires-Dist: requests
38
+ Requires-Dist: watchdog
39
+
40
+ ```
41
+ .oooooo. oooo .o8 .oooooo..o
42
+ d8P' `Y8b `888 "888 d8P' `Y8
43
+ 888 888 .oooo. oooo oooo .oooo888 .ooooo. Y88bo. oooo ooo ooo. .oo. .ooooo.
44
+ 888 888 `P )88b `888 `888 d88' `888 d88' `88b `"Y8888o. `88. .8' `888P"Y88b d88' `"Y8
45
+ 888 888 .oP"888 888 888 888 888 888ooo888 `"Y88b `88..8' 888 888 888
46
+ `88b ooo 888 d8( 888 888 888 888 888 888 .o oo .d8P `888' 888 888 888 .o8
47
+ `Y8bood8P' o888o `Y888""8o `V88V"V8P' `Y8bod88P" `Y8bod8P' 8""88888P' .8' o888o o888o `Y8bod8P'
48
+ .o..P'
49
+ `Y8P'
50
+ ```
51
+ ![License](https://img.shields.io/badge/License-MIT-blue.svg)
52
+ [![PyPI version](https://badge.fury.io/py/claudesync.svg)](https://badge.fury.io/py/claudesync)
53
+
54
+ ClaudeSync is a powerful tool designed to seamlessly synchronize your local files with [Claude.ai](https://www.anthropic.com/claude) projects.
55
+
56
+ ## Overview and Scope
57
+
58
+ ClaudeSync bridges the gap between your local development environment and Claude.ai's knowledge base. At a high level, the scope of ClaudeSync includes:
59
+
60
+ - Real-time synchronization with Claude.ai projects
61
+ - Command-line interface (CLI) for easy management
62
+ - Multiple organization and project support
63
+ - Automatic handling of file creation, modification, and deletion
64
+ - Intelligent file filtering based on .gitignore rules
65
+ - Configurable sync interval with cron job support
66
+ - Seamless integration with your existing workflow
67
+
68
+ ## Roadmap
69
+
70
+ 1. Enhanced support for large file synchronization
71
+ 2. Improved conflict resolution mechanisms
72
+ 3. GUI client for easier management
73
+ 4. Integration with popular IDEs and text editors
74
+ 5. Support for additional AI platforms beyond Claude.ai
75
+
76
+ ## Quick Start
77
+
78
+ 1. **Install ClaudeSync:**
79
+ ```bash
80
+ pip install claudesync
81
+ ```
82
+
83
+ 2. **Login to Claude.ai:**
84
+ ```bash
85
+ claudesync login claude.ai
86
+ ```
87
+
88
+ 3. **Select an organization:**
89
+ ```bash
90
+ claudesync organization select
91
+ ```
92
+
93
+ 4. **Select or create a project:**
94
+ ```bash
95
+ claudesync project select
96
+ # or
97
+ claudesync project create
98
+ ```
99
+
100
+ 5. **Start syncing:**
101
+ ```bash
102
+ claudesync sync
103
+ ```
104
+
105
+ ## Advanced Usage
106
+
107
+ ### Organization Management
108
+ - List organizations: `claudesync organization list`
109
+ - Select active organization: `claudesync organization select`
110
+
111
+ ### Project Management
112
+ - List projects: `claudesync project ls`
113
+ - Create a new project: `claudesync project create`
114
+ - Archive a project: `claudesync project archive`
115
+ - Select active project: `claudesync project select`
116
+
117
+ ### File Management
118
+ - List remote files: `claudesync ls`
119
+ - Sync files: `claudesync sync`
120
+
121
+ ### Configuration
122
+ - View current status: `claudesync status`
123
+
124
+ ### Scheduled Sync
125
+ Set up automatic syncing at regular intervals:
126
+ ```bash
127
+ claudesync schedule
128
+ ```
129
+
130
+ ## Contributing
131
+
132
+ We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information.
133
+
134
+ ## Communication Channels
135
+
136
+ - **Issues**: For bug reports and feature requests, please use our [GitHub Issues](https://github.com/jahwag/claudesync/issues).
137
+
138
+ ## License
139
+
140
+ ClaudeSync is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
141
+
142
+ ## Related Projects
143
+
144
+ - [Claude.ai](https://www.anthropic.com/claude): The AI assistant that ClaudeSync integrates with.
145
+
146
+ ---
147
+
148
+ Made with ❤️ by the ClaudeSync team
@@ -0,0 +1,109 @@
1
+ ```
2
+ .oooooo. oooo .o8 .oooooo..o
3
+ d8P' `Y8b `888 "888 d8P' `Y8
4
+ 888 888 .oooo. oooo oooo .oooo888 .ooooo. Y88bo. oooo ooo ooo. .oo. .ooooo.
5
+ 888 888 `P )88b `888 `888 d88' `888 d88' `88b `"Y8888o. `88. .8' `888P"Y88b d88' `"Y8
6
+ 888 888 .oP"888 888 888 888 888 888ooo888 `"Y88b `88..8' 888 888 888
7
+ `88b ooo 888 d8( 888 888 888 888 888 888 .o oo .d8P `888' 888 888 888 .o8
8
+ `Y8bood8P' o888o `Y888""8o `V88V"V8P' `Y8bod88P" `Y8bod8P' 8""88888P' .8' o888o o888o `Y8bod8P'
9
+ .o..P'
10
+ `Y8P'
11
+ ```
12
+ ![License](https://img.shields.io/badge/License-MIT-blue.svg)
13
+ [![PyPI version](https://badge.fury.io/py/claudesync.svg)](https://badge.fury.io/py/claudesync)
14
+
15
+ ClaudeSync is a powerful tool designed to seamlessly synchronize your local files with [Claude.ai](https://www.anthropic.com/claude) projects.
16
+
17
+ ## Overview and Scope
18
+
19
+ ClaudeSync bridges the gap between your local development environment and Claude.ai's knowledge base. At a high level, the scope of ClaudeSync includes:
20
+
21
+ - Real-time synchronization with Claude.ai projects
22
+ - Command-line interface (CLI) for easy management
23
+ - Multiple organization and project support
24
+ - Automatic handling of file creation, modification, and deletion
25
+ - Intelligent file filtering based on .gitignore rules
26
+ - Configurable sync interval with cron job support
27
+ - Seamless integration with your existing workflow
28
+
29
+ ## Roadmap
30
+
31
+ 1. Enhanced support for large file synchronization
32
+ 2. Improved conflict resolution mechanisms
33
+ 3. GUI client for easier management
34
+ 4. Integration with popular IDEs and text editors
35
+ 5. Support for additional AI platforms beyond Claude.ai
36
+
37
+ ## Quick Start
38
+
39
+ 1. **Install ClaudeSync:**
40
+ ```bash
41
+ pip install claudesync
42
+ ```
43
+
44
+ 2. **Login to Claude.ai:**
45
+ ```bash
46
+ claudesync login claude.ai
47
+ ```
48
+
49
+ 3. **Select an organization:**
50
+ ```bash
51
+ claudesync organization select
52
+ ```
53
+
54
+ 4. **Select or create a project:**
55
+ ```bash
56
+ claudesync project select
57
+ # or
58
+ claudesync project create
59
+ ```
60
+
61
+ 5. **Start syncing:**
62
+ ```bash
63
+ claudesync sync
64
+ ```
65
+
66
+ ## Advanced Usage
67
+
68
+ ### Organization Management
69
+ - List organizations: `claudesync organization list`
70
+ - Select active organization: `claudesync organization select`
71
+
72
+ ### Project Management
73
+ - List projects: `claudesync project ls`
74
+ - Create a new project: `claudesync project create`
75
+ - Archive a project: `claudesync project archive`
76
+ - Select active project: `claudesync project select`
77
+
78
+ ### File Management
79
+ - List remote files: `claudesync ls`
80
+ - Sync files: `claudesync sync`
81
+
82
+ ### Configuration
83
+ - View current status: `claudesync status`
84
+
85
+ ### Scheduled Sync
86
+ Set up automatic syncing at regular intervals:
87
+ ```bash
88
+ claudesync schedule
89
+ ```
90
+
91
+ ## Contributing
92
+
93
+ We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information.
94
+
95
+ ## Communication Channels
96
+
97
+ - **Issues**: For bug reports and feature requests, please use our [GitHub Issues](https://github.com/jahwag/claudesync/issues).
98
+
99
+ ## License
100
+
101
+ ClaudeSync is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
102
+
103
+ ## Related Projects
104
+
105
+ - [Claude.ai](https://www.anthropic.com/claude): The AI assistant that ClaudeSync integrates with.
106
+
107
+ ---
108
+
109
+ Made with ❤️ by the ClaudeSync team
@@ -4,22 +4,23 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claudesync"
7
- version = "0.2.6"
7
+ version = "0.2.8"
8
8
  authors = [
9
9
  {name = "Jahziah Wagner", email = "jahziah.wagner+pypi@gmail.com"},
10
10
  ]
11
11
  description = "A tool to synchronize local files with Claude.ai projects"
12
+ license = {file = "LICENSE"}
12
13
  readme = "README.md"
13
- requires-python = ">=3.6"
14
+ requires-python = ">=3.7"
14
15
  classifiers = [
15
16
  "Programming Language :: Python :: 3",
16
17
  "License :: OSI Approved :: MIT License",
17
18
  "Operating System :: OS Independent",
18
19
  ]
19
20
  dependencies = [
20
- "watchdog",
21
+ "Click",
21
22
  "requests",
22
- "pathspec",
23
+ "watchdog",
23
24
  ]
24
25
 
25
26
  [project.urls]
@@ -27,7 +28,8 @@ dependencies = [
27
28
  "Bug Tracker" = "https://github.com/jahwag/claudesync/issues"
28
29
 
29
30
  [project.scripts]
30
- claudesync = "claudesync.main:main"
31
+ claudesync = "claudesync.cli:cli"
31
32
 
32
33
  [tool.setuptools.packages.find]
33
34
  where = ["src"]
35
+ include = ["claudesync*"]
File without changes
@@ -0,0 +1,357 @@
1
+ import click
2
+ import sys
3
+ import os
4
+ import shutil
5
+
6
+ from functools import wraps
7
+ from crontab import CronTab
8
+ from .config_manager import ConfigManager
9
+ from .provider_factory import get_provider
10
+ from .exceptions import ConfigurationError, ProviderError
11
+ from .utils import calculate_checksum, get_local_files
12
+
13
+ def handle_errors(func):
14
+ @wraps(func)
15
+ def wrapper(*args, **kwargs):
16
+ try:
17
+ return func(*args, **kwargs)
18
+ except (ConfigurationError, ProviderError) as e:
19
+ click.echo(f"Error: {str(e)}")
20
+ return wrapper
21
+
22
+ def validate_and_get_provider(config, require_org=True):
23
+ active_provider = config.get('active_provider')
24
+ session_key = config.get('session_key')
25
+ if not active_provider or not session_key:
26
+ raise ConfigurationError("No active provider or session key. Please login first.")
27
+ if require_org and not config.get('active_organization_id'):
28
+ raise ConfigurationError("No active organization set. Please select an organization.")
29
+ return get_provider(active_provider, session_key)
30
+
31
+ def validate_and_store_local_path(config):
32
+ while True:
33
+ local_path = click.prompt("Enter the absolute path to your local project directory", type=str)
34
+ if os.path.isabs(local_path):
35
+ if os.path.exists(local_path):
36
+ config.set('local_path', local_path)
37
+ click.echo(f"Local path set to: {local_path}")
38
+ break
39
+ else:
40
+ click.echo("The specified path does not exist. Please enter a valid path.")
41
+ else:
42
+ click.echo("Please enter an absolute path.")
43
+
44
+ @click.group()
45
+ @click.pass_context
46
+ def cli(ctx):
47
+ """ClaudeSync: Synchronize local files with ai projects."""
48
+ ctx.obj = ConfigManager()
49
+
50
+ @cli.command()
51
+ @click.argument('provider', required=False)
52
+ @click.pass_obj
53
+ @handle_errors
54
+ def login(config, provider):
55
+ """
56
+ Authenticate with an AI provider.
57
+
58
+ If no provider is specified, lists available providers.
59
+ Otherwise, initiates the login process for the specified provider.
60
+ """
61
+ providers = get_provider()
62
+ if not provider:
63
+ click.echo("Available providers:\n" + "\n".join(f" - {p}" for p in providers))
64
+ return
65
+ if provider not in providers:
66
+ click.echo(f"Error: Unknown provider '{provider}'. Available: {', '.join(providers)}")
67
+ return
68
+ provider_instance = get_provider(provider)
69
+ session_key = provider_instance.login()
70
+ config.set('session_key', session_key)
71
+ config.set('active_provider', provider)
72
+ click.echo("Logged in successfully.")
73
+
74
+ @cli.command()
75
+ @click.pass_obj
76
+ def logout(config):
77
+ """
78
+ Log out from the current AI provider.
79
+
80
+ Clears all stored authentication and active selection data.
81
+ """
82
+ for key in ['session_key', 'active_provider', 'active_organization_id']:
83
+ config.set(key, None)
84
+ click.echo("Logged out successfully.")
85
+
86
+ @cli.group()
87
+ def organization():
88
+ """Manage ai organizations."""
89
+ pass
90
+
91
+ @organization.command()
92
+ @click.pass_obj
93
+ @handle_errors
94
+ def list(config):
95
+ """
96
+ List all available organizations.
97
+
98
+ Displays organizations the user has access to, including their names and IDs.
99
+ """
100
+ provider = validate_and_get_provider(config, require_org=False)
101
+ organizations = provider.get_organizations()
102
+ if not organizations:
103
+ click.echo("No organizations found.")
104
+ else:
105
+ click.echo("Available organizations:")
106
+ for idx, org in enumerate(organizations, 1):
107
+ click.echo(f" {idx}. {org['name']} (ID: {org['id']})")
108
+
109
+ @organization.command()
110
+ @click.pass_obj
111
+ @handle_errors
112
+ def select(config):
113
+ """
114
+ Set the active organization.
115
+
116
+ Prompts the user to choose from available organizations and sets it as active.
117
+ """
118
+ provider = validate_and_get_provider(config, require_org=False)
119
+ organizations = provider.get_organizations()
120
+ if not organizations:
121
+ click.echo("No organizations found.")
122
+ return
123
+ click.echo("Available organizations:")
124
+ for idx, org in enumerate(organizations, 1):
125
+ click.echo(f" {idx}. {org['name']} (ID: {org['id']})")
126
+ selection = click.prompt("Enter the number of the organization to select", type=int)
127
+ if 1 <= selection <= len(organizations):
128
+ selected_org = organizations[selection - 1]
129
+ config.set('active_organization_id', selected_org['id'])
130
+ click.echo(f"Selected organization: {selected_org['name']} (ID: {selected_org['id']})")
131
+ else:
132
+ click.echo("Invalid selection. Please try again.")
133
+
134
+ @cli.group()
135
+ def project():
136
+ """Manage ai projects within the active organization."""
137
+ pass
138
+
139
+ @project.command()
140
+ @click.pass_obj
141
+ @handle_errors
142
+ def create(config):
143
+ """
144
+ Create a new project in the active organization.
145
+
146
+ Prompts for project title and description, then creates the project and sets it as active.
147
+ Also prompts for the local directory to sync with the new project.
148
+ """
149
+ provider = validate_and_get_provider(config)
150
+ active_organization_id = config.get('active_organization_id')
151
+
152
+ title = click.prompt("Enter the project title")
153
+ description = click.prompt("Enter the project description (optional)", default="")
154
+
155
+ try:
156
+ new_project = provider.create_project(active_organization_id, title, description)
157
+ click.echo(f"Project '{new_project['name']}' (uuid: {new_project['uuid']}) has been created successfully.")
158
+
159
+ config.set('active_project_id', new_project['uuid'])
160
+ config.set('active_project_name', new_project['name'])
161
+ click.echo(f"Active project set to: {new_project['name']} (uuid: {new_project['uuid']})")
162
+
163
+ validate_and_store_local_path(config)
164
+
165
+ except ProviderError as e:
166
+ click.echo(f"Failed to create project: {str(e)}")
167
+
168
+ @project.command()
169
+ @click.pass_obj
170
+ @handle_errors
171
+ def archive(config):
172
+ """
173
+ Archive an existing project.
174
+
175
+ Lists active projects and allows the user to select one for archiving.
176
+ Archived projects are no longer available for syncing but can be viewed with the --all flag.
177
+ """
178
+ provider = validate_and_get_provider(config)
179
+ active_organization_id = config.get('active_organization_id')
180
+ projects = provider.get_projects(active_organization_id, include_archived=False)
181
+ if not projects:
182
+ click.echo("No active projects found.")
183
+ return
184
+ click.echo("Available projects to archive:")
185
+ for idx, project in enumerate(projects, 1):
186
+ click.echo(f" {idx}. {project['name']} (ID: {project['id']})")
187
+ selection = click.prompt("Enter the number of the project to archive", type=int)
188
+ if 1 <= selection <= len(projects):
189
+ selected_project = projects[selection - 1]
190
+ if click.confirm(f"Are you sure you want to archive '{selected_project['name']}'?"):
191
+ provider.archive_project(active_organization_id, selected_project['id'])
192
+ click.echo(f"Project '{selected_project['name']}' has been archived.")
193
+ else:
194
+ click.echo("Invalid selection. Please try again.")
195
+
196
+ @project.command()
197
+ @click.pass_obj
198
+ @handle_errors
199
+ def select(config):
200
+ """
201
+ Set the active project for syncing.
202
+
203
+ Lists available projects in the active organization and prompts user to select one.
204
+ Also prompts for the local directory to sync with the selected project.
205
+ """
206
+ provider = validate_and_get_provider(config)
207
+ active_organization_id = config.get('active_organization_id')
208
+ projects = provider.get_projects(active_organization_id, include_archived=False)
209
+ if not projects:
210
+ click.echo("No active projects found.")
211
+ return
212
+ click.echo("Available projects:")
213
+ for idx, project in enumerate(projects, 1):
214
+ click.echo(f" {idx}. {project['name']} (ID: {project['id']})")
215
+ selection = click.prompt("Enter the number of the project to select", type=int)
216
+ if 1 <= selection <= len(projects):
217
+ selected_project = projects[selection - 1]
218
+ config.set('active_project_id', selected_project['id'])
219
+ config.set('active_project_name', selected_project['name'])
220
+ click.echo(f"Selected project: {selected_project['name']} (ID: {selected_project['id']})")
221
+
222
+ validate_and_store_local_path(config)
223
+ else:
224
+ click.echo("Invalid selection. Please try again.")
225
+
226
+ @project.command()
227
+ @click.option('-a', '--all', 'show_all', is_flag=True, help="Include archived projects in the list")
228
+ @click.pass_obj
229
+ @handle_errors
230
+ def ls(config, show_all):
231
+ """
232
+ List all projects in the active organization.
233
+
234
+ Displays project names and IDs. Use --all flag to include archived projects.
235
+ """
236
+ provider = validate_and_get_provider(config)
237
+ active_organization_id = config.get('active_organization_id')
238
+ projects = provider.get_projects(active_organization_id, include_archived=show_all)
239
+ if not projects:
240
+ click.echo("No projects found.")
241
+ else:
242
+ click.echo("Remote projects:")
243
+ for project in projects:
244
+ status = " (Archived)" if project.get('archived_at') else ""
245
+ click.echo(f" - {project['name']} (ID: {project['id']}){status}")
246
+
247
+ @cli.command()
248
+ @click.pass_obj
249
+ def status(config):
250
+ """
251
+ Display current configuration status.
252
+
253
+ Shows active provider, organization, project, local sync path, and log level.
254
+ """
255
+ for key in ['active_provider', 'active_organization_id', 'active_project_id', 'active_project_name', 'local_path', 'log_level']:
256
+ value = config.get(key)
257
+ click.echo(f"{key.replace('_', ' ').capitalize()}: {value or 'Not set'}")
258
+
259
+ @cli.command()
260
+ @click.pass_obj
261
+ @handle_errors
262
+ def ls(config):
263
+ """
264
+ List files in the active remote project.
265
+
266
+ Displays file names, IDs, and creation dates for all files in the current ai project.
267
+ """
268
+ provider = validate_and_get_provider(config)
269
+ active_organization_id = config.get('active_organization_id')
270
+ active_project_id = config.get('active_project_id')
271
+ files = provider.list_files(active_organization_id, active_project_id)
272
+ if not files:
273
+ click.echo("No files found in the active project.")
274
+ else:
275
+ click.echo(f"Files in project '{config.get('active_project_name')}' (ID: {active_project_id}):")
276
+ for file in files:
277
+ click.echo(f" - {file['file_name']} (ID: {file['uuid']}, Created: {file['created_at']})")
278
+
279
+ @cli.command()
280
+ @click.pass_obj
281
+ @handle_errors
282
+ def sync(config):
283
+ """
284
+ Synchronize local files with the active remote project.
285
+
286
+ Compares local and remote files, uploading new or modified local files and updating changed remote files.
287
+ """
288
+ provider = validate_and_get_provider(config)
289
+ active_organization_id = config.get('active_organization_id')
290
+ active_project_id = config.get('active_project_id')
291
+ local_path = config.get('local_path')
292
+
293
+ if not local_path:
294
+ click.echo("No local path set. Please select or create a project to set the local path.")
295
+ sys.exit(1)
296
+
297
+ if not os.path.exists(local_path):
298
+ click.echo(f"The configured local path does not exist: {local_path}")
299
+ click.echo("Please update the local path by selecting or creating a project.")
300
+ sys.exit(1)
301
+
302
+ remote_files = provider.list_files(active_organization_id, active_project_id)
303
+ local_files = get_local_files(local_path)
304
+
305
+ for local_file, local_checksum in local_files.items():
306
+ remote_file = next((rf for rf in remote_files if rf['file_name'] == local_file), None)
307
+ if remote_file:
308
+ remote_checksum = calculate_checksum(remote_file['content'])
309
+ if local_checksum != remote_checksum:
310
+ click.echo(f"Updating {local_file} on remote...")
311
+ for rf in remote_files:
312
+ if rf['file_name'] == local_file:
313
+ provider.delete_file(active_organization_id, active_project_id, rf['uuid'])
314
+ with open(os.path.join(local_path, local_file), 'r', encoding='utf-8') as file:
315
+ content = file.read()
316
+ provider.upload_file(active_organization_id, active_project_id, local_file, content)
317
+ else:
318
+ click.echo(f"Uploading new file {local_file} to remote...")
319
+ with open(os.path.join(local_path, local_file), 'r', encoding='utf-8') as file:
320
+ content = file.read()
321
+ provider.upload_file(active_organization_id, active_project_id, local_file, content)
322
+
323
+ click.echo("Sync completed successfully.")
324
+
325
+ @cli.command()
326
+ @click.pass_obj
327
+ @click.option('--interval', type=int, default=5, prompt='Enter sync interval in minutes')
328
+ @handle_errors
329
+ def schedule(config, interval):
330
+ """
331
+ Set up automated synchronization at regular intervals.
332
+
333
+ Creates a cron job (Unix/Linux/macOS) or scheduled task (Windows) to run sync command periodically.
334
+ Prompts for sync interval in minutes.
335
+ """
336
+ claudesync_path = shutil.which('claudesync')
337
+ if not claudesync_path:
338
+ click.echo("Error: claudesync not found in PATH. Please ensure it's installed correctly.")
339
+ sys.exit(1)
340
+
341
+ if sys.platform.startswith('win'):
342
+ click.echo("Windows Task Scheduler setup:")
343
+ command = f'schtasks /create /tn "ClaudeSync" /tr "{claudesync_path} sync" /sc minute /mo {interval}'
344
+ click.echo(f"Run this command to create the task:\n{command}")
345
+ click.echo("\nTo remove the task, run: schtasks /delete /tn \"ClaudeSync\" /f")
346
+ else:
347
+ # Unix-like systems (Linux, macOS)
348
+ cron = CronTab(user=True)
349
+ job = cron.new(command=f'{claudesync_path} sync')
350
+ job.minute.every(interval)
351
+
352
+ cron.write()
353
+ click.echo(f"Cron job created successfully! It will run every {interval} minutes.")
354
+ click.echo("\nTo remove the cron job, run: crontab -e and remove the line for ClaudeSync")
355
+
356
+ if __name__ == '__main__':
357
+ cli()
@@ -0,0 +1,30 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ class ConfigManager:
6
+ def __init__(self):
7
+ self.config_dir = Path.home() / '.claudesync'
8
+ self.config_file = self.config_dir / 'config.json'
9
+ self.config = self._load_config()
10
+
11
+ def _load_config(self):
12
+ if not self.config_file.exists():
13
+ self.config_dir.mkdir(parents=True, exist_ok=True)
14
+ return {'log_level': 'INFO'} # Default log level
15
+ with open(self.config_file, 'r') as f:
16
+ config = json.load(f)
17
+ if 'log_level' not in config:
18
+ config['log_level'] = 'INFO' # Set default if not present
19
+ return config
20
+
21
+ def _save_config(self):
22
+ with open(self.config_file, 'w') as f:
23
+ json.dump(self.config, f, indent=2)
24
+
25
+ def get(self, key, default=None):
26
+ return self.config.get(key, default)
27
+
28
+ def set(self, key, value):
29
+ self.config[key] = value
30
+ self._save_config()