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,390 @@
1
+ """Component translators for converting package components to IDE-specific formats."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from aiconfigkit.core.models import (
9
+ AIToolType,
10
+ CommandComponent,
11
+ ComponentType,
12
+ HookComponent,
13
+ InstructionComponent,
14
+ MCPServerComponent,
15
+ ResourceComponent,
16
+ )
17
+
18
+
19
+ @dataclass
20
+ class TranslatedComponent:
21
+ """
22
+ Result of translating a package component to IDE-specific format.
23
+
24
+ Contains the file content, target path, and any metadata needed
25
+ for installing the component to a specific IDE.
26
+ """
27
+
28
+ component_type: ComponentType
29
+ component_name: str
30
+ target_path: str
31
+ content: str
32
+ metadata: dict[str, Any] | None = None
33
+ needs_processing: bool = False
34
+
35
+ def __post_init__(self) -> None:
36
+ """Validate translated component."""
37
+ if not self.component_name:
38
+ raise ValueError("Component name cannot be empty")
39
+ if not self.target_path:
40
+ raise ValueError("Target path cannot be empty")
41
+
42
+
43
+ class ComponentTranslator(ABC):
44
+ """
45
+ Abstract base class for translating package components to IDE-specific formats.
46
+
47
+ Each IDE (Cursor, Claude Code, Windsurf, Copilot) has a translator that
48
+ converts IDE-agnostic package components into the format expected by that IDE.
49
+ """
50
+
51
+ @property
52
+ @abstractmethod
53
+ def tool_type(self) -> AIToolType:
54
+ """Return the AI tool type this translator targets."""
55
+ pass
56
+
57
+ @abstractmethod
58
+ def translate_instruction(self, component: InstructionComponent, package_root: Path) -> TranslatedComponent:
59
+ """
60
+ Translate instruction component to IDE-specific format.
61
+
62
+ Args:
63
+ component: Instruction component from package
64
+ package_root: Root directory of the package
65
+
66
+ Returns:
67
+ Translated component ready for installation
68
+ """
69
+ pass
70
+
71
+ @abstractmethod
72
+ def translate_mcp_server(self, component: MCPServerComponent, package_root: Path) -> TranslatedComponent:
73
+ """
74
+ Translate MCP server component to IDE-specific format.
75
+
76
+ Args:
77
+ component: MCP server component from package
78
+ package_root: Root directory of the package
79
+
80
+ Returns:
81
+ Translated component ready for installation
82
+ """
83
+ pass
84
+
85
+ def translate_hook(self, component: HookComponent, package_root: Path) -> TranslatedComponent:
86
+ """
87
+ Translate hook component to IDE-specific format.
88
+
89
+ Args:
90
+ component: Hook component from package
91
+ package_root: Root directory of the package
92
+
93
+ Returns:
94
+ Translated component ready for installation
95
+
96
+ Raises:
97
+ NotImplementedError: If IDE doesn't support hooks
98
+ """
99
+ raise NotImplementedError(f"{self.tool_type.value} does not support hooks")
100
+
101
+ def translate_command(self, component: CommandComponent, package_root: Path) -> TranslatedComponent:
102
+ """
103
+ Translate command component to IDE-specific format.
104
+
105
+ Args:
106
+ component: Command component from package
107
+ package_root: Root directory of the package
108
+
109
+ Returns:
110
+ Translated component ready for installation
111
+
112
+ Raises:
113
+ NotImplementedError: If IDE doesn't support commands
114
+ """
115
+ raise NotImplementedError(f"{self.tool_type.value} does not support commands")
116
+
117
+ def translate_resource(self, component: ResourceComponent, package_root: Path) -> TranslatedComponent:
118
+ """
119
+ Translate resource component (generic file copy).
120
+
121
+ Args:
122
+ component: Resource component from package
123
+ package_root: Root directory of the package
124
+
125
+ Returns:
126
+ Translated component ready for installation
127
+ """
128
+ # For resources, we don't read the content here - it will be copied in installation
129
+ # Just provide the source path in metadata
130
+ resource_path = package_root / component.file
131
+
132
+ return TranslatedComponent(
133
+ component_type=ComponentType.RESOURCE,
134
+ component_name=component.name,
135
+ target_path=component.install_path, # Use install_path instead of file
136
+ content="", # Content not used - file will be copied directly
137
+ metadata={
138
+ "checksum": component.checksum,
139
+ "size": component.size,
140
+ "source_path": str(resource_path), # Source file path for copying
141
+ },
142
+ )
143
+
144
+
145
+ class CursorTranslator(ComponentTranslator):
146
+ """Translator for Cursor IDE (.cursor/rules/*.mdc)."""
147
+
148
+ @property
149
+ def tool_type(self) -> AIToolType:
150
+ return AIToolType.CURSOR
151
+
152
+ def translate_instruction(self, component: InstructionComponent, package_root: Path) -> TranslatedComponent:
153
+ """Translate instruction to Cursor .mdc format."""
154
+ # Read instruction content
155
+ instruction_path = package_root / component.file
156
+ with open(instruction_path, "r") as f:
157
+ content = f.read()
158
+
159
+ # Cursor uses .mdc files with optional frontmatter
160
+ # If instruction has tags, add them as metadata
161
+ if component.tags:
162
+ frontmatter = "---\n"
163
+ frontmatter += f"description: {component.description}\n"
164
+ frontmatter += f"tags: [{', '.join(component.tags)}]\n"
165
+ frontmatter += "---\n\n"
166
+ content = frontmatter + content
167
+
168
+ # Target path: .cursor/rules/name.mdc
169
+ target_path = f".cursor/rules/{component.name}.mdc"
170
+
171
+ return TranslatedComponent(
172
+ component_type=ComponentType.INSTRUCTION,
173
+ component_name=component.name,
174
+ target_path=target_path,
175
+ content=content,
176
+ )
177
+
178
+ def translate_mcp_server(self, component: MCPServerComponent, package_root: Path) -> TranslatedComponent:
179
+ """Translate MCP server config to Cursor format."""
180
+ # Read MCP config
181
+ mcp_path = package_root / component.file
182
+ with open(mcp_path, "r") as f:
183
+ content = f.read()
184
+
185
+ # Store MCP config in project-specific location (.cursor/mcp/)
186
+ target_path = f".cursor/mcp/{component.name}.json"
187
+
188
+ return TranslatedComponent(
189
+ component_type=ComponentType.MCP_SERVER,
190
+ component_name=component.name,
191
+ target_path=target_path,
192
+ content=content,
193
+ metadata={"credentials": [c.to_dict() for c in component.credentials]},
194
+ )
195
+
196
+
197
+ class ClaudeCodeTranslator(ComponentTranslator):
198
+ """Translator for Claude Code (.claude/rules/*.md, .claude/hooks/, .claude/commands/)."""
199
+
200
+ @property
201
+ def tool_type(self) -> AIToolType:
202
+ return AIToolType.CLAUDE
203
+
204
+ def translate_instruction(self, component: InstructionComponent, package_root: Path) -> TranslatedComponent:
205
+ """Translate instruction to Claude Code .md format."""
206
+ # Read instruction content
207
+ instruction_path = package_root / component.file
208
+ with open(instruction_path, "r") as f:
209
+ content = f.read()
210
+
211
+ # Target path: .claude/rules/name.md
212
+ target_path = f".claude/rules/{component.name}.md"
213
+
214
+ return TranslatedComponent(
215
+ component_type=ComponentType.INSTRUCTION,
216
+ component_name=component.name,
217
+ target_path=target_path,
218
+ content=content,
219
+ metadata={"tags": component.tags} if component.tags else None,
220
+ )
221
+
222
+ def translate_mcp_server(self, component: MCPServerComponent, package_root: Path) -> TranslatedComponent:
223
+ """Translate MCP server config to Claude Code format."""
224
+ # Read MCP config
225
+ mcp_path = package_root / component.file
226
+ with open(mcp_path, "r") as f:
227
+ content = f.read()
228
+
229
+ # Store MCP config in project-specific location
230
+ target_path = f".claude/mcp/{component.name}.json"
231
+
232
+ return TranslatedComponent(
233
+ component_type=ComponentType.MCP_SERVER,
234
+ component_name=component.name,
235
+ target_path=target_path,
236
+ content=content,
237
+ metadata={"credentials": [c.to_dict() for c in component.credentials]},
238
+ )
239
+
240
+ def translate_hook(self, component: HookComponent, package_root: Path) -> TranslatedComponent:
241
+ """Translate hook script to Claude Code format."""
242
+ # Read hook script
243
+ hook_path = package_root / component.file
244
+ with open(hook_path, "r") as f:
245
+ content = f.read()
246
+
247
+ # Target path: .claude/hooks/name.sh (or appropriate extension)
248
+ file_ext = Path(component.file).suffix
249
+ target_path = f".claude/hooks/{component.name}{file_ext}"
250
+
251
+ return TranslatedComponent(
252
+ component_type=ComponentType.HOOK,
253
+ component_name=component.name,
254
+ target_path=target_path,
255
+ content=content,
256
+ metadata={"hook_type": component.hook_type},
257
+ )
258
+
259
+ def translate_command(self, component: CommandComponent, package_root: Path) -> TranslatedComponent:
260
+ """Translate command script to Claude Code format."""
261
+ # Read command script
262
+ command_path = package_root / component.file
263
+ with open(command_path, "r") as f:
264
+ content = f.read()
265
+
266
+ # Target path: .claude/commands/name.sh (or appropriate extension)
267
+ file_ext = Path(component.file).suffix
268
+ target_path = f".claude/commands/{component.name}{file_ext}"
269
+
270
+ return TranslatedComponent(
271
+ component_type=ComponentType.COMMAND,
272
+ component_name=component.name,
273
+ target_path=target_path,
274
+ content=content,
275
+ metadata={"command_type": component.command_type},
276
+ )
277
+
278
+
279
+ class WindsurfTranslator(ComponentTranslator):
280
+ """Translator for Windsurf (.windsurf/rules/*.md)."""
281
+
282
+ @property
283
+ def tool_type(self) -> AIToolType:
284
+ return AIToolType.WINSURF
285
+
286
+ def translate_instruction(self, component: InstructionComponent, package_root: Path) -> TranslatedComponent:
287
+ """Translate instruction to Windsurf .md format."""
288
+ # Read instruction content
289
+ instruction_path = package_root / component.file
290
+ with open(instruction_path, "r") as f:
291
+ content = f.read()
292
+
293
+ # Target path: .windsurf/rules/name.md
294
+ target_path = f".windsurf/rules/{component.name}.md"
295
+
296
+ return TranslatedComponent(
297
+ component_type=ComponentType.INSTRUCTION,
298
+ component_name=component.name,
299
+ target_path=target_path,
300
+ content=content,
301
+ )
302
+
303
+ def translate_mcp_server(self, component: MCPServerComponent, package_root: Path) -> TranslatedComponent:
304
+ """Translate MCP server config to Windsurf format."""
305
+ # Read MCP config
306
+ mcp_path = package_root / component.file
307
+ with open(mcp_path, "r") as f:
308
+ content = f.read()
309
+
310
+ # Store MCP config in project-specific location
311
+ target_path = f".windsurf/mcp/{component.name}.json"
312
+
313
+ return TranslatedComponent(
314
+ component_type=ComponentType.MCP_SERVER,
315
+ component_name=component.name,
316
+ target_path=target_path,
317
+ content=content,
318
+ metadata={"credentials": [c.to_dict() for c in component.credentials]},
319
+ )
320
+
321
+
322
+ class CopilotTranslator(ComponentTranslator):
323
+ """Translator for GitHub Copilot (.github/instructions/)."""
324
+
325
+ @property
326
+ def tool_type(self) -> AIToolType:
327
+ return AIToolType.COPILOT
328
+
329
+ def translate_instruction(self, component: InstructionComponent, package_root: Path) -> TranslatedComponent:
330
+ """Translate instruction to GitHub Copilot format."""
331
+ # Read instruction content
332
+ instruction_path = package_root / component.file
333
+ with open(instruction_path, "r") as f:
334
+ content = f.read()
335
+
336
+ # GitHub Copilot uses directory approach
337
+ # Target path: .github/instructions/name.md
338
+ target_path = f".github/instructions/{component.name}.md"
339
+
340
+ return TranslatedComponent(
341
+ component_type=ComponentType.INSTRUCTION,
342
+ component_name=component.name,
343
+ target_path=target_path,
344
+ content=content,
345
+ )
346
+
347
+ def translate_mcp_server(self, component: MCPServerComponent, package_root: Path) -> TranslatedComponent:
348
+ """Translate MCP server config to VS Code/Copilot format."""
349
+ # Read MCP config
350
+ mcp_path = package_root / component.file
351
+ with open(mcp_path, "r") as f:
352
+ content = f.read()
353
+
354
+ # Store MCP config in project-specific location (.vscode/mcp/)
355
+ target_path = f".vscode/mcp/{component.name}.json"
356
+
357
+ return TranslatedComponent(
358
+ component_type=ComponentType.MCP_SERVER,
359
+ component_name=component.name,
360
+ target_path=target_path,
361
+ content=content,
362
+ metadata={"credentials": [c.to_dict() for c in component.credentials]},
363
+ )
364
+
365
+
366
+ def get_translator(tool_type: AIToolType) -> ComponentTranslator:
367
+ """
368
+ Factory function to get translator for an AI tool.
369
+
370
+ Args:
371
+ tool_type: AI tool type
372
+
373
+ Returns:
374
+ ComponentTranslator instance for the tool
375
+
376
+ Raises:
377
+ ValueError: If tool type is not supported
378
+ """
379
+ translators: dict[AIToolType, type[ComponentTranslator]] = {
380
+ AIToolType.CURSOR: CursorTranslator,
381
+ AIToolType.CLAUDE: ClaudeCodeTranslator,
382
+ AIToolType.WINSURF: WindsurfTranslator,
383
+ AIToolType.COPILOT: CopilotTranslator,
384
+ }
385
+
386
+ translator_class = translators.get(tool_type)
387
+ if not translator_class:
388
+ raise ValueError(f"No translator found for tool type: {tool_type}")
389
+
390
+ return translator_class()
@@ -0,0 +1,102 @@
1
+ """Winsurf AI tool integration."""
2
+
3
+ from pathlib import Path
4
+
5
+ from aiconfigkit.ai_tools.base import AITool
6
+ from aiconfigkit.core.models import AIToolType
7
+ from aiconfigkit.utils.paths import get_windsurf_mcp_config_path, get_winsurf_config_dir
8
+
9
+
10
+ class WinsurfTool(AITool):
11
+ """Integration for Winsurf AI coding tool."""
12
+
13
+ @property
14
+ def tool_type(self) -> AIToolType:
15
+ """Return the AI tool type identifier."""
16
+ return AIToolType.WINSURF
17
+
18
+ @property
19
+ def tool_name(self) -> str:
20
+ """Return human-readable tool name."""
21
+ return "Windsurf"
22
+
23
+ def is_installed(self) -> bool:
24
+ """
25
+ Check if Windsurf is installed on the system.
26
+
27
+ Checks for existence of Windsurf configuration directory.
28
+
29
+ Returns:
30
+ True if Windsurf is detected
31
+ """
32
+ try:
33
+ config_dir = get_winsurf_config_dir()
34
+ # Check if parent directory exists
35
+ # Windsurf config dir structure: .../Windsurf/User/globalStorage
36
+ winsurf_base = config_dir.parent.parent
37
+ return winsurf_base.exists()
38
+ except Exception:
39
+ return False
40
+
41
+ def get_instructions_directory(self) -> Path:
42
+ """
43
+ Get the directory where Windsurf instructions should be installed.
44
+
45
+ Note: Windsurf uses ~/.windsurfrules as a single file for global rules.
46
+ This tool currently only supports project-level installations.
47
+
48
+ Returns:
49
+ Path to Windsurf instructions directory
50
+
51
+ Raises:
52
+ NotImplementedError: Global installation not supported for Windsurf
53
+ """
54
+ raise NotImplementedError(
55
+ f"{self.tool_name} global installation is not supported. "
56
+ "Windsurf uses a single ~/.windsurfrules file for global rules. "
57
+ "Please use project-level installation instead (--scope project)."
58
+ )
59
+
60
+ def get_instruction_file_extension(self) -> str:
61
+ """
62
+ Get the file extension for Windsurf instructions.
63
+
64
+ Windsurf uses markdown (.md) files for rules.
65
+
66
+ Returns:
67
+ File extension including the dot
68
+ """
69
+ return ".md"
70
+
71
+ def get_project_instructions_directory(self, project_root: Path) -> Path:
72
+ """
73
+ Get the directory for project-specific Windsurf instructions.
74
+
75
+ Windsurf stores project-specific rules in .windsurf/rules/ directory
76
+ in the project root. It supports multiple .md files in this directory.
77
+
78
+ Reference:
79
+ - Per-Project: .windsurf/rules/*.md (multiple files)
80
+ - Alternative: .windsurfrules (single file, not used by this tool)
81
+
82
+ Args:
83
+ project_root: Path to the project root directory
84
+
85
+ Returns:
86
+ Path to project instructions directory (.windsurf/rules/)
87
+ """
88
+ instructions_dir = project_root / ".windsurf" / "rules"
89
+ instructions_dir.mkdir(parents=True, exist_ok=True)
90
+ return instructions_dir
91
+
92
+ def get_mcp_config_path(self) -> Path:
93
+ """
94
+ Get the path to the Windsurf MCP configuration file.
95
+
96
+ Note: Windsurf may add MCP support in the future.
97
+ We provide this for compatibility.
98
+
99
+ Returns:
100
+ Path to MCP configuration file
101
+ """
102
+ return get_windsurf_mcp_config_path()
File without changes
@@ -0,0 +1,118 @@
1
+ """Delete command for removing instructions from library."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.prompt import Confirm
6
+
7
+ from aiconfigkit.storage.library import LibraryManager
8
+ from aiconfigkit.storage.tracker import InstallationTracker
9
+ from aiconfigkit.utils.ui import print_error, print_success, print_warning
10
+
11
+ console = Console()
12
+
13
+ app = typer.Typer()
14
+
15
+
16
+ def delete_from_library(
17
+ namespace: str,
18
+ force: bool = False,
19
+ ) -> int:
20
+ """
21
+ Delete a repository from the library.
22
+
23
+ Args:
24
+ namespace: Repository namespace to delete
25
+ force: Skip confirmation
26
+
27
+ Returns:
28
+ Exit code (0 = success)
29
+ """
30
+ library = LibraryManager()
31
+ tracker = InstallationTracker()
32
+
33
+ # Check if repository exists
34
+ repo = library.get_repository(namespace)
35
+ if not repo:
36
+ print_error(f"Repository not found: {namespace}")
37
+ print_error("Use 'instructionkit list library' to see available repositories")
38
+ return 1
39
+
40
+ # Check if any instructions from this repo are currently installed
41
+ installed_records = tracker.list_installations()
42
+ installed_from_repo = [
43
+ record for record in installed_records if any(inst.repo_namespace == namespace for inst in repo.instructions)
44
+ ]
45
+
46
+ if installed_from_repo and not force:
47
+ print_warning(
48
+ f"\n⚠️ Warning: {len(installed_from_repo)} instruction(s) from this repository are currently installed:"
49
+ )
50
+ for record in installed_from_repo[:5]: # Show first 5
51
+ console.print(f" - {record.instruction_name} ({record.ai_tool.value})")
52
+ if len(installed_from_repo) > 5:
53
+ console.print(f" ... and {len(installed_from_repo) - 5} more")
54
+ console.print()
55
+ print_warning("Deleting from library will not uninstall them from your AI tools.\n")
56
+
57
+ # Confirm deletion
58
+ if not force:
59
+ console.print(f"\n[bold]Repository:[/bold] {repo.name}")
60
+ console.print(f"[bold]Namespace:[/bold] {repo.namespace}")
61
+ console.print(f"[bold]Instructions:[/bold] {len(repo.instructions)}\n")
62
+
63
+ confirmed = Confirm.ask("[yellow]Are you sure you want to delete this repository from your library?[/yellow]")
64
+
65
+ if not confirmed:
66
+ console.print("[dim]Cancelled[/dim]")
67
+ return 0
68
+
69
+ # Delete repository
70
+ success = library.remove_repository(namespace)
71
+
72
+ if success:
73
+ print_success(
74
+ f"✓ Deleted repository '{repo.name}' from library\n"
75
+ f" {len(repo.instructions)} instruction(s) removed from library"
76
+ )
77
+ if installed_from_repo:
78
+ print_warning(
79
+ f"\n Note: {len(installed_from_repo)} instruction(s) are still installed in your AI tools.\n"
80
+ f" Use 'instructionkit uninstall <name>' to remove them."
81
+ )
82
+ return 0
83
+ else:
84
+ print_error(f"Failed to delete repository: {namespace}")
85
+ return 1
86
+
87
+
88
+ @app.command(name="delete")
89
+ def delete_command(
90
+ namespace: str = typer.Argument(
91
+ ...,
92
+ help="Repository namespace to delete from library",
93
+ ),
94
+ force: bool = typer.Option(
95
+ False,
96
+ "--force",
97
+ "-f",
98
+ help="Skip confirmation prompt",
99
+ ),
100
+ ) -> None:
101
+ """
102
+ Delete a repository from your local library.
103
+
104
+ This removes the downloaded instructions from your library but does NOT
105
+ uninstall them from your AI tools. To uninstall, use 'instructionkit uninstall'.
106
+
107
+ Examples:
108
+ # Delete a repository
109
+ instructionkit delete github.com_company_instructions
110
+
111
+ # Skip confirmation
112
+ instructionkit delete github.com_company_instructions --force
113
+
114
+ # List repositories to find namespace
115
+ instructionkit list library
116
+ """
117
+ exit_code = delete_from_library(namespace=namespace, force=force)
118
+ raise typer.Exit(code=exit_code)