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,229 @@
1
+ """Template library management for downloaded template repositories."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from aiconfigkit.core.models import TemplateManifest
7
+ from aiconfigkit.core.template_manifest import load_manifest
8
+ from aiconfigkit.utils.git_helpers import clone_template_repo, get_repo_version
9
+ from aiconfigkit.utils.namespace import derive_namespace
10
+
11
+
12
+ class TemplateLibraryManager:
13
+ """Manages template repositories in local library (~/.instructionkit/templates/)."""
14
+
15
+ def __init__(self, library_path: Optional[Path] = None):
16
+ """
17
+ Initialize template library manager.
18
+
19
+ Args:
20
+ library_path: Custom library path (default: ~/.instructionkit/templates/)
21
+ """
22
+ if library_path is None:
23
+ library_path = Path.home() / ".instructionkit" / "templates"
24
+
25
+ self.library_path = library_path
26
+ self.library_path.mkdir(parents=True, exist_ok=True)
27
+
28
+ def clone_repository(
29
+ self, repo_url: str, namespace_override: Optional[str] = None
30
+ ) -> tuple[Path, TemplateManifest]:
31
+ """
32
+ Clone template repository to library.
33
+
34
+ Args:
35
+ repo_url: Git repository URL
36
+ namespace_override: Optional namespace override
37
+
38
+ Returns:
39
+ Tuple of (repository path, parsed manifest)
40
+
41
+ Raises:
42
+ TemplateAuthError: If authentication fails
43
+ TemplateNetworkError: If network/repository unavailable
44
+ TemplateManifestError: If manifest is invalid
45
+
46
+ Example:
47
+ >>> manager = TemplateLibraryManager()
48
+ >>> repo_path, manifest = manager.clone_repository(
49
+ ... "https://github.com/acme/templates"
50
+ ... )
51
+ >>> manifest.name
52
+ 'ACME Templates'
53
+ """
54
+ # Derive namespace
55
+ namespace = derive_namespace(repo_url, namespace_override)
56
+
57
+ # Clone to library
58
+ destination = self.library_path / namespace
59
+
60
+ # Remove existing directory if it exists
61
+ if destination.exists():
62
+ import shutil
63
+
64
+ shutil.rmtree(destination)
65
+
66
+ clone_template_repo(repo_url, destination)
67
+
68
+ # Load and validate manifest
69
+ manifest_path = destination / "templatekit.yaml"
70
+ manifest = load_manifest(manifest_path)
71
+
72
+ return destination, manifest
73
+
74
+ def get_template_repository(self, namespace: str) -> tuple[Path, TemplateManifest]:
75
+ """
76
+ Get template repository from library by namespace.
77
+
78
+ Args:
79
+ namespace: Repository namespace
80
+
81
+ Returns:
82
+ Tuple of (repository path, parsed manifest)
83
+
84
+ Raises:
85
+ FileNotFoundError: If repository not found in library
86
+ TemplateManifestError: If manifest is invalid
87
+
88
+ Example:
89
+ >>> manager = TemplateLibraryManager()
90
+ >>> repo_path, manifest = manager.get_template_repository("acme-templates")
91
+ """
92
+ repo_path = self.library_path / namespace
93
+
94
+ if not repo_path.exists():
95
+ raise FileNotFoundError(
96
+ f"Template repository '{namespace}' not found in library.\n"
97
+ f"Install it with: inskit template install <repo-url>"
98
+ )
99
+
100
+ manifest_path = repo_path / "templatekit.yaml"
101
+ manifest = load_manifest(manifest_path)
102
+
103
+ return repo_path, manifest
104
+
105
+ def list_available_templates(self, namespace: str) -> list[str]:
106
+ """
107
+ List all available templates in a repository.
108
+
109
+ Args:
110
+ namespace: Repository namespace
111
+
112
+ Returns:
113
+ List of template names
114
+
115
+ Raises:
116
+ FileNotFoundError: If repository not found
117
+ TemplateManifestError: If manifest is invalid
118
+
119
+ Example:
120
+ >>> manager = TemplateLibraryManager()
121
+ >>> templates = manager.list_available_templates("acme-templates")
122
+ >>> "test-command" in templates
123
+ True
124
+ """
125
+ _, manifest = self.get_template_repository(namespace)
126
+ return [template.name for template in manifest.templates]
127
+
128
+ def get_repository_version(self, namespace: str) -> Optional[str]:
129
+ """
130
+ Get version of repository in library.
131
+
132
+ Args:
133
+ namespace: Repository namespace
134
+
135
+ Returns:
136
+ Version string (tag or commit hash) or None if not found
137
+
138
+ Example:
139
+ >>> manager = TemplateLibraryManager()
140
+ >>> version = manager.get_repository_version("acme-templates")
141
+ >>> version
142
+ 'v1.2.0'
143
+ """
144
+ repo_path = self.library_path / namespace
145
+
146
+ if not repo_path.exists():
147
+ return None
148
+
149
+ try:
150
+ return get_repo_version(repo_path)
151
+ except Exception:
152
+ return None
153
+
154
+ def list_installed_repositories(self) -> list[str]:
155
+ """
156
+ List all template repositories in library.
157
+
158
+ Returns:
159
+ List of repository namespaces
160
+
161
+ Example:
162
+ >>> manager = TemplateLibraryManager()
163
+ >>> repos = manager.list_installed_repositories()
164
+ >>> "acme-templates" in repos
165
+ True
166
+ """
167
+ if not self.library_path.exists():
168
+ return []
169
+
170
+ repositories = []
171
+ for item in self.library_path.iterdir():
172
+ if item.is_dir() and (item / "templatekit.yaml").exists():
173
+ repositories.append(item.name)
174
+
175
+ return sorted(repositories)
176
+
177
+ def remove_repository(self, namespace: str) -> None:
178
+ """
179
+ Remove template repository from library.
180
+
181
+ Args:
182
+ namespace: Repository namespace
183
+
184
+ Raises:
185
+ FileNotFoundError: If repository not found
186
+
187
+ Example:
188
+ >>> manager = TemplateLibraryManager()
189
+ >>> manager.remove_repository("acme-templates")
190
+ """
191
+ repo_path = self.library_path / namespace
192
+
193
+ if not repo_path.exists():
194
+ raise FileNotFoundError(f"Repository '{namespace}' not found in library")
195
+
196
+ import shutil
197
+
198
+ shutil.rmtree(repo_path)
199
+
200
+ def get_template_file_path(self, namespace: str, template_name: str, file_path: str) -> Path:
201
+ """
202
+ Get absolute path to a template file in repository.
203
+
204
+ Args:
205
+ namespace: Repository namespace
206
+ template_name: Template name
207
+ file_path: Relative file path from manifest
208
+
209
+ Returns:
210
+ Absolute path to template file
211
+
212
+ Raises:
213
+ FileNotFoundError: If repository or file not found
214
+
215
+ Example:
216
+ >>> manager = TemplateLibraryManager()
217
+ >>> path = manager.get_template_file_path(
218
+ ... "acme-templates",
219
+ ... "test-command",
220
+ ... "templates/test.md"
221
+ ... )
222
+ """
223
+ repo_path, _ = self.get_template_repository(namespace)
224
+ template_file = repo_path / file_path
225
+
226
+ if not template_file.exists():
227
+ raise FileNotFoundError(f"Template file not found: {file_path}")
228
+
229
+ return template_file
@@ -0,0 +1,296 @@
1
+ """Installation tracking for templates."""
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from aiconfigkit.core.models import TemplateInstallationRecord
9
+
10
+
11
+ class TemplateInstallationTracker:
12
+ """Tracks installed templates in projects and globally."""
13
+
14
+ def __init__(self, tracking_file: Path):
15
+ """
16
+ Initialize installation tracker.
17
+
18
+ Args:
19
+ tracking_file: Path to installations JSON file
20
+ """
21
+ self.tracking_file = tracking_file
22
+
23
+ @classmethod
24
+ def for_project(cls, project_root: Path) -> "TemplateInstallationTracker":
25
+ """
26
+ Create tracker for project-level installations.
27
+
28
+ Args:
29
+ project_root: Project root directory
30
+
31
+ Returns:
32
+ TemplateInstallationTracker instance
33
+
34
+ Example:
35
+ >>> from pathlib import Path
36
+ >>> tracker = TemplateInstallationTracker.for_project(Path.cwd())
37
+ """
38
+ tracking_dir = project_root / ".instructionkit"
39
+ tracking_dir.mkdir(parents=True, exist_ok=True)
40
+ tracking_file = tracking_dir / "template-installations.json"
41
+ return cls(tracking_file)
42
+
43
+ @classmethod
44
+ def for_global(cls) -> "TemplateInstallationTracker":
45
+ """
46
+ Create tracker for global installations.
47
+
48
+ Returns:
49
+ TemplateInstallationTracker instance
50
+
51
+ Example:
52
+ >>> tracker = TemplateInstallationTracker.for_global()
53
+ """
54
+ tracking_dir = Path.home() / ".instructionkit"
55
+ tracking_dir.mkdir(parents=True, exist_ok=True)
56
+ tracking_file = tracking_dir / "global-template-installations.json"
57
+ return cls(tracking_file)
58
+
59
+ def load_installation_records(self) -> list[TemplateInstallationRecord]:
60
+ """
61
+ Load installation records from JSON file.
62
+
63
+ Returns:
64
+ List of installation records (empty if file doesn't exist)
65
+
66
+ Example:
67
+ >>> tracker = TemplateInstallationTracker.for_project(Path.cwd())
68
+ >>> records = tracker.load_installation_records()
69
+ >>> len(records)
70
+ 0
71
+ """
72
+ if not self.tracking_file.exists():
73
+ return []
74
+
75
+ try:
76
+ with open(self.tracking_file, "r", encoding="utf-8") as f:
77
+ data = json.load(f)
78
+
79
+ records = []
80
+ for record_data in data.get("installations", []):
81
+ try:
82
+ record = TemplateInstallationRecord.from_dict(record_data)
83
+ records.append(record)
84
+ except (ValueError, KeyError) as e:
85
+ # Skip invalid records
86
+ print(f"Warning: Skipping invalid installation record: {e}")
87
+ continue
88
+
89
+ return records
90
+
91
+ except (json.JSONDecodeError, OSError) as e:
92
+ print(f"Warning: Failed to load installation records: {e}")
93
+ return []
94
+
95
+ def save_installation_records(self, records: list[TemplateInstallationRecord]) -> None:
96
+ """
97
+ Save installation records to JSON file.
98
+
99
+ Args:
100
+ records: List of installation records to save
101
+
102
+ Example:
103
+ >>> tracker = TemplateInstallationTracker.for_project(Path.cwd())
104
+ >>> tracker.save_installation_records([record])
105
+ """
106
+ # Ensure directory exists
107
+ self.tracking_file.parent.mkdir(parents=True, exist_ok=True)
108
+
109
+ data = {
110
+ "installations": [record.to_dict() for record in records],
111
+ "last_updated": datetime.now().isoformat(),
112
+ "schema_version": "1.0",
113
+ }
114
+
115
+ with open(self.tracking_file, "w", encoding="utf-8") as f:
116
+ json.dump(data, f, indent=2, ensure_ascii=False)
117
+
118
+ def add_installation(self, record: TemplateInstallationRecord) -> None:
119
+ """
120
+ Add a new installation record.
121
+
122
+ Args:
123
+ record: Installation record to add
124
+
125
+ Example:
126
+ >>> from aiconfigkit.core.models import AIToolType, InstallationScope
127
+ >>> from datetime import datetime
128
+ >>> record = TemplateInstallationRecord(
129
+ ... id="550e8400-e29b-41d4-a716-446655440000",
130
+ ... template_name="test-command",
131
+ ... source_repo="acme-templates",
132
+ ... source_version="1.0.0",
133
+ ... namespace="acme",
134
+ ... installed_path="/project/.cursor/rules/acme.test.md",
135
+ ... scope=InstallationScope.PROJECT,
136
+ ... installed_at=datetime.now(),
137
+ ... checksum="a" * 64,
138
+ ... ide_type=AIToolType.CURSOR
139
+ ... )
140
+ >>> tracker.add_installation(record)
141
+ """
142
+ records = self.load_installation_records()
143
+ records.append(record)
144
+ self.save_installation_records(records)
145
+
146
+ def get_installation_by_id(self, installation_id: str) -> Optional[TemplateInstallationRecord]:
147
+ """
148
+ Get installation record by ID.
149
+
150
+ Args:
151
+ installation_id: Installation UUID
152
+
153
+ Returns:
154
+ Installation record or None if not found
155
+
156
+ Example:
157
+ >>> record = tracker.get_installation_by_id("550e8400-...")
158
+ """
159
+ records = self.load_installation_records()
160
+ for record in records:
161
+ if record.id == installation_id:
162
+ return record
163
+ return None
164
+
165
+ def get_installations_by_repo(self, source_repo: str) -> list[TemplateInstallationRecord]:
166
+ """
167
+ Get all installations from a specific repository.
168
+
169
+ Args:
170
+ source_repo: Repository name
171
+
172
+ Returns:
173
+ List of installation records from that repository
174
+
175
+ Example:
176
+ >>> records = tracker.get_installations_by_repo("acme-templates")
177
+ >>> len(records)
178
+ 3
179
+ """
180
+ records = self.load_installation_records()
181
+ return [r for r in records if r.source_repo == source_repo]
182
+
183
+ def get_installations_by_namespace(self, namespace: str) -> list[TemplateInstallationRecord]:
184
+ """
185
+ Get all installations from a specific namespace.
186
+
187
+ Args:
188
+ namespace: Repository namespace
189
+
190
+ Returns:
191
+ List of installation records with that namespace
192
+
193
+ Example:
194
+ >>> records = tracker.get_installations_by_namespace("acme")
195
+ """
196
+ records = self.load_installation_records()
197
+ return [r for r in records if r.namespace == namespace]
198
+
199
+ def remove_installation(self, installation_id: str) -> bool:
200
+ """
201
+ Remove an installation record.
202
+
203
+ Args:
204
+ installation_id: Installation UUID
205
+
206
+ Returns:
207
+ True if removed, False if not found
208
+
209
+ Example:
210
+ >>> removed = tracker.remove_installation("550e8400-...")
211
+ >>> removed
212
+ True
213
+ """
214
+ records = self.load_installation_records()
215
+ original_count = len(records)
216
+
217
+ records = [r for r in records if r.id != installation_id]
218
+
219
+ if len(records) < original_count:
220
+ self.save_installation_records(records)
221
+ return True
222
+
223
+ return False
224
+
225
+ def remove_installations_by_repo(self, source_repo: str) -> int:
226
+ """
227
+ Remove all installations from a specific repository.
228
+
229
+ Args:
230
+ source_repo: Repository name
231
+
232
+ Returns:
233
+ Number of installations removed
234
+
235
+ Example:
236
+ >>> count = tracker.remove_installations_by_repo("acme-templates")
237
+ >>> count
238
+ 3
239
+ """
240
+ records = self.load_installation_records()
241
+ original_count = len(records)
242
+
243
+ records = [r for r in records if r.source_repo != source_repo]
244
+ removed_count = original_count - len(records)
245
+
246
+ if removed_count > 0:
247
+ self.save_installation_records(records)
248
+
249
+ return removed_count
250
+
251
+ def update_installation(self, installation_id: str, updated_record: TemplateInstallationRecord) -> bool:
252
+ """
253
+ Update an existing installation record.
254
+
255
+ Args:
256
+ installation_id: Installation UUID
257
+ updated_record: New installation record
258
+
259
+ Returns:
260
+ True if updated, False if not found
261
+
262
+ Example:
263
+ >>> updated = tracker.update_installation("550e8400-...", new_record)
264
+ >>> updated
265
+ True
266
+ """
267
+ records = self.load_installation_records()
268
+
269
+ for i, record in enumerate(records):
270
+ if record.id == installation_id:
271
+ records[i] = updated_record
272
+ self.save_installation_records(records)
273
+ return True
274
+
275
+ return False
276
+
277
+ def get_all_installations(self) -> list[TemplateInstallationRecord]:
278
+ """
279
+ Get all installation records.
280
+
281
+ Returns:
282
+ List of all installation records
283
+
284
+ Example:
285
+ >>> all_records = tracker.get_all_installations()
286
+ """
287
+ return self.load_installation_records()
288
+
289
+ def clear_all_installations(self) -> None:
290
+ """
291
+ Remove all installation records.
292
+
293
+ Example:
294
+ >>> tracker.clear_all_installations()
295
+ """
296
+ self.save_installation_records([])