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.
Files changed (99) hide show
  1. {devsync-0.13.0 → devsync-0.14.0}/PKG-INFO +1 -1
  2. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/capability_registry.py +2 -0
  3. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/extract.py +168 -6
  4. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/main.py +61 -4
  5. devsync-0.14.0/devsync/cli/tools.py +74 -0
  6. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/component_detector.py +277 -163
  7. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/extractor.py +27 -5
  8. {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/PKG-INFO +1 -1
  9. {devsync-0.13.0 → devsync-0.14.0}/pyproject.toml +1 -1
  10. devsync-0.13.0/devsync/cli/tools.py +0 -47
  11. {devsync-0.13.0 → devsync-0.14.0}/LICENSE +0 -0
  12. {devsync-0.13.0 → devsync-0.14.0}/README.md +0 -0
  13. {devsync-0.13.0 → devsync-0.14.0}/devsync/__init__.py +0 -0
  14. {devsync-0.13.0 → devsync-0.14.0}/devsync/__main__.py +0 -0
  15. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/__init__.py +0 -0
  16. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/aider.py +0 -0
  17. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/amazonq.py +0 -0
  18. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/amp.py +0 -0
  19. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/anteroom.py +0 -0
  20. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/antigravity.py +0 -0
  21. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/augment.py +0 -0
  22. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/base.py +0 -0
  23. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/claude.py +0 -0
  24. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/claude_desktop.py +0 -0
  25. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/cline.py +0 -0
  26. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/codex.py +0 -0
  27. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/continuedev.py +0 -0
  28. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/copilot.py +0 -0
  29. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/cursor.py +0 -0
  30. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/detector.py +0 -0
  31. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/gemini.py +0 -0
  32. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/jetbrains.py +0 -0
  33. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/junie.py +0 -0
  34. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/kiro.py +0 -0
  35. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/mcp_syncer.py +0 -0
  36. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/opencode.py +0 -0
  37. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/openhands.py +0 -0
  38. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/roo.py +0 -0
  39. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/tabnine.py +0 -0
  40. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/trae.py +0 -0
  41. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/translator.py +0 -0
  42. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/winsurf.py +0 -0
  43. {devsync-0.13.0 → devsync-0.14.0}/devsync/ai_tools/zed.py +0 -0
  44. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/__init__.py +0 -0
  45. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/install_v2.py +0 -0
  46. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/list_v2.py +0 -0
  47. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/setup.py +0 -0
  48. {devsync-0.13.0 → devsync-0.14.0}/devsync/cli/uninstall.py +0 -0
  49. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/__init__.py +0 -0
  50. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/adapter.py +0 -0
  51. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/checksum.py +0 -0
  52. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/conflict_resolution.py +0 -0
  53. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/git_operations.py +0 -0
  54. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/__init__.py +0 -0
  55. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/credentials.py +0 -0
  56. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/manager.py +0 -0
  57. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/set_manager.py +0 -0
  58. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp/validator.py +0 -0
  59. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/mcp_credential_prompter.py +0 -0
  60. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/models.py +0 -0
  61. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/package_creator.py +0 -0
  62. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/package_manifest.py +0 -0
  63. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/package_manifest_v2.py +0 -0
  64. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/pip_utils.py +0 -0
  65. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/practice.py +0 -0
  66. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/repository.py +0 -0
  67. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/secret_detector.py +0 -0
  68. {devsync-0.13.0 → devsync-0.14.0}/devsync/core/version.py +0 -0
  69. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/__init__.py +0 -0
  70. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/anthropic.py +0 -0
  71. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/config.py +0 -0
  72. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/openai_provider.py +0 -0
  73. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/openrouter.py +0 -0
  74. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/prompts.py +0 -0
  75. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/provider.py +0 -0
  76. {devsync-0.13.0 → devsync-0.14.0}/devsync/llm/response_models.py +0 -0
  77. {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/__init__.py +0 -0
  78. {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/mcp_tracker.py +0 -0
  79. {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/package_tracker.py +0 -0
  80. {devsync-0.13.0 → devsync-0.14.0}/devsync/storage/tracker.py +0 -0
  81. {devsync-0.13.0 → devsync-0.14.0}/devsync/tui/__init__.py +0 -0
  82. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/__init__.py +0 -0
  83. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/atomic_write.py +0 -0
  84. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/backup.py +0 -0
  85. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/dotenv.py +0 -0
  86. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/git_helpers.py +0 -0
  87. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/logging.py +0 -0
  88. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/namespace.py +0 -0
  89. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/paths.py +0 -0
  90. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/project.py +0 -0
  91. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/streaming.py +0 -0
  92. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/ui.py +0 -0
  93. {devsync-0.13.0 → devsync-0.14.0}/devsync/utils/validation.py +0 -0
  94. {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/SOURCES.txt +0 -0
  95. {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/dependency_links.txt +0 -0
  96. {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/entry_points.txt +0 -0
  97. {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/requires.txt +0 -0
  98. {devsync-0.13.0 → devsync-0.14.0}/devsync.egg-info/top_level.txt +0 -0
  99. {devsync-0.13.0 → devsync-0.14.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devsync
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: Distribute and sync dev tool configurations across teams
5
5
  Author-email: Troy Larson <troy@calvinware.com>
6
6
  License: MIT License
@@ -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("Scanning project...", total=None)
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"\nExtracted ({mode}):")
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"\nPackage written to: [cyan]{output_path}[/cyan]")
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() -> None:
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 with abstract practice declarations.
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