devsync 0.5.5__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.
Files changed (84) hide show
  1. aiconfigkit/__init__.py +0 -0
  2. aiconfigkit/__main__.py +6 -0
  3. aiconfigkit/ai_tools/__init__.py +0 -0
  4. aiconfigkit/ai_tools/base.py +236 -0
  5. aiconfigkit/ai_tools/capability_registry.py +262 -0
  6. aiconfigkit/ai_tools/claude.py +91 -0
  7. aiconfigkit/ai_tools/claude_desktop.py +97 -0
  8. aiconfigkit/ai_tools/cline.py +92 -0
  9. aiconfigkit/ai_tools/copilot.py +92 -0
  10. aiconfigkit/ai_tools/cursor.py +109 -0
  11. aiconfigkit/ai_tools/detector.py +169 -0
  12. aiconfigkit/ai_tools/kiro.py +85 -0
  13. aiconfigkit/ai_tools/mcp_syncer.py +291 -0
  14. aiconfigkit/ai_tools/roo.py +110 -0
  15. aiconfigkit/ai_tools/translator.py +390 -0
  16. aiconfigkit/ai_tools/winsurf.py +102 -0
  17. aiconfigkit/cli/__init__.py +0 -0
  18. aiconfigkit/cli/delete.py +118 -0
  19. aiconfigkit/cli/download.py +274 -0
  20. aiconfigkit/cli/install.py +237 -0
  21. aiconfigkit/cli/install_new.py +937 -0
  22. aiconfigkit/cli/list.py +275 -0
  23. aiconfigkit/cli/main.py +454 -0
  24. aiconfigkit/cli/mcp_configure.py +232 -0
  25. aiconfigkit/cli/mcp_install.py +166 -0
  26. aiconfigkit/cli/mcp_sync.py +165 -0
  27. aiconfigkit/cli/package.py +383 -0
  28. aiconfigkit/cli/package_create.py +323 -0
  29. aiconfigkit/cli/package_install.py +472 -0
  30. aiconfigkit/cli/template.py +19 -0
  31. aiconfigkit/cli/template_backup.py +261 -0
  32. aiconfigkit/cli/template_init.py +499 -0
  33. aiconfigkit/cli/template_install.py +261 -0
  34. aiconfigkit/cli/template_list.py +172 -0
  35. aiconfigkit/cli/template_uninstall.py +146 -0
  36. aiconfigkit/cli/template_update.py +225 -0
  37. aiconfigkit/cli/template_validate.py +234 -0
  38. aiconfigkit/cli/tools.py +47 -0
  39. aiconfigkit/cli/uninstall.py +125 -0
  40. aiconfigkit/cli/update.py +309 -0
  41. aiconfigkit/core/__init__.py +0 -0
  42. aiconfigkit/core/checksum.py +211 -0
  43. aiconfigkit/core/component_detector.py +905 -0
  44. aiconfigkit/core/conflict_resolution.py +329 -0
  45. aiconfigkit/core/git_operations.py +539 -0
  46. aiconfigkit/core/mcp/__init__.py +1 -0
  47. aiconfigkit/core/mcp/credentials.py +279 -0
  48. aiconfigkit/core/mcp/manager.py +308 -0
  49. aiconfigkit/core/mcp/set_manager.py +1 -0
  50. aiconfigkit/core/mcp/validator.py +1 -0
  51. aiconfigkit/core/models.py +1661 -0
  52. aiconfigkit/core/package_creator.py +743 -0
  53. aiconfigkit/core/package_manifest.py +248 -0
  54. aiconfigkit/core/repository.py +298 -0
  55. aiconfigkit/core/secret_detector.py +438 -0
  56. aiconfigkit/core/template_manifest.py +283 -0
  57. aiconfigkit/core/version.py +201 -0
  58. aiconfigkit/storage/__init__.py +0 -0
  59. aiconfigkit/storage/library.py +429 -0
  60. aiconfigkit/storage/mcp_tracker.py +1 -0
  61. aiconfigkit/storage/package_tracker.py +234 -0
  62. aiconfigkit/storage/template_library.py +229 -0
  63. aiconfigkit/storage/template_tracker.py +296 -0
  64. aiconfigkit/storage/tracker.py +416 -0
  65. aiconfigkit/tui/__init__.py +5 -0
  66. aiconfigkit/tui/installer.py +511 -0
  67. aiconfigkit/utils/__init__.py +0 -0
  68. aiconfigkit/utils/atomic_write.py +90 -0
  69. aiconfigkit/utils/backup.py +169 -0
  70. aiconfigkit/utils/dotenv.py +128 -0
  71. aiconfigkit/utils/git_helpers.py +187 -0
  72. aiconfigkit/utils/logging.py +60 -0
  73. aiconfigkit/utils/namespace.py +134 -0
  74. aiconfigkit/utils/paths.py +205 -0
  75. aiconfigkit/utils/project.py +109 -0
  76. aiconfigkit/utils/streaming.py +216 -0
  77. aiconfigkit/utils/ui.py +194 -0
  78. aiconfigkit/utils/validation.py +187 -0
  79. devsync-0.5.5.dist-info/LICENSE +21 -0
  80. devsync-0.5.5.dist-info/METADATA +477 -0
  81. devsync-0.5.5.dist-info/RECORD +84 -0
  82. devsync-0.5.5.dist-info/WHEEL +5 -0
  83. devsync-0.5.5.dist-info/entry_points.txt +2 -0
  84. devsync-0.5.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,205 @@
1
+ """Cross-platform path utilities for AI coding tool directories."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+
8
+ def get_home_directory() -> Path:
9
+ """Get user's home directory in a cross-platform way."""
10
+ return Path.home()
11
+
12
+
13
+ def get_cursor_config_dir() -> Path:
14
+ """Get Cursor configuration directory based on platform."""
15
+ home = get_home_directory()
16
+
17
+ if os.name == "nt": # Windows
18
+ return home / "AppData" / "Roaming" / "Cursor" / "User" / "globalStorage"
19
+ elif os.name == "posix":
20
+ if "darwin" in os.uname().sysname.lower(): # macOS
21
+ return home / "Library" / "Application Support" / "Cursor" / "User" / "globalStorage"
22
+ else: # Linux
23
+ return home / ".config" / "Cursor" / "User" / "globalStorage"
24
+
25
+ raise OSError(f"Unsupported operating system: {os.name}")
26
+
27
+
28
+ def get_copilot_config_dir() -> Path:
29
+ """Get GitHub Copilot (VS Code) configuration directory based on platform."""
30
+ home = get_home_directory()
31
+
32
+ if os.name == "nt": # Windows
33
+ return home / "AppData" / "Roaming" / "Code" / "User" / "globalStorage" / "github.copilot"
34
+ elif os.name == "posix":
35
+ if "darwin" in os.uname().sysname.lower(): # macOS
36
+ return home / "Library" / "Application Support" / "Code" / "User" / "globalStorage" / "github.copilot"
37
+ else: # Linux
38
+ return home / ".config" / "Code" / "User" / "globalStorage" / "github.copilot"
39
+
40
+ raise OSError(f"Unsupported operating system: {os.name}")
41
+
42
+
43
+ def get_winsurf_config_dir() -> Path:
44
+ """Get Windsurf configuration directory based on platform."""
45
+ home = get_home_directory()
46
+
47
+ if os.name == "nt": # Windows
48
+ return home / "AppData" / "Roaming" / "Windsurf" / "User" / "globalStorage"
49
+ elif os.name == "posix":
50
+ if "darwin" in os.uname().sysname.lower(): # macOS
51
+ return home / "Library" / "Application Support" / "Windsurf" / "User" / "globalStorage"
52
+ else: # Linux
53
+ return home / ".config" / "Windsurf" / "User" / "globalStorage"
54
+
55
+ raise OSError(f"Unsupported operating system: {os.name}")
56
+
57
+
58
+ def get_kiro_config_dir() -> Path:
59
+ """Get Kiro configuration directory based on platform."""
60
+ home = get_home_directory()
61
+
62
+ if os.name == "nt": # Windows
63
+ return home / "AppData" / "Roaming" / "Kiro" / "User" / "globalStorage"
64
+ elif os.name == "posix":
65
+ if "darwin" in os.uname().sysname.lower(): # macOS
66
+ return home / "Library" / "Application Support" / "Kiro" / "User" / "globalStorage"
67
+ else: # Linux
68
+ return home / ".config" / "Kiro" / "User" / "globalStorage"
69
+
70
+ raise OSError(f"Unsupported operating system: {os.name}")
71
+
72
+
73
+ def get_cline_config_dir() -> Path:
74
+ """Get Cline (VS Code extension) configuration directory based on platform."""
75
+ home = get_home_directory()
76
+
77
+ if os.name == "nt": # Windows
78
+ return home / "AppData" / "Roaming" / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev"
79
+ elif os.name == "posix":
80
+ if "darwin" in os.uname().sysname.lower(): # macOS
81
+ return (
82
+ home / "Library" / "Application Support" / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev"
83
+ )
84
+ else: # Linux
85
+ return home / ".config" / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev"
86
+
87
+ raise OSError(f"Unsupported operating system: {os.name}")
88
+
89
+
90
+ def get_roo_config_dir() -> Path:
91
+ """Get Roo Code (VS Code extension) configuration directory based on platform."""
92
+ home = get_home_directory()
93
+
94
+ if os.name == "nt": # Windows
95
+ return home / "AppData" / "Roaming" / "Code" / "User" / "globalStorage" / "rooveterinaryinc.roo-cline"
96
+ elif os.name == "posix":
97
+ if "darwin" in os.uname().sysname.lower(): # macOS
98
+ return (
99
+ home
100
+ / "Library"
101
+ / "Application Support"
102
+ / "Code"
103
+ / "User"
104
+ / "globalStorage"
105
+ / "rooveterinaryinc.roo-cline"
106
+ )
107
+ else: # Linux
108
+ return home / ".config" / "Code" / "User" / "globalStorage" / "rooveterinaryinc.roo-cline"
109
+
110
+ raise OSError(f"Unsupported operating system: {os.name}")
111
+
112
+
113
+ def get_claude_config_dir() -> Path:
114
+ """Get Claude Code configuration directory based on platform."""
115
+ home = get_home_directory()
116
+
117
+ # Claude Code uses ~/.claude/rules/ for global rules
118
+ # This is consistent across all platforms
119
+ return home / ".claude" / "rules"
120
+
121
+
122
+ def get_claude_desktop_config_path() -> Path:
123
+ """Get Claude Desktop MCP configuration file path based on platform."""
124
+ home = get_home_directory()
125
+
126
+ if os.name == "nt": # Windows
127
+ return home / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json"
128
+ elif os.name == "posix":
129
+ if "darwin" in os.uname().sysname.lower(): # macOS
130
+ return home / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
131
+ else: # Linux
132
+ return home / ".config" / "Claude" / "claude_desktop_config.json"
133
+
134
+ raise OSError(f"Unsupported operating system: {os.name}")
135
+
136
+
137
+ def get_cursor_mcp_config_path() -> Path:
138
+ """Get Cursor MCP configuration file path based on platform."""
139
+ # Cursor doesn't natively support MCP yet, but we'll prepare for it
140
+ # Expected location would be similar to their settings structure
141
+ config_dir = get_cursor_config_dir()
142
+ return config_dir / "mcp_config.json"
143
+
144
+
145
+ def get_windsurf_mcp_config_path() -> Path:
146
+ """Get Windsurf MCP configuration file path based on platform."""
147
+ # Windsurf may have MCP support in future
148
+ # Expected location would be similar to their settings structure
149
+ config_dir = get_winsurf_config_dir()
150
+ return config_dir / "mcp_config.json"
151
+
152
+
153
+ def get_instructionkit_data_dir() -> Path:
154
+ """Get InstructionKit data directory for tracking installations."""
155
+ home = get_home_directory()
156
+ data_dir = home / ".instructionkit"
157
+ data_dir.mkdir(parents=True, exist_ok=True)
158
+ return data_dir
159
+
160
+
161
+ def get_library_dir() -> Path:
162
+ """Get InstructionKit library directory for downloaded instructions."""
163
+ library_dir = get_instructionkit_data_dir() / "library"
164
+ library_dir.mkdir(parents=True, exist_ok=True)
165
+ return library_dir
166
+
167
+
168
+ def get_installation_tracker_path() -> Path:
169
+ """Get path to installation tracking JSON file."""
170
+ return get_instructionkit_data_dir() / "installations.json"
171
+
172
+
173
+ def ensure_directory_exists(path: Path) -> None:
174
+ """Ensure a directory exists, creating it if necessary."""
175
+ path.mkdir(parents=True, exist_ok=True)
176
+
177
+
178
+ def safe_file_name(name: str) -> str:
179
+ """Sanitize a string to be safe as a filename."""
180
+ # Replace unsafe characters with underscores
181
+ unsafe_chars = '<>:"/\\|?*'
182
+ safe_name = name
183
+ for char in unsafe_chars:
184
+ safe_name = safe_name.replace(char, "_")
185
+ return safe_name
186
+
187
+
188
+ def resolve_conflict_name(original_path: Path, suffix: Optional[str] = None) -> Path:
189
+ """Generate a new path for conflict resolution with optional suffix."""
190
+ if suffix:
191
+ stem = original_path.stem
192
+ extension = original_path.suffix
193
+ new_name = f"{stem}-{suffix}{extension}"
194
+ return original_path.parent / new_name
195
+
196
+ # Auto-increment: file.md -> file-1.md -> file-2.md
197
+ counter = 1
198
+ while True:
199
+ stem = original_path.stem
200
+ extension = original_path.suffix
201
+ new_name = f"{stem}-{counter}{extension}"
202
+ new_path = original_path.parent / new_name
203
+ if not new_path.exists():
204
+ return new_path
205
+ counter += 1
@@ -0,0 +1,109 @@
1
+ """Project detection utilities."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+
7
+ def find_project_root(start_path: Optional[Path] = None) -> Optional[Path]:
8
+ """
9
+ Find the project root directory by looking for common project markers.
10
+
11
+ Searches upward from the start path for files/directories that indicate
12
+ a project root:
13
+ - .git directory (Git repository)
14
+ - pyproject.toml (Python project)
15
+ - package.json (Node.js project)
16
+ - Cargo.toml (Rust project)
17
+ - go.mod (Go project)
18
+ - pom.xml (Java/Maven project)
19
+ - build.gradle (Java/Gradle project)
20
+ - composer.json (PHP project)
21
+ - Gemfile (Ruby project)
22
+
23
+ Args:
24
+ start_path: Directory to start searching from. Defaults to current directory.
25
+
26
+ Returns:
27
+ Path to project root if found, None otherwise
28
+ """
29
+ if start_path is None:
30
+ start_path = Path.cwd()
31
+ else:
32
+ start_path = Path(start_path).resolve()
33
+
34
+ # Common project markers
35
+ markers = [
36
+ ".git",
37
+ "pyproject.toml",
38
+ "package.json",
39
+ "Cargo.toml",
40
+ "go.mod",
41
+ "pom.xml",
42
+ "build.gradle",
43
+ "composer.json",
44
+ "Gemfile",
45
+ ".project", # Eclipse project
46
+ "Makefile",
47
+ ]
48
+
49
+ current = start_path
50
+
51
+ # Search upward through parent directories
52
+ while True:
53
+ for marker in markers:
54
+ marker_path = current / marker
55
+ if marker_path.exists():
56
+ return current
57
+
58
+ # Check if we've reached the filesystem root
59
+ parent = current.parent
60
+ if parent == current:
61
+ # No project root found
62
+ return None
63
+
64
+ current = parent
65
+
66
+
67
+ def is_in_project() -> bool:
68
+ """
69
+ Check if the current directory is within a project.
70
+
71
+ Returns:
72
+ True if a project root can be found
73
+ """
74
+ return find_project_root() is not None
75
+
76
+
77
+ def get_project_instructions_dir(project_root: Path, create: bool = True) -> Path:
78
+ """
79
+ Get the directory for project-specific instructions.
80
+
81
+ Creates a .instructionkit directory in the project root for storing
82
+ project-specific instructions and metadata.
83
+
84
+ Args:
85
+ project_root: Path to the project root directory
86
+ create: Whether to create the directory if it doesn't exist
87
+
88
+ Returns:
89
+ Path to project instructions directory
90
+ """
91
+ instructions_dir = project_root / ".instructionkit"
92
+
93
+ if create:
94
+ instructions_dir.mkdir(parents=True, exist_ok=True)
95
+
96
+ return instructions_dir
97
+
98
+
99
+ def get_project_installation_tracker_path(project_root: Path) -> Path:
100
+ """
101
+ Get path to project-specific installation tracking file.
102
+
103
+ Args:
104
+ project_root: Path to the project root directory
105
+
106
+ Returns:
107
+ Path to project installation tracking JSON file
108
+ """
109
+ return get_project_instructions_dir(project_root) / "installations.json"
@@ -0,0 +1,216 @@
1
+ """Streaming utilities for large file operations."""
2
+
3
+ from pathlib import Path
4
+ from typing import Callable, Optional
5
+
6
+
7
+ class StreamingError(Exception):
8
+ """Raised when streaming operations fail."""
9
+
10
+ pass
11
+
12
+
13
+ def stream_copy_file(
14
+ source: Path,
15
+ destination: Path,
16
+ chunk_size: int = 8192,
17
+ progress_callback: Optional[Callable[[int, int], None]] = None,
18
+ ) -> None:
19
+ """
20
+ Copy a file in streaming chunks for memory-efficient large file handling.
21
+
22
+ Args:
23
+ source: Source file path
24
+ destination: Destination file path
25
+ chunk_size: Size of chunks to read/write (default 8KB)
26
+ progress_callback: Optional callback for progress updates (bytes_copied, total_size)
27
+
28
+ Raises:
29
+ FileNotFoundError: If source file doesn't exist
30
+ StreamingError: If copy operation fails
31
+ """
32
+ if not source.exists():
33
+ raise FileNotFoundError(f"Source file not found: {source}")
34
+
35
+ try:
36
+ # Get total file size for progress tracking
37
+ total_size = source.stat().st_size
38
+ bytes_copied = 0
39
+
40
+ # Ensure destination directory exists
41
+ destination.parent.mkdir(parents=True, exist_ok=True)
42
+
43
+ # Open both files and copy in chunks
44
+ with open(source, "rb") as src_file:
45
+ with open(destination, "wb") as dst_file:
46
+ while True:
47
+ chunk = src_file.read(chunk_size)
48
+ if not chunk:
49
+ break
50
+
51
+ dst_file.write(chunk)
52
+ bytes_copied += len(chunk)
53
+
54
+ # Call progress callback if provided
55
+ if progress_callback:
56
+ progress_callback(bytes_copied, total_size)
57
+
58
+ except Exception as e:
59
+ # Clean up partial destination file on error
60
+ if destination.exists():
61
+ destination.unlink()
62
+ raise StreamingError(f"Failed to copy file: {e}") from e
63
+
64
+
65
+ def stream_copy_with_verification(
66
+ source: Path,
67
+ destination: Path,
68
+ checksum: Optional[str] = None,
69
+ chunk_size: int = 8192,
70
+ ) -> bool:
71
+ """
72
+ Copy a file with optional checksum verification.
73
+
74
+ Args:
75
+ source: Source file path
76
+ destination: Destination file path
77
+ checksum: Expected SHA-256 checksum (optional)
78
+ chunk_size: Size of chunks to read/write
79
+
80
+ Returns:
81
+ True if copy and verification succeeded
82
+
83
+ Raises:
84
+ FileNotFoundError: If source file doesn't exist
85
+ StreamingError: If copy or verification fails
86
+ """
87
+ # First, copy the file
88
+ stream_copy_file(source, destination, chunk_size)
89
+
90
+ # Verify checksum if provided
91
+ if checksum:
92
+ from aiconfigkit.core.checksum import calculate_file_checksum
93
+
94
+ actual_checksum = calculate_file_checksum(str(destination), "sha256")
95
+
96
+ if actual_checksum.lower() != checksum.lower():
97
+ # Delete corrupted file
98
+ destination.unlink()
99
+ raise StreamingError(f"Checksum verification failed! " f"Expected: {checksum}, Actual: {actual_checksum}")
100
+
101
+ return True
102
+
103
+
104
+ def get_file_size(file_path: Path) -> int:
105
+ """
106
+ Get file size in bytes.
107
+
108
+ Args:
109
+ file_path: Path to file
110
+
111
+ Returns:
112
+ File size in bytes
113
+
114
+ Raises:
115
+ FileNotFoundError: If file doesn't exist
116
+ """
117
+ if not file_path.exists():
118
+ raise FileNotFoundError(f"File not found: {file_path}")
119
+
120
+ return file_path.stat().st_size
121
+
122
+
123
+ def format_file_size(size_bytes: int) -> str:
124
+ """
125
+ Format file size in human-readable format.
126
+
127
+ Args:
128
+ size_bytes: Size in bytes
129
+
130
+ Returns:
131
+ Formatted string (e.g., "1.5 MB", "100 KB")
132
+ """
133
+ units = ["B", "KB", "MB", "GB", "TB"]
134
+ size = float(size_bytes)
135
+ unit_index = 0
136
+
137
+ while size >= 1024 and unit_index < len(units) - 1:
138
+ size /= 1024
139
+ unit_index += 1
140
+
141
+ if unit_index == 0:
142
+ # Bytes - no decimal places
143
+ return f"{int(size)} {units[unit_index]}"
144
+ else:
145
+ # Other units - 1 decimal place
146
+ return f"{size:.1f} {units[unit_index]}"
147
+
148
+
149
+ def is_large_file(file_path: Path, threshold_mb: int = 10) -> bool:
150
+ """
151
+ Check if file is considered "large" based on size threshold.
152
+
153
+ Args:
154
+ file_path: Path to file
155
+ threshold_mb: Size threshold in megabytes (default 10 MB)
156
+
157
+ Returns:
158
+ True if file size exceeds threshold
159
+
160
+ Raises:
161
+ FileNotFoundError: If file doesn't exist
162
+ """
163
+ size_bytes = get_file_size(file_path)
164
+ threshold_bytes = threshold_mb * 1024 * 1024
165
+ return size_bytes > threshold_bytes
166
+
167
+
168
+ def copy_directory_tree(
169
+ source: Path,
170
+ destination: Path,
171
+ progress_callback: Optional[Callable[[str, int, int], None]] = None,
172
+ ) -> int:
173
+ """
174
+ Copy an entire directory tree with progress tracking.
175
+
176
+ Args:
177
+ source: Source directory path
178
+ destination: Destination directory path
179
+ progress_callback: Optional callback(file_path, current, total)
180
+
181
+ Returns:
182
+ Number of files copied
183
+
184
+ Raises:
185
+ StreamingError: If copy operation fails
186
+ """
187
+ if not source.is_dir():
188
+ raise StreamingError(f"Source is not a directory: {source}")
189
+
190
+ try:
191
+ # Count total files for progress tracking
192
+ all_files = list(source.rglob("*"))
193
+ total_files = len([f for f in all_files if f.is_file()])
194
+ files_copied = 0
195
+
196
+ # Recreate directory structure
197
+ destination.mkdir(parents=True, exist_ok=True)
198
+
199
+ for item in all_files:
200
+ if item.is_file():
201
+ # Calculate relative path
202
+ rel_path = item.relative_to(source)
203
+ dest_file = destination / rel_path
204
+
205
+ # Copy file
206
+ stream_copy_file(item, dest_file)
207
+ files_copied += 1
208
+
209
+ # Call progress callback
210
+ if progress_callback:
211
+ progress_callback(str(rel_path), files_copied, total_files)
212
+
213
+ return files_copied
214
+
215
+ except Exception as e:
216
+ raise StreamingError(f"Failed to copy directory tree: {e}") from e