mcp-ticketer 0.4.11__py3-none-any.whl → 2.0.1__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 +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +394 -9
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1416 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +836 -105
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +772 -1
- mcp_ticketer/adapters/linear/adapter.py +2293 -108
- mcp_ticketer/adapters/linear/client.py +146 -12
- mcp_ticketer/adapters/linear/mappers.py +105 -11
- mcp_ticketer/adapters/linear/queries.py +168 -1
- mcp_ticketer/adapters/linear/types.py +80 -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/cache/memory.py +3 -3
- mcp_ticketer/cli/adapter_diagnostics.py +4 -2
- mcp_ticketer/cli/auggie_configure.py +18 -6
- mcp_ticketer/cli/codex_configure.py +175 -60
- mcp_ticketer/cli/configure.py +884 -146
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +31 -28
- mcp_ticketer/cli/discover.py +293 -21
- mcp_ticketer/cli/gemini_configure.py +18 -6
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +99 -15
- mcp_ticketer/cli/main.py +109 -2055
- mcp_ticketer/cli/mcp_configure.py +673 -99
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +6 -6
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +13 -11
- mcp_ticketer/cli/ticket_commands.py +277 -36
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +45 -41
- mcp_ticketer/core/__init__.py +35 -1
- mcp_ticketer/core/adapter.py +170 -5
- mcp_ticketer/core/config.py +38 -31
- mcp_ticketer/core/env_discovery.py +33 -3
- mcp_ticketer/core/env_loader.py +7 -6
- mcp_ticketer/core/exceptions.py +10 -4
- mcp_ticketer/core/http_client.py +10 -10
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +32 -20
- mcp_ticketer/core/models.py +136 -1
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +148 -14
- mcp_ticketer/core/registry.py +1 -1
- mcp_ticketer/core/session_state.py +171 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +2 -2
- mcp_ticketer/mcp/server/__init__.py +2 -2
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +187 -93
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +37 -9
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
- 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 +1429 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -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 +180 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1182 -82
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/health_monitor.py +1 -0
- mcp_ticketer/queue/manager.py +4 -4
- mcp_ticketer/queue/queue.py +3 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +2 -2
- mcp_ticketer/queue/worker.py +15 -13
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
- mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
- mcp_ticketer-0.4.11.dist-info/METADATA +0 -496
- mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"""AI client platform auto-detection for mcp-ticketer.
|
|
2
|
+
|
|
3
|
+
This module provides automatic detection of AI client frameworks that
|
|
4
|
+
support MCP servers. It detects installation status, configuration paths,
|
|
5
|
+
and scope (project/global) for each platform.
|
|
6
|
+
|
|
7
|
+
Supported platforms:
|
|
8
|
+
- Claude Code (project-level, ~/.claude.json)
|
|
9
|
+
- Claude Desktop (global, platform-specific paths)
|
|
10
|
+
- Auggie (CLI + ~/.augment/settings.json)
|
|
11
|
+
- Codex (CLI + ~/.codex/config.toml)
|
|
12
|
+
- Gemini (CLI + .gemini/settings.json or ~/.gemini/settings.json)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import sys
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class DetectedPlatform:
|
|
25
|
+
"""Represents a detected AI client platform.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
name: Platform identifier (e.g., "claude-code")
|
|
29
|
+
display_name: Human-readable name (e.g., "Claude Code")
|
|
30
|
+
config_path: Path to platform configuration file
|
|
31
|
+
is_installed: Whether platform is installed and usable
|
|
32
|
+
scope: Configuration scope - "project", "global", or "both"
|
|
33
|
+
executable_path: Path to CLI executable (if applicable)
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
display_name: str
|
|
39
|
+
config_path: Path
|
|
40
|
+
is_installed: bool
|
|
41
|
+
scope: str
|
|
42
|
+
executable_path: str | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PlatformDetector:
|
|
46
|
+
"""Detects installed AI client platforms that support MCP servers."""
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def detect_claude_code() -> DetectedPlatform | None:
|
|
50
|
+
"""Detect Claude Code installation.
|
|
51
|
+
|
|
52
|
+
Claude Code uses project-level configuration stored in either:
|
|
53
|
+
- ~/.config/claude/mcp.json (new global location with flat structure)
|
|
54
|
+
- ~/.claude.json (legacy location with projects structure)
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
DetectedPlatform if Claude Code config exists, None otherwise
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
# Check new global location first
|
|
61
|
+
new_config_path = Path.home() / ".config" / "claude" / "mcp.json"
|
|
62
|
+
old_config_path = Path.home() / ".claude.json"
|
|
63
|
+
|
|
64
|
+
# Priority: Use new location if it exists
|
|
65
|
+
config_path = new_config_path if new_config_path.exists() else old_config_path
|
|
66
|
+
|
|
67
|
+
# Check if config file exists
|
|
68
|
+
if not config_path.exists():
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# Validate it's valid JSON (but don't require specific structure)
|
|
72
|
+
try:
|
|
73
|
+
with config_path.open() as f:
|
|
74
|
+
content = f.read().strip()
|
|
75
|
+
if content: # Only validate if not empty
|
|
76
|
+
json.loads(content)
|
|
77
|
+
|
|
78
|
+
return DetectedPlatform(
|
|
79
|
+
name="claude-code",
|
|
80
|
+
display_name="Claude Code",
|
|
81
|
+
config_path=config_path,
|
|
82
|
+
is_installed=True,
|
|
83
|
+
scope="project",
|
|
84
|
+
executable_path=None, # Claude Code doesn't have a CLI
|
|
85
|
+
)
|
|
86
|
+
except (json.JSONDecodeError, OSError):
|
|
87
|
+
# Config exists but is corrupted - still consider it "detected"
|
|
88
|
+
# but mark as not installed/usable
|
|
89
|
+
return DetectedPlatform(
|
|
90
|
+
name="claude-code",
|
|
91
|
+
display_name="Claude Code",
|
|
92
|
+
config_path=config_path,
|
|
93
|
+
is_installed=False,
|
|
94
|
+
scope="project",
|
|
95
|
+
executable_path=None,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def detect_claude_desktop() -> DetectedPlatform | None:
|
|
100
|
+
"""Detect Claude Desktop installation.
|
|
101
|
+
|
|
102
|
+
Claude Desktop uses global configuration with platform-specific paths:
|
|
103
|
+
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
104
|
+
- Linux: ~/.config/Claude/claude_desktop_config.json
|
|
105
|
+
- Windows: %APPDATA%/Claude/claude_desktop_config.json
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
DetectedPlatform if Claude Desktop config exists, None otherwise
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
# Determine platform-specific config path
|
|
112
|
+
if sys.platform == "darwin": # macOS
|
|
113
|
+
config_path = (
|
|
114
|
+
Path.home()
|
|
115
|
+
/ "Library"
|
|
116
|
+
/ "Application Support"
|
|
117
|
+
/ "Claude"
|
|
118
|
+
/ "claude_desktop_config.json"
|
|
119
|
+
)
|
|
120
|
+
elif sys.platform == "win32": # Windows
|
|
121
|
+
appdata = os.environ.get("APPDATA", "")
|
|
122
|
+
if not appdata:
|
|
123
|
+
return None
|
|
124
|
+
config_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
|
|
125
|
+
else: # Linux
|
|
126
|
+
config_path = (
|
|
127
|
+
Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Check if config file exists
|
|
131
|
+
if not config_path.exists():
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
# Validate it's valid JSON
|
|
135
|
+
try:
|
|
136
|
+
with config_path.open() as f:
|
|
137
|
+
content = f.read().strip()
|
|
138
|
+
if content: # Only validate if not empty
|
|
139
|
+
json.loads(content)
|
|
140
|
+
|
|
141
|
+
return DetectedPlatform(
|
|
142
|
+
name="claude-desktop",
|
|
143
|
+
display_name="Claude Desktop",
|
|
144
|
+
config_path=config_path,
|
|
145
|
+
is_installed=True,
|
|
146
|
+
scope="global",
|
|
147
|
+
executable_path=None, # Claude Desktop is a GUI app
|
|
148
|
+
)
|
|
149
|
+
except (json.JSONDecodeError, OSError):
|
|
150
|
+
# Config exists but is corrupted
|
|
151
|
+
return DetectedPlatform(
|
|
152
|
+
name="claude-desktop",
|
|
153
|
+
display_name="Claude Desktop",
|
|
154
|
+
config_path=config_path,
|
|
155
|
+
is_installed=False,
|
|
156
|
+
scope="global",
|
|
157
|
+
executable_path=None,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def detect_auggie() -> DetectedPlatform | None:
|
|
162
|
+
"""Detect Auggie installation.
|
|
163
|
+
|
|
164
|
+
Auggie requires both:
|
|
165
|
+
1. `auggie` CLI executable in PATH
|
|
166
|
+
2. Configuration file at ~/.augment/settings.json
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
DetectedPlatform if Auggie is installed, None otherwise
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
# Check for CLI executable
|
|
173
|
+
executable_path = shutil.which("auggie")
|
|
174
|
+
if not executable_path:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
# Check for config file
|
|
178
|
+
config_path = Path.home() / ".augment" / "settings.json"
|
|
179
|
+
|
|
180
|
+
# Auggie is installed if CLI exists, even without config
|
|
181
|
+
is_installed = True
|
|
182
|
+
|
|
183
|
+
# If config exists, validate it
|
|
184
|
+
if config_path.exists():
|
|
185
|
+
try:
|
|
186
|
+
with config_path.open() as f:
|
|
187
|
+
content = f.read().strip()
|
|
188
|
+
if content:
|
|
189
|
+
json.loads(content)
|
|
190
|
+
except (json.JSONDecodeError, OSError):
|
|
191
|
+
# Config exists but is corrupted
|
|
192
|
+
is_installed = False
|
|
193
|
+
|
|
194
|
+
return DetectedPlatform(
|
|
195
|
+
name="auggie",
|
|
196
|
+
display_name="Auggie",
|
|
197
|
+
config_path=config_path,
|
|
198
|
+
is_installed=is_installed,
|
|
199
|
+
scope="global",
|
|
200
|
+
executable_path=executable_path,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def detect_cursor() -> DetectedPlatform | None:
|
|
205
|
+
"""Detect Cursor code editor installation.
|
|
206
|
+
|
|
207
|
+
Cursor uses project-level MCP configuration stored in:
|
|
208
|
+
- ~/.cursor/mcp.json (global location with flat structure)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
DetectedPlatform if Cursor config exists, None otherwise
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
# Check global configuration location
|
|
215
|
+
config_path = Path.home() / ".cursor" / "mcp.json"
|
|
216
|
+
|
|
217
|
+
# Check if config file exists
|
|
218
|
+
if not config_path.exists():
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
# Validate it's valid JSON
|
|
222
|
+
try:
|
|
223
|
+
with config_path.open() as f:
|
|
224
|
+
content = f.read().strip()
|
|
225
|
+
if content: # Only validate if not empty
|
|
226
|
+
json.loads(content)
|
|
227
|
+
|
|
228
|
+
return DetectedPlatform(
|
|
229
|
+
name="cursor",
|
|
230
|
+
display_name="Cursor",
|
|
231
|
+
config_path=config_path,
|
|
232
|
+
is_installed=True,
|
|
233
|
+
scope="project",
|
|
234
|
+
executable_path=None, # Cursor doesn't have a CLI
|
|
235
|
+
)
|
|
236
|
+
except (json.JSONDecodeError, OSError):
|
|
237
|
+
# Config exists but is corrupted - still consider it "detected"
|
|
238
|
+
# but mark as not installed/usable
|
|
239
|
+
return DetectedPlatform(
|
|
240
|
+
name="cursor",
|
|
241
|
+
display_name="Cursor",
|
|
242
|
+
config_path=config_path,
|
|
243
|
+
is_installed=False,
|
|
244
|
+
scope="project",
|
|
245
|
+
executable_path=None,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def detect_codex() -> DetectedPlatform | None:
|
|
250
|
+
"""Detect Codex installation.
|
|
251
|
+
|
|
252
|
+
Codex requires both:
|
|
253
|
+
1. `codex` CLI executable in PATH
|
|
254
|
+
2. Configuration file at ~/.codex/config.toml
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
DetectedPlatform if Codex is installed, None otherwise
|
|
258
|
+
|
|
259
|
+
"""
|
|
260
|
+
# Check for CLI executable
|
|
261
|
+
executable_path = shutil.which("codex")
|
|
262
|
+
if not executable_path:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
# Check for config file
|
|
266
|
+
config_path = Path.home() / ".codex" / "config.toml"
|
|
267
|
+
|
|
268
|
+
# Codex is installed if CLI exists, even without config
|
|
269
|
+
is_installed = True
|
|
270
|
+
|
|
271
|
+
# If config exists, validate it exists and is readable
|
|
272
|
+
if config_path.exists():
|
|
273
|
+
try:
|
|
274
|
+
with config_path.open() as f:
|
|
275
|
+
f.read() # Just check if readable
|
|
276
|
+
except OSError:
|
|
277
|
+
is_installed = False
|
|
278
|
+
|
|
279
|
+
return DetectedPlatform(
|
|
280
|
+
name="codex",
|
|
281
|
+
display_name="Codex",
|
|
282
|
+
config_path=config_path,
|
|
283
|
+
is_installed=is_installed,
|
|
284
|
+
scope="global",
|
|
285
|
+
executable_path=executable_path,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def detect_gemini(project_path: Path | None = None) -> DetectedPlatform | None:
|
|
290
|
+
"""Detect Gemini installation.
|
|
291
|
+
|
|
292
|
+
Gemini supports both project-level and global configurations:
|
|
293
|
+
1. `gemini` CLI executable in PATH
|
|
294
|
+
2. Configuration at .gemini/settings.json (project) or
|
|
295
|
+
~/.gemini/settings.json (global)
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
project_path: Optional project directory to check for project-level config
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
DetectedPlatform if Gemini is installed, None otherwise
|
|
302
|
+
|
|
303
|
+
"""
|
|
304
|
+
# Check for CLI executable
|
|
305
|
+
executable_path = shutil.which("gemini")
|
|
306
|
+
if not executable_path:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
# Check for config files (project-level first, then global)
|
|
310
|
+
project_config = None
|
|
311
|
+
global_config = Path.home() / ".gemini" / "settings.json"
|
|
312
|
+
|
|
313
|
+
if project_path:
|
|
314
|
+
project_config = project_path / ".gemini" / "settings.json"
|
|
315
|
+
|
|
316
|
+
# Determine which config exists
|
|
317
|
+
config_path = None
|
|
318
|
+
scope = "global"
|
|
319
|
+
|
|
320
|
+
if project_config and project_config.exists():
|
|
321
|
+
config_path = project_config
|
|
322
|
+
scope = "project"
|
|
323
|
+
elif global_config.exists():
|
|
324
|
+
config_path = global_config
|
|
325
|
+
scope = "global"
|
|
326
|
+
else:
|
|
327
|
+
# No config found, use global path as default
|
|
328
|
+
config_path = global_config
|
|
329
|
+
|
|
330
|
+
# Gemini is installed if CLI exists, even without config
|
|
331
|
+
is_installed = True
|
|
332
|
+
|
|
333
|
+
# If config exists, validate it
|
|
334
|
+
if config_path.exists():
|
|
335
|
+
try:
|
|
336
|
+
with config_path.open() as f:
|
|
337
|
+
content = f.read().strip()
|
|
338
|
+
if content:
|
|
339
|
+
json.loads(content)
|
|
340
|
+
except (json.JSONDecodeError, OSError):
|
|
341
|
+
# Config exists but is corrupted
|
|
342
|
+
is_installed = False
|
|
343
|
+
|
|
344
|
+
# Check if both configs exist
|
|
345
|
+
if project_config and project_config.exists() and global_config.exists():
|
|
346
|
+
scope = "both"
|
|
347
|
+
|
|
348
|
+
return DetectedPlatform(
|
|
349
|
+
name="gemini",
|
|
350
|
+
display_name="Gemini",
|
|
351
|
+
config_path=config_path,
|
|
352
|
+
is_installed=is_installed,
|
|
353
|
+
scope=scope,
|
|
354
|
+
executable_path=executable_path,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def detect_all(
|
|
359
|
+
cls, project_path: Path | None = None, exclude_desktop: bool = False
|
|
360
|
+
) -> list[DetectedPlatform]:
|
|
361
|
+
"""Detect all installed AI client platforms.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
project_path: Optional project directory for project-level detection
|
|
365
|
+
exclude_desktop: If True, exclude desktop AI assistants (Claude Desktop)
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
List of detected platforms (empty if none found)
|
|
369
|
+
|
|
370
|
+
Examples:
|
|
371
|
+
>>> detector = PlatformDetector()
|
|
372
|
+
>>> platforms = detector.detect_all()
|
|
373
|
+
>>> for platform in platforms:
|
|
374
|
+
... print(f"{platform.display_name}: {platform.is_installed}")
|
|
375
|
+
Claude Code: True
|
|
376
|
+
Claude Desktop: False
|
|
377
|
+
|
|
378
|
+
>>> # With project path for Gemini detection
|
|
379
|
+
>>> platforms = detector.detect_all(Path("/home/user/project"))
|
|
380
|
+
>>> gemini = next(p for p in platforms if p.name == "gemini")
|
|
381
|
+
>>> print(gemini.scope) # "project" or "global" or "both"
|
|
382
|
+
|
|
383
|
+
>>> # Exclude desktop AI assistants (code editors only)
|
|
384
|
+
>>> platforms = detector.detect_all(exclude_desktop=True)
|
|
385
|
+
>>> # Returns: Claude Code, Cursor, Auggie, Codex, Gemini (NOT Claude Desktop)
|
|
386
|
+
|
|
387
|
+
"""
|
|
388
|
+
detected = []
|
|
389
|
+
|
|
390
|
+
# Detect Claude Code (project-level code editor)
|
|
391
|
+
claude_code = cls.detect_claude_code()
|
|
392
|
+
if claude_code:
|
|
393
|
+
detected.append(claude_code)
|
|
394
|
+
|
|
395
|
+
# Detect Claude Desktop (desktop AI assistant - optional)
|
|
396
|
+
if not exclude_desktop:
|
|
397
|
+
claude_desktop = cls.detect_claude_desktop()
|
|
398
|
+
if claude_desktop:
|
|
399
|
+
detected.append(claude_desktop)
|
|
400
|
+
|
|
401
|
+
# Detect Cursor (code editor)
|
|
402
|
+
cursor = cls.detect_cursor()
|
|
403
|
+
if cursor:
|
|
404
|
+
detected.append(cursor)
|
|
405
|
+
|
|
406
|
+
# Detect Auggie (code assistant)
|
|
407
|
+
auggie = cls.detect_auggie()
|
|
408
|
+
if auggie:
|
|
409
|
+
detected.append(auggie)
|
|
410
|
+
|
|
411
|
+
# Detect Codex (code assistant)
|
|
412
|
+
codex = cls.detect_codex()
|
|
413
|
+
if codex:
|
|
414
|
+
detected.append(codex)
|
|
415
|
+
|
|
416
|
+
# Detect Gemini (code assistant with project path support)
|
|
417
|
+
gemini = cls.detect_gemini(project_path=project_path)
|
|
418
|
+
if gemini:
|
|
419
|
+
detected.append(gemini)
|
|
420
|
+
|
|
421
|
+
return detected
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def get_platform_by_name(
|
|
425
|
+
platform_name: str, project_path: Path | None = None
|
|
426
|
+
) -> DetectedPlatform | None:
|
|
427
|
+
"""Get detection result for a specific platform by name.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
platform_name: Platform identifier (e.g., "claude-code", "auggie")
|
|
431
|
+
project_path: Optional project directory for project-level detection
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
DetectedPlatform if found, None if platform doesn't exist or isn't installed
|
|
435
|
+
|
|
436
|
+
Examples:
|
|
437
|
+
>>> platform = get_platform_by_name("claude-code")
|
|
438
|
+
>>> if platform and platform.is_installed:
|
|
439
|
+
... print(f"Config at: {platform.config_path}")
|
|
440
|
+
|
|
441
|
+
"""
|
|
442
|
+
detector = PlatformDetector()
|
|
443
|
+
|
|
444
|
+
# Map platform names to detection methods
|
|
445
|
+
detection_map = {
|
|
446
|
+
"claude-code": detector.detect_claude_code,
|
|
447
|
+
"claude-desktop": detector.detect_claude_desktop,
|
|
448
|
+
"cursor": detector.detect_cursor,
|
|
449
|
+
"auggie": detector.detect_auggie,
|
|
450
|
+
"codex": detector.detect_codex,
|
|
451
|
+
"gemini": lambda: detector.detect_gemini(project_path),
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
detect_func = detection_map.get(platform_name)
|
|
455
|
+
if not detect_func:
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
return detect_func()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def is_platform_installed(platform_name: str, project_path: Path | None = None) -> bool:
|
|
462
|
+
"""Check if a specific platform is installed and usable.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
platform_name: Platform identifier (e.g., "claude-code", "auggie")
|
|
466
|
+
project_path: Optional project directory for project-level detection
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
True if platform is installed and has valid configuration
|
|
470
|
+
|
|
471
|
+
Examples:
|
|
472
|
+
>>> if is_platform_installed("claude-code"):
|
|
473
|
+
... print("Claude Code is installed and configured")
|
|
474
|
+
|
|
475
|
+
"""
|
|
476
|
+
platform = get_platform_by_name(platform_name, project_path)
|
|
477
|
+
return platform is not None and platform.is_installed
|