claudesync 0.2.8__tar.gz → 0.2.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {claudesync-0.2.8/src/claudesync.egg-info → claudesync-0.2.9}/PKG-INFO +8 -3
- {claudesync-0.2.8 → claudesync-0.2.9}/README.md +1 -1
- {claudesync-0.2.8 → claudesync-0.2.9}/pyproject.toml +15 -4
- {claudesync-0.2.8 → claudesync-0.2.9}/setup.py +1 -1
- claudesync-0.2.9/src/claudesync/cli/__init__.py +3 -0
- claudesync-0.2.9/src/claudesync/cli/auth.py +34 -0
- claudesync-0.2.9/src/claudesync/cli/main.py +60 -0
- claudesync-0.2.9/src/claudesync/cli/organization.py +47 -0
- claudesync-0.2.9/src/claudesync/cli/project.py +122 -0
- claudesync-0.2.9/src/claudesync/cli/sync.py +120 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync/config_manager.py +9 -9
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync/exceptions.py +3 -2
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync/provider_factory.py +4 -2
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync/providers/claude_ai.py +55 -43
- claudesync-0.2.9/src/claudesync/utils.py +122 -0
- {claudesync-0.2.8 → claudesync-0.2.9/src/claudesync.egg-info}/PKG-INFO +8 -3
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync.egg-info/SOURCES.txt +11 -2
- claudesync-0.2.9/src/claudesync.egg-info/entry_points.txt +2 -0
- claudesync-0.2.9/src/claudesync.egg-info/requires.txt +8 -0
- claudesync-0.2.9/tests/test_auth.py +29 -0
- claudesync-0.2.9/tests/test_main.py +29 -0
- claudesync-0.2.9/tests/test_organization.py +81 -0
- claudesync-0.2.9/tests/test_project.py +128 -0
- claudesync-0.2.9/tests/test_sync.py +147 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/tests/test_utils.py +22 -17
- claudesync-0.2.8/src/claudesync/cli.py +0 -357
- claudesync-0.2.8/src/claudesync/utils.py +0 -57
- claudesync-0.2.8/src/claudesync.egg-info/entry_points.txt +0 -2
- claudesync-0.2.8/src/claudesync.egg-info/requires.txt +0 -3
- claudesync-0.2.8/tests/test_cli.py +0 -56
- {claudesync-0.2.8 → claudesync-0.2.9}/LICENSE +0 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/setup.cfg +0 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync/__init__.py +0 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync/providers/__init__.py +0 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync.egg-info/dependency_links.txt +0 -0
- {claudesync-0.2.8 → claudesync-0.2.9}/src/claudesync.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: claudesync
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
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
|
|
@@ -35,7 +35,12 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
License-File: LICENSE
|
|
36
36
|
Requires-Dist: Click
|
|
37
37
|
Requires-Dist: requests
|
|
38
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: pathspec
|
|
39
|
+
Requires-Dist: crontab
|
|
40
|
+
Requires-Dist: setuptools
|
|
41
|
+
Requires-Dist: pytest
|
|
42
|
+
Requires-Dist: pytest-cov
|
|
43
|
+
Requires-Dist: click_completion
|
|
39
44
|
|
|
40
45
|
```
|
|
41
46
|
.oooooo. oooo .o8 .oooooo..o
|
|
@@ -105,7 +110,7 @@ ClaudeSync bridges the gap between your local development environment and Claude
|
|
|
105
110
|
## Advanced Usage
|
|
106
111
|
|
|
107
112
|
### Organization Management
|
|
108
|
-
- List organizations: `claudesync organization
|
|
113
|
+
- List organizations: `claudesync organization ls`
|
|
109
114
|
- Select active organization: `claudesync organization select`
|
|
110
115
|
|
|
111
116
|
### Project Management
|
|
@@ -66,7 +66,7 @@ ClaudeSync bridges the gap between your local development environment and Claude
|
|
|
66
66
|
## Advanced Usage
|
|
67
67
|
|
|
68
68
|
### Organization Management
|
|
69
|
-
- List organizations: `claudesync organization
|
|
69
|
+
- List organizations: `claudesync organization ls`
|
|
70
70
|
- Select active organization: `claudesync organization select`
|
|
71
71
|
|
|
72
72
|
### Project Management
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claudesync"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.9"
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "Jahziah Wagner", email = "jahziah.wagner+pypi@gmail.com"},
|
|
10
10
|
]
|
|
@@ -20,7 +20,12 @@ classifiers = [
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"Click",
|
|
22
22
|
"requests",
|
|
23
|
-
"
|
|
23
|
+
"pathspec",
|
|
24
|
+
"crontab",
|
|
25
|
+
"setuptools",
|
|
26
|
+
"pytest",
|
|
27
|
+
"pytest-cov",
|
|
28
|
+
"click_completion",
|
|
24
29
|
]
|
|
25
30
|
|
|
26
31
|
[project.urls]
|
|
@@ -28,8 +33,14 @@ dependencies = [
|
|
|
28
33
|
"Bug Tracker" = "https://github.com/jahwag/claudesync/issues"
|
|
29
34
|
|
|
30
35
|
[project.scripts]
|
|
31
|
-
claudesync = "claudesync.cli:cli"
|
|
36
|
+
claudesync = "claudesync.cli.main:cli"
|
|
32
37
|
|
|
33
38
|
[tool.setuptools.packages.find]
|
|
34
39
|
where = ["src"]
|
|
35
|
-
include = ["claudesync*"]
|
|
40
|
+
include = ["claudesync*"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
python_files = "test_*.py"
|
|
46
|
+
addopts = "-v --cov=claudesync --cov-report=term-missing"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from claudesync.provider_factory import get_provider
|
|
3
|
+
from ..utils import handle_errors
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.argument("provider", required=False)
|
|
8
|
+
@click.pass_obj
|
|
9
|
+
@handle_errors
|
|
10
|
+
def login(config, provider):
|
|
11
|
+
"""Authenticate with an AI provider."""
|
|
12
|
+
providers = get_provider()
|
|
13
|
+
if not provider:
|
|
14
|
+
click.echo("Available providers:\n" + "\n".join(f" - {p}" for p in providers))
|
|
15
|
+
return
|
|
16
|
+
if provider not in providers:
|
|
17
|
+
click.echo(
|
|
18
|
+
f"Error: Unknown provider '{provider}'. Available: {', '.join(providers)}"
|
|
19
|
+
)
|
|
20
|
+
return
|
|
21
|
+
provider_instance = get_provider(provider)
|
|
22
|
+
session_key = provider_instance.login()
|
|
23
|
+
config.set("session_key", session_key)
|
|
24
|
+
config.set("active_provider", provider)
|
|
25
|
+
click.echo("Logged in successfully.")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command()
|
|
29
|
+
@click.pass_obj
|
|
30
|
+
def logout(config):
|
|
31
|
+
"""Log out from the current AI provider."""
|
|
32
|
+
for key in ["session_key", "active_provider", "active_organization_id"]:
|
|
33
|
+
config.set(key, None)
|
|
34
|
+
click.echo("Logged out successfully.")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from claudesync.config_manager import ConfigManager
|
|
3
|
+
import click_completion
|
|
4
|
+
import click_completion.core
|
|
5
|
+
|
|
6
|
+
# Import commands from other CLI files
|
|
7
|
+
from .auth import login, logout
|
|
8
|
+
from .organization import organization
|
|
9
|
+
from .project import project
|
|
10
|
+
from .sync import ls, sync, schedule
|
|
11
|
+
|
|
12
|
+
click_completion.init()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
@click.pass_context
|
|
17
|
+
def cli(ctx):
|
|
18
|
+
"""ClaudeSync: Synchronize local files with ai projects."""
|
|
19
|
+
ctx.obj = ConfigManager()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cli.command()
|
|
23
|
+
@click.argument(
|
|
24
|
+
"shell", required=False, type=click.Choice(["bash", "zsh", "fish", "powershell"])
|
|
25
|
+
)
|
|
26
|
+
def install_completion(shell):
|
|
27
|
+
"""Install completion for the specified shell."""
|
|
28
|
+
if shell is None:
|
|
29
|
+
shell = click_completion.get_auto_shell()
|
|
30
|
+
click.echo("Shell is set to '%s'" % shell)
|
|
31
|
+
click_completion.install(shell=shell)
|
|
32
|
+
click.echo("Completion installed.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@cli.command()
|
|
36
|
+
@click.pass_obj
|
|
37
|
+
def status(config):
|
|
38
|
+
"""Display current configuration status."""
|
|
39
|
+
for key in [
|
|
40
|
+
"active_provider",
|
|
41
|
+
"active_organization_id",
|
|
42
|
+
"active_project_id",
|
|
43
|
+
"active_project_name",
|
|
44
|
+
"local_path",
|
|
45
|
+
"log_level",
|
|
46
|
+
]:
|
|
47
|
+
value = config.get(key)
|
|
48
|
+
click.echo(f"{key.replace('_', ' ').capitalize()}: {value or 'Not set'}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
cli.add_command(login)
|
|
52
|
+
cli.add_command(logout)
|
|
53
|
+
cli.add_command(organization)
|
|
54
|
+
cli.add_command(project)
|
|
55
|
+
cli.add_command(ls)
|
|
56
|
+
cli.add_command(sync)
|
|
57
|
+
cli.add_command(schedule)
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
cli()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from ..utils import handle_errors, validate_and_get_provider
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@click.group()
|
|
6
|
+
def organization():
|
|
7
|
+
"""Manage ai organizations."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@organization.command()
|
|
12
|
+
@click.pass_obj
|
|
13
|
+
@handle_errors
|
|
14
|
+
def ls(config):
|
|
15
|
+
"""List all available organizations."""
|
|
16
|
+
provider = validate_and_get_provider(config, require_org=False)
|
|
17
|
+
organizations = provider.get_organizations()
|
|
18
|
+
if not organizations:
|
|
19
|
+
click.echo("No organizations found.")
|
|
20
|
+
else:
|
|
21
|
+
click.echo("Available organizations:")
|
|
22
|
+
for idx, org in enumerate(organizations, 1):
|
|
23
|
+
click.echo(f" {idx}. {org['name']} (ID: {org['id']})")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@organization.command()
|
|
27
|
+
@click.pass_obj
|
|
28
|
+
@handle_errors
|
|
29
|
+
def select(config):
|
|
30
|
+
"""Set the active organization."""
|
|
31
|
+
provider = validate_and_get_provider(config, require_org=False)
|
|
32
|
+
organizations = provider.get_organizations()
|
|
33
|
+
if not organizations:
|
|
34
|
+
click.echo("No organizations found.")
|
|
35
|
+
return
|
|
36
|
+
click.echo("Available organizations:")
|
|
37
|
+
for idx, org in enumerate(organizations, 1):
|
|
38
|
+
click.echo(f" {idx}. {org['name']} (ID: {org['id']})")
|
|
39
|
+
selection = click.prompt("Enter the number of the organization to select", type=int)
|
|
40
|
+
if 1 <= selection <= len(organizations):
|
|
41
|
+
selected_org = organizations[selection - 1]
|
|
42
|
+
config.set("active_organization_id", selected_org["id"])
|
|
43
|
+
click.echo(
|
|
44
|
+
f"Selected organization: {selected_org['name']} (ID: {selected_org['id']})"
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
click.echo("Invalid selection. Please try again.")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from claudesync.exceptions import ProviderError
|
|
3
|
+
from ..utils import (
|
|
4
|
+
handle_errors,
|
|
5
|
+
validate_and_get_provider,
|
|
6
|
+
validate_and_store_local_path,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def project():
|
|
12
|
+
"""Manage ai projects within the active organization."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@project.command()
|
|
17
|
+
@click.pass_obj
|
|
18
|
+
@handle_errors
|
|
19
|
+
def create(config):
|
|
20
|
+
"""Create a new project in the active organization."""
|
|
21
|
+
provider = validate_and_get_provider(config)
|
|
22
|
+
active_organization_id = config.get("active_organization_id")
|
|
23
|
+
|
|
24
|
+
title = click.prompt("Enter the project title")
|
|
25
|
+
description = click.prompt("Enter the project description (optional)", default="")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
new_project = provider.create_project(
|
|
29
|
+
active_organization_id, title, description
|
|
30
|
+
)
|
|
31
|
+
click.echo(
|
|
32
|
+
f"Project '{new_project['name']}' (uuid: {new_project['uuid']}) has been created successfully."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
config.set("active_project_id", new_project["uuid"])
|
|
36
|
+
config.set("active_project_name", new_project["name"])
|
|
37
|
+
click.echo(
|
|
38
|
+
f"Active project set to: {new_project['name']} (uuid: {new_project['uuid']})"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
validate_and_store_local_path(config)
|
|
42
|
+
|
|
43
|
+
except ProviderError as e:
|
|
44
|
+
click.echo(f"Failed to create project: {str(e)}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@project.command()
|
|
48
|
+
@click.pass_obj
|
|
49
|
+
@handle_errors
|
|
50
|
+
def archive(config):
|
|
51
|
+
"""Archive an existing project."""
|
|
52
|
+
provider = validate_and_get_provider(config)
|
|
53
|
+
active_organization_id = config.get("active_organization_id")
|
|
54
|
+
projects = provider.get_projects(active_organization_id, include_archived=False)
|
|
55
|
+
if not projects:
|
|
56
|
+
click.echo("No active projects found.")
|
|
57
|
+
return
|
|
58
|
+
click.echo("Available projects to archive:")
|
|
59
|
+
for idx, project in enumerate(projects, 1):
|
|
60
|
+
click.echo(f" {idx}. {project['name']} (ID: {project['id']})")
|
|
61
|
+
selection = click.prompt("Enter the number of the project to archive", type=int)
|
|
62
|
+
if 1 <= selection <= len(projects):
|
|
63
|
+
selected_project = projects[selection - 1]
|
|
64
|
+
if click.confirm(
|
|
65
|
+
f"Are you sure you want to archive '{selected_project['name']}'?"
|
|
66
|
+
):
|
|
67
|
+
provider.archive_project(active_organization_id, selected_project["id"])
|
|
68
|
+
click.echo(f"Project '{selected_project['name']}' has been archived.")
|
|
69
|
+
else:
|
|
70
|
+
click.echo("Invalid selection. Please try again.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@project.command()
|
|
74
|
+
@click.pass_obj
|
|
75
|
+
@handle_errors
|
|
76
|
+
def select(config):
|
|
77
|
+
"""Set the active project for syncing."""
|
|
78
|
+
provider = validate_and_get_provider(config)
|
|
79
|
+
active_organization_id = config.get("active_organization_id")
|
|
80
|
+
projects = provider.get_projects(active_organization_id, include_archived=False)
|
|
81
|
+
if not projects:
|
|
82
|
+
click.echo("No active projects found.")
|
|
83
|
+
return
|
|
84
|
+
click.echo("Available projects:")
|
|
85
|
+
for idx, project in enumerate(projects, 1):
|
|
86
|
+
click.echo(f" {idx}. {project['name']} (ID: {project['id']})")
|
|
87
|
+
selection = click.prompt("Enter the number of the project to select", type=int)
|
|
88
|
+
if 1 <= selection <= len(projects):
|
|
89
|
+
selected_project = projects[selection - 1]
|
|
90
|
+
config.set("active_project_id", selected_project["id"])
|
|
91
|
+
config.set("active_project_name", selected_project["name"])
|
|
92
|
+
click.echo(
|
|
93
|
+
f"Selected project: {selected_project['name']} (ID: {selected_project['id']})"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
validate_and_store_local_path(config)
|
|
97
|
+
else:
|
|
98
|
+
click.echo("Invalid selection. Please try again.")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@project.command()
|
|
102
|
+
@click.option(
|
|
103
|
+
"-a",
|
|
104
|
+
"--all",
|
|
105
|
+
"show_all",
|
|
106
|
+
is_flag=True,
|
|
107
|
+
help="Include archived projects in the list",
|
|
108
|
+
)
|
|
109
|
+
@click.pass_obj
|
|
110
|
+
@handle_errors
|
|
111
|
+
def ls(config, show_all):
|
|
112
|
+
"""List all projects in the active organization."""
|
|
113
|
+
provider = validate_and_get_provider(config)
|
|
114
|
+
active_organization_id = config.get("active_organization_id")
|
|
115
|
+
projects = provider.get_projects(active_organization_id, include_archived=show_all)
|
|
116
|
+
if not projects:
|
|
117
|
+
click.echo("No projects found.")
|
|
118
|
+
else:
|
|
119
|
+
click.echo("Remote projects:")
|
|
120
|
+
for project in projects:
|
|
121
|
+
status = " (Archived)" if project.get("archived_at") else ""
|
|
122
|
+
click.echo(f" - {project['name']} (ID: {project['id']}){status}")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from crontab import CronTab
|
|
6
|
+
from claudesync.utils import calculate_checksum, get_local_files
|
|
7
|
+
from ..utils import handle_errors, validate_and_get_provider
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.pass_obj
|
|
12
|
+
@handle_errors
|
|
13
|
+
def ls(config):
|
|
14
|
+
"""List files in the active remote project."""
|
|
15
|
+
provider = validate_and_get_provider(config)
|
|
16
|
+
active_organization_id = config.get("active_organization_id")
|
|
17
|
+
active_project_id = config.get("active_project_id")
|
|
18
|
+
files = provider.list_files(active_organization_id, active_project_id)
|
|
19
|
+
if not files:
|
|
20
|
+
click.echo("No files found in the active project.")
|
|
21
|
+
else:
|
|
22
|
+
click.echo(
|
|
23
|
+
f"Files in project '{config.get('active_project_name')}' (ID: {active_project_id}):"
|
|
24
|
+
)
|
|
25
|
+
for file in files:
|
|
26
|
+
click.echo(
|
|
27
|
+
f" - {file['file_name']} (ID: {file['uuid']}, Created: {file['created_at']})"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.command()
|
|
32
|
+
@click.pass_obj
|
|
33
|
+
@handle_errors
|
|
34
|
+
def sync(config):
|
|
35
|
+
"""Synchronize local files with the active remote project."""
|
|
36
|
+
provider = validate_and_get_provider(config)
|
|
37
|
+
active_organization_id = config.get("active_organization_id")
|
|
38
|
+
active_project_id = config.get("active_project_id")
|
|
39
|
+
local_path = config.get("local_path")
|
|
40
|
+
|
|
41
|
+
if not local_path:
|
|
42
|
+
click.echo(
|
|
43
|
+
"No local path set. Please select or create a project to set the local path."
|
|
44
|
+
)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
if not os.path.exists(local_path):
|
|
48
|
+
click.echo(f"The configured local path does not exist: {local_path}")
|
|
49
|
+
click.echo("Please update the local path by selecting or creating a project.")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
remote_files = provider.list_files(active_organization_id, active_project_id)
|
|
53
|
+
local_files = get_local_files(local_path)
|
|
54
|
+
|
|
55
|
+
for local_file, local_checksum in local_files.items():
|
|
56
|
+
remote_file = next(
|
|
57
|
+
(rf for rf in remote_files if rf["file_name"] == local_file), None
|
|
58
|
+
)
|
|
59
|
+
if remote_file:
|
|
60
|
+
remote_checksum = calculate_checksum(remote_file["content"])
|
|
61
|
+
if local_checksum != remote_checksum:
|
|
62
|
+
click.echo(f"Updating {local_file} on remote...")
|
|
63
|
+
for rf in remote_files:
|
|
64
|
+
if rf["file_name"] == local_file:
|
|
65
|
+
provider.delete_file(
|
|
66
|
+
active_organization_id, active_project_id, rf["uuid"]
|
|
67
|
+
)
|
|
68
|
+
with open(
|
|
69
|
+
os.path.join(local_path, local_file), "r", encoding="utf-8"
|
|
70
|
+
) as file:
|
|
71
|
+
content = file.read()
|
|
72
|
+
provider.upload_file(
|
|
73
|
+
active_organization_id, active_project_id, local_file, content
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
click.echo(f"Uploading new file {local_file} to remote...")
|
|
77
|
+
with open(
|
|
78
|
+
os.path.join(local_path, local_file), "r", encoding="utf-8"
|
|
79
|
+
) as file:
|
|
80
|
+
content = file.read()
|
|
81
|
+
provider.upload_file(
|
|
82
|
+
active_organization_id, active_project_id, local_file, content
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
click.echo("Sync completed successfully.")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@click.command()
|
|
89
|
+
@click.pass_obj
|
|
90
|
+
@click.option(
|
|
91
|
+
"--interval", type=int, default=5, prompt="Enter sync interval in minutes"
|
|
92
|
+
)
|
|
93
|
+
@handle_errors
|
|
94
|
+
def schedule(config, interval):
|
|
95
|
+
"""Set up automated synchronization at regular intervals."""
|
|
96
|
+
claudesync_path = shutil.which("claudesync")
|
|
97
|
+
if not claudesync_path:
|
|
98
|
+
click.echo(
|
|
99
|
+
"Error: claudesync not found in PATH. Please ensure it's installed correctly."
|
|
100
|
+
)
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
|
|
103
|
+
if sys.platform.startswith("win"):
|
|
104
|
+
click.echo("Windows Task Scheduler setup:")
|
|
105
|
+
command = f'schtasks /create /tn "ClaudeSync" /tr "{claudesync_path} sync" /sc minute /mo {interval}'
|
|
106
|
+
click.echo(f"Run this command to create the task:\n{command}")
|
|
107
|
+
click.echo('\nTo remove the task, run: schtasks /delete /tn "ClaudeSync" /f')
|
|
108
|
+
else:
|
|
109
|
+
# Unix-like systems (Linux, macOS)
|
|
110
|
+
cron = CronTab(user=True)
|
|
111
|
+
job = cron.new(command=f"{claudesync_path} sync")
|
|
112
|
+
job.minute.every(interval)
|
|
113
|
+
|
|
114
|
+
cron.write()
|
|
115
|
+
click.echo(
|
|
116
|
+
f"Cron job created successfully! It will run every {interval} minutes."
|
|
117
|
+
)
|
|
118
|
+
click.echo(
|
|
119
|
+
"\nTo remove the cron job, run: crontab -e and remove the line for ClaudeSync"
|
|
120
|
+
)
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
4
|
+
|
|
5
5
|
class ConfigManager:
|
|
6
6
|
def __init__(self):
|
|
7
|
-
self.config_dir = Path.home() /
|
|
8
|
-
self.config_file = self.config_dir /
|
|
7
|
+
self.config_dir = Path.home() / ".claudesync"
|
|
8
|
+
self.config_file = self.config_dir / "config.json"
|
|
9
9
|
self.config = self._load_config()
|
|
10
10
|
|
|
11
11
|
def _load_config(self):
|
|
12
12
|
if not self.config_file.exists():
|
|
13
13
|
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
14
|
-
return {
|
|
15
|
-
with open(self.config_file,
|
|
14
|
+
return {"log_level": "INFO"} # Default log level
|
|
15
|
+
with open(self.config_file, "r") as f:
|
|
16
16
|
config = json.load(f)
|
|
17
|
-
if
|
|
18
|
-
config[
|
|
17
|
+
if "log_level" not in config:
|
|
18
|
+
config["log_level"] = "INFO" # Set default if not present
|
|
19
19
|
return config
|
|
20
20
|
|
|
21
21
|
def _save_config(self):
|
|
22
|
-
with open(self.config_file,
|
|
22
|
+
with open(self.config_file, "w") as f:
|
|
23
23
|
json.dump(self.config, f, indent=2)
|
|
24
24
|
|
|
25
25
|
def get(self, key, default=None):
|
|
@@ -27,4 +27,4 @@ class ConfigManager:
|
|
|
27
27
|
|
|
28
28
|
def set(self, key, value):
|
|
29
29
|
self.config[key] = value
|
|
30
|
-
self._save_config()
|
|
30
|
+
self._save_config()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from .providers.claude_ai import ClaudeAIProvider
|
|
2
|
+
|
|
2
3
|
# Import other providers here as they are added
|
|
3
4
|
|
|
5
|
+
|
|
4
6
|
def get_provider(provider_name=None, session_key=None):
|
|
5
7
|
providers = {
|
|
6
|
-
|
|
8
|
+
"claude.ai": ClaudeAIProvider,
|
|
7
9
|
# Add other providers here as they are implemented
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -14,4 +16,4 @@ def get_provider(provider_name=None, session_key=None):
|
|
|
14
16
|
if provider_class is None:
|
|
15
17
|
raise ValueError(f"Unsupported provider: {provider_name}")
|
|
16
18
|
|
|
17
|
-
return provider_class(session_key) if session_key else provider_class()
|
|
19
|
+
return provider_class(session_key) if session_key else provider_class()
|