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.

Files changed (111) 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 +394 -9
  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 +836 -105
  11. mcp_ticketer/adapters/hybrid.py +47 -5
  12. mcp_ticketer/adapters/jira.py +772 -1
  13. mcp_ticketer/adapters/linear/adapter.py +2293 -108
  14. mcp_ticketer/adapters/linear/client.py +146 -12
  15. mcp_ticketer/adapters/linear/mappers.py +105 -11
  16. mcp_ticketer/adapters/linear/queries.py +168 -1
  17. mcp_ticketer/adapters/linear/types.py +80 -4
  18. mcp_ticketer/analysis/__init__.py +56 -0
  19. mcp_ticketer/analysis/dependency_graph.py +255 -0
  20. mcp_ticketer/analysis/health_assessment.py +304 -0
  21. mcp_ticketer/analysis/orphaned.py +218 -0
  22. mcp_ticketer/analysis/project_status.py +594 -0
  23. mcp_ticketer/analysis/similarity.py +224 -0
  24. mcp_ticketer/analysis/staleness.py +266 -0
  25. mcp_ticketer/automation/__init__.py +11 -0
  26. mcp_ticketer/automation/project_updates.py +378 -0
  27. mcp_ticketer/cache/memory.py +3 -3
  28. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  29. mcp_ticketer/cli/auggie_configure.py +18 -6
  30. mcp_ticketer/cli/codex_configure.py +175 -60
  31. mcp_ticketer/cli/configure.py +884 -146
  32. mcp_ticketer/cli/cursor_configure.py +314 -0
  33. mcp_ticketer/cli/diagnostics.py +31 -28
  34. mcp_ticketer/cli/discover.py +293 -21
  35. mcp_ticketer/cli/gemini_configure.py +18 -6
  36. mcp_ticketer/cli/init_command.py +880 -0
  37. mcp_ticketer/cli/instruction_commands.py +435 -0
  38. mcp_ticketer/cli/linear_commands.py +99 -15
  39. mcp_ticketer/cli/main.py +109 -2055
  40. mcp_ticketer/cli/mcp_configure.py +673 -99
  41. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  42. mcp_ticketer/cli/migrate_config.py +12 -8
  43. mcp_ticketer/cli/platform_commands.py +6 -6
  44. mcp_ticketer/cli/platform_detection.py +477 -0
  45. mcp_ticketer/cli/platform_installer.py +536 -0
  46. mcp_ticketer/cli/project_update_commands.py +350 -0
  47. mcp_ticketer/cli/queue_commands.py +15 -15
  48. mcp_ticketer/cli/setup_command.py +639 -0
  49. mcp_ticketer/cli/simple_health.py +13 -11
  50. mcp_ticketer/cli/ticket_commands.py +277 -36
  51. mcp_ticketer/cli/update_checker.py +313 -0
  52. mcp_ticketer/cli/utils.py +45 -41
  53. mcp_ticketer/core/__init__.py +35 -1
  54. mcp_ticketer/core/adapter.py +170 -5
  55. mcp_ticketer/core/config.py +38 -31
  56. mcp_ticketer/core/env_discovery.py +33 -3
  57. mcp_ticketer/core/env_loader.py +7 -6
  58. mcp_ticketer/core/exceptions.py +10 -4
  59. mcp_ticketer/core/http_client.py +10 -10
  60. mcp_ticketer/core/instructions.py +405 -0
  61. mcp_ticketer/core/label_manager.py +732 -0
  62. mcp_ticketer/core/mappers.py +32 -20
  63. mcp_ticketer/core/models.py +136 -1
  64. mcp_ticketer/core/onepassword_secrets.py +379 -0
  65. mcp_ticketer/core/priority_matcher.py +463 -0
  66. mcp_ticketer/core/project_config.py +148 -14
  67. mcp_ticketer/core/registry.py +1 -1
  68. mcp_ticketer/core/session_state.py +171 -0
  69. mcp_ticketer/core/state_matcher.py +592 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  73. mcp_ticketer/mcp/__init__.py +2 -2
  74. mcp_ticketer/mcp/server/__init__.py +2 -2
  75. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  76. mcp_ticketer/mcp/server/main.py +187 -93
  77. mcp_ticketer/mcp/server/routing.py +655 -0
  78. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  79. mcp_ticketer/mcp/server/tools/__init__.py +37 -9
  80. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  81. mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
  82. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  83. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  84. mcp_ticketer/mcp/server/tools/config_tools.py +1429 -0
  85. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  86. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
  87. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  88. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  89. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  90. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  91. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  92. mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
  93. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  94. mcp_ticketer/mcp/server/tools/ticket_tools.py +1182 -82
  95. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  96. mcp_ticketer/queue/health_monitor.py +1 -0
  97. mcp_ticketer/queue/manager.py +4 -4
  98. mcp_ticketer/queue/queue.py +3 -3
  99. mcp_ticketer/queue/run_worker.py +1 -1
  100. mcp_ticketer/queue/ticket_registry.py +2 -2
  101. mcp_ticketer/queue/worker.py +15 -13
  102. mcp_ticketer/utils/__init__.py +5 -0
  103. mcp_ticketer/utils/token_utils.py +246 -0
  104. mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
  105. mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
  106. mcp_ticketer-0.4.11.dist-info/METADATA +0 -496
  107. mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
  108. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
  109. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
  110. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
  111. {mcp_ticketer-0.4.11.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
+ )
@@ -150,18 +150,22 @@ def simple_health_check() -> int:
150
150
 
151
151
 
152
152
  def simple_diagnose() -> dict[str, Any]:
153
- """Simple diagnosis that works without full config system."""
153
+ """Perform simple diagnosis without full config system."""
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
- report["issues"].append(issue)
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 report["issues"]:
211
- report["recommendations"].append("āœ… System appears healthy")
214
+ if not issues:
215
+ recommendations.append("āœ… System appears healthy")
212
216
  else:
213
- report["recommendations"].append("🚨 Critical issues detected - see above")
217
+ recommendations.append("🚨 Critical issues detected - see above")
214
218
 
215
219
  if not config_found and env_count == 0:
216
- report["recommendations"].append(
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)