mcp-ticketer 0.12.0__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 +1 -1
- mcp_ticketer/adapters/aitrackdown.py +385 -6
- mcp_ticketer/adapters/asana/adapter.py +108 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github.py +525 -11
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +521 -0
- mcp_ticketer/adapters/linear/adapter.py +1784 -101
- mcp_ticketer/adapters/linear/client.py +85 -3
- mcp_ticketer/adapters/linear/mappers.py +96 -8
- 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/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 +851 -103
- 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/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +233 -3151
- mcp_ticketer/cli/mcp_configure.py +672 -98
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +264 -24
- mcp_ticketer/core/__init__.py +28 -6
- mcp_ticketer/core/adapter.py +166 -1
- 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/models.py +135 -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/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/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +31 -12
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +6 -8
- 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 +1184 -136
- 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/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 +1070 -123
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- 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.0.1.dist-info/METADATA +1366 -0
- mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
- 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 ā mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info ā mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info ā mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.12.0.dist-info ā mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
"""Setup command for mcp-ticketer - smart initialization with platform detection."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
# Mapping of adapter types to their required dependencies
|
|
14
|
+
ADAPTER_DEPENDENCIES = {
|
|
15
|
+
"linear": {"package": "gql[httpx]", "extras": "linear"},
|
|
16
|
+
"jira": {"package": "jira", "extras": "jira"},
|
|
17
|
+
"github": {"package": "PyGithub", "extras": "github"},
|
|
18
|
+
"aitrackdown": None, # No extra dependencies
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _check_package_installed(adapter_type: str) -> bool:
|
|
23
|
+
"""Check if adapter-specific package is installed.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
adapter_type: Type of adapter (linear, jira, github)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
True if package is installed, False otherwise
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
# Try to import the package
|
|
34
|
+
if adapter_type == "linear":
|
|
35
|
+
import gql # noqa: F401
|
|
36
|
+
elif adapter_type == "jira":
|
|
37
|
+
import jira # noqa: F401
|
|
38
|
+
elif adapter_type == "github":
|
|
39
|
+
import github # noqa: F401
|
|
40
|
+
return True
|
|
41
|
+
except ImportError:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _check_and_install_adapter_dependencies(
|
|
46
|
+
adapter_type: str, console: Console
|
|
47
|
+
) -> bool:
|
|
48
|
+
"""Check if adapter-specific dependencies are installed and offer to install.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
adapter_type: Type of adapter (linear, jira, github, aitrackdown)
|
|
52
|
+
console: Rich console for output
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if dependencies are satisfied (installed or not needed), False if failed
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
# Check if adapter needs extra dependencies
|
|
59
|
+
dependency_info = ADAPTER_DEPENDENCIES.get(adapter_type)
|
|
60
|
+
|
|
61
|
+
if dependency_info is None:
|
|
62
|
+
# No extra dependencies needed (e.g., aitrackdown)
|
|
63
|
+
console.print(
|
|
64
|
+
f"[green]ā[/green] No extra dependencies required for {adapter_type}\n"
|
|
65
|
+
)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Check if the required package is already installed
|
|
69
|
+
if _check_package_installed(adapter_type):
|
|
70
|
+
console.print(
|
|
71
|
+
f"[green]ā[/green] {adapter_type.capitalize()} dependencies already installed\n"
|
|
72
|
+
)
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# Dependencies not installed
|
|
76
|
+
console.print(
|
|
77
|
+
f"[yellow]ā [/yellow] {adapter_type.capitalize()} adapter requires additional dependencies\n"
|
|
78
|
+
)
|
|
79
|
+
console.print(f"[dim]Required package: {dependency_info['package']}[/dim]\n")
|
|
80
|
+
|
|
81
|
+
# Prompt user to install
|
|
82
|
+
try:
|
|
83
|
+
if not typer.confirm("Install dependencies now?", default=True):
|
|
84
|
+
console.print(
|
|
85
|
+
"\n[yellow]Skipping installation. Install manually with:[/yellow]"
|
|
86
|
+
)
|
|
87
|
+
console.print(
|
|
88
|
+
f"[cyan] pip install mcp-ticketer[{dependency_info['extras']}][/cyan]\n"
|
|
89
|
+
)
|
|
90
|
+
return True # User declined, but we continue
|
|
91
|
+
|
|
92
|
+
except typer.Abort:
|
|
93
|
+
console.print("\n[yellow]Installation cancelled[/yellow]\n")
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
# Install dependencies
|
|
97
|
+
console.print(f"[cyan]Installing {adapter_type} dependencies...[/cyan]\n")
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Run pip install with the extras
|
|
101
|
+
subprocess.check_call(
|
|
102
|
+
[
|
|
103
|
+
sys.executable,
|
|
104
|
+
"-m",
|
|
105
|
+
"pip",
|
|
106
|
+
"install",
|
|
107
|
+
f"mcp-ticketer[{dependency_info['extras']}]",
|
|
108
|
+
],
|
|
109
|
+
stdout=subprocess.DEVNULL,
|
|
110
|
+
stderr=subprocess.PIPE,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
console.print(
|
|
114
|
+
f"[green]ā[/green] Successfully installed {adapter_type} dependencies\n"
|
|
115
|
+
)
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
except subprocess.CalledProcessError as e:
|
|
119
|
+
console.print(
|
|
120
|
+
f"[red]ā[/red] Failed to install dependencies: {e.stderr.decode() if e.stderr else 'Unknown error'}\n"
|
|
121
|
+
)
|
|
122
|
+
console.print("[yellow]Please install manually with:[/yellow]")
|
|
123
|
+
console.print(
|
|
124
|
+
f"[cyan] pip install mcp-ticketer[{dependency_info['extras']}][/cyan]\n"
|
|
125
|
+
)
|
|
126
|
+
return True # Continue even if installation failed
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _prompt_for_adapter_selection(console: Console) -> str:
|
|
130
|
+
"""Interactive prompt for adapter selection.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
console: Rich console for output
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Selected adapter type
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
console.print("\n[bold blue]š MCP Ticketer Setup[/bold blue]")
|
|
140
|
+
console.print("Choose which ticket system you want to connect to:\n")
|
|
141
|
+
|
|
142
|
+
# Define adapter options with descriptions
|
|
143
|
+
adapters = [
|
|
144
|
+
{
|
|
145
|
+
"name": "linear",
|
|
146
|
+
"title": "Linear",
|
|
147
|
+
"description": "Modern project management (linear.app)",
|
|
148
|
+
"requirements": "API key and team ID",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "github",
|
|
152
|
+
"title": "GitHub Issues",
|
|
153
|
+
"description": "GitHub repository issues",
|
|
154
|
+
"requirements": "Personal access token, owner, and repo",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "jira",
|
|
158
|
+
"title": "JIRA",
|
|
159
|
+
"description": "Atlassian JIRA project management",
|
|
160
|
+
"requirements": "Server URL, email, and API token",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "aitrackdown",
|
|
164
|
+
"title": "Local Files (AITrackdown)",
|
|
165
|
+
"description": "Store tickets in local files (no external service)",
|
|
166
|
+
"requirements": "None - works offline",
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
# Display options
|
|
171
|
+
for i, adapter in enumerate(adapters, 1):
|
|
172
|
+
console.print(f"[cyan]{i}.[/cyan] [bold]{adapter['title']}[/bold]")
|
|
173
|
+
console.print(f" {adapter['description']}")
|
|
174
|
+
console.print(f" [dim]Requirements: {adapter['requirements']}[/dim]\n")
|
|
175
|
+
|
|
176
|
+
# Get user selection
|
|
177
|
+
while True:
|
|
178
|
+
try:
|
|
179
|
+
choice = typer.prompt("Select adapter (1-4)", type=int, default=1)
|
|
180
|
+
if 1 <= choice <= len(adapters):
|
|
181
|
+
selected_adapter = adapters[choice - 1]
|
|
182
|
+
console.print(
|
|
183
|
+
f"\n[green]ā Selected: {selected_adapter['title']}[/green]"
|
|
184
|
+
)
|
|
185
|
+
return selected_adapter["name"]
|
|
186
|
+
else:
|
|
187
|
+
console.print(
|
|
188
|
+
f"[red]Please enter a number between 1 and {len(adapters)}[/red]"
|
|
189
|
+
)
|
|
190
|
+
except (ValueError, typer.Abort):
|
|
191
|
+
console.print("[yellow]Setup cancelled.[/yellow]")
|
|
192
|
+
raise typer.Exit(0) from None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def setup(
|
|
196
|
+
project_path: str | None = typer.Option(
|
|
197
|
+
None, "--path", help="Project path (default: current directory)"
|
|
198
|
+
),
|
|
199
|
+
skip_platforms: bool = typer.Option(
|
|
200
|
+
False,
|
|
201
|
+
"--skip-platforms",
|
|
202
|
+
help="Skip platform installation (only initialize adapter)",
|
|
203
|
+
),
|
|
204
|
+
force_reinit: bool = typer.Option(
|
|
205
|
+
False,
|
|
206
|
+
"--force-reinit",
|
|
207
|
+
help="Force re-initialization even if config exists",
|
|
208
|
+
),
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Smart setup command - combines init + platform installation.
|
|
211
|
+
|
|
212
|
+
This command intelligently detects your current setup state and only
|
|
213
|
+
performs necessary configuration. It's the recommended way to get started.
|
|
214
|
+
|
|
215
|
+
Detection & Smart Actions:
|
|
216
|
+
- First run: Full setup (init + platform installation)
|
|
217
|
+
- Existing config: Skip init, offer platform installation
|
|
218
|
+
- Detects changes: Offers to update configurations
|
|
219
|
+
- Respects existing: Won't overwrite without confirmation
|
|
220
|
+
|
|
221
|
+
Examples:
|
|
222
|
+
# Smart setup (recommended for first-time setup)
|
|
223
|
+
mcp-ticketer setup
|
|
224
|
+
|
|
225
|
+
# Setup for different project
|
|
226
|
+
mcp-ticketer setup --path /path/to/project
|
|
227
|
+
|
|
228
|
+
# Re-initialize configuration
|
|
229
|
+
mcp-ticketer setup --force-reinit
|
|
230
|
+
|
|
231
|
+
# Only init adapter, skip platform installation
|
|
232
|
+
mcp-ticketer setup --skip-platforms
|
|
233
|
+
|
|
234
|
+
Note: For advanced configuration, use 'init' and 'install' separately.
|
|
235
|
+
|
|
236
|
+
"""
|
|
237
|
+
from .platform_detection import PlatformDetector
|
|
238
|
+
|
|
239
|
+
proj_path = Path(project_path) if project_path else Path.cwd()
|
|
240
|
+
config_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
241
|
+
|
|
242
|
+
console.print("[bold cyan]š MCP Ticketer Smart Setup[/bold cyan]\n")
|
|
243
|
+
|
|
244
|
+
# Step 1: Detect existing configuration
|
|
245
|
+
config_exists = config_path.exists()
|
|
246
|
+
config_valid = False
|
|
247
|
+
current_adapter = None
|
|
248
|
+
|
|
249
|
+
if config_exists and not force_reinit:
|
|
250
|
+
try:
|
|
251
|
+
with open(config_path) as f:
|
|
252
|
+
config = json.load(f)
|
|
253
|
+
current_adapter = config.get("default_adapter")
|
|
254
|
+
config_valid = bool(current_adapter and config.get("adapters"))
|
|
255
|
+
except (json.JSONDecodeError, OSError):
|
|
256
|
+
config_valid = False
|
|
257
|
+
|
|
258
|
+
if config_valid:
|
|
259
|
+
console.print("[green]ā[/green] Configuration detected")
|
|
260
|
+
console.print(f"[dim] Adapter: {current_adapter}[/dim]")
|
|
261
|
+
console.print(f"[dim] Location: {config_path}[/dim]\n")
|
|
262
|
+
|
|
263
|
+
# Offer to reconfigure
|
|
264
|
+
if not typer.confirm(
|
|
265
|
+
"Configuration already exists. Keep existing settings?", default=True
|
|
266
|
+
):
|
|
267
|
+
console.print("[cyan]Re-initializing configuration...[/cyan]\n")
|
|
268
|
+
force_reinit = True
|
|
269
|
+
config_valid = False
|
|
270
|
+
else:
|
|
271
|
+
if config_exists:
|
|
272
|
+
console.print(
|
|
273
|
+
"[yellow]ā [/yellow] Configuration file exists but is invalid\n"
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
console.print("[yellow]ā [/yellow] No configuration found\n")
|
|
277
|
+
|
|
278
|
+
# Step 2: Initialize adapter configuration if needed
|
|
279
|
+
if not config_valid or force_reinit:
|
|
280
|
+
console.print("[bold]Step 1/2: Adapter Configuration[/bold]\n")
|
|
281
|
+
|
|
282
|
+
# Run init command non-interactively through function call
|
|
283
|
+
# We'll use the discover and prompt flow from init
|
|
284
|
+
from ..core.env_discovery import discover_config
|
|
285
|
+
from .init_command import _init_adapter_internal
|
|
286
|
+
|
|
287
|
+
discovered = discover_config(proj_path)
|
|
288
|
+
adapter_type = None
|
|
289
|
+
|
|
290
|
+
# Try auto-discovery
|
|
291
|
+
if discovered and discovered.adapters:
|
|
292
|
+
primary = discovered.get_primary_adapter()
|
|
293
|
+
if primary:
|
|
294
|
+
adapter_type = primary.adapter_type
|
|
295
|
+
console.print(f"[green]ā Auto-detected {adapter_type} adapter[/green]")
|
|
296
|
+
console.print(f"[dim] Source: {primary.found_in}[/dim]")
|
|
297
|
+
console.print(f"[dim] Confidence: {primary.confidence:.0%}[/dim]\n")
|
|
298
|
+
|
|
299
|
+
if not typer.confirm(
|
|
300
|
+
f"Use detected {adapter_type} adapter?", default=True
|
|
301
|
+
):
|
|
302
|
+
adapter_type = None
|
|
303
|
+
|
|
304
|
+
# If no adapter detected, prompt for selection
|
|
305
|
+
if not adapter_type:
|
|
306
|
+
adapter_type = _prompt_for_adapter_selection(console)
|
|
307
|
+
|
|
308
|
+
# Now run the full init with the selected adapter
|
|
309
|
+
console.print(f"\n[cyan]Initializing {adapter_type} adapter...[/cyan]\n")
|
|
310
|
+
|
|
311
|
+
# Call internal init function programmatically (NOT the CLI command)
|
|
312
|
+
# Note: Only pass required parameters - all optional params should be None
|
|
313
|
+
# to avoid passing OptionInfo objects which are not JSON serializable
|
|
314
|
+
success = _init_adapter_internal(
|
|
315
|
+
adapter=adapter_type,
|
|
316
|
+
project_path=str(proj_path),
|
|
317
|
+
global_config=False,
|
|
318
|
+
base_path=None,
|
|
319
|
+
api_key=None,
|
|
320
|
+
team_id=None,
|
|
321
|
+
jira_server=None,
|
|
322
|
+
jira_email=None,
|
|
323
|
+
jira_project=None,
|
|
324
|
+
github_url=None,
|
|
325
|
+
github_token=None,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if not success:
|
|
329
|
+
console.print("[red]Failed to initialize adapter configuration[/red]")
|
|
330
|
+
raise typer.Exit(1) from None
|
|
331
|
+
|
|
332
|
+
# Check and install adapter-specific dependencies
|
|
333
|
+
_check_and_install_adapter_dependencies(adapter_type, console)
|
|
334
|
+
|
|
335
|
+
console.print("\n[green]ā Adapter configuration complete[/green]\n")
|
|
336
|
+
else:
|
|
337
|
+
console.print("[green]ā Step 1/2: Adapter already configured[/green]\n")
|
|
338
|
+
|
|
339
|
+
# Even though adapter is configured, prompt for default values
|
|
340
|
+
# This handles the case where credentials exist but defaults were never set
|
|
341
|
+
_prompt_and_update_default_values(config_path, current_adapter, console)
|
|
342
|
+
|
|
343
|
+
# Step 3: Platform installation
|
|
344
|
+
if skip_platforms:
|
|
345
|
+
console.print(
|
|
346
|
+
"[yellow]ā [/yellow] Skipping platform installation (--skip-platforms)\n"
|
|
347
|
+
)
|
|
348
|
+
_show_setup_complete_message(console, proj_path)
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
console.print("[bold]Step 2/2: Platform Installation[/bold]\n")
|
|
352
|
+
|
|
353
|
+
# Detect available platforms
|
|
354
|
+
detector = PlatformDetector()
|
|
355
|
+
detected = detector.detect_all(project_path=proj_path)
|
|
356
|
+
|
|
357
|
+
if not detected:
|
|
358
|
+
console.print("[yellow]No AI platforms detected on this system.[/yellow]")
|
|
359
|
+
console.print(
|
|
360
|
+
"\n[dim]Supported platforms: Claude Code, Claude Desktop, Gemini, Codex, Auggie[/dim]"
|
|
361
|
+
)
|
|
362
|
+
console.print(
|
|
363
|
+
"[dim]Install these platforms to use them with mcp-ticketer.[/dim]\n"
|
|
364
|
+
)
|
|
365
|
+
_show_setup_complete_message(console, proj_path)
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
# Filter to only installed platforms
|
|
369
|
+
installed = [p for p in detected if p.is_installed]
|
|
370
|
+
|
|
371
|
+
if not installed:
|
|
372
|
+
console.print(
|
|
373
|
+
"[yellow]AI platforms detected but have configuration issues.[/yellow]"
|
|
374
|
+
)
|
|
375
|
+
console.print(
|
|
376
|
+
"\n[dim]Run 'mcp-ticketer install --auto-detect' for details.[/dim]\n"
|
|
377
|
+
)
|
|
378
|
+
_show_setup_complete_message(console, proj_path)
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
# Show detected platforms
|
|
382
|
+
console.print(f"[green]ā[/green] Detected {len(installed)} platform(s):\n")
|
|
383
|
+
for plat in installed:
|
|
384
|
+
console.print(f" ⢠{plat.display_name} ({plat.scope})")
|
|
385
|
+
|
|
386
|
+
console.print()
|
|
387
|
+
|
|
388
|
+
# Check if mcp-ticketer is already configured for these platforms
|
|
389
|
+
already_configured = _check_existing_platform_configs(installed, proj_path)
|
|
390
|
+
|
|
391
|
+
if already_configured:
|
|
392
|
+
console.print(
|
|
393
|
+
f"[green]ā[/green] mcp-ticketer already configured for {len(already_configured)} platform(s)\n"
|
|
394
|
+
)
|
|
395
|
+
for plat_name in already_configured:
|
|
396
|
+
console.print(f" ⢠{plat_name}")
|
|
397
|
+
console.print()
|
|
398
|
+
|
|
399
|
+
if not typer.confirm("Update platform configurations anyway?", default=False):
|
|
400
|
+
console.print("[yellow]Skipping platform installation[/yellow]\n")
|
|
401
|
+
_show_setup_complete_message(console, proj_path)
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
# Offer to install for all or select specific
|
|
405
|
+
console.print("[bold]Platform Installation Options:[/bold]")
|
|
406
|
+
console.print("1. Install for all detected platforms")
|
|
407
|
+
console.print("2. Select specific platform")
|
|
408
|
+
console.print("3. Skip platform installation")
|
|
409
|
+
|
|
410
|
+
try:
|
|
411
|
+
choice = typer.prompt("\nSelect option (1-3)", type=int, default=1)
|
|
412
|
+
except typer.Abort:
|
|
413
|
+
console.print("[yellow]Setup cancelled[/yellow]")
|
|
414
|
+
raise typer.Exit(0) from None
|
|
415
|
+
|
|
416
|
+
if choice == 3:
|
|
417
|
+
console.print("[yellow]Skipping platform installation[/yellow]\n")
|
|
418
|
+
_show_setup_complete_message(console, proj_path)
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
# Import configuration functions
|
|
422
|
+
from .auggie_configure import configure_auggie_mcp
|
|
423
|
+
from .codex_configure import configure_codex_mcp
|
|
424
|
+
from .gemini_configure import configure_gemini_mcp
|
|
425
|
+
from .mcp_configure import configure_claude_mcp
|
|
426
|
+
|
|
427
|
+
platform_mapping = {
|
|
428
|
+
"claude-code": lambda: configure_claude_mcp(global_config=False, force=True),
|
|
429
|
+
"claude-desktop": lambda: configure_claude_mcp(global_config=True, force=True),
|
|
430
|
+
"auggie": lambda: configure_auggie_mcp(force=True),
|
|
431
|
+
"gemini": lambda: configure_gemini_mcp(scope="project", force=True),
|
|
432
|
+
"codex": lambda: configure_codex_mcp(force=True),
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
platforms_to_install = []
|
|
436
|
+
|
|
437
|
+
if choice == 1:
|
|
438
|
+
# Install for all
|
|
439
|
+
platforms_to_install = installed
|
|
440
|
+
elif choice == 2:
|
|
441
|
+
# Select specific platform
|
|
442
|
+
console.print("\n[bold]Select platform:[/bold]")
|
|
443
|
+
for idx, plat in enumerate(installed, 1):
|
|
444
|
+
console.print(f" {idx}. {plat.display_name} ({plat.scope})")
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
plat_choice = typer.prompt("\nSelect platform number", type=int)
|
|
448
|
+
if 1 <= plat_choice <= len(installed):
|
|
449
|
+
platforms_to_install = [installed[plat_choice - 1]]
|
|
450
|
+
else:
|
|
451
|
+
console.print("[red]Invalid selection[/red]")
|
|
452
|
+
raise typer.Exit(1) from None
|
|
453
|
+
except typer.Abort:
|
|
454
|
+
console.print("[yellow]Setup cancelled[/yellow]")
|
|
455
|
+
raise typer.Exit(0) from None
|
|
456
|
+
|
|
457
|
+
# Install for selected platforms
|
|
458
|
+
console.print()
|
|
459
|
+
success_count = 0
|
|
460
|
+
failed = []
|
|
461
|
+
|
|
462
|
+
for plat in platforms_to_install:
|
|
463
|
+
config_func = platform_mapping.get(plat.name)
|
|
464
|
+
if not config_func:
|
|
465
|
+
console.print(f"[yellow]ā [/yellow] No installer for {plat.display_name}")
|
|
466
|
+
continue
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
console.print(f"[cyan]Installing for {plat.display_name}...[/cyan]")
|
|
470
|
+
config_func()
|
|
471
|
+
console.print(f"[green]ā[/green] {plat.display_name} configured\n")
|
|
472
|
+
success_count += 1
|
|
473
|
+
except Exception as e:
|
|
474
|
+
console.print(
|
|
475
|
+
f"[red]ā[/red] Failed to configure {plat.display_name}: {e}\n"
|
|
476
|
+
)
|
|
477
|
+
failed.append(plat.display_name)
|
|
478
|
+
|
|
479
|
+
# Summary
|
|
480
|
+
console.print(
|
|
481
|
+
f"[bold]Platform Installation:[/bold] {success_count}/{len(platforms_to_install)} succeeded"
|
|
482
|
+
)
|
|
483
|
+
if failed:
|
|
484
|
+
console.print(f"[red]Failed:[/red] {', '.join(failed)}")
|
|
485
|
+
|
|
486
|
+
console.print()
|
|
487
|
+
_show_setup_complete_message(console, proj_path)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _prompt_and_update_default_values(
|
|
491
|
+
config_path: Path, adapter_type: str, console: Console
|
|
492
|
+
) -> None:
|
|
493
|
+
"""Prompt user for default values and update configuration.
|
|
494
|
+
|
|
495
|
+
This function handles the case where adapter credentials exist but
|
|
496
|
+
default values (default_user, default_epic, default_project, default_tags)
|
|
497
|
+
need to be set or updated.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
config_path: Path to the configuration file (.mcp-ticketer/config.json)
|
|
501
|
+
adapter_type: Type of adapter (linear, jira, github, aitrackdown)
|
|
502
|
+
console: Rich console for output
|
|
503
|
+
|
|
504
|
+
Raises:
|
|
505
|
+
typer.Exit: If configuration cannot be loaded or updated
|
|
506
|
+
|
|
507
|
+
"""
|
|
508
|
+
from .configure import prompt_default_values
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
# Load current config to get existing default values
|
|
512
|
+
with open(config_path) as f:
|
|
513
|
+
existing_config = json.load(f)
|
|
514
|
+
|
|
515
|
+
existing_defaults = {
|
|
516
|
+
"default_user": existing_config.get("default_user"),
|
|
517
|
+
"default_epic": existing_config.get("default_epic"),
|
|
518
|
+
"default_project": existing_config.get("default_project"),
|
|
519
|
+
"default_tags": existing_config.get("default_tags"),
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
# Prompt for default values
|
|
523
|
+
console.print("[bold]Configure Default Values[/bold] (for ticket creation)\n")
|
|
524
|
+
default_values = prompt_default_values(
|
|
525
|
+
adapter_type=adapter_type, existing_values=existing_defaults
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Update config with new default values
|
|
529
|
+
if default_values:
|
|
530
|
+
existing_config.update(default_values)
|
|
531
|
+
with open(config_path, "w") as f:
|
|
532
|
+
json.dump(existing_config, f, indent=2)
|
|
533
|
+
console.print("\n[green]ā Default values updated[/green]\n")
|
|
534
|
+
else:
|
|
535
|
+
console.print("\n[dim]No default values set[/dim]\n")
|
|
536
|
+
|
|
537
|
+
except json.JSONDecodeError as e:
|
|
538
|
+
console.print(f"[red]ā Invalid JSON in configuration file: {e}[/red]\n")
|
|
539
|
+
console.print(
|
|
540
|
+
"[yellow]Please fix the configuration file manually or run 'mcp-ticketer init --force'[/yellow]\n"
|
|
541
|
+
)
|
|
542
|
+
except OSError as e:
|
|
543
|
+
console.print(f"[red]ā Could not read/write configuration file: {e}[/red]\n")
|
|
544
|
+
console.print("[yellow]Please check file permissions and try again[/yellow]\n")
|
|
545
|
+
except Exception as e:
|
|
546
|
+
console.print(f"[red]ā Unexpected error updating default values: {e}[/red]\n")
|
|
547
|
+
console.print(
|
|
548
|
+
"[yellow]Configuration may be incomplete. Run 'mcp-ticketer doctor' to verify[/yellow]\n"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _check_existing_platform_configs(platforms: list, proj_path: Path) -> list[str]:
|
|
553
|
+
"""Check if mcp-ticketer is already configured for given platforms.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
platforms: List of DetectedPlatform objects
|
|
557
|
+
proj_path: Project path
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
List of platform display names that are already configured
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
configured = []
|
|
564
|
+
|
|
565
|
+
for plat in platforms:
|
|
566
|
+
try:
|
|
567
|
+
if plat.name == "claude-code":
|
|
568
|
+
# Check both new and old locations
|
|
569
|
+
new_config = Path.home() / ".config" / "claude" / "mcp.json"
|
|
570
|
+
old_config = Path.home() / ".claude.json"
|
|
571
|
+
|
|
572
|
+
is_configured = False
|
|
573
|
+
|
|
574
|
+
# Check new global location (flat structure)
|
|
575
|
+
if new_config.exists():
|
|
576
|
+
with open(new_config) as f:
|
|
577
|
+
config = json.load(f)
|
|
578
|
+
if "mcp-ticketer" in config.get("mcpServers", {}):
|
|
579
|
+
is_configured = True
|
|
580
|
+
|
|
581
|
+
# Check old location (nested structure)
|
|
582
|
+
if not is_configured and old_config.exists():
|
|
583
|
+
with open(old_config) as f:
|
|
584
|
+
config = json.load(f)
|
|
585
|
+
projects = config.get("projects", {})
|
|
586
|
+
proj_key = str(proj_path)
|
|
587
|
+
if proj_key in projects:
|
|
588
|
+
mcp_servers = projects[proj_key].get("mcpServers", {})
|
|
589
|
+
if "mcp-ticketer" in mcp_servers:
|
|
590
|
+
is_configured = True
|
|
591
|
+
|
|
592
|
+
if is_configured:
|
|
593
|
+
configured.append(plat.display_name)
|
|
594
|
+
|
|
595
|
+
elif plat.name == "claude-desktop":
|
|
596
|
+
if plat.config_path.exists():
|
|
597
|
+
with open(plat.config_path) as f:
|
|
598
|
+
config = json.load(f)
|
|
599
|
+
if "mcp-ticketer" in config.get("mcpServers", {}):
|
|
600
|
+
configured.append(plat.display_name)
|
|
601
|
+
|
|
602
|
+
elif plat.name in ["auggie", "codex", "gemini"]:
|
|
603
|
+
if plat.config_path.exists():
|
|
604
|
+
# Check if mcp-ticketer is configured
|
|
605
|
+
# Implementation depends on each platform's config format
|
|
606
|
+
# For now, just check if config exists (simplified)
|
|
607
|
+
pass
|
|
608
|
+
|
|
609
|
+
except (json.JSONDecodeError, OSError):
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
return configured
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _show_setup_complete_message(console: Console, proj_path: Path) -> None:
|
|
616
|
+
"""Show setup complete message with next steps.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
console: Rich console for output
|
|
620
|
+
proj_path: Project path
|
|
621
|
+
|
|
622
|
+
"""
|
|
623
|
+
console.print("[bold green]š Setup Complete![/bold green]\n")
|
|
624
|
+
|
|
625
|
+
console.print("[bold]Quick Start:[/bold]")
|
|
626
|
+
console.print("1. Create a test ticket:")
|
|
627
|
+
console.print(" [cyan]mcp-ticketer create 'My first ticket'[/cyan]\n")
|
|
628
|
+
|
|
629
|
+
console.print("2. List tickets:")
|
|
630
|
+
console.print(" [cyan]mcp-ticketer list[/cyan]\n")
|
|
631
|
+
|
|
632
|
+
console.print("[bold]Useful Commands:[/bold]")
|
|
633
|
+
console.print(" [cyan]mcp-ticketer doctor[/cyan] - Validate configuration")
|
|
634
|
+
console.print(" [cyan]mcp-ticketer install <platform>[/cyan] - Add more platforms")
|
|
635
|
+
console.print(" [cyan]mcp-ticketer --help[/cyan] - See all commands\n")
|
|
636
|
+
|
|
637
|
+
console.print(
|
|
638
|
+
f"[dim]Configuration: {proj_path / '.mcp-ticketer' / 'config.json'}[/dim]"
|
|
639
|
+
)
|
|
@@ -154,14 +154,18 @@ def simple_diagnose() -> dict[str, Any]:
|
|
|
154
154
|
console.print("\nš [bold blue]MCP Ticketer Simple Diagnosis[/bold blue]")
|
|
155
155
|
console.print("=" * 60)
|
|
156
156
|
|
|
157
|
+
issues: list[str] = []
|
|
158
|
+
warnings: list[str] = []
|
|
159
|
+
recommendations: list[str] = []
|
|
160
|
+
|
|
157
161
|
report = {
|
|
158
162
|
"timestamp": "2025-10-24", # Static for now
|
|
159
163
|
"version": "0.1.28",
|
|
160
164
|
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
161
165
|
"working_directory": str(Path.cwd()),
|
|
162
|
-
"issues":
|
|
163
|
-
"warnings":
|
|
164
|
-
"recommendations":
|
|
166
|
+
"issues": issues,
|
|
167
|
+
"warnings": warnings,
|
|
168
|
+
"recommendations": recommendations,
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
# Basic checks
|
|
@@ -179,7 +183,7 @@ def simple_diagnose() -> dict[str, Any]:
|
|
|
179
183
|
console.print(f"ā
mcp-ticketer {mcp_ticketer.__version__} installed")
|
|
180
184
|
except Exception as e:
|
|
181
185
|
issue = f"Installation check failed: {e}"
|
|
182
|
-
|
|
186
|
+
issues.append(issue)
|
|
183
187
|
console.print(f"ā {issue}")
|
|
184
188
|
|
|
185
189
|
# Configuration check
|
|
@@ -207,15 +211,13 @@ def simple_diagnose() -> dict[str, Any]:
|
|
|
207
211
|
console.print("ā¹ļø No adapter environment variables (using aitrackdown)")
|
|
208
212
|
|
|
209
213
|
# Recommendations
|
|
210
|
-
if not
|
|
211
|
-
|
|
214
|
+
if not issues:
|
|
215
|
+
recommendations.append("ā
System appears healthy")
|
|
212
216
|
else:
|
|
213
|
-
|
|
217
|
+
recommendations.append("šØ Critical issues detected - see above")
|
|
214
218
|
|
|
215
219
|
if not config_found and env_count == 0:
|
|
216
|
-
|
|
217
|
-
"š” Consider running: mcp-ticketer init-aitrackdown"
|
|
218
|
-
)
|
|
220
|
+
recommendations.append("š” Consider running: mcp-ticketer init-aitrackdown")
|
|
219
221
|
|
|
220
222
|
# Display summary
|
|
221
223
|
console.print("\n" + "=" * 60)
|