mcp-ticketer 0.12.0__py3-none-any.whl → 2.2.13__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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +507 -6
- mcp_ticketer/adapters/asana/adapter.py +229 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/adapter.py +2730 -139
- mcp_ticketer/adapters/linear/client.py +175 -3
- mcp_ticketer/adapters/linear/mappers.py +203 -8
- mcp_ticketer/adapters/linear/queries.py +280 -3
- mcp_ticketer/adapters/linear/types.py +120 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +1288 -105
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +267 -3175
- mcp_ticketer/cli/mcp_configure.py +821 -119
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +795 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +705 -103
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +56 -6
- mcp_ticketer/core/adapter.py +533 -2
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +480 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +625 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +33 -11
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/queue.py +68 -0
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1574
- mcp_ticketer/adapters/jira.py +0 -1258
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Platform detection logic for all supported AI coding tools.
|
|
2
|
+
|
|
3
|
+
This module implements comprehensive platform detection with confidence scoring
|
|
4
|
+
and multi-layered validation.
|
|
5
|
+
|
|
6
|
+
Detection Strategy:
|
|
7
|
+
1. Check for config file existence (+0.4 confidence)
|
|
8
|
+
2. Validate config format (JSON/TOML parsing) (+0.3 confidence)
|
|
9
|
+
3. Check CLI availability (+0.2 confidence)
|
|
10
|
+
4. Check environment variables (+0.1 confidence)
|
|
11
|
+
|
|
12
|
+
Supported Platforms:
|
|
13
|
+
- Claude Code (claude_code)
|
|
14
|
+
- Claude Desktop (claude_desktop)
|
|
15
|
+
- Cursor (cursor)
|
|
16
|
+
- Auggie (auggie)
|
|
17
|
+
- Codex (codex)
|
|
18
|
+
- Gemini CLI (gemini_cli)
|
|
19
|
+
- Windsurf (windsurf)
|
|
20
|
+
- Antigravity (antigravity) - TBD
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from .exceptions import PlatformDetectionError
|
|
28
|
+
from .types import Platform, PlatformInfo, Scope
|
|
29
|
+
from .utils import parse_json_safe, parse_toml_safe, resolve_command_path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PlatformDetector:
|
|
33
|
+
"""Detect which AI coding tool platform is currently running.
|
|
34
|
+
|
|
35
|
+
This class provides methods to detect all supported platforms with
|
|
36
|
+
confidence scoring, allowing intelligent selection in multi-platform
|
|
37
|
+
environments.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> detector = PlatformDetector()
|
|
41
|
+
>>> info = detector.detect()
|
|
42
|
+
>>> print(f"{info.platform}: {info.confidence}")
|
|
43
|
+
Platform.CLAUDE_CODE: 1.0
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
"""Initialize platform detector."""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def detect(self) -> PlatformInfo:
|
|
51
|
+
"""Auto-detect current platform with highest confidence.
|
|
52
|
+
|
|
53
|
+
Runs all platform-specific detectors and returns the one with
|
|
54
|
+
highest confidence score.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
PlatformInfo for detected platform
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
PlatformDetectionError: If no platforms detected
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> detector = PlatformDetector()
|
|
64
|
+
>>> info = detector.detect()
|
|
65
|
+
>>> if info.confidence > 0.8:
|
|
66
|
+
... print(f"Detected {info.platform} with high confidence")
|
|
67
|
+
"""
|
|
68
|
+
# Run all detectors
|
|
69
|
+
detectors = [
|
|
70
|
+
self.detect_claude_code,
|
|
71
|
+
self.detect_claude_desktop,
|
|
72
|
+
self.detect_cursor,
|
|
73
|
+
self.detect_auggie,
|
|
74
|
+
self.detect_codex,
|
|
75
|
+
self.detect_gemini_cli,
|
|
76
|
+
self.detect_windsurf,
|
|
77
|
+
self.detect_antigravity,
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
results: list[tuple[float, Path | None]] = []
|
|
81
|
+
for detector_func in detectors:
|
|
82
|
+
confidence, config_path = detector_func()
|
|
83
|
+
results.append((confidence, config_path))
|
|
84
|
+
|
|
85
|
+
# Find platform with highest confidence
|
|
86
|
+
max_confidence = max(r[0] for r in results)
|
|
87
|
+
|
|
88
|
+
if max_confidence == 0.0:
|
|
89
|
+
raise PlatformDetectionError("No supported platforms detected")
|
|
90
|
+
|
|
91
|
+
# Map results back to platforms
|
|
92
|
+
platform_results = list(
|
|
93
|
+
zip(
|
|
94
|
+
[
|
|
95
|
+
Platform.CLAUDE_CODE,
|
|
96
|
+
Platform.CLAUDE_DESKTOP,
|
|
97
|
+
Platform.CURSOR,
|
|
98
|
+
Platform.AUGGIE,
|
|
99
|
+
Platform.CODEX,
|
|
100
|
+
Platform.GEMINI_CLI,
|
|
101
|
+
Platform.WINDSURF,
|
|
102
|
+
Platform.ANTIGRAVITY,
|
|
103
|
+
],
|
|
104
|
+
results,
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Get platform with max confidence
|
|
109
|
+
for platform, (confidence, config_path) in platform_results:
|
|
110
|
+
if confidence == max_confidence:
|
|
111
|
+
# Determine CLI availability
|
|
112
|
+
cli_available = False
|
|
113
|
+
if platform in (Platform.CLAUDE_CODE, Platform.CLAUDE_DESKTOP):
|
|
114
|
+
cli_available = resolve_command_path("claude") is not None
|
|
115
|
+
elif platform == Platform.CURSOR:
|
|
116
|
+
cli_available = resolve_command_path("cursor") is not None
|
|
117
|
+
|
|
118
|
+
return PlatformInfo(
|
|
119
|
+
platform=platform,
|
|
120
|
+
confidence=confidence,
|
|
121
|
+
config_path=config_path,
|
|
122
|
+
cli_available=cli_available,
|
|
123
|
+
scope_support=Scope.BOTH,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Should never reach here, but fallback to unknown
|
|
127
|
+
return PlatformInfo(
|
|
128
|
+
platform=Platform.UNKNOWN,
|
|
129
|
+
confidence=0.0,
|
|
130
|
+
config_path=None,
|
|
131
|
+
cli_available=False,
|
|
132
|
+
scope_support=Scope.BOTH,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# ========================================================================
|
|
136
|
+
# Platform-Specific Detectors
|
|
137
|
+
# ========================================================================
|
|
138
|
+
|
|
139
|
+
def detect_claude_code(self) -> tuple[float, Path | None]:
|
|
140
|
+
"""Detect Claude Code installation.
|
|
141
|
+
|
|
142
|
+
Config locations (in priority order):
|
|
143
|
+
1. ~/.config/claude/mcp.json (new location)
|
|
144
|
+
2. .claude.json (legacy project-level)
|
|
145
|
+
3. ~/.claude.json (legacy global)
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Tuple of (confidence, config_path)
|
|
149
|
+
- confidence: 0.0-1.0
|
|
150
|
+
- config_path: Path if found, None otherwise
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> detector = PlatformDetector()
|
|
154
|
+
>>> confidence, path = detector.detect_claude_code()
|
|
155
|
+
>>> if confidence > 0.5:
|
|
156
|
+
... print(f"Found Claude Code config at {path}")
|
|
157
|
+
"""
|
|
158
|
+
confidence = 0.0
|
|
159
|
+
config_path: Path | None = None
|
|
160
|
+
|
|
161
|
+
# Priority 1: New location (~/.config/claude/mcp.json)
|
|
162
|
+
new_config = Path.home() / ".config" / "claude" / "mcp.json"
|
|
163
|
+
if new_config.exists():
|
|
164
|
+
config_path = new_config
|
|
165
|
+
confidence += 0.4
|
|
166
|
+
|
|
167
|
+
# Validate JSON
|
|
168
|
+
try:
|
|
169
|
+
parse_json_safe(new_config)
|
|
170
|
+
confidence += 0.3
|
|
171
|
+
except Exception:
|
|
172
|
+
pass # Invalid JSON reduces confidence
|
|
173
|
+
|
|
174
|
+
# Priority 2: Legacy project location (.claude.json)
|
|
175
|
+
elif Path(".claude.json").exists():
|
|
176
|
+
config_path = Path(".claude.json")
|
|
177
|
+
confidence += 0.4
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
parse_json_safe(config_path)
|
|
181
|
+
confidence += 0.3
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
# Priority 3: Legacy global location (~/.claude.json)
|
|
186
|
+
elif (Path.home() / ".claude.json").exists():
|
|
187
|
+
config_path = Path.home() / ".claude.json"
|
|
188
|
+
confidence += 0.4
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
parse_json_safe(config_path)
|
|
192
|
+
confidence += 0.3
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# Check CLI availability
|
|
197
|
+
if resolve_command_path("claude") is not None:
|
|
198
|
+
confidence += 0.2
|
|
199
|
+
|
|
200
|
+
# Check environment variables
|
|
201
|
+
if os.getenv("CLAUDE_CODE_ENV"):
|
|
202
|
+
confidence += 0.1
|
|
203
|
+
|
|
204
|
+
return (min(confidence, 1.0), config_path)
|
|
205
|
+
|
|
206
|
+
def detect_claude_desktop(self) -> tuple[float, Path | None]:
|
|
207
|
+
"""Detect Claude Desktop installation.
|
|
208
|
+
|
|
209
|
+
Config locations (platform-specific):
|
|
210
|
+
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
211
|
+
- Linux: ~/.config/Claude/claude_desktop_config.json
|
|
212
|
+
- Windows: %APPDATA%/Claude/claude_desktop_config.json
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Tuple of (confidence, config_path)
|
|
216
|
+
"""
|
|
217
|
+
confidence = 0.0
|
|
218
|
+
config_path: Path | None = None
|
|
219
|
+
|
|
220
|
+
# Determine platform-specific config path
|
|
221
|
+
if sys.platform == "darwin":
|
|
222
|
+
# macOS
|
|
223
|
+
config_path = (
|
|
224
|
+
Path.home()
|
|
225
|
+
/ "Library"
|
|
226
|
+
/ "Application Support"
|
|
227
|
+
/ "Claude"
|
|
228
|
+
/ "claude_desktop_config.json"
|
|
229
|
+
)
|
|
230
|
+
elif sys.platform == "win32":
|
|
231
|
+
# Windows
|
|
232
|
+
appdata = os.environ.get("APPDATA", "")
|
|
233
|
+
if appdata:
|
|
234
|
+
config_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
|
|
235
|
+
else:
|
|
236
|
+
# Linux
|
|
237
|
+
config_path = (
|
|
238
|
+
Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Check if config exists
|
|
242
|
+
if config_path and config_path.exists():
|
|
243
|
+
confidence += 0.4
|
|
244
|
+
|
|
245
|
+
# Validate JSON
|
|
246
|
+
try:
|
|
247
|
+
parse_json_safe(config_path)
|
|
248
|
+
confidence += 0.3
|
|
249
|
+
except Exception:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
# Check CLI availability (Claude Desktop uses same CLI as Claude Code)
|
|
253
|
+
if resolve_command_path("claude") is not None:
|
|
254
|
+
confidence += 0.2
|
|
255
|
+
|
|
256
|
+
# Check for Claude Desktop process
|
|
257
|
+
if sys.platform == "darwin":
|
|
258
|
+
# Check if Claude.app exists
|
|
259
|
+
claude_app = Path("/Applications/Claude.app")
|
|
260
|
+
if claude_app.exists():
|
|
261
|
+
confidence += 0.1
|
|
262
|
+
|
|
263
|
+
return (min(confidence, 1.0), config_path)
|
|
264
|
+
|
|
265
|
+
def detect_cursor(self) -> tuple[float, Path | None]:
|
|
266
|
+
"""Detect Cursor installation.
|
|
267
|
+
|
|
268
|
+
Config location: ~/.cursor/mcp.json
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Tuple of (confidence, config_path)
|
|
272
|
+
"""
|
|
273
|
+
confidence = 0.0
|
|
274
|
+
config_path = Path.home() / ".cursor" / "mcp.json"
|
|
275
|
+
|
|
276
|
+
# Check if config exists
|
|
277
|
+
if config_path.exists():
|
|
278
|
+
confidence += 0.4
|
|
279
|
+
|
|
280
|
+
# Validate JSON
|
|
281
|
+
try:
|
|
282
|
+
parse_json_safe(config_path)
|
|
283
|
+
confidence += 0.3
|
|
284
|
+
except Exception:
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
# Check CLI availability
|
|
288
|
+
if resolve_command_path("cursor") is not None:
|
|
289
|
+
confidence += 0.2
|
|
290
|
+
|
|
291
|
+
# Check for Cursor directory
|
|
292
|
+
if (Path.home() / ".cursor").exists():
|
|
293
|
+
confidence += 0.1
|
|
294
|
+
|
|
295
|
+
return (min(confidence, 1.0), config_path if config_path.exists() else None)
|
|
296
|
+
|
|
297
|
+
def detect_auggie(self) -> tuple[float, Path | None]:
|
|
298
|
+
"""Detect Auggie installation.
|
|
299
|
+
|
|
300
|
+
Config location: ~/.augment/settings.json
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Tuple of (confidence, config_path)
|
|
304
|
+
"""
|
|
305
|
+
confidence = 0.0
|
|
306
|
+
config_path = Path.home() / ".augment" / "settings.json"
|
|
307
|
+
|
|
308
|
+
# Check if config exists
|
|
309
|
+
if config_path.exists():
|
|
310
|
+
confidence += 0.4
|
|
311
|
+
|
|
312
|
+
# Validate JSON
|
|
313
|
+
try:
|
|
314
|
+
parse_json_safe(config_path)
|
|
315
|
+
confidence += 0.3
|
|
316
|
+
except Exception:
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
# Check for Auggie directory
|
|
320
|
+
if (Path.home() / ".augment").exists():
|
|
321
|
+
confidence += 0.2
|
|
322
|
+
|
|
323
|
+
# Check environment
|
|
324
|
+
if os.getenv("AUGGIE_HOME"):
|
|
325
|
+
confidence += 0.1
|
|
326
|
+
|
|
327
|
+
return (min(confidence, 1.0), config_path if config_path.exists() else None)
|
|
328
|
+
|
|
329
|
+
def detect_codex(self) -> tuple[float, Path | None]:
|
|
330
|
+
"""Detect Codex installation.
|
|
331
|
+
|
|
332
|
+
Config location: ~/.codex/config.toml
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Tuple of (confidence, config_path)
|
|
336
|
+
"""
|
|
337
|
+
confidence = 0.0
|
|
338
|
+
config_path = Path.home() / ".codex" / "config.toml"
|
|
339
|
+
|
|
340
|
+
# Check if config exists
|
|
341
|
+
if config_path.exists():
|
|
342
|
+
confidence += 0.4
|
|
343
|
+
|
|
344
|
+
# Validate TOML
|
|
345
|
+
try:
|
|
346
|
+
parse_toml_safe(config_path)
|
|
347
|
+
confidence += 0.3
|
|
348
|
+
except Exception:
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
# Check for Codex directory
|
|
352
|
+
if (Path.home() / ".codex").exists():
|
|
353
|
+
confidence += 0.2
|
|
354
|
+
|
|
355
|
+
# Check CLI
|
|
356
|
+
if resolve_command_path("codex") is not None:
|
|
357
|
+
confidence += 0.1
|
|
358
|
+
|
|
359
|
+
return (min(confidence, 1.0), config_path if config_path.exists() else None)
|
|
360
|
+
|
|
361
|
+
def detect_gemini_cli(self) -> tuple[float, Path | None]:
|
|
362
|
+
"""Detect Gemini CLI installation.
|
|
363
|
+
|
|
364
|
+
Config locations (in priority order):
|
|
365
|
+
1. .gemini/settings.json (project-level)
|
|
366
|
+
2. ~/.gemini/settings.json (user-level)
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Tuple of (confidence, config_path)
|
|
370
|
+
"""
|
|
371
|
+
confidence = 0.0
|
|
372
|
+
config_path: Path | None = None
|
|
373
|
+
|
|
374
|
+
# Priority 1: Project-level config
|
|
375
|
+
project_config = Path(".gemini") / "settings.json"
|
|
376
|
+
if project_config.exists():
|
|
377
|
+
config_path = project_config
|
|
378
|
+
confidence += 0.4
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
parse_json_safe(config_path)
|
|
382
|
+
confidence += 0.3
|
|
383
|
+
except Exception:
|
|
384
|
+
pass
|
|
385
|
+
|
|
386
|
+
# Priority 2: User-level config
|
|
387
|
+
elif (Path.home() / ".gemini" / "settings.json").exists():
|
|
388
|
+
config_path = Path.home() / ".gemini" / "settings.json"
|
|
389
|
+
confidence += 0.4
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
parse_json_safe(config_path)
|
|
393
|
+
confidence += 0.3
|
|
394
|
+
except Exception:
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
# Check for Gemini directory
|
|
398
|
+
if (Path.home() / ".gemini").exists() or Path(".gemini").exists():
|
|
399
|
+
confidence += 0.2
|
|
400
|
+
|
|
401
|
+
# Check CLI
|
|
402
|
+
if resolve_command_path("gemini") is not None:
|
|
403
|
+
confidence += 0.1
|
|
404
|
+
|
|
405
|
+
return (min(confidence, 1.0), config_path)
|
|
406
|
+
|
|
407
|
+
def detect_windsurf(self) -> tuple[float, Path | None]:
|
|
408
|
+
"""Detect Windsurf installation.
|
|
409
|
+
|
|
410
|
+
Config location: ~/.codeium/windsurf/mcp_config.json
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Tuple of (confidence, config_path)
|
|
414
|
+
"""
|
|
415
|
+
confidence = 0.0
|
|
416
|
+
config_path = Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
|
|
417
|
+
|
|
418
|
+
# Check if config exists
|
|
419
|
+
if config_path.exists():
|
|
420
|
+
confidence += 0.4
|
|
421
|
+
|
|
422
|
+
# Validate JSON
|
|
423
|
+
try:
|
|
424
|
+
parse_json_safe(config_path)
|
|
425
|
+
confidence += 0.3
|
|
426
|
+
except Exception:
|
|
427
|
+
pass
|
|
428
|
+
|
|
429
|
+
# Check for Windsurf directory
|
|
430
|
+
if (Path.home() / ".codeium" / "windsurf").exists():
|
|
431
|
+
confidence += 0.2
|
|
432
|
+
|
|
433
|
+
# Check for Windsurf app (macOS)
|
|
434
|
+
if sys.platform == "darwin":
|
|
435
|
+
windsurf_app = Path("/Applications/Windsurf.app")
|
|
436
|
+
if windsurf_app.exists():
|
|
437
|
+
confidence += 0.1
|
|
438
|
+
|
|
439
|
+
return (min(confidence, 1.0), config_path if config_path.exists() else None)
|
|
440
|
+
|
|
441
|
+
def detect_antigravity(self) -> tuple[float, Path | None]:
|
|
442
|
+
"""Detect Antigravity installation.
|
|
443
|
+
|
|
444
|
+
Note: Config location not yet documented. This is a placeholder
|
|
445
|
+
implementation that always returns 0.0 confidence.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Tuple of (confidence=0.0, config_path=None)
|
|
449
|
+
"""
|
|
450
|
+
# TODO: Update when Antigravity config location is documented
|
|
451
|
+
return (0.0, None)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Platform-specific implementations for MCP installation.
|
|
2
|
+
|
|
3
|
+
This module provides platform-specific strategies and configurations
|
|
4
|
+
for different AI coding tools.
|
|
5
|
+
|
|
6
|
+
Supported platforms:
|
|
7
|
+
- Claude Code (claude_code.py)
|
|
8
|
+
- Cursor (cursor.py)
|
|
9
|
+
- Codex (codex.py)
|
|
10
|
+
|
|
11
|
+
Each platform module provides:
|
|
12
|
+
- Configuration path detection
|
|
13
|
+
- Installation strategy selection
|
|
14
|
+
- Platform-specific validation
|
|
15
|
+
- Command building utilities
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .claude_code import ClaudeCodeStrategy
|
|
19
|
+
from .codex import CodexStrategy
|
|
20
|
+
from .cursor import CursorStrategy
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ClaudeCodeStrategy",
|
|
24
|
+
"CursorStrategy",
|
|
25
|
+
"CodexStrategy",
|
|
26
|
+
]
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Claude Code platform implementation.
|
|
2
|
+
|
|
3
|
+
This module provides platform-specific logic for Claude Code, including
|
|
4
|
+
configuration paths, installation strategies, and validation.
|
|
5
|
+
|
|
6
|
+
Claude Code supports:
|
|
7
|
+
- Project-level config: .claude.json or ~/.config/claude/mcp.json
|
|
8
|
+
- Global config: ~/.config/claude/mcp.json
|
|
9
|
+
- Native CLI: claude mcp add/remove
|
|
10
|
+
- JSON manipulation fallback
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from ..command_builder import CommandBuilder
|
|
16
|
+
from ..installation_strategy import (
|
|
17
|
+
InstallationStrategy,
|
|
18
|
+
JSONManipulationStrategy,
|
|
19
|
+
NativeCLIStrategy,
|
|
20
|
+
)
|
|
21
|
+
from ..types import InstallMethod, MCPServerConfig, Platform, Scope
|
|
22
|
+
from ..utils import resolve_command_path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ClaudeCodeStrategy:
|
|
26
|
+
"""Claude Code platform implementation.
|
|
27
|
+
|
|
28
|
+
Provides configuration paths and installation strategies for Claude Code.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
32
|
+
>>> config_path = strategy.get_config_path(Scope.PROJECT)
|
|
33
|
+
>>> installer = strategy.get_strategy(Scope.PROJECT)
|
|
34
|
+
>>> result = installer.install(server, Scope.PROJECT)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
"""Initialize Claude Code strategy."""
|
|
39
|
+
self.platform = Platform.CLAUDE_CODE
|
|
40
|
+
self.config_format = "json"
|
|
41
|
+
|
|
42
|
+
def get_config_path(self, scope: Scope) -> Path:
|
|
43
|
+
"""Get configuration path for scope.
|
|
44
|
+
|
|
45
|
+
Priority order:
|
|
46
|
+
1. Project scope: .claude.json (legacy) or ~/.config/claude/mcp.json (new)
|
|
47
|
+
2. Global scope: ~/.config/claude/mcp.json
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
scope: Installation scope (PROJECT or GLOBAL)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Path to configuration file
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
57
|
+
>>> path = strategy.get_config_path(Scope.PROJECT)
|
|
58
|
+
>>> print(path)
|
|
59
|
+
/home/user/.config/claude/mcp.json
|
|
60
|
+
"""
|
|
61
|
+
if scope == Scope.PROJECT:
|
|
62
|
+
# Check for new location first
|
|
63
|
+
new_config = Path.home() / ".config" / "claude" / "mcp.json"
|
|
64
|
+
if new_config.exists():
|
|
65
|
+
return new_config
|
|
66
|
+
|
|
67
|
+
# Fallback to legacy project-level config
|
|
68
|
+
legacy_project = Path(".claude.json")
|
|
69
|
+
if legacy_project.exists():
|
|
70
|
+
return legacy_project
|
|
71
|
+
|
|
72
|
+
# Default to new location if creating new config
|
|
73
|
+
return new_config
|
|
74
|
+
|
|
75
|
+
else: # Scope.GLOBAL
|
|
76
|
+
# Global config always in new location
|
|
77
|
+
return Path.home() / ".config" / "claude" / "mcp.json"
|
|
78
|
+
|
|
79
|
+
def get_strategy(self, scope: Scope) -> InstallationStrategy:
|
|
80
|
+
"""Get appropriate installation strategy for scope.
|
|
81
|
+
|
|
82
|
+
Prefers native CLI if available, falls back to JSON manipulation.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
scope: Installation scope
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Installation strategy instance
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
92
|
+
>>> installer = strategy.get_strategy(Scope.PROJECT)
|
|
93
|
+
>>> if installer.validate():
|
|
94
|
+
... result = installer.install(server, Scope.PROJECT)
|
|
95
|
+
"""
|
|
96
|
+
# Prefer native CLI if available
|
|
97
|
+
if resolve_command_path("claude"):
|
|
98
|
+
return NativeCLIStrategy(self.platform, "claude")
|
|
99
|
+
|
|
100
|
+
# Fallback to JSON manipulation
|
|
101
|
+
config_path = self.get_config_path(scope)
|
|
102
|
+
return JSONManipulationStrategy(self.platform, config_path)
|
|
103
|
+
|
|
104
|
+
def get_strategy_with_fallback(
|
|
105
|
+
self, scope: Scope
|
|
106
|
+
) -> tuple[InstallationStrategy, InstallationStrategy | None]:
|
|
107
|
+
"""Get primary strategy and fallback strategy.
|
|
108
|
+
|
|
109
|
+
Returns both native CLI and JSON strategies for graceful fallback.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
scope: Installation scope
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Tuple of (primary_strategy, fallback_strategy)
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
119
|
+
>>> primary, fallback = strategy.get_strategy_with_fallback(Scope.PROJECT)
|
|
120
|
+
>>> try:
|
|
121
|
+
... result = primary.install(server, Scope.PROJECT)
|
|
122
|
+
... except InstallationError:
|
|
123
|
+
... if fallback:
|
|
124
|
+
... result = fallback.install(server, Scope.PROJECT)
|
|
125
|
+
"""
|
|
126
|
+
config_path = self.get_config_path(scope)
|
|
127
|
+
|
|
128
|
+
# Primary: Native CLI if available
|
|
129
|
+
primary: InstallationStrategy | None = None
|
|
130
|
+
if resolve_command_path("claude"):
|
|
131
|
+
primary = NativeCLIStrategy(self.platform, "claude")
|
|
132
|
+
|
|
133
|
+
# Fallback: Always JSON
|
|
134
|
+
fallback = JSONManipulationStrategy(self.platform, config_path)
|
|
135
|
+
|
|
136
|
+
# If no CLI, use JSON as primary
|
|
137
|
+
if primary is None:
|
|
138
|
+
return (fallback, None)
|
|
139
|
+
|
|
140
|
+
return (primary, fallback)
|
|
141
|
+
|
|
142
|
+
def validate_installation(self) -> bool:
|
|
143
|
+
"""Validate Claude Code is available.
|
|
144
|
+
|
|
145
|
+
Checks for config file existence or CLI availability.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
True if Claude Code appears to be installed
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
152
|
+
>>> if strategy.validate_installation():
|
|
153
|
+
... print("Claude Code is available")
|
|
154
|
+
"""
|
|
155
|
+
# Check for any config file
|
|
156
|
+
global_config = Path.home() / ".config" / "claude" / "mcp.json"
|
|
157
|
+
legacy_config = Path.home() / ".claude.json"
|
|
158
|
+
project_config = Path(".claude.json")
|
|
159
|
+
|
|
160
|
+
has_config = (
|
|
161
|
+
global_config.exists() or legacy_config.exists() or project_config.exists()
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Check for CLI
|
|
165
|
+
has_cli = resolve_command_path("claude") is not None
|
|
166
|
+
|
|
167
|
+
return has_config or has_cli
|
|
168
|
+
|
|
169
|
+
def build_server_config(
|
|
170
|
+
self,
|
|
171
|
+
package: str,
|
|
172
|
+
install_method: InstallMethod | None = None,
|
|
173
|
+
env: dict[str, str] | None = None,
|
|
174
|
+
description: str = "",
|
|
175
|
+
) -> MCPServerConfig:
|
|
176
|
+
"""Build server configuration for Claude Code.
|
|
177
|
+
|
|
178
|
+
Uses CommandBuilder to auto-detect best installation method.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
package: Package name (e.g., "mcp-ticketer")
|
|
182
|
+
install_method: Installation method (auto-detected if None)
|
|
183
|
+
env: Environment variables
|
|
184
|
+
description: Server description
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Complete server configuration
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
191
|
+
>>> config = strategy.build_server_config(
|
|
192
|
+
... "mcp-ticketer",
|
|
193
|
+
... env={"LINEAR_API_KEY": "..."}
|
|
194
|
+
... )
|
|
195
|
+
>>> print(f"{config.command} {' '.join(config.args)}")
|
|
196
|
+
uv run mcp-ticketer mcp
|
|
197
|
+
"""
|
|
198
|
+
builder = CommandBuilder(self.platform)
|
|
199
|
+
return builder.to_server_config(
|
|
200
|
+
package=package,
|
|
201
|
+
install_method=install_method,
|
|
202
|
+
env=env,
|
|
203
|
+
description=description,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def get_platform_info(self) -> dict[str, str]:
|
|
207
|
+
"""Get platform information.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dict with platform details
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
>>> strategy = ClaudeCodeStrategy()
|
|
214
|
+
>>> info = strategy.get_platform_info()
|
|
215
|
+
>>> print(info["name"])
|
|
216
|
+
Claude Code
|
|
217
|
+
"""
|
|
218
|
+
return {
|
|
219
|
+
"name": "Claude Code",
|
|
220
|
+
"platform": self.platform.value,
|
|
221
|
+
"config_format": "json",
|
|
222
|
+
"scope_support": "both",
|
|
223
|
+
"cli_available": str(resolve_command_path("claude") is not None),
|
|
224
|
+
"config_key": "mcpServers",
|
|
225
|
+
}
|