mcp-ticketer 0.3.0__py3-none-any.whl → 2.2.9__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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +930 -52
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1537 -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/__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 +58 -16
- 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/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +3810 -462
- mcp_ticketer/adapters/linear/client.py +312 -69
- mcp_ticketer/adapters/linear/mappers.py +305 -85
- mcp_ticketer/adapters/linear/queries.py +317 -17
- mcp_ticketer/adapters/linear/types.py +187 -64
- mcp_ticketer/adapters/linear.py +2 -2
- 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 +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +91 -54
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +1323 -151
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +209 -114
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +256 -130
- mcp_ticketer/cli/main.py +140 -1544
- mcp_ticketer/cli/mcp_configure.py +1013 -100
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +794 -0
- mcp_ticketer/cli/simple_health.py +84 -59
- mcp_ticketer/cli/ticket_commands.py +1375 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +195 -72
- mcp_ticketer/core/__init__.py +64 -1
- mcp_ticketer/core/adapter.py +618 -18
- mcp_ticketer/core/config.py +77 -68
- mcp_ticketer/core/env_discovery.py +75 -16
- mcp_ticketer/core/env_loader.py +121 -97
- mcp_ticketer/core/exceptions.py +32 -24
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +566 -19
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +189 -49
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +176 -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 +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +69 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
- 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/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
- 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 +318 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +78 -63
- mcp_ticketer/queue/queue.py +108 -21
- mcp_ticketer/queue/run_worker.py +2 -2
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +96 -58
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.9.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 -1354
- mcp_ticketer/adapters/jira.py +0 -1011
- mcp_ticketer/mcp/server.py +0 -2030
- mcp_ticketer-0.3.0.dist-info/METADATA +0 -414
- mcp_ticketer-0.3.0.dist-info/RECORD +0 -59
- mcp_ticketer-0.3.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +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")
|