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.
- aiconfigkit/__init__.py +0 -0
- aiconfigkit/__main__.py +6 -0
- aiconfigkit/ai_tools/__init__.py +0 -0
- aiconfigkit/ai_tools/base.py +236 -0
- aiconfigkit/ai_tools/capability_registry.py +262 -0
- aiconfigkit/ai_tools/claude.py +91 -0
- aiconfigkit/ai_tools/claude_desktop.py +97 -0
- aiconfigkit/ai_tools/cline.py +92 -0
- aiconfigkit/ai_tools/copilot.py +92 -0
- aiconfigkit/ai_tools/cursor.py +109 -0
- aiconfigkit/ai_tools/detector.py +169 -0
- aiconfigkit/ai_tools/kiro.py +85 -0
- aiconfigkit/ai_tools/mcp_syncer.py +291 -0
- aiconfigkit/ai_tools/roo.py +110 -0
- aiconfigkit/ai_tools/translator.py +390 -0
- aiconfigkit/ai_tools/winsurf.py +102 -0
- aiconfigkit/cli/__init__.py +0 -0
- aiconfigkit/cli/delete.py +118 -0
- aiconfigkit/cli/download.py +274 -0
- aiconfigkit/cli/install.py +237 -0
- aiconfigkit/cli/install_new.py +937 -0
- aiconfigkit/cli/list.py +275 -0
- aiconfigkit/cli/main.py +454 -0
- aiconfigkit/cli/mcp_configure.py +232 -0
- aiconfigkit/cli/mcp_install.py +166 -0
- aiconfigkit/cli/mcp_sync.py +165 -0
- aiconfigkit/cli/package.py +383 -0
- aiconfigkit/cli/package_create.py +323 -0
- aiconfigkit/cli/package_install.py +472 -0
- aiconfigkit/cli/template.py +19 -0
- aiconfigkit/cli/template_backup.py +261 -0
- aiconfigkit/cli/template_init.py +499 -0
- aiconfigkit/cli/template_install.py +261 -0
- aiconfigkit/cli/template_list.py +172 -0
- aiconfigkit/cli/template_uninstall.py +146 -0
- aiconfigkit/cli/template_update.py +225 -0
- aiconfigkit/cli/template_validate.py +234 -0
- aiconfigkit/cli/tools.py +47 -0
- aiconfigkit/cli/uninstall.py +125 -0
- aiconfigkit/cli/update.py +309 -0
- aiconfigkit/core/__init__.py +0 -0
- aiconfigkit/core/checksum.py +211 -0
- aiconfigkit/core/component_detector.py +905 -0
- aiconfigkit/core/conflict_resolution.py +329 -0
- aiconfigkit/core/git_operations.py +539 -0
- aiconfigkit/core/mcp/__init__.py +1 -0
- aiconfigkit/core/mcp/credentials.py +279 -0
- aiconfigkit/core/mcp/manager.py +308 -0
- aiconfigkit/core/mcp/set_manager.py +1 -0
- aiconfigkit/core/mcp/validator.py +1 -0
- aiconfigkit/core/models.py +1661 -0
- aiconfigkit/core/package_creator.py +743 -0
- aiconfigkit/core/package_manifest.py +248 -0
- aiconfigkit/core/repository.py +298 -0
- aiconfigkit/core/secret_detector.py +438 -0
- aiconfigkit/core/template_manifest.py +283 -0
- aiconfigkit/core/version.py +201 -0
- aiconfigkit/storage/__init__.py +0 -0
- aiconfigkit/storage/library.py +429 -0
- aiconfigkit/storage/mcp_tracker.py +1 -0
- aiconfigkit/storage/package_tracker.py +234 -0
- aiconfigkit/storage/template_library.py +229 -0
- aiconfigkit/storage/template_tracker.py +296 -0
- aiconfigkit/storage/tracker.py +416 -0
- aiconfigkit/tui/__init__.py +5 -0
- aiconfigkit/tui/installer.py +511 -0
- aiconfigkit/utils/__init__.py +0 -0
- aiconfigkit/utils/atomic_write.py +90 -0
- aiconfigkit/utils/backup.py +169 -0
- aiconfigkit/utils/dotenv.py +128 -0
- aiconfigkit/utils/git_helpers.py +187 -0
- aiconfigkit/utils/logging.py +60 -0
- aiconfigkit/utils/namespace.py +134 -0
- aiconfigkit/utils/paths.py +205 -0
- aiconfigkit/utils/project.py +109 -0
- aiconfigkit/utils/streaming.py +216 -0
- aiconfigkit/utils/ui.py +194 -0
- aiconfigkit/utils/validation.py +187 -0
- devsync-0.5.5.dist-info/LICENSE +21 -0
- devsync-0.5.5.dist-info/METADATA +477 -0
- devsync-0.5.5.dist-info/RECORD +84 -0
- devsync-0.5.5.dist-info/WHEEL +5 -0
- devsync-0.5.5.dist-info/entry_points.txt +2 -0
- 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([])
|