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,313 @@
1
+ """Update checker for mcp-ticketer package.
2
+
3
+ This module provides functionality to check PyPI for new versions and notify users.
4
+ Uses the existing HTTP client infrastructure to avoid code duplication.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import sys
10
+ from datetime import datetime, timedelta
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ # Try to import packaging, fall back to simple string comparison if unavailable
15
+ try:
16
+ from packaging.version import Version
17
+
18
+ HAS_PACKAGING = True
19
+ except ImportError:
20
+ HAS_PACKAGING = False
21
+
22
+ class Version:
23
+ """Fallback version comparison using simple string sorting.
24
+
25
+ This is a minimal fallback when packaging is not available.
26
+ Works correctly for most semantic versions (X.Y.Z format).
27
+ """
28
+
29
+ def __init__(self, version_string: str):
30
+ """Initialize with version string."""
31
+ self.version_string = version_string
32
+ # Parse into tuple of integers for proper comparison
33
+ try:
34
+ parts = version_string.split(".")
35
+ # Handle pre-release versions by splitting on non-digit chars
36
+ self.parts = []
37
+ for part in parts:
38
+ # Extract leading digits
39
+ digits = ""
40
+ for char in part:
41
+ if char.isdigit():
42
+ digits += char
43
+ else:
44
+ break
45
+ if digits:
46
+ self.parts.append(int(digits))
47
+ except (ValueError, AttributeError):
48
+ # Fallback to string comparison if parsing fails
49
+ self.parts = None
50
+
51
+ def __gt__(self, other: "Version") -> bool:
52
+ """Compare versions."""
53
+ if self.parts is not None and other.parts is not None:
54
+ # Proper numeric comparison
55
+ return self.parts > other.parts
56
+ # Fallback to string comparison
57
+ return self.version_string > other.version_string
58
+
59
+ def __eq__(self, other: object) -> bool:
60
+ """Check equality."""
61
+ if not isinstance(other, Version):
62
+ return False
63
+ if self.parts is not None and other.parts is not None:
64
+ return self.parts == other.parts
65
+ return self.version_string == other.version_string
66
+
67
+
68
+ from ..__version__ import __version__
69
+
70
+ logger = logging.getLogger(__name__)
71
+
72
+ # Cache configuration
73
+ CACHE_DIR = Path.home() / ".mcp-ticketer"
74
+ CACHE_FILE = CACHE_DIR / "update_check_cache.json"
75
+ CACHE_DURATION_HOURS = 24
76
+
77
+ # PyPI API configuration
78
+ PYPI_API_URL = "https://pypi.org/pypi/mcp-ticketer/json"
79
+ PYPI_PROJECT_URL = "https://pypi.org/project/mcp-ticketer/"
80
+
81
+
82
+ class UpdateInfo:
83
+ """Container for update information."""
84
+
85
+ def __init__(
86
+ self,
87
+ current_version: str,
88
+ latest_version: str,
89
+ needs_update: bool,
90
+ pypi_url: str,
91
+ release_date: str | None = None,
92
+ checked_at: str | None = None,
93
+ ):
94
+ """Initialize update information.
95
+
96
+ Args:
97
+ current_version: Currently installed version
98
+ latest_version: Latest version on PyPI
99
+ needs_update: Whether an update is available
100
+ pypi_url: URL to package on PyPI
101
+ release_date: Release date of latest version (ISO format)
102
+ checked_at: Timestamp of when check was performed (ISO format)
103
+
104
+ """
105
+ self.current_version = current_version
106
+ self.latest_version = latest_version
107
+ self.needs_update = needs_update
108
+ self.pypi_url = pypi_url
109
+ self.release_date = release_date
110
+ self.checked_at = checked_at or datetime.now().isoformat()
111
+
112
+ def to_dict(self) -> dict[str, Any]:
113
+ """Convert to dictionary."""
114
+ return {
115
+ "current_version": self.current_version,
116
+ "latest_version": self.latest_version,
117
+ "needs_update": self.needs_update,
118
+ "pypi_url": self.pypi_url,
119
+ "release_date": self.release_date,
120
+ "checked_at": self.checked_at,
121
+ }
122
+
123
+ @classmethod
124
+ def from_dict(cls, data: dict[str, Any]) -> "UpdateInfo":
125
+ """Create from dictionary."""
126
+ return cls(
127
+ current_version=data["current_version"],
128
+ latest_version=data["latest_version"],
129
+ needs_update=data["needs_update"],
130
+ pypi_url=data["pypi_url"],
131
+ release_date=data.get("release_date"),
132
+ checked_at=data.get("checked_at"),
133
+ )
134
+
135
+
136
+ def _ensure_cache_dir() -> None:
137
+ """Ensure cache directory exists."""
138
+ try:
139
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
140
+ except OSError as e:
141
+ logger.warning(f"Failed to create cache directory: {e}")
142
+
143
+
144
+ def _load_cache() -> dict[str, Any] | None:
145
+ """Load cached update information.
146
+
147
+ Returns:
148
+ Cached data or None if cache doesn't exist or is invalid
149
+
150
+ """
151
+ try:
152
+ if not CACHE_FILE.exists():
153
+ return None
154
+
155
+ with open(CACHE_FILE, encoding="utf-8") as f:
156
+ data = json.load(f)
157
+
158
+ # Validate cache structure
159
+ if not isinstance(data, dict) or "checked_at" not in data:
160
+ logger.debug("Invalid cache format, ignoring")
161
+ return None
162
+
163
+ return data
164
+ except (OSError, json.JSONDecodeError) as e:
165
+ logger.debug(f"Failed to load cache: {e}")
166
+ return None
167
+
168
+
169
+ def _save_cache(update_info: UpdateInfo) -> None:
170
+ """Save update information to cache.
171
+
172
+ Args:
173
+ update_info: Update information to cache
174
+
175
+ """
176
+ try:
177
+ _ensure_cache_dir()
178
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
179
+ json.dump(update_info.to_dict(), f, indent=2)
180
+ except OSError as e:
181
+ logger.warning(f"Failed to save cache: {e}")
182
+
183
+
184
+ def should_check_updates(force: bool = False) -> bool:
185
+ """Check if enough time has passed since last check.
186
+
187
+ Args:
188
+ force: If True, always return True (force check)
189
+
190
+ Returns:
191
+ True if check should be performed
192
+
193
+ """
194
+ if force:
195
+ return True
196
+
197
+ cache = _load_cache()
198
+ if not cache:
199
+ return True
200
+
201
+ try:
202
+ checked_at = datetime.fromisoformat(cache["checked_at"])
203
+ age = datetime.now() - checked_at
204
+ return age > timedelta(hours=CACHE_DURATION_HOURS)
205
+ except (ValueError, KeyError) as e:
206
+ logger.debug(f"Invalid cache timestamp: {e}")
207
+ return True
208
+
209
+
210
+ async def check_for_updates(force: bool = False) -> UpdateInfo:
211
+ """Check PyPI for latest version.
212
+
213
+ Args:
214
+ force: If True, bypass cache and force check
215
+
216
+ Returns:
217
+ UpdateInfo object with version information
218
+
219
+ Raises:
220
+ Exception: If PyPI API request fails
221
+
222
+ """
223
+ # Suppress httpx INFO logging to keep output clean
224
+ logging.getLogger("httpx").setLevel(logging.WARNING)
225
+
226
+ current_version = __version__
227
+
228
+ # Check cache first (unless forced)
229
+ if not force:
230
+ cache = _load_cache()
231
+ if cache and cache.get("current_version") == current_version:
232
+ # Return cached info if it's for the current version
233
+ return UpdateInfo.from_dict(cache)
234
+
235
+ # Fetch from PyPI - use httpx directly for simplicity
236
+ import httpx
237
+
238
+ async with httpx.AsyncClient(timeout=10.0) as client:
239
+ response = await client.get(PYPI_API_URL)
240
+ response.raise_for_status()
241
+ response_data = response.json()
242
+
243
+ # Extract version information
244
+ latest_version = response_data["info"]["version"]
245
+
246
+ # Get release date from releases data
247
+ releases = response_data.get("releases", {})
248
+ release_date = None
249
+ if latest_version in releases and releases[latest_version]:
250
+ # Get upload_time from first file in the release
251
+ upload_time = releases[latest_version][0].get("upload_time")
252
+ if upload_time:
253
+ # Convert to ISO format date only
254
+ release_date = upload_time.split("T")[0]
255
+
256
+ # Compare versions
257
+ needs_update = Version(latest_version) > Version(current_version)
258
+
259
+ # Create update info
260
+ update_info = UpdateInfo(
261
+ current_version=current_version,
262
+ latest_version=latest_version,
263
+ needs_update=needs_update,
264
+ pypi_url=PYPI_PROJECT_URL,
265
+ release_date=release_date,
266
+ )
267
+
268
+ # Cache the result
269
+ _save_cache(update_info)
270
+
271
+ return update_info
272
+
273
+
274
+ def detect_installation_method() -> str:
275
+ """Detect how mcp-ticketer was installed.
276
+
277
+ Returns:
278
+ Installation method: 'pipx', 'uv', or 'pip'
279
+
280
+ """
281
+ # Check for pipx
282
+ if "pipx" in sys.prefix or "pipx" in sys.executable:
283
+ return "pipx"
284
+
285
+ # Check for uv
286
+ if "uv" in sys.prefix or "uv" in sys.executable:
287
+ return "uv"
288
+ if ".venv" in sys.prefix and Path(sys.prefix).parent.name == ".venv":
289
+ # Common uv pattern
290
+ uv_bin = Path(sys.prefix).parent.parent / "uv"
291
+ if uv_bin.exists():
292
+ return "uv"
293
+
294
+ # Default to pip
295
+ return "pip"
296
+
297
+
298
+ def get_upgrade_command() -> str:
299
+ """Get the appropriate upgrade command for the installation method.
300
+
301
+ Returns:
302
+ Command string to upgrade mcp-ticketer
303
+
304
+ """
305
+ method = detect_installation_method()
306
+
307
+ commands = {
308
+ "pipx": "pipx upgrade mcp-ticketer",
309
+ "uv": "uv pip install --upgrade mcp-ticketer",
310
+ "pip": "pip install --upgrade mcp-ticketer",
311
+ }
312
+
313
+ return commands.get(method, "pip install --upgrade mcp-ticketer")