devsync 0.13.0__tar.gz → 0.14.0__tar.gz
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.
- {devsync-0.13.0 → devsync-0.14.0}/PKG-INFO +1 -1
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/capability_registry.py +2 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/extract.py +168 -6
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/main.py +61 -4
- devsync-0.14.0/devsync/cli/tools.py +74 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/component_detector.py +277 -163
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/extractor.py +27 -5
- {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/PKG-INFO +1 -1
- {devsync-0.13.0 → devsync-0.14.0}/pyproject.toml +1 -1
- devsync-0.13.0/devsync/cli/tools.py +0 -47
- {devsync-0.13.0 → devsync-0.14.0}/LICENSE +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/README.md +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/__main__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/aider.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/amazonq.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/amp.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/anteroom.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/antigravity.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/augment.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/base.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/claude.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/claude_desktop.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/cline.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/codex.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/continuedev.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/copilot.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/cursor.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/detector.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/gemini.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/jetbrains.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/junie.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/kiro.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/mcp_syncer.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/opencode.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/openhands.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/roo.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/tabnine.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/trae.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/translator.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/winsurf.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/zed.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/install_v2.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/list_v2.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/setup.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/uninstall.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/adapter.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/checksum.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/conflict_resolution.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/git_operations.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/credentials.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/manager.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/set_manager.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/validator.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp_credential_prompter.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/models.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/package_creator.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/package_manifest.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/package_manifest_v2.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/pip_utils.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/practice.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/repository.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/secret_detector.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/core/version.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/anthropic.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/config.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/openai_provider.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/openrouter.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/prompts.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/provider.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/response_models.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/mcp_tracker.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/package_tracker.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/tracker.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/tui/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/__init__.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/atomic_write.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/backup.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/dotenv.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/git_helpers.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/logging.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/namespace.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/paths.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/project.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/streaming.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/ui.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/validation.py +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/SOURCES.txt +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/dependency_links.txt +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/entry_points.txt +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/requires.txt +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/top_level.txt +0 -0
- {devsync-0.13.0 → devsync-0.14.0}/setup.cfg +0 -0
|
@@ -28,6 +28,7 @@ class IDECapability:
|
|
|
28
28
|
skills_directory: str | None = None # Claude skills
|
|
29
29
|
workflows_directory: str | None = None # Windsurf workflows
|
|
30
30
|
memory_file_name: str | None = None # CLAUDE.md
|
|
31
|
+
mcp_servers_json_key: str = "mcpServers" # JSON key for MCP servers in config files
|
|
31
32
|
notes: str = ""
|
|
32
33
|
|
|
33
34
|
def supports_component(self, component_type: ComponentType) -> bool:
|
|
@@ -494,6 +495,7 @@ CAPABILITY_REGISTRY: dict[AIToolType, IDECapability] = {
|
|
|
494
495
|
mcp_project_config_path=".vscode/mcp.json", # Workspace-level MCP config
|
|
495
496
|
hooks_directory=None, # Hooks not supported
|
|
496
497
|
commands_directory=None, # Commands not supported
|
|
498
|
+
mcp_servers_json_key="servers", # VS Code uses "servers" not "mcpServers"
|
|
497
499
|
notes=(
|
|
498
500
|
"GitHub Copilot uses .github/copilot-instructions.md (main) and "
|
|
499
501
|
".github/instructions/**/*.instructions.md (file-specific with globs). "
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
"""Extract command — reads project configs and produces a shareable package."""
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
|
+
import warnings
|
|
5
|
+
from collections import Counter
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import Optional
|
|
6
8
|
|
|
7
9
|
from rich.console import Console
|
|
8
10
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
|
-
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from devsync.core.component_detector import (
|
|
14
|
+
COMPONENT_TYPE_MAP,
|
|
15
|
+
ComponentDetector,
|
|
16
|
+
DetectionResult,
|
|
17
|
+
filter_detection_result,
|
|
18
|
+
)
|
|
10
19
|
from devsync.core.extractor import PracticeExtractor
|
|
11
20
|
from devsync.core.package_manifest_v2 import PackageManifestV2, detect_manifest_format, parse_manifest
|
|
12
21
|
from devsync.llm.config import load_config
|
|
@@ -14,6 +23,89 @@ from devsync.llm.provider import resolve_provider
|
|
|
14
23
|
|
|
15
24
|
console = Console()
|
|
16
25
|
|
|
26
|
+
# Map DetectionResult field names back to user-facing component names
|
|
27
|
+
_FIELD_TO_LABEL: dict[str, str] = {
|
|
28
|
+
"instructions": "Rules",
|
|
29
|
+
"mcp_servers": "MCP",
|
|
30
|
+
"hooks": "Hooks",
|
|
31
|
+
"commands": "Commands",
|
|
32
|
+
"skills": "Skills",
|
|
33
|
+
"workflows": "Workflows",
|
|
34
|
+
"memory_files": "Memory",
|
|
35
|
+
"resources": "Resources",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_detection_rows(detection: DetectionResult) -> list[tuple[str, str, int]]:
|
|
40
|
+
"""Build (component_label, source_tool, count) rows from detection.
|
|
41
|
+
|
|
42
|
+
Groups by component type + source tool for a concise table.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of (label, source, count) tuples, sorted by label then source.
|
|
46
|
+
"""
|
|
47
|
+
rows: list[tuple[str, str, int]] = []
|
|
48
|
+
|
|
49
|
+
for field_name, label in _FIELD_TO_LABEL.items():
|
|
50
|
+
items = getattr(detection, field_name, [])
|
|
51
|
+
if not items:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
source_counts: Counter[str] = Counter()
|
|
55
|
+
for item in items:
|
|
56
|
+
source = getattr(item, "source_tool", "") or getattr(item, "source_ide", "") or "project"
|
|
57
|
+
source_counts[source] += 1
|
|
58
|
+
|
|
59
|
+
for source, count in sorted(source_counts.items()):
|
|
60
|
+
rows.append((label, source, count))
|
|
61
|
+
|
|
62
|
+
return rows
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _display_detection_summary(detection: DetectionResult) -> None:
|
|
66
|
+
"""Display a Rich table summarizing detected components."""
|
|
67
|
+
rows = _get_detection_rows(detection)
|
|
68
|
+
|
|
69
|
+
if not rows:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
table = Table(title="Detected Components", show_header=True, header_style="bold cyan")
|
|
73
|
+
table.add_column("Component", style="cyan", no_wrap=True)
|
|
74
|
+
table.add_column("Source", style="green")
|
|
75
|
+
table.add_column("Count", justify="right")
|
|
76
|
+
|
|
77
|
+
for label, source, count in rows:
|
|
78
|
+
table.add_row(label, source, str(count))
|
|
79
|
+
|
|
80
|
+
console.print()
|
|
81
|
+
console.print(table)
|
|
82
|
+
|
|
83
|
+
tool_sources = {source for _, source, _ in rows if source not in ("project", "devsync")}
|
|
84
|
+
tool_count = len(tool_sources) if tool_sources else 1
|
|
85
|
+
console.print(f"\n Total: {detection.total_count} components from {tool_count} tool(s)")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _display_zero_result_warning(
|
|
89
|
+
tool: Optional[list[str]] = None,
|
|
90
|
+
component: Optional[list[str]] = None,
|
|
91
|
+
include_global: bool = False,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Display a helpful warning when filters match no components."""
|
|
94
|
+
console.print("\n[yellow]No components found matching your filters.[/yellow]")
|
|
95
|
+
|
|
96
|
+
active_filters = []
|
|
97
|
+
if tool:
|
|
98
|
+
active_filters.append(f"--tool {' --tool '.join(tool)}")
|
|
99
|
+
if component:
|
|
100
|
+
active_filters.append(f"--component {' --component '.join(component)}")
|
|
101
|
+
if active_filters:
|
|
102
|
+
console.print(f"\n Active: {' '.join(active_filters)}")
|
|
103
|
+
|
|
104
|
+
console.print("\n Suggestions:")
|
|
105
|
+
console.print(" - Run [cyan]devsync extract --dry-run[/cyan] without filters to see all available components")
|
|
106
|
+
if not include_global:
|
|
107
|
+
console.print(" - Try [cyan]--include-global[/cyan] to include home directory configs")
|
|
108
|
+
|
|
17
109
|
|
|
18
110
|
def extract_command(
|
|
19
111
|
output: Optional[str] = None,
|
|
@@ -21,6 +113,11 @@ def extract_command(
|
|
|
21
113
|
no_ai: bool = False,
|
|
22
114
|
project_dir: Optional[str] = None,
|
|
23
115
|
upgrade: Optional[str] = None,
|
|
116
|
+
tool: Optional[list[str]] = None,
|
|
117
|
+
component: Optional[list[str]] = None,
|
|
118
|
+
scope: str = "project",
|
|
119
|
+
dry_run: bool = False,
|
|
120
|
+
include_global: bool = False,
|
|
24
121
|
) -> int:
|
|
25
122
|
"""Extract practices from the current project into a shareable package.
|
|
26
123
|
|
|
@@ -30,6 +127,11 @@ def extract_command(
|
|
|
30
127
|
no_ai: Force file-copy mode (no LLM calls).
|
|
31
128
|
project_dir: Project directory to extract from. Defaults to cwd.
|
|
32
129
|
upgrade: Path to a v1 package to convert to v2 format.
|
|
130
|
+
tool: Only extract from these AI tool(s).
|
|
131
|
+
component: Only extract these component types.
|
|
132
|
+
scope: Detection scope — project, global, or all. Deprecated; use include_global.
|
|
133
|
+
dry_run: Show what would be extracted without writing files.
|
|
134
|
+
include_global: Include home directory / global configs.
|
|
33
135
|
|
|
34
136
|
Returns:
|
|
35
137
|
Exit code (0 = success).
|
|
@@ -37,11 +139,70 @@ def extract_command(
|
|
|
37
139
|
if upgrade:
|
|
38
140
|
return _upgrade_v1_package(upgrade, output=output, name=name, no_ai=no_ai)
|
|
39
141
|
|
|
142
|
+
# Resolve effective scope: --include-global takes precedence
|
|
143
|
+
effective_scope = "project"
|
|
144
|
+
if include_global:
|
|
145
|
+
effective_scope = "all"
|
|
146
|
+
elif scope != "project":
|
|
147
|
+
warnings.warn(
|
|
148
|
+
"--scope is deprecated, use --include-global instead",
|
|
149
|
+
DeprecationWarning,
|
|
150
|
+
stacklevel=2,
|
|
151
|
+
)
|
|
152
|
+
console.print("[yellow]--scope is deprecated. Use --include-global instead.[/yellow]")
|
|
153
|
+
effective_scope = scope
|
|
154
|
+
|
|
155
|
+
# Validate scope
|
|
156
|
+
if effective_scope not in ("project", "global", "all"):
|
|
157
|
+
console.print(f"[red]Invalid scope: {effective_scope}. Must be project, global, or all.[/red]")
|
|
158
|
+
return 1
|
|
159
|
+
|
|
160
|
+
# Validate tool names
|
|
161
|
+
if tool:
|
|
162
|
+
from devsync.ai_tools.detector import AIToolDetector
|
|
163
|
+
|
|
164
|
+
detector = AIToolDetector()
|
|
165
|
+
for t in tool:
|
|
166
|
+
if not detector.validate_tool_name(t):
|
|
167
|
+
console.print(f"[red]Unknown tool: {t}[/red]")
|
|
168
|
+
console.print(f"Supported tools: {', '.join(detector.get_tool_names())}")
|
|
169
|
+
return 1
|
|
170
|
+
|
|
171
|
+
# Validate component names
|
|
172
|
+
if component:
|
|
173
|
+
for c in component:
|
|
174
|
+
if c.lower() not in COMPONENT_TYPE_MAP:
|
|
175
|
+
valid = sorted(set(COMPONENT_TYPE_MAP.keys()))
|
|
176
|
+
console.print(f"[red]Unknown component type: {c}[/red]")
|
|
177
|
+
console.print(f"Valid types: {', '.join(valid)}")
|
|
178
|
+
return 1
|
|
179
|
+
|
|
40
180
|
project_path = Path(project_dir) if project_dir else Path.cwd()
|
|
41
181
|
if not project_path.is_dir():
|
|
42
182
|
console.print(f"[red]Not a directory: {project_path}[/red]")
|
|
43
183
|
return 1
|
|
44
184
|
|
|
185
|
+
# Phase 1: Detection + filtering
|
|
186
|
+
comp_detector = ComponentDetector(project_path, scope=effective_scope, tool_filter=tool)
|
|
187
|
+
detection = comp_detector.detect_all()
|
|
188
|
+
|
|
189
|
+
if component:
|
|
190
|
+
detection = filter_detection_result(detection, component_filter=component)
|
|
191
|
+
|
|
192
|
+
# Zero-result handling
|
|
193
|
+
if detection.total_count == 0:
|
|
194
|
+
_display_zero_result_warning(tool=tool, component=component, include_global=include_global)
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
# Display detection summary
|
|
198
|
+
_display_detection_summary(detection)
|
|
199
|
+
|
|
200
|
+
# Dry-run: stop here
|
|
201
|
+
if dry_run:
|
|
202
|
+
console.print("\n[dim]Dry run — no files written.[/dim]")
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
# Phase 2: Extraction
|
|
45
206
|
package_name = name or project_path.name
|
|
46
207
|
output_path = Path(output) if output else project_path / "devsync-package"
|
|
47
208
|
|
|
@@ -63,8 +224,8 @@ def extract_command(
|
|
|
63
224
|
TextColumn("[progress.description]{task.description}"),
|
|
64
225
|
console=console,
|
|
65
226
|
) as progress:
|
|
66
|
-
task = progress.add_task("
|
|
67
|
-
result = extractor.extract(project_path)
|
|
227
|
+
task = progress.add_task("Extracting practices...", total=None)
|
|
228
|
+
result = extractor.extract(project_path, detection=detection)
|
|
68
229
|
progress.update(task, description="Building package...")
|
|
69
230
|
|
|
70
231
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -96,12 +257,13 @@ def extract_command(
|
|
|
96
257
|
manifest_path = output_path / "devsync-package.yaml"
|
|
97
258
|
manifest_path.write_text(manifest.to_yaml())
|
|
98
259
|
|
|
260
|
+
# Enhanced output
|
|
99
261
|
mode = "[green]AI-powered[/green]" if result.ai_powered else "[yellow]file-copy[/yellow]"
|
|
100
|
-
console.print(f"\
|
|
101
|
-
console.print(f" Practices: {len(result.practices)}")
|
|
262
|
+
console.print(f"\nExtraction complete ({mode})")
|
|
263
|
+
console.print(f" Practices generated: {len(result.practices)}")
|
|
102
264
|
console.print(f" MCP servers: {len(result.mcp_servers)}")
|
|
103
265
|
console.print(f" Source files: {len(result.source_files)}")
|
|
104
|
-
console.print(f"\
|
|
266
|
+
console.print(f"\n Package written to: [cyan]{output_path}[/cyan]")
|
|
105
267
|
return 0
|
|
106
268
|
|
|
107
269
|
|
|
@@ -30,13 +30,20 @@ def setup() -> None:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@app.command()
|
|
33
|
-
def tools(
|
|
33
|
+
def tools(
|
|
34
|
+
verbose: bool = typer.Option(
|
|
35
|
+
False,
|
|
36
|
+
"--verbose",
|
|
37
|
+
"-v",
|
|
38
|
+
help="Show capabilities and valid filter names",
|
|
39
|
+
),
|
|
40
|
+
) -> None:
|
|
34
41
|
"""Show detected AI coding tools.
|
|
35
42
|
|
|
36
43
|
Display which AI coding tools are installed on your system
|
|
37
44
|
and where their configuration directories are located.
|
|
38
45
|
"""
|
|
39
|
-
exit_code = show_tools()
|
|
46
|
+
exit_code = show_tools(verbose=verbose)
|
|
40
47
|
raise typer.Exit(code=exit_code)
|
|
41
48
|
|
|
42
49
|
|
|
@@ -70,11 +77,41 @@ def extract(
|
|
|
70
77
|
"--upgrade",
|
|
71
78
|
help="Convert a v1 package to v2 format",
|
|
72
79
|
),
|
|
80
|
+
tool: Optional[list[str]] = typer.Option(
|
|
81
|
+
None,
|
|
82
|
+
"--tool",
|
|
83
|
+
"-t",
|
|
84
|
+
help="Only extract from specific AI tool(s). Repeatable.",
|
|
85
|
+
),
|
|
86
|
+
component: Optional[list[str]] = typer.Option(
|
|
87
|
+
None,
|
|
88
|
+
"--component",
|
|
89
|
+
"-c",
|
|
90
|
+
help="Component types to extract: rules, mcp, hooks, commands, skills, workflows, memory. Repeatable.",
|
|
91
|
+
),
|
|
92
|
+
scope: str = typer.Option(
|
|
93
|
+
"project",
|
|
94
|
+
"--scope",
|
|
95
|
+
"-s",
|
|
96
|
+
help="(Deprecated) Detection scope: project, global, or all. Use --include-global instead.",
|
|
97
|
+
hidden=True,
|
|
98
|
+
),
|
|
99
|
+
dry_run: bool = typer.Option(
|
|
100
|
+
False,
|
|
101
|
+
"--dry-run",
|
|
102
|
+
help="Show detected components without writing files or calling the LLM",
|
|
103
|
+
),
|
|
104
|
+
include_global: bool = typer.Option(
|
|
105
|
+
False,
|
|
106
|
+
"--include-global",
|
|
107
|
+
help="Include home directory / global configs in extraction",
|
|
108
|
+
),
|
|
73
109
|
) -> None:
|
|
74
110
|
"""Extract practices from a project into a shareable package.
|
|
75
111
|
|
|
76
|
-
Reads your project's AI tool configs (rules, MCP servers, hooks, commands
|
|
77
|
-
and produces a devsync-package.yaml
|
|
112
|
+
Reads your project's AI tool configs (rules, MCP servers, hooks, commands,
|
|
113
|
+
skills, workflows, memory files, resources) and produces a devsync-package.yaml
|
|
114
|
+
with abstract practice declarations.
|
|
78
115
|
|
|
79
116
|
Examples:
|
|
80
117
|
# AI-powered extraction
|
|
@@ -86,6 +123,21 @@ def extract(
|
|
|
86
123
|
# Custom output and name
|
|
87
124
|
devsync extract --output ./my-package --name team-standards
|
|
88
125
|
|
|
126
|
+
# Extract only from Cursor
|
|
127
|
+
devsync extract --tool cursor
|
|
128
|
+
|
|
129
|
+
# Extract only MCP configs
|
|
130
|
+
devsync extract --component mcp
|
|
131
|
+
|
|
132
|
+
# Preview what would be extracted (no files written)
|
|
133
|
+
devsync extract --dry-run
|
|
134
|
+
|
|
135
|
+
# Include home directory / global configs
|
|
136
|
+
devsync extract --include-global
|
|
137
|
+
|
|
138
|
+
# Combine filters: extract only rules and hooks from Claude Code
|
|
139
|
+
devsync extract --tool claude --component rules --component hooks
|
|
140
|
+
|
|
89
141
|
# Upgrade v1 package to v2
|
|
90
142
|
devsync extract --upgrade ./old-package
|
|
91
143
|
"""
|
|
@@ -97,6 +149,11 @@ def extract(
|
|
|
97
149
|
no_ai=no_ai,
|
|
98
150
|
project_dir=project_dir,
|
|
99
151
|
upgrade=upgrade,
|
|
152
|
+
tool=tool,
|
|
153
|
+
component=component,
|
|
154
|
+
scope=scope,
|
|
155
|
+
dry_run=dry_run,
|
|
156
|
+
include_global=include_global,
|
|
100
157
|
)
|
|
101
158
|
raise typer.Exit(code=exit_code)
|
|
102
159
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tools command to show detected AI coding tools."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
from devsync.ai_tools.detector import get_detector
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
# Map ComponentType enum values to user-facing filter names
|
|
11
|
+
_COMPONENT_TYPE_LABELS: dict[str, str] = {
|
|
12
|
+
"instruction": "rules",
|
|
13
|
+
"mcp_server": "mcp",
|
|
14
|
+
"hook": "hooks",
|
|
15
|
+
"command": "commands",
|
|
16
|
+
"skill": "skills",
|
|
17
|
+
"workflow": "workflows",
|
|
18
|
+
"resource": "resources",
|
|
19
|
+
"memory_file": "memory",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def show_tools(verbose: bool = False) -> int:
|
|
24
|
+
"""Show detected AI coding tools.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
verbose: Show capabilities column and valid filter names.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Exit code (0 for success)
|
|
31
|
+
"""
|
|
32
|
+
from devsync.ai_tools.capability_registry import CAPABILITY_REGISTRY
|
|
33
|
+
|
|
34
|
+
detector = get_detector()
|
|
35
|
+
|
|
36
|
+
table = Table(title="AI Coding Tools", show_header=True, header_style="bold cyan")
|
|
37
|
+
table.add_column("Tool", style="cyan", no_wrap=True)
|
|
38
|
+
table.add_column("Status", style="green")
|
|
39
|
+
if verbose:
|
|
40
|
+
table.add_column("Capabilities", style="dim")
|
|
41
|
+
|
|
42
|
+
for tool_type, tool in detector.tools.items():
|
|
43
|
+
is_installed = tool.is_installed()
|
|
44
|
+
status = "[green]✓ Installed[/green]" if is_installed else "[red]✗ Not found[/red]"
|
|
45
|
+
|
|
46
|
+
if verbose:
|
|
47
|
+
cap = CAPABILITY_REGISTRY.get(tool_type)
|
|
48
|
+
if cap:
|
|
49
|
+
labels = sorted(_COMPONENT_TYPE_LABELS.get(ct.value, ct.value) for ct in cap.supported_components)
|
|
50
|
+
caps_str = ", ".join(labels)
|
|
51
|
+
else:
|
|
52
|
+
caps_str = ""
|
|
53
|
+
table.add_row(tool.tool_name, status, caps_str)
|
|
54
|
+
else:
|
|
55
|
+
table.add_row(tool.tool_name, status)
|
|
56
|
+
|
|
57
|
+
console.print()
|
|
58
|
+
console.print(table)
|
|
59
|
+
console.print()
|
|
60
|
+
|
|
61
|
+
installed = detector.detect_installed_tools()
|
|
62
|
+
if installed:
|
|
63
|
+
tool_names = ", ".join([t.tool_name for t in installed])
|
|
64
|
+
console.print(f"[green]Found {len(installed)} installed tool(s):[/green] {tool_names}")
|
|
65
|
+
else:
|
|
66
|
+
console.print("[yellow]No AI coding tools detected[/yellow]")
|
|
67
|
+
console.print("\nSupported tools: Cursor, GitHub Copilot, Winsurf, Claude Code")
|
|
68
|
+
|
|
69
|
+
if verbose:
|
|
70
|
+
all_labels = sorted(set(_COMPONENT_TYPE_LABELS.values()))
|
|
71
|
+
console.print(f"\nValid --component names: {', '.join(all_labels)}")
|
|
72
|
+
|
|
73
|
+
console.print()
|
|
74
|
+
return 0
|