mcp-ticketer 0.1.30__py3-none-any.whl → 1.2.11__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.

Files changed (109) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/adapters/__init__.py +2 -0
  4. mcp_ticketer/adapters/aitrackdown.py +796 -46
  5. mcp_ticketer/adapters/asana/__init__.py +15 -0
  6. mcp_ticketer/adapters/asana/adapter.py +1416 -0
  7. mcp_ticketer/adapters/asana/client.py +292 -0
  8. mcp_ticketer/adapters/asana/mappers.py +348 -0
  9. mcp_ticketer/adapters/asana/types.py +146 -0
  10. mcp_ticketer/adapters/github.py +879 -129
  11. mcp_ticketer/adapters/hybrid.py +11 -11
  12. mcp_ticketer/adapters/jira.py +973 -73
  13. mcp_ticketer/adapters/linear/__init__.py +24 -0
  14. mcp_ticketer/adapters/linear/adapter.py +2732 -0
  15. mcp_ticketer/adapters/linear/client.py +344 -0
  16. mcp_ticketer/adapters/linear/mappers.py +420 -0
  17. mcp_ticketer/adapters/linear/queries.py +479 -0
  18. mcp_ticketer/adapters/linear/types.py +360 -0
  19. mcp_ticketer/adapters/linear.py +10 -2315
  20. mcp_ticketer/analysis/__init__.py +23 -0
  21. mcp_ticketer/analysis/orphaned.py +218 -0
  22. mcp_ticketer/analysis/similarity.py +224 -0
  23. mcp_ticketer/analysis/staleness.py +266 -0
  24. mcp_ticketer/cache/memory.py +9 -8
  25. mcp_ticketer/cli/adapter_diagnostics.py +421 -0
  26. mcp_ticketer/cli/auggie_configure.py +116 -15
  27. mcp_ticketer/cli/codex_configure.py +274 -82
  28. mcp_ticketer/cli/configure.py +888 -151
  29. mcp_ticketer/cli/diagnostics.py +400 -157
  30. mcp_ticketer/cli/discover.py +297 -26
  31. mcp_ticketer/cli/gemini_configure.py +119 -26
  32. mcp_ticketer/cli/init_command.py +880 -0
  33. mcp_ticketer/cli/instruction_commands.py +435 -0
  34. mcp_ticketer/cli/linear_commands.py +616 -0
  35. mcp_ticketer/cli/main.py +203 -1165
  36. mcp_ticketer/cli/mcp_configure.py +474 -90
  37. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  38. mcp_ticketer/cli/migrate_config.py +12 -8
  39. mcp_ticketer/cli/platform_commands.py +123 -0
  40. mcp_ticketer/cli/platform_detection.py +418 -0
  41. mcp_ticketer/cli/platform_installer.py +513 -0
  42. mcp_ticketer/cli/python_detection.py +126 -0
  43. mcp_ticketer/cli/queue_commands.py +15 -15
  44. mcp_ticketer/cli/setup_command.py +639 -0
  45. mcp_ticketer/cli/simple_health.py +90 -65
  46. mcp_ticketer/cli/ticket_commands.py +1013 -0
  47. mcp_ticketer/cli/update_checker.py +313 -0
  48. mcp_ticketer/cli/utils.py +114 -66
  49. mcp_ticketer/core/__init__.py +24 -1
  50. mcp_ticketer/core/adapter.py +250 -16
  51. mcp_ticketer/core/config.py +145 -37
  52. mcp_ticketer/core/env_discovery.py +101 -22
  53. mcp_ticketer/core/env_loader.py +349 -0
  54. mcp_ticketer/core/exceptions.py +160 -0
  55. mcp_ticketer/core/http_client.py +26 -26
  56. mcp_ticketer/core/instructions.py +405 -0
  57. mcp_ticketer/core/label_manager.py +732 -0
  58. mcp_ticketer/core/mappers.py +42 -30
  59. mcp_ticketer/core/models.py +280 -28
  60. mcp_ticketer/core/onepassword_secrets.py +379 -0
  61. mcp_ticketer/core/project_config.py +183 -49
  62. mcp_ticketer/core/registry.py +3 -3
  63. mcp_ticketer/core/session_state.py +171 -0
  64. mcp_ticketer/core/state_matcher.py +592 -0
  65. mcp_ticketer/core/url_parser.py +425 -0
  66. mcp_ticketer/core/validators.py +69 -0
  67. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  68. mcp_ticketer/mcp/__init__.py +29 -1
  69. mcp_ticketer/mcp/__main__.py +60 -0
  70. mcp_ticketer/mcp/server/__init__.py +25 -0
  71. mcp_ticketer/mcp/server/__main__.py +60 -0
  72. mcp_ticketer/mcp/server/constants.py +58 -0
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/dto.py +195 -0
  75. mcp_ticketer/mcp/server/main.py +1343 -0
  76. mcp_ticketer/mcp/server/response_builder.py +206 -0
  77. mcp_ticketer/mcp/server/routing.py +655 -0
  78. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  79. mcp_ticketer/mcp/server/tools/__init__.py +56 -0
  80. mcp_ticketer/mcp/server/tools/analysis_tools.py +495 -0
  81. mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
  82. mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
  83. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  84. mcp_ticketer/mcp/server/tools/config_tools.py +1439 -0
  85. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  86. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +921 -0
  87. mcp_ticketer/mcp/server/tools/instruction_tools.py +300 -0
  88. mcp_ticketer/mcp/server/tools/label_tools.py +948 -0
  89. mcp_ticketer/mcp/server/tools/pr_tools.py +152 -0
  90. mcp_ticketer/mcp/server/tools/search_tools.py +215 -0
  91. mcp_ticketer/mcp/server/tools/session_tools.py +170 -0
  92. mcp_ticketer/mcp/server/tools/ticket_tools.py +1268 -0
  93. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +547 -0
  94. mcp_ticketer/queue/__init__.py +1 -0
  95. mcp_ticketer/queue/health_monitor.py +168 -136
  96. mcp_ticketer/queue/manager.py +95 -25
  97. mcp_ticketer/queue/queue.py +40 -21
  98. mcp_ticketer/queue/run_worker.py +6 -1
  99. mcp_ticketer/queue/ticket_registry.py +213 -155
  100. mcp_ticketer/queue/worker.py +109 -49
  101. mcp_ticketer-1.2.11.dist-info/METADATA +792 -0
  102. mcp_ticketer-1.2.11.dist-info/RECORD +110 -0
  103. mcp_ticketer/mcp/server.py +0 -1895
  104. mcp_ticketer-0.1.30.dist-info/METADATA +0 -413
  105. mcp_ticketer-0.1.30.dist-info/RECORD +0 -49
  106. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/WHEEL +0 -0
  107. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/entry_points.txt +0 -0
  108. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/licenses/LICENSE +0 -0
  109. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,418 @@
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_codex() -> DetectedPlatform | None:
205
+ """Detect Codex installation.
206
+
207
+ Codex requires both:
208
+ 1. `codex` CLI executable in PATH
209
+ 2. Configuration file at ~/.codex/config.toml
210
+
211
+ Returns:
212
+ DetectedPlatform if Codex is installed, None otherwise
213
+
214
+ """
215
+ # Check for CLI executable
216
+ executable_path = shutil.which("codex")
217
+ if not executable_path:
218
+ return None
219
+
220
+ # Check for config file
221
+ config_path = Path.home() / ".codex" / "config.toml"
222
+
223
+ # Codex is installed if CLI exists, even without config
224
+ is_installed = True
225
+
226
+ # If config exists, validate it exists and is readable
227
+ if config_path.exists():
228
+ try:
229
+ with config_path.open() as f:
230
+ f.read() # Just check if readable
231
+ except OSError:
232
+ is_installed = False
233
+
234
+ return DetectedPlatform(
235
+ name="codex",
236
+ display_name="Codex",
237
+ config_path=config_path,
238
+ is_installed=is_installed,
239
+ scope="global",
240
+ executable_path=executable_path,
241
+ )
242
+
243
+ @staticmethod
244
+ def detect_gemini(project_path: Path | None = None) -> DetectedPlatform | None:
245
+ """Detect Gemini installation.
246
+
247
+ Gemini supports both project-level and global configurations:
248
+ 1. `gemini` CLI executable in PATH
249
+ 2. Configuration at .gemini/settings.json (project) or
250
+ ~/.gemini/settings.json (global)
251
+
252
+ Args:
253
+ project_path: Optional project directory to check for project-level config
254
+
255
+ Returns:
256
+ DetectedPlatform if Gemini is installed, None otherwise
257
+
258
+ """
259
+ # Check for CLI executable
260
+ executable_path = shutil.which("gemini")
261
+ if not executable_path:
262
+ return None
263
+
264
+ # Check for config files (project-level first, then global)
265
+ project_config = None
266
+ global_config = Path.home() / ".gemini" / "settings.json"
267
+
268
+ if project_path:
269
+ project_config = project_path / ".gemini" / "settings.json"
270
+
271
+ # Determine which config exists
272
+ config_path = None
273
+ scope = "global"
274
+
275
+ if project_config and project_config.exists():
276
+ config_path = project_config
277
+ scope = "project"
278
+ elif global_config.exists():
279
+ config_path = global_config
280
+ scope = "global"
281
+ else:
282
+ # No config found, use global path as default
283
+ config_path = global_config
284
+
285
+ # Gemini is installed if CLI exists, even without config
286
+ is_installed = True
287
+
288
+ # If config exists, validate it
289
+ if config_path.exists():
290
+ try:
291
+ with config_path.open() as f:
292
+ content = f.read().strip()
293
+ if content:
294
+ json.loads(content)
295
+ except (json.JSONDecodeError, OSError):
296
+ # Config exists but is corrupted
297
+ is_installed = False
298
+
299
+ # Check if both configs exist
300
+ if project_config and project_config.exists() and global_config.exists():
301
+ scope = "both"
302
+
303
+ return DetectedPlatform(
304
+ name="gemini",
305
+ display_name="Gemini",
306
+ config_path=config_path,
307
+ is_installed=is_installed,
308
+ scope=scope,
309
+ executable_path=executable_path,
310
+ )
311
+
312
+ @classmethod
313
+ def detect_all(cls, project_path: Path | None = None) -> list[DetectedPlatform]:
314
+ """Detect all installed AI client platforms.
315
+
316
+ Args:
317
+ project_path: Optional project directory for project-level detection
318
+
319
+ Returns:
320
+ List of detected platforms (empty if none found)
321
+
322
+ Examples:
323
+ >>> detector = PlatformDetector()
324
+ >>> platforms = detector.detect_all()
325
+ >>> for platform in platforms:
326
+ ... print(f"{platform.display_name}: {platform.is_installed}")
327
+ Claude Code: True
328
+ Claude Desktop: False
329
+
330
+ >>> # With project path for Gemini detection
331
+ >>> platforms = detector.detect_all(Path("/home/user/project"))
332
+ >>> gemini = next(p for p in platforms if p.name == "gemini")
333
+ >>> print(gemini.scope) # "project" or "global" or "both"
334
+
335
+ """
336
+ detected = []
337
+
338
+ # Detect Claude Code
339
+ claude_code = cls.detect_claude_code()
340
+ if claude_code:
341
+ detected.append(claude_code)
342
+
343
+ # Detect Claude Desktop
344
+ claude_desktop = cls.detect_claude_desktop()
345
+ if claude_desktop:
346
+ detected.append(claude_desktop)
347
+
348
+ # Detect Auggie
349
+ auggie = cls.detect_auggie()
350
+ if auggie:
351
+ detected.append(auggie)
352
+
353
+ # Detect Codex
354
+ codex = cls.detect_codex()
355
+ if codex:
356
+ detected.append(codex)
357
+
358
+ # Detect Gemini (with project path support)
359
+ gemini = cls.detect_gemini(project_path=project_path)
360
+ if gemini:
361
+ detected.append(gemini)
362
+
363
+ return detected
364
+
365
+
366
+ def get_platform_by_name(
367
+ platform_name: str, project_path: Path | None = None
368
+ ) -> DetectedPlatform | None:
369
+ """Get detection result for a specific platform by name.
370
+
371
+ Args:
372
+ platform_name: Platform identifier (e.g., "claude-code", "auggie")
373
+ project_path: Optional project directory for project-level detection
374
+
375
+ Returns:
376
+ DetectedPlatform if found, None if platform doesn't exist or isn't installed
377
+
378
+ Examples:
379
+ >>> platform = get_platform_by_name("claude-code")
380
+ >>> if platform and platform.is_installed:
381
+ ... print(f"Config at: {platform.config_path}")
382
+
383
+ """
384
+ detector = PlatformDetector()
385
+
386
+ # Map platform names to detection methods
387
+ detection_map = {
388
+ "claude-code": detector.detect_claude_code,
389
+ "claude-desktop": detector.detect_claude_desktop,
390
+ "auggie": detector.detect_auggie,
391
+ "codex": detector.detect_codex,
392
+ "gemini": lambda: detector.detect_gemini(project_path),
393
+ }
394
+
395
+ detect_func = detection_map.get(platform_name)
396
+ if not detect_func:
397
+ return None
398
+
399
+ return detect_func()
400
+
401
+
402
+ def is_platform_installed(platform_name: str, project_path: Path | None = None) -> bool:
403
+ """Check if a specific platform is installed and usable.
404
+
405
+ Args:
406
+ platform_name: Platform identifier (e.g., "claude-code", "auggie")
407
+ project_path: Optional project directory for project-level detection
408
+
409
+ Returns:
410
+ True if platform is installed and has valid configuration
411
+
412
+ Examples:
413
+ >>> if is_platform_installed("claude-code"):
414
+ ... print("Claude Code is installed and configured")
415
+
416
+ """
417
+ platform = get_platform_by_name(platform_name, project_path)
418
+ return platform is not None and platform.is_installed