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,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)
|