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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +394 -9
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1416 -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.py +836 -105
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +772 -1
- mcp_ticketer/adapters/linear/adapter.py +2293 -108
- mcp_ticketer/adapters/linear/client.py +146 -12
- mcp_ticketer/adapters/linear/mappers.py +105 -11
- 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/cache/memory.py +3 -3
- mcp_ticketer/cli/adapter_diagnostics.py +4 -2
- mcp_ticketer/cli/auggie_configure.py +18 -6
- mcp_ticketer/cli/codex_configure.py +175 -60
- mcp_ticketer/cli/configure.py +884 -146
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +31 -28
- mcp_ticketer/cli/discover.py +293 -21
- mcp_ticketer/cli/gemini_configure.py +18 -6
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +99 -15
- mcp_ticketer/cli/main.py +109 -2055
- mcp_ticketer/cli/mcp_configure.py +673 -99
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +6 -6
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +13 -11
- mcp_ticketer/cli/ticket_commands.py +277 -36
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +45 -41
- mcp_ticketer/core/__init__.py +35 -1
- mcp_ticketer/core/adapter.py +170 -5
- mcp_ticketer/core/config.py +38 -31
- mcp_ticketer/core/env_discovery.py +33 -3
- mcp_ticketer/core/env_loader.py +7 -6
- mcp_ticketer/core/exceptions.py +10 -4
- mcp_ticketer/core/http_client.py +10 -10
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +32 -20
- mcp_ticketer/core/models.py +136 -1
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +148 -14
- mcp_ticketer/core/registry.py +1 -1
- 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/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +2 -2
- mcp_ticketer/mcp/server/__init__.py +2 -2
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +187 -93
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +37 -9
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
- 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 +1429 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
- 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/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 +1182 -82
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/health_monitor.py +1 -0
- mcp_ticketer/queue/manager.py +4 -4
- mcp_ticketer/queue/queue.py +3 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +2 -2
- mcp_ticketer/queue/worker.py +15 -13
- 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.4.11.dist-info/METADATA +0 -496
- mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py
CHANGED
|
@@ -5,27 +5,29 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
import typer
|
|
10
11
|
from dotenv import load_dotenv
|
|
11
12
|
from rich.console import Console
|
|
12
|
-
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
# Import adapters module to trigger registration
|
|
15
15
|
import mcp_ticketer.adapters # noqa: F401
|
|
16
16
|
|
|
17
17
|
from ..__version__ import __version__
|
|
18
|
-
from ..core import AdapterRegistry
|
|
19
|
-
from ..core.models import Comment, SearchQuery
|
|
20
|
-
from ..queue import Queue, QueueStatus, WorkerManager
|
|
21
|
-
from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
|
|
22
|
-
from ..queue.ticket_registry import TicketRegistry
|
|
18
|
+
from ..core import AdapterRegistry
|
|
23
19
|
from .configure import configure_wizard, set_adapter_config, show_current_config
|
|
24
20
|
from .diagnostics import run_diagnostics
|
|
25
21
|
from .discover import app as discover_app
|
|
22
|
+
from .init_command import init
|
|
23
|
+
from .instruction_commands import app as instruction_app
|
|
24
|
+
from .mcp_server_commands import mcp_app
|
|
26
25
|
from .migrate_config import migrate_config_command
|
|
27
26
|
from .platform_commands import app as platform_app
|
|
27
|
+
from .platform_installer import install, remove, uninstall
|
|
28
|
+
from .project_update_commands import app as project_update_app
|
|
28
29
|
from .queue_commands import app as queue_app
|
|
30
|
+
from .setup_command import setup
|
|
29
31
|
from .ticket_commands import app as ticket_app
|
|
30
32
|
|
|
31
33
|
# Load environment variables from .env files
|
|
@@ -48,11 +50,11 @@ app = typer.Typer(
|
|
|
48
50
|
console = Console()
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
def version_callback(value: bool):
|
|
53
|
+
def version_callback(value: bool) -> None:
|
|
52
54
|
"""Print version and exit."""
|
|
53
55
|
if value:
|
|
54
56
|
console.print(f"mcp-ticketer version {__version__}")
|
|
55
|
-
raise typer.Exit()
|
|
57
|
+
raise typer.Exit() from None
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
@app.callback()
|
|
@@ -65,7 +67,7 @@ def main_callback(
|
|
|
65
67
|
is_eager=True,
|
|
66
68
|
help="Show version and exit",
|
|
67
69
|
),
|
|
68
|
-
):
|
|
70
|
+
) -> None:
|
|
69
71
|
"""MCP Ticketer - Universal ticket management interface."""
|
|
70
72
|
pass
|
|
71
73
|
|
|
@@ -91,6 +93,7 @@ def load_config(project_dir: Path | None = None) -> dict:
|
|
|
91
93
|
from user home directory or system-wide locations.
|
|
92
94
|
|
|
93
95
|
Args:
|
|
96
|
+
----
|
|
94
97
|
project_dir: Optional project directory to load config from
|
|
95
98
|
|
|
96
99
|
Resolution order:
|
|
@@ -98,6 +101,7 @@ def load_config(project_dir: Path | None = None) -> dict:
|
|
|
98
101
|
2. Default to aitrackdown adapter
|
|
99
102
|
|
|
100
103
|
Returns:
|
|
104
|
+
-------
|
|
101
105
|
Configuration dictionary with adapter and config keys.
|
|
102
106
|
Defaults to aitrackdown if no local config exists.
|
|
103
107
|
|
|
@@ -149,6 +153,7 @@ def _discover_from_env_files() -> str | None:
|
|
|
149
153
|
"""Discover adapter configuration from .env or .env.local files.
|
|
150
154
|
|
|
151
155
|
Returns:
|
|
156
|
+
-------
|
|
152
157
|
Adapter name if discovered, None otherwise
|
|
153
158
|
|
|
154
159
|
"""
|
|
@@ -194,6 +199,7 @@ def _save_adapter_to_config(adapter_name: str) -> None:
|
|
|
194
199
|
"""Save adapter configuration to config file.
|
|
195
200
|
|
|
196
201
|
Args:
|
|
202
|
+
----
|
|
197
203
|
adapter_name: Name of the adapter to save as default
|
|
198
204
|
|
|
199
205
|
"""
|
|
@@ -244,9 +250,11 @@ def merge_config(updates: dict) -> dict:
|
|
|
244
250
|
"""Merge updates into existing config.
|
|
245
251
|
|
|
246
252
|
Args:
|
|
253
|
+
----
|
|
247
254
|
updates: Configuration updates to merge
|
|
248
255
|
|
|
249
256
|
Returns:
|
|
257
|
+
-------
|
|
250
258
|
Updated configuration
|
|
251
259
|
|
|
252
260
|
"""
|
|
@@ -270,10 +278,11 @@ def merge_config(updates: dict) -> dict:
|
|
|
270
278
|
|
|
271
279
|
def get_adapter(
|
|
272
280
|
override_adapter: str | None = None, override_config: dict | None = None
|
|
273
|
-
):
|
|
281
|
+
) -> Any:
|
|
274
282
|
"""Get configured adapter instance.
|
|
275
283
|
|
|
276
284
|
Args:
|
|
285
|
+
----
|
|
277
286
|
override_adapter: Override the default adapter type
|
|
278
287
|
override_config: Override configuration for the adapter
|
|
279
288
|
|
|
@@ -301,7 +310,6 @@ def get_adapter(
|
|
|
301
310
|
adapter_config = config["config"]
|
|
302
311
|
|
|
303
312
|
# Add environment variables for authentication
|
|
304
|
-
import os
|
|
305
313
|
|
|
306
314
|
if adapter_type == "linear":
|
|
307
315
|
if not adapter_config.get("api_key"):
|
|
@@ -318,592 +326,6 @@ def get_adapter(
|
|
|
318
326
|
return AdapterRegistry.get_adapter(adapter_type, adapter_config)
|
|
319
327
|
|
|
320
328
|
|
|
321
|
-
def _prompt_for_adapter_selection(console: Console) -> str:
|
|
322
|
-
"""Interactive prompt for adapter selection.
|
|
323
|
-
|
|
324
|
-
Args:
|
|
325
|
-
console: Rich console for output
|
|
326
|
-
|
|
327
|
-
Returns:
|
|
328
|
-
Selected adapter type
|
|
329
|
-
|
|
330
|
-
"""
|
|
331
|
-
console.print("\n[bold blue]🚀 MCP Ticketer Setup[/bold blue]")
|
|
332
|
-
console.print("Choose which ticket system you want to connect to:\n")
|
|
333
|
-
|
|
334
|
-
# Define adapter options with descriptions
|
|
335
|
-
adapters = [
|
|
336
|
-
{
|
|
337
|
-
"name": "linear",
|
|
338
|
-
"title": "Linear",
|
|
339
|
-
"description": "Modern project management (linear.app)",
|
|
340
|
-
"requirements": "API key and team ID",
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
"name": "github",
|
|
344
|
-
"title": "GitHub Issues",
|
|
345
|
-
"description": "GitHub repository issues",
|
|
346
|
-
"requirements": "Personal access token, owner, and repo",
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
"name": "jira",
|
|
350
|
-
"title": "JIRA",
|
|
351
|
-
"description": "Atlassian JIRA project management",
|
|
352
|
-
"requirements": "Server URL, email, and API token",
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
"name": "aitrackdown",
|
|
356
|
-
"title": "Local Files (AITrackdown)",
|
|
357
|
-
"description": "Store tickets in local files (no external service)",
|
|
358
|
-
"requirements": "None - works offline",
|
|
359
|
-
},
|
|
360
|
-
]
|
|
361
|
-
|
|
362
|
-
# Display options
|
|
363
|
-
for i, adapter in enumerate(adapters, 1):
|
|
364
|
-
console.print(f"[cyan]{i}.[/cyan] [bold]{adapter['title']}[/bold]")
|
|
365
|
-
console.print(f" {adapter['description']}")
|
|
366
|
-
console.print(f" [dim]Requirements: {adapter['requirements']}[/dim]\n")
|
|
367
|
-
|
|
368
|
-
# Get user selection
|
|
369
|
-
while True:
|
|
370
|
-
try:
|
|
371
|
-
choice = typer.prompt("Select adapter (1-4)", type=int, default=1)
|
|
372
|
-
if 1 <= choice <= len(adapters):
|
|
373
|
-
selected_adapter = adapters[choice - 1]
|
|
374
|
-
console.print(
|
|
375
|
-
f"\n[green]✓ Selected: {selected_adapter['title']}[/green]"
|
|
376
|
-
)
|
|
377
|
-
return selected_adapter["name"]
|
|
378
|
-
else:
|
|
379
|
-
console.print(
|
|
380
|
-
f"[red]Please enter a number between 1 and {len(adapters)}[/red]"
|
|
381
|
-
)
|
|
382
|
-
except (ValueError, typer.Abort):
|
|
383
|
-
console.print("[yellow]Setup cancelled.[/yellow]")
|
|
384
|
-
raise typer.Exit(0)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
@app.command()
|
|
388
|
-
def setup(
|
|
389
|
-
adapter: str | None = typer.Option(
|
|
390
|
-
None,
|
|
391
|
-
"--adapter",
|
|
392
|
-
"-a",
|
|
393
|
-
help="Adapter type to use (interactive prompt if not specified)",
|
|
394
|
-
),
|
|
395
|
-
project_path: str | None = typer.Option(
|
|
396
|
-
None, "--path", help="Project path (default: current directory)"
|
|
397
|
-
),
|
|
398
|
-
global_config: bool = typer.Option(
|
|
399
|
-
False,
|
|
400
|
-
"--global",
|
|
401
|
-
"-g",
|
|
402
|
-
help="Save to global config instead of project-specific",
|
|
403
|
-
),
|
|
404
|
-
base_path: str | None = typer.Option(
|
|
405
|
-
None,
|
|
406
|
-
"--base-path",
|
|
407
|
-
"-p",
|
|
408
|
-
help="Base path for ticket storage (AITrackdown only)",
|
|
409
|
-
),
|
|
410
|
-
api_key: str | None = typer.Option(
|
|
411
|
-
None, "--api-key", help="API key for Linear or API token for JIRA"
|
|
412
|
-
),
|
|
413
|
-
team_id: str | None = typer.Option(
|
|
414
|
-
None, "--team-id", help="Linear team ID (required for Linear adapter)"
|
|
415
|
-
),
|
|
416
|
-
jira_server: str | None = typer.Option(
|
|
417
|
-
None,
|
|
418
|
-
"--jira-server",
|
|
419
|
-
help="JIRA server URL (e.g., https://company.atlassian.net)",
|
|
420
|
-
),
|
|
421
|
-
jira_email: str | None = typer.Option(
|
|
422
|
-
None, "--jira-email", help="JIRA user email for authentication"
|
|
423
|
-
),
|
|
424
|
-
jira_project: str | None = typer.Option(
|
|
425
|
-
None, "--jira-project", help="Default JIRA project key"
|
|
426
|
-
),
|
|
427
|
-
github_owner: str | None = typer.Option(
|
|
428
|
-
None, "--github-owner", help="GitHub repository owner"
|
|
429
|
-
),
|
|
430
|
-
github_repo: str | None = typer.Option(
|
|
431
|
-
None, "--github-repo", help="GitHub repository name"
|
|
432
|
-
),
|
|
433
|
-
github_token: str | None = typer.Option(
|
|
434
|
-
None, "--github-token", help="GitHub Personal Access Token"
|
|
435
|
-
),
|
|
436
|
-
) -> None:
|
|
437
|
-
"""Interactive setup wizard for MCP Ticketer (alias for init).
|
|
438
|
-
|
|
439
|
-
This command provides a user-friendly setup experience with prompts
|
|
440
|
-
to guide you through configuring MCP Ticketer for your preferred
|
|
441
|
-
ticket management system. It's identical to 'init' and 'install'.
|
|
442
|
-
|
|
443
|
-
Examples:
|
|
444
|
-
# Run interactive setup
|
|
445
|
-
mcp-ticketer setup
|
|
446
|
-
|
|
447
|
-
# Setup with specific adapter
|
|
448
|
-
mcp-ticketer setup --adapter linear
|
|
449
|
-
|
|
450
|
-
# Setup for different project
|
|
451
|
-
mcp-ticketer setup --path /path/to/project
|
|
452
|
-
|
|
453
|
-
"""
|
|
454
|
-
# Call init with all parameters
|
|
455
|
-
init(
|
|
456
|
-
adapter=adapter,
|
|
457
|
-
project_path=project_path,
|
|
458
|
-
global_config=global_config,
|
|
459
|
-
base_path=base_path,
|
|
460
|
-
api_key=api_key,
|
|
461
|
-
team_id=team_id,
|
|
462
|
-
jira_server=jira_server,
|
|
463
|
-
jira_email=jira_email,
|
|
464
|
-
jira_project=jira_project,
|
|
465
|
-
github_owner=github_owner,
|
|
466
|
-
github_repo=github_repo,
|
|
467
|
-
github_token=github_token,
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
@app.command()
|
|
472
|
-
def init(
|
|
473
|
-
adapter: str | None = typer.Option(
|
|
474
|
-
None,
|
|
475
|
-
"--adapter",
|
|
476
|
-
"-a",
|
|
477
|
-
help="Adapter type to use (interactive prompt if not specified)",
|
|
478
|
-
),
|
|
479
|
-
project_path: str | None = typer.Option(
|
|
480
|
-
None, "--path", help="Project path (default: current directory)"
|
|
481
|
-
),
|
|
482
|
-
global_config: bool = typer.Option(
|
|
483
|
-
False,
|
|
484
|
-
"--global",
|
|
485
|
-
"-g",
|
|
486
|
-
help="Save to global config instead of project-specific",
|
|
487
|
-
),
|
|
488
|
-
base_path: str | None = typer.Option(
|
|
489
|
-
None,
|
|
490
|
-
"--base-path",
|
|
491
|
-
"-p",
|
|
492
|
-
help="Base path for ticket storage (AITrackdown only)",
|
|
493
|
-
),
|
|
494
|
-
api_key: str | None = typer.Option(
|
|
495
|
-
None, "--api-key", help="API key for Linear or API token for JIRA"
|
|
496
|
-
),
|
|
497
|
-
team_id: str | None = typer.Option(
|
|
498
|
-
None, "--team-id", help="Linear team ID (required for Linear adapter)"
|
|
499
|
-
),
|
|
500
|
-
jira_server: str | None = typer.Option(
|
|
501
|
-
None,
|
|
502
|
-
"--jira-server",
|
|
503
|
-
help="JIRA server URL (e.g., https://company.atlassian.net)",
|
|
504
|
-
),
|
|
505
|
-
jira_email: str | None = typer.Option(
|
|
506
|
-
None, "--jira-email", help="JIRA user email for authentication"
|
|
507
|
-
),
|
|
508
|
-
jira_project: str | None = typer.Option(
|
|
509
|
-
None, "--jira-project", help="Default JIRA project key"
|
|
510
|
-
),
|
|
511
|
-
github_owner: str | None = typer.Option(
|
|
512
|
-
None, "--github-owner", help="GitHub repository owner"
|
|
513
|
-
),
|
|
514
|
-
github_repo: str | None = typer.Option(
|
|
515
|
-
None, "--github-repo", help="GitHub repository name"
|
|
516
|
-
),
|
|
517
|
-
github_token: str | None = typer.Option(
|
|
518
|
-
None, "--github-token", help="GitHub Personal Access Token"
|
|
519
|
-
),
|
|
520
|
-
) -> None:
|
|
521
|
-
"""Initialize mcp-ticketer for the current project (synonymous with 'install' and 'setup').
|
|
522
|
-
|
|
523
|
-
This command sets up MCP Ticketer configuration with interactive prompts
|
|
524
|
-
to guide you through the process. It auto-detects adapter configuration
|
|
525
|
-
from .env files or prompts for interactive setup if no configuration is found.
|
|
526
|
-
|
|
527
|
-
Creates .mcp-ticketer/config.json in the current directory with
|
|
528
|
-
auto-detected or specified adapter configuration.
|
|
529
|
-
|
|
530
|
-
Note: 'init', 'install', and 'setup' are all synonyms - use whichever feels natural.
|
|
531
|
-
|
|
532
|
-
Examples:
|
|
533
|
-
# Interactive setup (all three commands are identical)
|
|
534
|
-
mcp-ticketer init
|
|
535
|
-
mcp-ticketer install
|
|
536
|
-
mcp-ticketer setup
|
|
537
|
-
|
|
538
|
-
# Force specific adapter
|
|
539
|
-
mcp-ticketer init --adapter linear
|
|
540
|
-
|
|
541
|
-
# Initialize for different project
|
|
542
|
-
mcp-ticketer init --path /path/to/project
|
|
543
|
-
|
|
544
|
-
# Save globally (not recommended)
|
|
545
|
-
mcp-ticketer init --global
|
|
546
|
-
|
|
547
|
-
"""
|
|
548
|
-
from pathlib import Path
|
|
549
|
-
|
|
550
|
-
from ..core.env_discovery import discover_config
|
|
551
|
-
from ..core.project_config import ConfigResolver
|
|
552
|
-
|
|
553
|
-
# Determine project path
|
|
554
|
-
proj_path = Path(project_path) if project_path else Path.cwd()
|
|
555
|
-
|
|
556
|
-
# Check if already initialized (unless using --global)
|
|
557
|
-
if not global_config:
|
|
558
|
-
config_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
559
|
-
|
|
560
|
-
if config_path.exists():
|
|
561
|
-
if not typer.confirm(
|
|
562
|
-
f"Configuration already exists at {config_path}. Overwrite?",
|
|
563
|
-
default=False,
|
|
564
|
-
):
|
|
565
|
-
console.print("[yellow]Initialization cancelled.[/yellow]")
|
|
566
|
-
raise typer.Exit(0)
|
|
567
|
-
|
|
568
|
-
# 1. Try auto-discovery if no adapter specified
|
|
569
|
-
discovered = None
|
|
570
|
-
adapter_type = adapter
|
|
571
|
-
|
|
572
|
-
if not adapter_type:
|
|
573
|
-
console.print(
|
|
574
|
-
"[cyan]🔍 Auto-discovering configuration from .env files...[/cyan]"
|
|
575
|
-
)
|
|
576
|
-
|
|
577
|
-
# First try our improved .env configuration loader
|
|
578
|
-
from ..mcp.server.main import _load_env_configuration
|
|
579
|
-
|
|
580
|
-
env_config = _load_env_configuration()
|
|
581
|
-
|
|
582
|
-
if env_config:
|
|
583
|
-
adapter_type = env_config["adapter_type"]
|
|
584
|
-
console.print(
|
|
585
|
-
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
586
|
-
)
|
|
587
|
-
|
|
588
|
-
# Show what was discovered
|
|
589
|
-
console.print("\n[dim]Configuration found in: .env files[/dim]")
|
|
590
|
-
console.print("[dim]Confidence: 100%[/dim]")
|
|
591
|
-
|
|
592
|
-
# Ask user to confirm auto-detected adapter
|
|
593
|
-
if not typer.confirm(
|
|
594
|
-
f"Use detected {adapter_type} adapter?",
|
|
595
|
-
default=True,
|
|
596
|
-
):
|
|
597
|
-
adapter_type = None # Will trigger interactive selection
|
|
598
|
-
else:
|
|
599
|
-
# Fallback to old discovery system for backward compatibility
|
|
600
|
-
discovered = discover_config(proj_path)
|
|
601
|
-
|
|
602
|
-
if discovered and discovered.adapters:
|
|
603
|
-
primary = discovered.get_primary_adapter()
|
|
604
|
-
if primary:
|
|
605
|
-
adapter_type = primary.adapter_type
|
|
606
|
-
console.print(
|
|
607
|
-
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
# Show what was discovered
|
|
611
|
-
console.print(
|
|
612
|
-
f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
|
|
613
|
-
)
|
|
614
|
-
console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
|
|
615
|
-
|
|
616
|
-
# Ask user to confirm auto-detected adapter
|
|
617
|
-
if not typer.confirm(
|
|
618
|
-
f"Use detected {adapter_type} adapter?",
|
|
619
|
-
default=True,
|
|
620
|
-
):
|
|
621
|
-
adapter_type = None # Will trigger interactive selection
|
|
622
|
-
else:
|
|
623
|
-
adapter_type = None # Will trigger interactive selection
|
|
624
|
-
else:
|
|
625
|
-
adapter_type = None # Will trigger interactive selection
|
|
626
|
-
|
|
627
|
-
# If no adapter determined, show interactive selection
|
|
628
|
-
if not adapter_type:
|
|
629
|
-
adapter_type = _prompt_for_adapter_selection(console)
|
|
630
|
-
|
|
631
|
-
# 2. Create configuration based on adapter type
|
|
632
|
-
config = {"default_adapter": adapter_type, "adapters": {}}
|
|
633
|
-
|
|
634
|
-
# 3. If discovered and matches adapter_type, use discovered config
|
|
635
|
-
if discovered and adapter_type != "aitrackdown":
|
|
636
|
-
discovered_adapter = discovered.get_adapter_by_type(adapter_type)
|
|
637
|
-
if discovered_adapter:
|
|
638
|
-
adapter_config = discovered_adapter.config.copy()
|
|
639
|
-
# Ensure the config has the correct 'type' field
|
|
640
|
-
adapter_config["type"] = adapter_type
|
|
641
|
-
# Remove 'adapter' field if present (legacy)
|
|
642
|
-
adapter_config.pop("adapter", None)
|
|
643
|
-
config["adapters"][adapter_type] = adapter_config
|
|
644
|
-
|
|
645
|
-
# 4. Handle manual configuration for specific adapters
|
|
646
|
-
if adapter_type == "aitrackdown":
|
|
647
|
-
config["adapters"]["aitrackdown"] = {
|
|
648
|
-
"type": "aitrackdown",
|
|
649
|
-
"base_path": base_path or ".aitrackdown",
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
elif adapter_type == "linear":
|
|
653
|
-
# If not auto-discovered, build from CLI params or prompt
|
|
654
|
-
if adapter_type not in config["adapters"]:
|
|
655
|
-
linear_config = {}
|
|
656
|
-
|
|
657
|
-
# API Key
|
|
658
|
-
linear_api_key = api_key or os.getenv("LINEAR_API_KEY")
|
|
659
|
-
if not linear_api_key and not discovered:
|
|
660
|
-
console.print("\n[bold]Linear Configuration[/bold]")
|
|
661
|
-
console.print("You need a Linear API key to connect to Linear.")
|
|
662
|
-
console.print(
|
|
663
|
-
"[dim]Get your API key at: https://linear.app/settings/api[/dim]\n"
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
linear_api_key = typer.prompt(
|
|
667
|
-
"Enter your Linear API key", hide_input=True
|
|
668
|
-
)
|
|
669
|
-
|
|
670
|
-
if linear_api_key:
|
|
671
|
-
linear_config["api_key"] = linear_api_key
|
|
672
|
-
|
|
673
|
-
# Team ID or Team Key
|
|
674
|
-
# Try environment variables first
|
|
675
|
-
linear_team_key = os.getenv("LINEAR_TEAM_KEY")
|
|
676
|
-
linear_team_id = team_id or os.getenv("LINEAR_TEAM_ID")
|
|
677
|
-
|
|
678
|
-
if not linear_team_key and not linear_team_id and not discovered:
|
|
679
|
-
console.print("\n[bold]Linear Team Configuration[/bold]")
|
|
680
|
-
console.print("Enter your team key (e.g., 'ENG', 'DESIGN', 'PRODUCT')")
|
|
681
|
-
console.print(
|
|
682
|
-
"[dim]Find it in: Linear Settings → Teams → Your Team → Key field[/dim]\n"
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
linear_team_key = typer.prompt("Team key")
|
|
686
|
-
|
|
687
|
-
# Save whichever was provided
|
|
688
|
-
if linear_team_key:
|
|
689
|
-
linear_config["team_key"] = linear_team_key
|
|
690
|
-
if linear_team_id:
|
|
691
|
-
linear_config["team_id"] = linear_team_id
|
|
692
|
-
|
|
693
|
-
if not linear_config.get("api_key") or (
|
|
694
|
-
not linear_config.get("team_id") and not linear_config.get("team_key")
|
|
695
|
-
):
|
|
696
|
-
console.print(
|
|
697
|
-
"[red]Error:[/red] Linear requires both API key and team ID/key"
|
|
698
|
-
)
|
|
699
|
-
console.print(
|
|
700
|
-
"Run 'mcp-ticketer init --adapter linear' with proper credentials"
|
|
701
|
-
)
|
|
702
|
-
raise typer.Exit(1)
|
|
703
|
-
|
|
704
|
-
linear_config["type"] = "linear"
|
|
705
|
-
config["adapters"]["linear"] = linear_config
|
|
706
|
-
|
|
707
|
-
elif adapter_type == "jira":
|
|
708
|
-
# If not auto-discovered, build from CLI params or prompt
|
|
709
|
-
if adapter_type not in config["adapters"]:
|
|
710
|
-
server = jira_server or os.getenv("JIRA_SERVER")
|
|
711
|
-
email = jira_email or os.getenv("JIRA_EMAIL")
|
|
712
|
-
token = api_key or os.getenv("JIRA_API_TOKEN")
|
|
713
|
-
project = jira_project or os.getenv("JIRA_PROJECT_KEY")
|
|
714
|
-
|
|
715
|
-
# Interactive prompts for missing values
|
|
716
|
-
if not server and not discovered:
|
|
717
|
-
console.print("\n[bold]JIRA Configuration[/bold]")
|
|
718
|
-
console.print("Enter your JIRA server details.\n")
|
|
719
|
-
|
|
720
|
-
server = typer.prompt(
|
|
721
|
-
"JIRA server URL (e.g., https://company.atlassian.net)"
|
|
722
|
-
)
|
|
723
|
-
|
|
724
|
-
if not email and not discovered:
|
|
725
|
-
email = typer.prompt("Your JIRA email address")
|
|
726
|
-
|
|
727
|
-
if not token and not discovered:
|
|
728
|
-
console.print("\nYou need a JIRA API token.")
|
|
729
|
-
console.print(
|
|
730
|
-
"[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n"
|
|
731
|
-
)
|
|
732
|
-
|
|
733
|
-
token = typer.prompt("Enter your JIRA API token", hide_input=True)
|
|
734
|
-
|
|
735
|
-
if not project and not discovered:
|
|
736
|
-
project = typer.prompt(
|
|
737
|
-
"Default JIRA project key (optional, press Enter to skip)",
|
|
738
|
-
default="",
|
|
739
|
-
show_default=False,
|
|
740
|
-
)
|
|
741
|
-
|
|
742
|
-
# Validate required fields
|
|
743
|
-
if not server:
|
|
744
|
-
console.print("[red]Error:[/red] JIRA server URL is required")
|
|
745
|
-
raise typer.Exit(1)
|
|
746
|
-
|
|
747
|
-
if not email:
|
|
748
|
-
console.print("[red]Error:[/red] JIRA email is required")
|
|
749
|
-
raise typer.Exit(1)
|
|
750
|
-
|
|
751
|
-
if not token:
|
|
752
|
-
console.print("[red]Error:[/red] JIRA API token is required")
|
|
753
|
-
raise typer.Exit(1)
|
|
754
|
-
|
|
755
|
-
jira_config = {
|
|
756
|
-
"server": server,
|
|
757
|
-
"email": email,
|
|
758
|
-
"api_token": token,
|
|
759
|
-
"type": "jira",
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
if project:
|
|
763
|
-
jira_config["project_key"] = project
|
|
764
|
-
|
|
765
|
-
config["adapters"]["jira"] = jira_config
|
|
766
|
-
|
|
767
|
-
elif adapter_type == "github":
|
|
768
|
-
# If not auto-discovered, build from CLI params or prompt
|
|
769
|
-
if adapter_type not in config["adapters"]:
|
|
770
|
-
owner = github_owner or os.getenv("GITHUB_OWNER")
|
|
771
|
-
repo = github_repo or os.getenv("GITHUB_REPO")
|
|
772
|
-
token = github_token or os.getenv("GITHUB_TOKEN")
|
|
773
|
-
|
|
774
|
-
# Interactive prompts for missing values
|
|
775
|
-
if not owner and not discovered:
|
|
776
|
-
console.print("\n[bold]GitHub Configuration[/bold]")
|
|
777
|
-
console.print("Enter your GitHub repository details.\n")
|
|
778
|
-
|
|
779
|
-
owner = typer.prompt(
|
|
780
|
-
"GitHub repository owner (username or organization)"
|
|
781
|
-
)
|
|
782
|
-
|
|
783
|
-
if not repo and not discovered:
|
|
784
|
-
repo = typer.prompt("GitHub repository name")
|
|
785
|
-
|
|
786
|
-
if not token and not discovered:
|
|
787
|
-
console.print("\nYou need a GitHub Personal Access Token.")
|
|
788
|
-
console.print(
|
|
789
|
-
"[dim]Create one at: https://github.com/settings/tokens/new[/dim]"
|
|
790
|
-
)
|
|
791
|
-
console.print(
|
|
792
|
-
"[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]\n"
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
token = typer.prompt(
|
|
796
|
-
"Enter your GitHub Personal Access Token", hide_input=True
|
|
797
|
-
)
|
|
798
|
-
|
|
799
|
-
# Validate required fields
|
|
800
|
-
if not owner:
|
|
801
|
-
console.print("[red]Error:[/red] GitHub repository owner is required")
|
|
802
|
-
raise typer.Exit(1)
|
|
803
|
-
|
|
804
|
-
if not repo:
|
|
805
|
-
console.print("[red]Error:[/red] GitHub repository name is required")
|
|
806
|
-
raise typer.Exit(1)
|
|
807
|
-
|
|
808
|
-
if not token:
|
|
809
|
-
console.print(
|
|
810
|
-
"[red]Error:[/red] GitHub Personal Access Token is required"
|
|
811
|
-
)
|
|
812
|
-
raise typer.Exit(1)
|
|
813
|
-
|
|
814
|
-
config["adapters"]["github"] = {
|
|
815
|
-
"owner": owner,
|
|
816
|
-
"repo": repo,
|
|
817
|
-
"token": token,
|
|
818
|
-
"type": "github",
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
# 5. Save to appropriate location
|
|
822
|
-
if global_config:
|
|
823
|
-
# Save to ~/.mcp-ticketer/config.json
|
|
824
|
-
resolver = ConfigResolver(project_path=proj_path)
|
|
825
|
-
config_file_path = resolver.GLOBAL_CONFIG_PATH
|
|
826
|
-
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
827
|
-
|
|
828
|
-
with open(config_file_path, "w") as f:
|
|
829
|
-
json.dump(config, f, indent=2)
|
|
830
|
-
|
|
831
|
-
console.print(f"[green]✓ Initialized with {adapter_type} adapter[/green]")
|
|
832
|
-
console.print(f"[dim]Global configuration saved to {config_file_path}[/dim]")
|
|
833
|
-
else:
|
|
834
|
-
# Save to ./.mcp-ticketer/config.json (PROJECT-SPECIFIC)
|
|
835
|
-
config_file_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
836
|
-
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
837
|
-
|
|
838
|
-
with open(config_file_path, "w") as f:
|
|
839
|
-
json.dump(config, f, indent=2)
|
|
840
|
-
|
|
841
|
-
console.print(f"[green]✓ Initialized with {adapter_type} adapter[/green]")
|
|
842
|
-
console.print(f"[dim]Project configuration saved to {config_file_path}[/dim]")
|
|
843
|
-
|
|
844
|
-
# Add .mcp-ticketer to .gitignore if not already there
|
|
845
|
-
gitignore_path = proj_path / ".gitignore"
|
|
846
|
-
if gitignore_path.exists():
|
|
847
|
-
gitignore_content = gitignore_path.read_text()
|
|
848
|
-
if ".mcp-ticketer" not in gitignore_content:
|
|
849
|
-
with open(gitignore_path, "a") as f:
|
|
850
|
-
f.write("\n# MCP Ticketer\n.mcp-ticketer/\n")
|
|
851
|
-
console.print("[dim]✓ Added .mcp-ticketer/ to .gitignore[/dim]")
|
|
852
|
-
else:
|
|
853
|
-
# Create .gitignore if it doesn't exist
|
|
854
|
-
with open(gitignore_path, "w") as f:
|
|
855
|
-
f.write("# MCP Ticketer\n.mcp-ticketer/\n")
|
|
856
|
-
console.print("[dim]✓ Created .gitignore with .mcp-ticketer/[/dim]")
|
|
857
|
-
|
|
858
|
-
# Show next steps
|
|
859
|
-
_show_next_steps(console, adapter_type, config_file_path)
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
def _show_next_steps(
|
|
863
|
-
console: Console, adapter_type: str, config_file_path: Path
|
|
864
|
-
) -> None:
|
|
865
|
-
"""Show helpful next steps after initialization.
|
|
866
|
-
|
|
867
|
-
Args:
|
|
868
|
-
console: Rich console for output
|
|
869
|
-
adapter_type: Type of adapter that was configured
|
|
870
|
-
config_file_path: Path to the configuration file
|
|
871
|
-
|
|
872
|
-
"""
|
|
873
|
-
console.print("\n[bold green]🎉 Setup Complete![/bold green]")
|
|
874
|
-
console.print(f"MCP Ticketer is now configured to use {adapter_type.title()}.\n")
|
|
875
|
-
|
|
876
|
-
console.print("[bold]Next Steps:[/bold]")
|
|
877
|
-
console.print("1. [cyan]Test your configuration:[/cyan]")
|
|
878
|
-
console.print(" mcp-ticketer diagnose")
|
|
879
|
-
console.print("\n2. [cyan]Create a test ticket:[/cyan]")
|
|
880
|
-
console.print(" mcp-ticketer create 'Test ticket from MCP Ticketer'")
|
|
881
|
-
|
|
882
|
-
if adapter_type != "aitrackdown":
|
|
883
|
-
console.print(
|
|
884
|
-
f"\n3. [cyan]Verify the ticket appears in {adapter_type.title()}[/cyan]"
|
|
885
|
-
)
|
|
886
|
-
|
|
887
|
-
if adapter_type == "linear":
|
|
888
|
-
console.print(" Check your Linear workspace for the new ticket")
|
|
889
|
-
elif adapter_type == "github":
|
|
890
|
-
console.print(" Check your GitHub repository's Issues tab")
|
|
891
|
-
elif adapter_type == "jira":
|
|
892
|
-
console.print(" Check your JIRA project for the new ticket")
|
|
893
|
-
else:
|
|
894
|
-
console.print("\n3. [cyan]Check local ticket storage:[/cyan]")
|
|
895
|
-
console.print(" ls .aitrackdown/")
|
|
896
|
-
|
|
897
|
-
console.print("\n4. [cyan]Install MCP for AI clients (optional):[/cyan]")
|
|
898
|
-
console.print(" mcp-ticketer install claude-code # For Claude Code")
|
|
899
|
-
console.print(" mcp-ticketer install claude-desktop # For Claude Desktop")
|
|
900
|
-
console.print(" mcp-ticketer install auggie # For Auggie")
|
|
901
|
-
console.print(" mcp-ticketer install gemini # For Gemini CLI")
|
|
902
|
-
|
|
903
|
-
console.print(f"\n[dim]Configuration saved to: {config_file_path}[/dim]")
|
|
904
|
-
console.print("[dim]Run 'mcp-ticketer --help' for more commands[/dim]")
|
|
905
|
-
|
|
906
|
-
|
|
907
329
|
@app.command("set")
|
|
908
330
|
def set_config(
|
|
909
331
|
adapter: AdapterType | None = typer.Option(
|
|
@@ -1046,6 +468,26 @@ def configure_command(
|
|
|
1046
468
|
configure_wizard()
|
|
1047
469
|
|
|
1048
470
|
|
|
471
|
+
@app.command("config")
|
|
472
|
+
def config_alias(
|
|
473
|
+
show: bool = typer.Option(False, "--show", help="Show current configuration"),
|
|
474
|
+
adapter: str | None = typer.Option(
|
|
475
|
+
None, "--adapter", help="Set default adapter type"
|
|
476
|
+
),
|
|
477
|
+
api_key: str | None = typer.Option(None, "--api-key", help="Set API key/token"),
|
|
478
|
+
project_id: str | None = typer.Option(None, "--project-id", help="Set project ID"),
|
|
479
|
+
team_id: str | None = typer.Option(None, "--team-id", help="Set team ID (Linear)"),
|
|
480
|
+
global_scope: bool = typer.Option(
|
|
481
|
+
False,
|
|
482
|
+
"--global",
|
|
483
|
+
"-g",
|
|
484
|
+
help="Save to global config instead of project-specific",
|
|
485
|
+
),
|
|
486
|
+
) -> None:
|
|
487
|
+
"""Alias for configure command - shorter syntax."""
|
|
488
|
+
configure_command(show, adapter, api_key, project_id, team_id, global_scope)
|
|
489
|
+
|
|
490
|
+
|
|
1049
491
|
@app.command("migrate-config")
|
|
1050
492
|
def migrate_config(
|
|
1051
493
|
dry_run: bool = typer.Option(
|
|
@@ -1063,784 +505,97 @@ def migrate_config(
|
|
|
1063
505
|
migrate_config_command(dry_run=dry_run)
|
|
1064
506
|
|
|
1065
507
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
"""Show queue and worker status.
|
|
508
|
+
# Add ticket command group to main app
|
|
509
|
+
app.add_typer(ticket_app, name="ticket")
|
|
1069
510
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
console.print(
|
|
1073
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer queue status' instead.[/yellow]\n"
|
|
1074
|
-
)
|
|
1075
|
-
|
|
1076
|
-
queue = Queue()
|
|
1077
|
-
manager = WorkerManager()
|
|
1078
|
-
|
|
1079
|
-
# Get queue stats
|
|
1080
|
-
stats = queue.get_stats()
|
|
1081
|
-
pending = stats.get(QueueStatus.PENDING.value, 0)
|
|
1082
|
-
|
|
1083
|
-
# Show queue status
|
|
1084
|
-
console.print("[bold]Queue Status:[/bold]")
|
|
1085
|
-
console.print(f" Pending: {pending}")
|
|
1086
|
-
console.print(f" Processing: {stats.get(QueueStatus.PROCESSING.value, 0)}")
|
|
1087
|
-
console.print(f" Completed: {stats.get(QueueStatus.COMPLETED.value, 0)}")
|
|
1088
|
-
console.print(f" Failed: {stats.get(QueueStatus.FAILED.value, 0)}")
|
|
1089
|
-
|
|
1090
|
-
# Show worker status
|
|
1091
|
-
worker_status = manager.get_status()
|
|
1092
|
-
if worker_status["running"]:
|
|
1093
|
-
console.print(
|
|
1094
|
-
f"\n[green]● Worker is running[/green] (PID: {worker_status.get('pid')})"
|
|
1095
|
-
)
|
|
1096
|
-
else:
|
|
1097
|
-
console.print("\n[red]○ Worker is not running[/red]")
|
|
1098
|
-
if pending > 0:
|
|
1099
|
-
console.print(
|
|
1100
|
-
"[yellow]Note: There are pending items. Start worker with 'mcp-ticketer queue worker start'[/yellow]"
|
|
1101
|
-
)
|
|
511
|
+
# Add platform command group to main app
|
|
512
|
+
app.add_typer(platform_app, name="platform")
|
|
1102
513
|
|
|
514
|
+
# Add queue command to main app
|
|
515
|
+
app.add_typer(queue_app, name="queue")
|
|
1103
516
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
auto_repair: bool = typer.Option(
|
|
1107
|
-
False, "--auto-repair", help="Attempt automatic repair of issues"
|
|
1108
|
-
),
|
|
1109
|
-
verbose: bool = typer.Option(
|
|
1110
|
-
False, "--verbose", "-v", help="Show detailed health information"
|
|
1111
|
-
),
|
|
1112
|
-
) -> None:
|
|
1113
|
-
"""Check queue system health and detect issues immediately.
|
|
517
|
+
# Add discover command to main app
|
|
518
|
+
app.add_typer(discover_app, name="discover")
|
|
1114
519
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
console.print(
|
|
1118
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer queue health' instead.[/yellow]\n"
|
|
1119
|
-
)
|
|
1120
|
-
health_monitor = QueueHealthMonitor()
|
|
1121
|
-
health = health_monitor.check_health()
|
|
1122
|
-
|
|
1123
|
-
# Display overall status
|
|
1124
|
-
status_color = {
|
|
1125
|
-
HealthStatus.HEALTHY: "green",
|
|
1126
|
-
HealthStatus.WARNING: "yellow",
|
|
1127
|
-
HealthStatus.CRITICAL: "red",
|
|
1128
|
-
HealthStatus.FAILED: "red",
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
status_icon = {
|
|
1132
|
-
HealthStatus.HEALTHY: "✓",
|
|
1133
|
-
HealthStatus.WARNING: "⚠️",
|
|
1134
|
-
HealthStatus.CRITICAL: "🚨",
|
|
1135
|
-
HealthStatus.FAILED: "❌",
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
color = status_color.get(health["status"], "white")
|
|
1139
|
-
icon = status_icon.get(health["status"], "?")
|
|
1140
|
-
|
|
1141
|
-
console.print(f"[{color}]{icon} Queue Health: {health['status'].upper()}[/{color}]")
|
|
1142
|
-
console.print(f"Last checked: {health['timestamp']}")
|
|
1143
|
-
|
|
1144
|
-
# Display alerts
|
|
1145
|
-
if health["alerts"]:
|
|
1146
|
-
console.print("\n[bold]Issues Found:[/bold]")
|
|
1147
|
-
for alert in health["alerts"]:
|
|
1148
|
-
alert_color = status_color.get(alert["level"], "white")
|
|
1149
|
-
console.print(f"[{alert_color}] • {alert['message']}[/{alert_color}]")
|
|
1150
|
-
|
|
1151
|
-
if verbose and alert.get("details"):
|
|
1152
|
-
for key, value in alert["details"].items():
|
|
1153
|
-
console.print(f" {key}: {value}")
|
|
1154
|
-
else:
|
|
1155
|
-
console.print("\n[green]✓ No issues detected[/green]")
|
|
1156
|
-
|
|
1157
|
-
# Auto-repair if requested
|
|
1158
|
-
if auto_repair and health["status"] in [
|
|
1159
|
-
HealthStatus.CRITICAL,
|
|
1160
|
-
HealthStatus.WARNING,
|
|
1161
|
-
]:
|
|
1162
|
-
console.print("\n[yellow]Attempting automatic repair...[/yellow]")
|
|
1163
|
-
repair_result = health_monitor.auto_repair()
|
|
1164
|
-
|
|
1165
|
-
if repair_result["actions_taken"]:
|
|
1166
|
-
console.print("[green]Repair actions taken:[/green]")
|
|
1167
|
-
for action in repair_result["actions_taken"]:
|
|
1168
|
-
console.print(f"[green] ✓ {action}[/green]")
|
|
1169
|
-
|
|
1170
|
-
# Re-check health
|
|
1171
|
-
console.print("\n[yellow]Re-checking health after repair...[/yellow]")
|
|
1172
|
-
new_health = health_monitor.check_health()
|
|
1173
|
-
new_color = status_color.get(new_health["status"], "white")
|
|
1174
|
-
new_icon = status_icon.get(new_health["status"], "?")
|
|
1175
|
-
console.print(
|
|
1176
|
-
f"[{new_color}]{new_icon} Updated Health: {new_health['status'].upper()}[/{new_color}]"
|
|
1177
|
-
)
|
|
1178
|
-
else:
|
|
1179
|
-
console.print("[yellow]No repair actions available[/yellow]")
|
|
520
|
+
# Add instructions command to main app
|
|
521
|
+
app.add_typer(instruction_app, name="instructions")
|
|
1180
522
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
raise typer.Exit(1)
|
|
1184
|
-
elif health["status"] == HealthStatus.WARNING:
|
|
1185
|
-
raise typer.Exit(2)
|
|
523
|
+
# Add project-update command group to main app
|
|
524
|
+
app.add_typer(project_update_app, name="project-update")
|
|
1186
525
|
|
|
526
|
+
# Add setup and init commands to main app
|
|
527
|
+
app.command()(setup)
|
|
528
|
+
app.command()(init)
|
|
1187
529
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
None, "--
|
|
1199
|
-
),
|
|
1200
|
-
assignee: str | None = typer.Option(
|
|
1201
|
-
None, "--assignee", "-a", help="Assignee username"
|
|
1202
|
-
),
|
|
1203
|
-
project: str | None = typer.Option(
|
|
1204
|
-
None,
|
|
1205
|
-
"--project",
|
|
1206
|
-
help="Parent project/epic ID (synonym for --epic)",
|
|
530
|
+
# Add platform installer commands to main app
|
|
531
|
+
app.command()(install)
|
|
532
|
+
app.command()(remove)
|
|
533
|
+
app.command()(uninstall)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# Add diagnostics command
|
|
537
|
+
@app.command("doctor")
|
|
538
|
+
def doctor_command(
|
|
539
|
+
output_file: str | None = typer.Option(
|
|
540
|
+
None, "--output", "-o", help="Save full report to file"
|
|
1207
541
|
),
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
"--epic",
|
|
1211
|
-
help="Parent epic/project ID (synonym for --project)",
|
|
542
|
+
json_output: bool = typer.Option(
|
|
543
|
+
False, "--json", help="Output report in JSON format"
|
|
1212
544
|
),
|
|
1213
|
-
|
|
1214
|
-
|
|
545
|
+
simple: bool = typer.Option(
|
|
546
|
+
False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
|
|
1215
547
|
),
|
|
1216
548
|
) -> None:
|
|
1217
|
-
"""
|
|
549
|
+
"""Run comprehensive system diagnostics and health check (alias: diagnose)."""
|
|
550
|
+
if simple:
|
|
551
|
+
from .simple_health import simple_diagnose
|
|
1218
552
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket create' instead.[/yellow]\n"
|
|
1223
|
-
)
|
|
1224
|
-
|
|
1225
|
-
# IMMEDIATE HEALTH CHECK - Critical for reliability
|
|
1226
|
-
health_monitor = QueueHealthMonitor()
|
|
1227
|
-
health = health_monitor.check_health()
|
|
1228
|
-
|
|
1229
|
-
# Display health status
|
|
1230
|
-
if health["status"] == HealthStatus.CRITICAL:
|
|
1231
|
-
console.print("[red]🚨 CRITICAL: Queue system has serious issues![/red]")
|
|
1232
|
-
for alert in health["alerts"]:
|
|
1233
|
-
if alert["level"] == "critical":
|
|
1234
|
-
console.print(f"[red] • {alert['message']}[/red]")
|
|
1235
|
-
|
|
1236
|
-
# Attempt auto-repair
|
|
1237
|
-
console.print("[yellow]Attempting automatic repair...[/yellow]")
|
|
1238
|
-
repair_result = health_monitor.auto_repair()
|
|
1239
|
-
|
|
1240
|
-
if repair_result["actions_taken"]:
|
|
1241
|
-
for action in repair_result["actions_taken"]:
|
|
1242
|
-
console.print(f"[yellow] ✓ {action}[/yellow]")
|
|
1243
|
-
|
|
1244
|
-
# Re-check health after repair
|
|
1245
|
-
health = health_monitor.check_health()
|
|
1246
|
-
if health["status"] == HealthStatus.CRITICAL:
|
|
1247
|
-
console.print(
|
|
1248
|
-
"[red]❌ Auto-repair failed. Manual intervention required.[/red]"
|
|
1249
|
-
)
|
|
1250
|
-
console.print(
|
|
1251
|
-
"[red]Cannot safely create ticket. Please check system status.[/red]"
|
|
1252
|
-
)
|
|
1253
|
-
raise typer.Exit(1)
|
|
1254
|
-
else:
|
|
1255
|
-
console.print(
|
|
1256
|
-
"[green]✓ Auto-repair successful. Proceeding with ticket creation.[/green]"
|
|
1257
|
-
)
|
|
1258
|
-
else:
|
|
1259
|
-
console.print(
|
|
1260
|
-
"[red]❌ No repair actions available. Manual intervention required.[/red]"
|
|
1261
|
-
)
|
|
1262
|
-
raise typer.Exit(1)
|
|
553
|
+
report = simple_diagnose()
|
|
554
|
+
if output_file:
|
|
555
|
+
import json
|
|
1263
556
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
console.print("[yellow]Proceeding with ticket creation...[/yellow]")
|
|
557
|
+
with open(output_file, "w") as f:
|
|
558
|
+
json.dump(report, f, indent=2)
|
|
559
|
+
console.print(f"\n📄 Report saved to: {output_file}")
|
|
560
|
+
if json_output:
|
|
561
|
+
import json
|
|
1270
562
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
adapter_name = adapter.value
|
|
1275
|
-
_save_adapter_to_config(adapter_name)
|
|
563
|
+
console.print("\n" + json.dumps(report, indent=2))
|
|
564
|
+
if report["issues"]:
|
|
565
|
+
raise typer.Exit(1) from None
|
|
1276
566
|
else:
|
|
1277
|
-
# Priority 2: Check existing config
|
|
1278
|
-
config = load_config()
|
|
1279
|
-
adapter_name = config.get("default_adapter")
|
|
1280
|
-
|
|
1281
|
-
if not adapter_name or adapter_name == "aitrackdown":
|
|
1282
|
-
# Priority 3: Check .env files and save if found
|
|
1283
|
-
env_adapter = _discover_from_env_files()
|
|
1284
|
-
if env_adapter:
|
|
1285
|
-
adapter_name = env_adapter
|
|
1286
|
-
_save_adapter_to_config(adapter_name)
|
|
1287
|
-
else:
|
|
1288
|
-
# Priority 4: Default
|
|
1289
|
-
adapter_name = "aitrackdown"
|
|
1290
|
-
|
|
1291
|
-
# Resolve project/epic synonym - prefer whichever is provided
|
|
1292
|
-
parent_epic_id = project or epic
|
|
1293
|
-
|
|
1294
|
-
# Create task data
|
|
1295
|
-
# Import Priority for type checking
|
|
1296
|
-
from ..core.models import Priority as PriorityEnum
|
|
1297
|
-
|
|
1298
|
-
task_data = {
|
|
1299
|
-
"title": title,
|
|
1300
|
-
"description": description,
|
|
1301
|
-
"priority": priority.value if isinstance(priority, PriorityEnum) else priority,
|
|
1302
|
-
"tags": tags or [],
|
|
1303
|
-
"assignee": assignee,
|
|
1304
|
-
"parent_epic": parent_epic_id,
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
# WORKAROUND: Use direct operation for Linear adapter to bypass worker subprocess issue
|
|
1308
|
-
if adapter_name == "linear":
|
|
1309
|
-
console.print(
|
|
1310
|
-
"[yellow]⚠️[/yellow] Using direct operation for Linear adapter (bypassing queue)"
|
|
1311
|
-
)
|
|
1312
567
|
try:
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
adapter_config = config.get("adapters", {}).get(adapter_name, {})
|
|
1316
|
-
|
|
1317
|
-
# Import and create adapter
|
|
1318
|
-
from ..core.registry import AdapterRegistry
|
|
1319
|
-
|
|
1320
|
-
adapter = AdapterRegistry.get_adapter(adapter_name, adapter_config)
|
|
1321
|
-
|
|
1322
|
-
# Create task directly
|
|
1323
|
-
from ..core.models import Priority, Task
|
|
1324
|
-
|
|
1325
|
-
task = Task(
|
|
1326
|
-
title=task_data["title"],
|
|
1327
|
-
description=task_data.get("description"),
|
|
1328
|
-
priority=(
|
|
1329
|
-
Priority(task_data["priority"])
|
|
1330
|
-
if task_data.get("priority")
|
|
1331
|
-
else Priority.MEDIUM
|
|
1332
|
-
),
|
|
1333
|
-
tags=task_data.get("tags", []),
|
|
1334
|
-
assignee=task_data.get("assignee"),
|
|
1335
|
-
parent_epic=task_data.get("parent_epic"),
|
|
568
|
+
asyncio.run(
|
|
569
|
+
run_diagnostics(output_file=output_file, json_output=json_output)
|
|
1336
570
|
)
|
|
1337
|
-
|
|
1338
|
-
#
|
|
1339
|
-
import asyncio
|
|
1340
|
-
|
|
1341
|
-
result = asyncio.run(adapter.create(task))
|
|
1342
|
-
|
|
1343
|
-
console.print(f"[green]✓[/green] Ticket created successfully: {result.id}")
|
|
1344
|
-
console.print(f" Title: {result.title}")
|
|
1345
|
-
console.print(f" Priority: {result.priority}")
|
|
1346
|
-
console.print(f" State: {result.state}")
|
|
1347
|
-
# Get URL from metadata if available
|
|
1348
|
-
if (
|
|
1349
|
-
result.metadata
|
|
1350
|
-
and "linear" in result.metadata
|
|
1351
|
-
and "url" in result.metadata["linear"]
|
|
1352
|
-
):
|
|
1353
|
-
console.print(f" URL: {result.metadata['linear']['url']}")
|
|
1354
|
-
|
|
1355
|
-
return result.id
|
|
1356
|
-
|
|
1357
|
-
except Exception as e:
|
|
1358
|
-
console.print(f"[red]❌[/red] Failed to create ticket: {e}")
|
|
571
|
+
except typer.Exit:
|
|
572
|
+
# typer.Exit is expected - don't fall back to simple diagnostics
|
|
1359
573
|
raise
|
|
574
|
+
except Exception as e:
|
|
575
|
+
console.print(f"⚠️ Full diagnostics failed: {e}")
|
|
576
|
+
console.print("🔄 Falling back to simple diagnostics...")
|
|
577
|
+
from .simple_health import simple_diagnose
|
|
1360
578
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
ticket_data=task_data,
|
|
1365
|
-
adapter=adapter_name,
|
|
1366
|
-
operation="create",
|
|
1367
|
-
project_dir=str(Path.cwd()), # Explicitly pass current project directory
|
|
1368
|
-
)
|
|
1369
|
-
|
|
1370
|
-
# Register in ticket registry for tracking
|
|
1371
|
-
registry = TicketRegistry()
|
|
1372
|
-
registry.register_ticket_operation(
|
|
1373
|
-
queue_id, adapter_name, "create", title, task_data
|
|
1374
|
-
)
|
|
1375
|
-
|
|
1376
|
-
console.print(f"[green]✓[/green] Queued ticket creation: {queue_id}")
|
|
1377
|
-
console.print(f" Title: {title}")
|
|
1378
|
-
console.print(f" Priority: {priority}")
|
|
1379
|
-
console.print(f" Adapter: {adapter_name}")
|
|
1380
|
-
console.print("[dim]Use 'mcp-ticketer check {queue_id}' to check progress[/dim]")
|
|
1381
|
-
|
|
1382
|
-
# Start worker if needed with immediate feedback
|
|
1383
|
-
manager = WorkerManager()
|
|
1384
|
-
worker_started = manager.start_if_needed()
|
|
1385
|
-
|
|
1386
|
-
if worker_started:
|
|
1387
|
-
console.print("[dim]Worker started to process request[/dim]")
|
|
1388
|
-
|
|
1389
|
-
# Give immediate feedback on processing
|
|
1390
|
-
import time
|
|
1391
|
-
|
|
1392
|
-
time.sleep(1) # Brief pause to let worker start
|
|
1393
|
-
|
|
1394
|
-
# Check if item is being processed
|
|
1395
|
-
item = queue.get_item(queue_id)
|
|
1396
|
-
if item and item.status == QueueStatus.PROCESSING:
|
|
1397
|
-
console.print("[green]✓ Item is being processed by worker[/green]")
|
|
1398
|
-
elif item and item.status == QueueStatus.PENDING:
|
|
1399
|
-
console.print("[yellow]⏳ Item is queued for processing[/yellow]")
|
|
1400
|
-
else:
|
|
1401
|
-
console.print(
|
|
1402
|
-
"[red]⚠️ Item status unclear - check with 'mcp-ticketer check {queue_id}'[/red]"
|
|
1403
|
-
)
|
|
1404
|
-
else:
|
|
1405
|
-
# Worker didn't start - this is a problem
|
|
1406
|
-
pending_count = queue.get_pending_count()
|
|
1407
|
-
if pending_count > 1: # More than just this item
|
|
1408
|
-
console.print(
|
|
1409
|
-
f"[red]❌ Worker failed to start with {pending_count} pending items![/red]"
|
|
1410
|
-
)
|
|
1411
|
-
console.print(
|
|
1412
|
-
"[red]This is a critical issue. Try 'mcp-ticketer queue worker start' manually.[/red]"
|
|
1413
|
-
)
|
|
1414
|
-
else:
|
|
1415
|
-
console.print(
|
|
1416
|
-
"[yellow]Worker not started (no other pending items)[/yellow]"
|
|
1417
|
-
)
|
|
579
|
+
report = simple_diagnose()
|
|
580
|
+
if report["issues"]:
|
|
581
|
+
raise typer.Exit(1) from None
|
|
1418
582
|
|
|
1419
583
|
|
|
1420
|
-
@app.command("
|
|
1421
|
-
def
|
|
1422
|
-
|
|
1423
|
-
None, "--
|
|
584
|
+
@app.command("diagnose", hidden=True)
|
|
585
|
+
def diagnose_alias(
|
|
586
|
+
output_file: str | None = typer.Option(
|
|
587
|
+
None, "--output", "-o", help="Save full report to file"
|
|
1424
588
|
),
|
|
1425
|
-
|
|
1426
|
-
|
|
589
|
+
json_output: bool = typer.Option(
|
|
590
|
+
False, "--json", help="Output report in JSON format"
|
|
1427
591
|
),
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
None, "--adapter", help="Override default adapter"
|
|
592
|
+
simple: bool = typer.Option(
|
|
593
|
+
False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
|
|
1431
594
|
),
|
|
1432
595
|
) -> None:
|
|
1433
|
-
"""
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
"""
|
|
1437
|
-
console.print(
|
|
1438
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket list' instead.[/yellow]\n"
|
|
1439
|
-
)
|
|
1440
|
-
|
|
1441
|
-
async def _list():
|
|
1442
|
-
adapter_instance = get_adapter(
|
|
1443
|
-
override_adapter=adapter.value if adapter else None
|
|
1444
|
-
)
|
|
1445
|
-
filters = {}
|
|
1446
|
-
if state:
|
|
1447
|
-
filters["state"] = state
|
|
1448
|
-
if priority:
|
|
1449
|
-
filters["priority"] = priority
|
|
1450
|
-
return await adapter_instance.list(limit=limit, filters=filters)
|
|
1451
|
-
|
|
1452
|
-
tickets = asyncio.run(_list())
|
|
1453
|
-
|
|
1454
|
-
if not tickets:
|
|
1455
|
-
console.print("[yellow]No tickets found[/yellow]")
|
|
1456
|
-
return
|
|
1457
|
-
|
|
1458
|
-
# Create table
|
|
1459
|
-
table = Table(title="Tickets")
|
|
1460
|
-
table.add_column("ID", style="cyan", no_wrap=True)
|
|
1461
|
-
table.add_column("Title", style="white")
|
|
1462
|
-
table.add_column("State", style="green")
|
|
1463
|
-
table.add_column("Priority", style="yellow")
|
|
1464
|
-
table.add_column("Assignee", style="blue")
|
|
1465
|
-
|
|
1466
|
-
for ticket in tickets:
|
|
1467
|
-
# Handle assignee field - Epic doesn't have assignee, Task does
|
|
1468
|
-
assignee = getattr(ticket, "assignee", None) or "-"
|
|
1469
|
-
|
|
1470
|
-
table.add_row(
|
|
1471
|
-
ticket.id or "N/A",
|
|
1472
|
-
ticket.title,
|
|
1473
|
-
ticket.state,
|
|
1474
|
-
ticket.priority,
|
|
1475
|
-
assignee,
|
|
1476
|
-
)
|
|
1477
|
-
|
|
1478
|
-
console.print(table)
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
@app.command(deprecated=True, hidden=True)
|
|
1482
|
-
def show(
|
|
1483
|
-
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
1484
|
-
comments: bool = typer.Option(False, "--comments", "-c", help="Show comments"),
|
|
1485
|
-
adapter: AdapterType | None = typer.Option(
|
|
1486
|
-
None, "--adapter", help="Override default adapter"
|
|
1487
|
-
),
|
|
1488
|
-
) -> None:
|
|
1489
|
-
"""Show detailed ticket information.
|
|
1490
|
-
|
|
1491
|
-
DEPRECATED: Use 'mcp-ticketer ticket show' instead.
|
|
1492
|
-
"""
|
|
1493
|
-
console.print(
|
|
1494
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket show' instead.[/yellow]\n"
|
|
1495
|
-
)
|
|
1496
|
-
|
|
1497
|
-
async def _show():
|
|
1498
|
-
adapter_instance = get_adapter(
|
|
1499
|
-
override_adapter=adapter.value if adapter else None
|
|
1500
|
-
)
|
|
1501
|
-
ticket = await adapter_instance.read(ticket_id)
|
|
1502
|
-
ticket_comments = None
|
|
1503
|
-
if comments and ticket:
|
|
1504
|
-
ticket_comments = await adapter_instance.get_comments(ticket_id)
|
|
1505
|
-
return ticket, ticket_comments
|
|
1506
|
-
|
|
1507
|
-
ticket, ticket_comments = asyncio.run(_show())
|
|
1508
|
-
|
|
1509
|
-
if not ticket:
|
|
1510
|
-
console.print(f"[red]✗[/red] Ticket not found: {ticket_id}")
|
|
1511
|
-
raise typer.Exit(1)
|
|
1512
|
-
|
|
1513
|
-
# Display ticket details
|
|
1514
|
-
console.print(f"\n[bold]Ticket: {ticket.id}[/bold]")
|
|
1515
|
-
console.print(f"Title: {ticket.title}")
|
|
1516
|
-
console.print(f"State: [green]{ticket.state}[/green]")
|
|
1517
|
-
console.print(f"Priority: [yellow]{ticket.priority}[/yellow]")
|
|
1518
|
-
|
|
1519
|
-
if ticket.description:
|
|
1520
|
-
console.print("\n[dim]Description:[/dim]")
|
|
1521
|
-
console.print(ticket.description)
|
|
1522
|
-
|
|
1523
|
-
if ticket.tags:
|
|
1524
|
-
console.print(f"\nTags: {', '.join(ticket.tags)}")
|
|
1525
|
-
|
|
1526
|
-
if ticket.assignee:
|
|
1527
|
-
console.print(f"Assignee: {ticket.assignee}")
|
|
1528
|
-
|
|
1529
|
-
# Display comments if requested
|
|
1530
|
-
if ticket_comments:
|
|
1531
|
-
console.print(f"\n[bold]Comments ({len(ticket_comments)}):[/bold]")
|
|
1532
|
-
for comment in ticket_comments:
|
|
1533
|
-
console.print(f"\n[dim]{comment.created_at} - {comment.author}:[/dim]")
|
|
1534
|
-
console.print(comment.content)
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
@app.command(deprecated=True, hidden=True)
|
|
1538
|
-
def comment(
|
|
1539
|
-
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
1540
|
-
content: str = typer.Argument(..., help="Comment content"),
|
|
1541
|
-
adapter: AdapterType | None = typer.Option(
|
|
1542
|
-
None, "--adapter", help="Override default adapter"
|
|
1543
|
-
),
|
|
1544
|
-
) -> None:
|
|
1545
|
-
"""Add a comment to a ticket.
|
|
1546
|
-
|
|
1547
|
-
DEPRECATED: Use 'mcp-ticketer ticket comment' instead.
|
|
1548
|
-
"""
|
|
1549
|
-
console.print(
|
|
1550
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket comment' instead.[/yellow]\n"
|
|
1551
|
-
)
|
|
1552
|
-
|
|
1553
|
-
async def _comment():
|
|
1554
|
-
adapter_instance = get_adapter(
|
|
1555
|
-
override_adapter=adapter.value if adapter else None
|
|
1556
|
-
)
|
|
1557
|
-
|
|
1558
|
-
# Create comment
|
|
1559
|
-
comment = Comment(
|
|
1560
|
-
ticket_id=ticket_id,
|
|
1561
|
-
content=content,
|
|
1562
|
-
author="cli-user", # Could be made configurable
|
|
1563
|
-
)
|
|
1564
|
-
|
|
1565
|
-
result = await adapter_instance.add_comment(comment)
|
|
1566
|
-
return result
|
|
1567
|
-
|
|
1568
|
-
try:
|
|
1569
|
-
result = asyncio.run(_comment())
|
|
1570
|
-
console.print("[green]✓[/green] Comment added successfully")
|
|
1571
|
-
if result.id:
|
|
1572
|
-
console.print(f"Comment ID: {result.id}")
|
|
1573
|
-
console.print(f"Content: {content}")
|
|
1574
|
-
except Exception as e:
|
|
1575
|
-
console.print(f"[red]✗[/red] Failed to add comment: {e}")
|
|
1576
|
-
raise typer.Exit(1)
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
@app.command(deprecated=True, hidden=True)
|
|
1580
|
-
def update(
|
|
1581
|
-
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
1582
|
-
title: str | None = typer.Option(None, "--title", help="New title"),
|
|
1583
|
-
description: str | None = typer.Option(
|
|
1584
|
-
None, "--description", "-d", help="New description"
|
|
1585
|
-
),
|
|
1586
|
-
priority: Priority | None = typer.Option(
|
|
1587
|
-
None, "--priority", "-p", help="New priority"
|
|
1588
|
-
),
|
|
1589
|
-
assignee: str | None = typer.Option(None, "--assignee", "-a", help="New assignee"),
|
|
1590
|
-
adapter: AdapterType | None = typer.Option(
|
|
1591
|
-
None, "--adapter", help="Override default adapter"
|
|
1592
|
-
),
|
|
1593
|
-
) -> None:
|
|
1594
|
-
"""Update ticket fields.
|
|
1595
|
-
|
|
1596
|
-
DEPRECATED: Use 'mcp-ticketer ticket update' instead.
|
|
1597
|
-
"""
|
|
1598
|
-
console.print(
|
|
1599
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket update' instead.[/yellow]\n"
|
|
1600
|
-
)
|
|
1601
|
-
updates = {}
|
|
1602
|
-
if title:
|
|
1603
|
-
updates["title"] = title
|
|
1604
|
-
if description:
|
|
1605
|
-
updates["description"] = description
|
|
1606
|
-
if priority:
|
|
1607
|
-
updates["priority"] = (
|
|
1608
|
-
priority.value if isinstance(priority, Priority) else priority
|
|
1609
|
-
)
|
|
1610
|
-
if assignee:
|
|
1611
|
-
updates["assignee"] = assignee
|
|
1612
|
-
|
|
1613
|
-
if not updates:
|
|
1614
|
-
console.print("[yellow]No updates specified[/yellow]")
|
|
1615
|
-
raise typer.Exit(1)
|
|
1616
|
-
|
|
1617
|
-
# Get the adapter name
|
|
1618
|
-
config = load_config()
|
|
1619
|
-
adapter_name = (
|
|
1620
|
-
adapter.value if adapter else config.get("default_adapter", "aitrackdown")
|
|
1621
|
-
)
|
|
1622
|
-
|
|
1623
|
-
# Add ticket_id to updates
|
|
1624
|
-
updates["ticket_id"] = ticket_id
|
|
1625
|
-
|
|
1626
|
-
# Add to queue with explicit project directory
|
|
1627
|
-
queue = Queue()
|
|
1628
|
-
queue_id = queue.add(
|
|
1629
|
-
ticket_data=updates,
|
|
1630
|
-
adapter=adapter_name,
|
|
1631
|
-
operation="update",
|
|
1632
|
-
project_dir=str(Path.cwd()), # Explicitly pass current project directory
|
|
1633
|
-
)
|
|
1634
|
-
|
|
1635
|
-
console.print(f"[green]✓[/green] Queued ticket update: {queue_id}")
|
|
1636
|
-
for key, value in updates.items():
|
|
1637
|
-
if key != "ticket_id":
|
|
1638
|
-
console.print(f" {key}: {value}")
|
|
1639
|
-
console.print("[dim]Use 'mcp-ticketer status {queue_id}' to check progress[/dim]")
|
|
1640
|
-
|
|
1641
|
-
# Start worker if needed
|
|
1642
|
-
manager = WorkerManager()
|
|
1643
|
-
if manager.start_if_needed():
|
|
1644
|
-
console.print("[dim]Worker started to process request[/dim]")
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
@app.command(deprecated=True, hidden=True)
|
|
1648
|
-
def transition(
|
|
1649
|
-
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
1650
|
-
state_positional: TicketState | None = typer.Argument(
|
|
1651
|
-
None, help="Target state (positional - deprecated, use --state instead)"
|
|
1652
|
-
),
|
|
1653
|
-
state: TicketState | None = typer.Option(
|
|
1654
|
-
None, "--state", "-s", help="Target state (recommended)"
|
|
1655
|
-
),
|
|
1656
|
-
adapter: AdapterType | None = typer.Option(
|
|
1657
|
-
None, "--adapter", help="Override default adapter"
|
|
1658
|
-
),
|
|
1659
|
-
) -> None:
|
|
1660
|
-
"""Change ticket state with validation.
|
|
1661
|
-
|
|
1662
|
-
DEPRECATED: Use 'mcp-ticketer ticket transition' instead.
|
|
1663
|
-
|
|
1664
|
-
Examples:
|
|
1665
|
-
# Recommended syntax with flag:
|
|
1666
|
-
mcp-ticketer ticket transition BTA-215 --state done
|
|
1667
|
-
mcp-ticketer ticket transition BTA-215 -s in_progress
|
|
1668
|
-
|
|
1669
|
-
# Legacy positional syntax (still supported):
|
|
1670
|
-
mcp-ticketer ticket transition BTA-215 done
|
|
1671
|
-
|
|
1672
|
-
"""
|
|
1673
|
-
console.print(
|
|
1674
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket transition' instead.[/yellow]\n"
|
|
1675
|
-
)
|
|
1676
|
-
|
|
1677
|
-
# Determine which state to use (prefer flag over positional)
|
|
1678
|
-
target_state = state if state is not None else state_positional
|
|
1679
|
-
|
|
1680
|
-
if target_state is None:
|
|
1681
|
-
console.print("[red]Error: State is required[/red]")
|
|
1682
|
-
console.print(
|
|
1683
|
-
"Use either:\n"
|
|
1684
|
-
" - Flag syntax (recommended): mcp-ticketer transition TICKET-ID --state STATE\n"
|
|
1685
|
-
" - Positional syntax: mcp-ticketer transition TICKET-ID STATE"
|
|
1686
|
-
)
|
|
1687
|
-
raise typer.Exit(1)
|
|
1688
|
-
|
|
1689
|
-
# Get the adapter name
|
|
1690
|
-
config = load_config()
|
|
1691
|
-
adapter_name = (
|
|
1692
|
-
adapter.value if adapter else config.get("default_adapter", "aitrackdown")
|
|
1693
|
-
)
|
|
1694
|
-
|
|
1695
|
-
# Add to queue with explicit project directory
|
|
1696
|
-
queue = Queue()
|
|
1697
|
-
queue_id = queue.add(
|
|
1698
|
-
ticket_data={
|
|
1699
|
-
"ticket_id": ticket_id,
|
|
1700
|
-
"state": (
|
|
1701
|
-
target_state.value if hasattr(target_state, "value") else target_state
|
|
1702
|
-
),
|
|
1703
|
-
},
|
|
1704
|
-
adapter=adapter_name,
|
|
1705
|
-
operation="transition",
|
|
1706
|
-
project_dir=str(Path.cwd()), # Explicitly pass current project directory
|
|
1707
|
-
)
|
|
1708
|
-
|
|
1709
|
-
console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
|
|
1710
|
-
console.print(f" Ticket: {ticket_id} → {target_state}")
|
|
1711
|
-
console.print("[dim]Use 'mcp-ticketer status {queue_id}' to check progress[/dim]")
|
|
1712
|
-
|
|
1713
|
-
# Start worker if needed
|
|
1714
|
-
manager = WorkerManager()
|
|
1715
|
-
if manager.start_if_needed():
|
|
1716
|
-
console.print("[dim]Worker started to process request[/dim]")
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
@app.command(deprecated=True, hidden=True)
|
|
1720
|
-
def search(
|
|
1721
|
-
query: str | None = typer.Argument(None, help="Search query"),
|
|
1722
|
-
state: TicketState | None = typer.Option(None, "--state", "-s"),
|
|
1723
|
-
priority: Priority | None = typer.Option(None, "--priority", "-p"),
|
|
1724
|
-
assignee: str | None = typer.Option(None, "--assignee", "-a"),
|
|
1725
|
-
limit: int = typer.Option(10, "--limit", "-l"),
|
|
1726
|
-
adapter: AdapterType | None = typer.Option(
|
|
1727
|
-
None, "--adapter", help="Override default adapter"
|
|
1728
|
-
),
|
|
1729
|
-
) -> None:
|
|
1730
|
-
"""Search tickets with advanced query.
|
|
1731
|
-
|
|
1732
|
-
DEPRECATED: Use 'mcp-ticketer ticket search' instead.
|
|
1733
|
-
"""
|
|
1734
|
-
console.print(
|
|
1735
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket search' instead.[/yellow]\n"
|
|
1736
|
-
)
|
|
1737
|
-
|
|
1738
|
-
async def _search():
|
|
1739
|
-
adapter_instance = get_adapter(
|
|
1740
|
-
override_adapter=adapter.value if adapter else None
|
|
1741
|
-
)
|
|
1742
|
-
search_query = SearchQuery(
|
|
1743
|
-
query=query,
|
|
1744
|
-
state=state,
|
|
1745
|
-
priority=priority,
|
|
1746
|
-
assignee=assignee,
|
|
1747
|
-
limit=limit,
|
|
1748
|
-
)
|
|
1749
|
-
return await adapter_instance.search(search_query)
|
|
1750
|
-
|
|
1751
|
-
tickets = asyncio.run(_search())
|
|
1752
|
-
|
|
1753
|
-
if not tickets:
|
|
1754
|
-
console.print("[yellow]No tickets found matching query[/yellow]")
|
|
1755
|
-
return
|
|
1756
|
-
|
|
1757
|
-
# Display results
|
|
1758
|
-
console.print(f"\n[bold]Found {len(tickets)} ticket(s)[/bold]\n")
|
|
1759
|
-
|
|
1760
|
-
for ticket in tickets:
|
|
1761
|
-
console.print(f"[cyan]{ticket.id}[/cyan]: {ticket.title}")
|
|
1762
|
-
console.print(f" State: {ticket.state} | Priority: {ticket.priority}")
|
|
1763
|
-
if ticket.assignee:
|
|
1764
|
-
console.print(f" Assignee: {ticket.assignee}")
|
|
1765
|
-
console.print()
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
# Add ticket command group to main app
|
|
1769
|
-
app.add_typer(ticket_app, name="ticket")
|
|
1770
|
-
|
|
1771
|
-
# Add platform command group to main app
|
|
1772
|
-
app.add_typer(platform_app, name="platform")
|
|
1773
|
-
|
|
1774
|
-
# Add queue command to main app
|
|
1775
|
-
app.add_typer(queue_app, name="queue")
|
|
1776
|
-
|
|
1777
|
-
# Add discover command to main app
|
|
1778
|
-
app.add_typer(discover_app, name="discover")
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
# Add diagnostics command
|
|
1782
|
-
@app.command("diagnose")
|
|
1783
|
-
def diagnose_command(
|
|
1784
|
-
output_file: str | None = typer.Option(
|
|
1785
|
-
None, "--output", "-o", help="Save full report to file"
|
|
1786
|
-
),
|
|
1787
|
-
json_output: bool = typer.Option(
|
|
1788
|
-
False, "--json", help="Output report in JSON format"
|
|
1789
|
-
),
|
|
1790
|
-
simple: bool = typer.Option(
|
|
1791
|
-
False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
|
|
1792
|
-
),
|
|
1793
|
-
) -> None:
|
|
1794
|
-
"""Run comprehensive system diagnostics and health check (alias: doctor)."""
|
|
1795
|
-
if simple:
|
|
1796
|
-
from .simple_health import simple_diagnose
|
|
1797
|
-
|
|
1798
|
-
report = simple_diagnose()
|
|
1799
|
-
if output_file:
|
|
1800
|
-
import json
|
|
1801
|
-
|
|
1802
|
-
with open(output_file, "w") as f:
|
|
1803
|
-
json.dump(report, f, indent=2)
|
|
1804
|
-
console.print(f"\n📄 Report saved to: {output_file}")
|
|
1805
|
-
if json_output:
|
|
1806
|
-
import json
|
|
1807
|
-
|
|
1808
|
-
console.print("\n" + json.dumps(report, indent=2))
|
|
1809
|
-
if report["issues"]:
|
|
1810
|
-
raise typer.Exit(1)
|
|
1811
|
-
else:
|
|
1812
|
-
try:
|
|
1813
|
-
asyncio.run(
|
|
1814
|
-
run_diagnostics(output_file=output_file, json_output=json_output)
|
|
1815
|
-
)
|
|
1816
|
-
except typer.Exit:
|
|
1817
|
-
# typer.Exit is expected - don't fall back to simple diagnostics
|
|
1818
|
-
raise
|
|
1819
|
-
except Exception as e:
|
|
1820
|
-
console.print(f"⚠️ Full diagnostics failed: {e}")
|
|
1821
|
-
console.print("🔄 Falling back to simple diagnostics...")
|
|
1822
|
-
from .simple_health import simple_diagnose
|
|
1823
|
-
|
|
1824
|
-
report = simple_diagnose()
|
|
1825
|
-
if report["issues"]:
|
|
1826
|
-
raise typer.Exit(1)
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
@app.command("doctor")
|
|
1830
|
-
def doctor_alias(
|
|
1831
|
-
output_file: str | None = typer.Option(
|
|
1832
|
-
None, "--output", "-o", help="Save full report to file"
|
|
1833
|
-
),
|
|
1834
|
-
json_output: bool = typer.Option(
|
|
1835
|
-
False, "--json", help="Output report in JSON format"
|
|
1836
|
-
),
|
|
1837
|
-
simple: bool = typer.Option(
|
|
1838
|
-
False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
|
|
1839
|
-
),
|
|
1840
|
-
) -> None:
|
|
1841
|
-
"""Run comprehensive system diagnostics and health check (alias for diagnose)."""
|
|
1842
|
-
# Call the diagnose_command function with the same parameters
|
|
1843
|
-
diagnose_command(output_file=output_file, json_output=json_output, simple=simple)
|
|
596
|
+
"""Run comprehensive system diagnostics and health check (alias for doctor)."""
|
|
597
|
+
# Call the doctor_command function with the same parameters
|
|
598
|
+
doctor_command(output_file=output_file, json_output=json_output, simple=simple)
|
|
1844
599
|
|
|
1845
600
|
|
|
1846
601
|
@app.command("status")
|
|
@@ -1850,7 +605,7 @@ def status_command() -> None:
|
|
|
1850
605
|
|
|
1851
606
|
result = simple_health_check()
|
|
1852
607
|
if result != 0:
|
|
1853
|
-
raise typer.Exit(result)
|
|
608
|
+
raise typer.Exit(result) from None
|
|
1854
609
|
|
|
1855
610
|
|
|
1856
611
|
@app.command("health")
|
|
@@ -1860,716 +615,15 @@ def health_alias() -> None:
|
|
|
1860
615
|
|
|
1861
616
|
result = simple_health_check()
|
|
1862
617
|
if result != 0:
|
|
1863
|
-
raise typer.Exit(result)
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
# Create MCP configuration command group
|
|
1867
|
-
mcp_app = typer.Typer(
|
|
1868
|
-
name="mcp",
|
|
1869
|
-
help="Configure MCP integration for AI clients (Claude, Gemini, Codex, Auggie)",
|
|
1870
|
-
add_completion=False,
|
|
1871
|
-
invoke_without_command=True,
|
|
1872
|
-
)
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
@mcp_app.callback()
|
|
1876
|
-
def mcp_callback(
|
|
1877
|
-
ctx: typer.Context,
|
|
1878
|
-
project_path: str | None = typer.Argument(
|
|
1879
|
-
None, help="Project directory path (optional - uses cwd if not provided)"
|
|
1880
|
-
),
|
|
1881
|
-
):
|
|
1882
|
-
"""MCP command group - runs MCP server if no subcommand provided."""
|
|
1883
|
-
if ctx.invoked_subcommand is None:
|
|
1884
|
-
# No subcommand provided, run the serve command
|
|
1885
|
-
# Change to project directory if provided
|
|
1886
|
-
if project_path:
|
|
1887
|
-
import os
|
|
1888
|
-
|
|
1889
|
-
os.chdir(project_path)
|
|
1890
|
-
# Invoke the serve command through context
|
|
1891
|
-
ctx.invoke(mcp_serve, adapter=None, base_path=None)
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
@app.command()
|
|
1895
|
-
def install(
|
|
1896
|
-
platform: str | None = typer.Argument(
|
|
1897
|
-
None,
|
|
1898
|
-
help="Platform to install (claude-code, claude-desktop, gemini, codex, auggie)",
|
|
1899
|
-
),
|
|
1900
|
-
adapter: str | None = typer.Option(
|
|
1901
|
-
None,
|
|
1902
|
-
"--adapter",
|
|
1903
|
-
"-a",
|
|
1904
|
-
help="Adapter type to use (interactive prompt if not specified)",
|
|
1905
|
-
),
|
|
1906
|
-
project_path: str | None = typer.Option(
|
|
1907
|
-
None, "--path", help="Project path (default: current directory)"
|
|
1908
|
-
),
|
|
1909
|
-
global_config: bool = typer.Option(
|
|
1910
|
-
False,
|
|
1911
|
-
"--global",
|
|
1912
|
-
"-g",
|
|
1913
|
-
help="Save to global config instead of project-specific",
|
|
1914
|
-
),
|
|
1915
|
-
base_path: str | None = typer.Option(
|
|
1916
|
-
None,
|
|
1917
|
-
"--base-path",
|
|
1918
|
-
"-p",
|
|
1919
|
-
help="Base path for ticket storage (AITrackdown only)",
|
|
1920
|
-
),
|
|
1921
|
-
api_key: str | None = typer.Option(
|
|
1922
|
-
None, "--api-key", help="API key for Linear or API token for JIRA"
|
|
1923
|
-
),
|
|
1924
|
-
team_id: str | None = typer.Option(
|
|
1925
|
-
None, "--team-id", help="Linear team ID (required for Linear adapter)"
|
|
1926
|
-
),
|
|
1927
|
-
jira_server: str | None = typer.Option(
|
|
1928
|
-
None,
|
|
1929
|
-
"--jira-server",
|
|
1930
|
-
help="JIRA server URL (e.g., https://company.atlassian.net)",
|
|
1931
|
-
),
|
|
1932
|
-
jira_email: str | None = typer.Option(
|
|
1933
|
-
None, "--jira-email", help="JIRA user email for authentication"
|
|
1934
|
-
),
|
|
1935
|
-
jira_project: str | None = typer.Option(
|
|
1936
|
-
None, "--jira-project", help="Default JIRA project key"
|
|
1937
|
-
),
|
|
1938
|
-
github_owner: str | None = typer.Option(
|
|
1939
|
-
None, "--github-owner", help="GitHub repository owner"
|
|
1940
|
-
),
|
|
1941
|
-
github_repo: str | None = typer.Option(
|
|
1942
|
-
None, "--github-repo", help="GitHub repository name"
|
|
1943
|
-
),
|
|
1944
|
-
github_token: str | None = typer.Option(
|
|
1945
|
-
None, "--github-token", help="GitHub Personal Access Token"
|
|
1946
|
-
),
|
|
1947
|
-
dry_run: bool = typer.Option(
|
|
1948
|
-
False,
|
|
1949
|
-
"--dry-run",
|
|
1950
|
-
help="Show what would be done without making changes (for platform installation)",
|
|
1951
|
-
),
|
|
1952
|
-
) -> None:
|
|
1953
|
-
"""Install MCP for AI platforms OR initialize adapter setup.
|
|
1954
|
-
|
|
1955
|
-
With platform argument (new syntax): Install MCP configuration for AI platforms
|
|
1956
|
-
Without platform argument (legacy): Run adapter setup wizard (same as 'init' and 'setup')
|
|
1957
|
-
|
|
1958
|
-
New Command Structure:
|
|
1959
|
-
# Install MCP for AI platforms
|
|
1960
|
-
mcp-ticketer install claude-code # Claude Code (project-level)
|
|
1961
|
-
mcp-ticketer install claude-desktop # Claude Desktop (global)
|
|
1962
|
-
mcp-ticketer install gemini # Gemini CLI
|
|
1963
|
-
mcp-ticketer install codex # Codex
|
|
1964
|
-
mcp-ticketer install auggie # Auggie
|
|
1965
|
-
|
|
1966
|
-
Legacy Adapter Setup (still supported):
|
|
1967
|
-
mcp-ticketer install # Interactive setup wizard
|
|
1968
|
-
mcp-ticketer install --adapter linear
|
|
1969
|
-
|
|
1970
|
-
"""
|
|
1971
|
-
# If platform argument is provided, handle MCP platform installation (NEW SYNTAX)
|
|
1972
|
-
if platform is not None:
|
|
1973
|
-
# Import configuration functions
|
|
1974
|
-
from .auggie_configure import configure_auggie_mcp
|
|
1975
|
-
from .codex_configure import configure_codex_mcp
|
|
1976
|
-
from .gemini_configure import configure_gemini_mcp
|
|
1977
|
-
from .mcp_configure import configure_claude_mcp
|
|
1978
|
-
|
|
1979
|
-
# Map platform names to configuration functions
|
|
1980
|
-
platform_mapping = {
|
|
1981
|
-
"claude-code": {
|
|
1982
|
-
"func": lambda: configure_claude_mcp(global_config=False, force=True),
|
|
1983
|
-
"name": "Claude Code",
|
|
1984
|
-
},
|
|
1985
|
-
"claude-desktop": {
|
|
1986
|
-
"func": lambda: configure_claude_mcp(global_config=True, force=True),
|
|
1987
|
-
"name": "Claude Desktop",
|
|
1988
|
-
},
|
|
1989
|
-
"auggie": {
|
|
1990
|
-
"func": lambda: configure_auggie_mcp(force=True),
|
|
1991
|
-
"name": "Auggie",
|
|
1992
|
-
},
|
|
1993
|
-
"gemini": {
|
|
1994
|
-
"func": lambda: configure_gemini_mcp(scope="project", force=True),
|
|
1995
|
-
"name": "Gemini CLI",
|
|
1996
|
-
},
|
|
1997
|
-
"codex": {
|
|
1998
|
-
"func": lambda: configure_codex_mcp(force=True),
|
|
1999
|
-
"name": "Codex",
|
|
2000
|
-
},
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
if platform not in platform_mapping:
|
|
2004
|
-
console.print(f"[red]Unknown platform: {platform}[/red]")
|
|
2005
|
-
console.print("\n[bold]Available platforms:[/bold]")
|
|
2006
|
-
for p in platform_mapping.keys():
|
|
2007
|
-
console.print(f" • {p}")
|
|
2008
|
-
raise typer.Exit(1)
|
|
2009
|
-
|
|
2010
|
-
config = platform_mapping[platform]
|
|
2011
|
-
|
|
2012
|
-
if dry_run:
|
|
2013
|
-
console.print(f"[cyan]DRY RUN - Would install for {config['name']}[/cyan]")
|
|
2014
|
-
return
|
|
2015
|
-
|
|
2016
|
-
try:
|
|
2017
|
-
config["func"]()
|
|
2018
|
-
except Exception as e:
|
|
2019
|
-
console.print(f"[red]Installation failed: {e}[/red]")
|
|
2020
|
-
raise typer.Exit(1)
|
|
2021
|
-
return
|
|
2022
|
-
|
|
2023
|
-
# Otherwise, delegate to init for adapter initialization (LEGACY BEHAVIOR)
|
|
2024
|
-
# This makes 'install' and 'init' synonymous when called without platform argument
|
|
2025
|
-
init(
|
|
2026
|
-
adapter=adapter,
|
|
2027
|
-
project_path=project_path,
|
|
2028
|
-
global_config=global_config,
|
|
2029
|
-
base_path=base_path,
|
|
2030
|
-
api_key=api_key,
|
|
2031
|
-
team_id=team_id,
|
|
2032
|
-
jira_server=jira_server,
|
|
2033
|
-
jira_email=jira_email,
|
|
2034
|
-
jira_project=jira_project,
|
|
2035
|
-
github_owner=github_owner,
|
|
2036
|
-
github_repo=github_repo,
|
|
2037
|
-
github_token=github_token,
|
|
2038
|
-
)
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
@app.command()
|
|
2042
|
-
def remove(
|
|
2043
|
-
platform: str | None = typer.Argument(
|
|
2044
|
-
None,
|
|
2045
|
-
help="Platform to remove (claude-code, claude-desktop, auggie, gemini, codex)",
|
|
2046
|
-
),
|
|
2047
|
-
dry_run: bool = typer.Option(
|
|
2048
|
-
False, "--dry-run", help="Show what would be done without making changes"
|
|
2049
|
-
),
|
|
2050
|
-
) -> None:
|
|
2051
|
-
"""Remove mcp-ticketer from AI platforms.
|
|
2052
|
-
|
|
2053
|
-
Without arguments, shows help and available platforms.
|
|
2054
|
-
With a platform argument, removes MCP configuration for that platform.
|
|
2055
|
-
|
|
2056
|
-
Examples:
|
|
2057
|
-
# Remove from Claude Code (project-level)
|
|
2058
|
-
mcp-ticketer remove claude-code
|
|
2059
|
-
|
|
2060
|
-
# Remove from Claude Desktop (global)
|
|
2061
|
-
mcp-ticketer remove claude-desktop
|
|
2062
|
-
|
|
2063
|
-
# Remove from Auggie
|
|
2064
|
-
mcp-ticketer remove auggie
|
|
2065
|
-
|
|
2066
|
-
# Dry run to preview changes
|
|
2067
|
-
mcp-ticketer remove claude-code --dry-run
|
|
2068
|
-
|
|
2069
|
-
"""
|
|
2070
|
-
# If no platform specified, show help message
|
|
2071
|
-
if platform is None:
|
|
2072
|
-
console.print("[bold]Remove mcp-ticketer from AI platforms[/bold]\n")
|
|
2073
|
-
console.print("Usage: mcp-ticketer remove <platform>\n")
|
|
2074
|
-
console.print("[bold]Available platforms:[/bold]")
|
|
2075
|
-
console.print(" • claude-code - Claude Code (project-level)")
|
|
2076
|
-
console.print(" • claude-desktop - Claude Desktop (global)")
|
|
2077
|
-
console.print(" • auggie - Auggie (global)")
|
|
2078
|
-
console.print(" • gemini - Gemini CLI (project-level by default)")
|
|
2079
|
-
console.print(" • codex - Codex (global)")
|
|
2080
|
-
return
|
|
2081
|
-
|
|
2082
|
-
# Import removal functions
|
|
2083
|
-
from .auggie_configure import remove_auggie_mcp
|
|
2084
|
-
from .codex_configure import remove_codex_mcp
|
|
2085
|
-
from .gemini_configure import remove_gemini_mcp
|
|
2086
|
-
from .mcp_configure import remove_claude_mcp
|
|
2087
|
-
|
|
2088
|
-
# Map platform names to removal functions
|
|
2089
|
-
platform_mapping = {
|
|
2090
|
-
"claude-code": {
|
|
2091
|
-
"func": lambda: remove_claude_mcp(global_config=False, dry_run=dry_run),
|
|
2092
|
-
"name": "Claude Code",
|
|
2093
|
-
},
|
|
2094
|
-
"claude-desktop": {
|
|
2095
|
-
"func": lambda: remove_claude_mcp(global_config=True, dry_run=dry_run),
|
|
2096
|
-
"name": "Claude Desktop",
|
|
2097
|
-
},
|
|
2098
|
-
"auggie": {
|
|
2099
|
-
"func": lambda: remove_auggie_mcp(dry_run=dry_run),
|
|
2100
|
-
"name": "Auggie",
|
|
2101
|
-
},
|
|
2102
|
-
"gemini": {
|
|
2103
|
-
"func": lambda: remove_gemini_mcp(scope="project", dry_run=dry_run),
|
|
2104
|
-
"name": "Gemini CLI",
|
|
2105
|
-
},
|
|
2106
|
-
"codex": {
|
|
2107
|
-
"func": lambda: remove_codex_mcp(dry_run=dry_run),
|
|
2108
|
-
"name": "Codex",
|
|
2109
|
-
},
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
if platform not in platform_mapping:
|
|
2113
|
-
console.print(f"[red]Unknown platform: {platform}[/red]")
|
|
2114
|
-
console.print("\n[bold]Available platforms:[/bold]")
|
|
2115
|
-
for p in platform_mapping.keys():
|
|
2116
|
-
console.print(f" • {p}")
|
|
2117
|
-
raise typer.Exit(1)
|
|
2118
|
-
|
|
2119
|
-
config = platform_mapping[platform]
|
|
2120
|
-
|
|
2121
|
-
try:
|
|
2122
|
-
config["func"]()
|
|
2123
|
-
except Exception as e:
|
|
2124
|
-
console.print(f"[red]Removal failed: {e}[/red]")
|
|
2125
|
-
raise typer.Exit(1)
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
@app.command()
|
|
2129
|
-
def uninstall(
|
|
2130
|
-
platform: str | None = typer.Argument(
|
|
2131
|
-
None,
|
|
2132
|
-
help="Platform to uninstall (claude-code, claude-desktop, auggie, gemini, codex)",
|
|
2133
|
-
),
|
|
2134
|
-
dry_run: bool = typer.Option(
|
|
2135
|
-
False, "--dry-run", help="Show what would be done without making changes"
|
|
2136
|
-
),
|
|
2137
|
-
) -> None:
|
|
2138
|
-
"""Uninstall mcp-ticketer from AI platforms (alias for remove).
|
|
2139
|
-
|
|
2140
|
-
This is an alias for the 'remove' command.
|
|
2141
|
-
|
|
2142
|
-
Without arguments, shows help and available platforms.
|
|
2143
|
-
With a platform argument, removes MCP configuration for that platform.
|
|
2144
|
-
|
|
2145
|
-
Examples:
|
|
2146
|
-
# Uninstall from Claude Code (project-level)
|
|
2147
|
-
mcp-ticketer uninstall claude-code
|
|
2148
|
-
|
|
2149
|
-
# Uninstall from Claude Desktop (global)
|
|
2150
|
-
mcp-ticketer uninstall claude-desktop
|
|
2151
|
-
|
|
2152
|
-
# Uninstall from Auggie
|
|
2153
|
-
mcp-ticketer uninstall auggie
|
|
2154
|
-
|
|
2155
|
-
# Dry run to preview changes
|
|
2156
|
-
mcp-ticketer uninstall claude-code --dry-run
|
|
2157
|
-
|
|
2158
|
-
"""
|
|
2159
|
-
# Call the remove command with the same parameters
|
|
2160
|
-
remove(platform=platform, dry_run=dry_run)
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
@app.command(deprecated=True, hidden=True)
|
|
2164
|
-
def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
|
|
2165
|
-
"""Check status of a queued operation.
|
|
2166
|
-
|
|
2167
|
-
DEPRECATED: Use 'mcp-ticketer ticket check' instead.
|
|
2168
|
-
"""
|
|
2169
|
-
console.print(
|
|
2170
|
-
"[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket check' instead.[/yellow]\n"
|
|
2171
|
-
)
|
|
2172
|
-
queue = Queue()
|
|
2173
|
-
item = queue.get_item(queue_id)
|
|
2174
|
-
|
|
2175
|
-
if not item:
|
|
2176
|
-
console.print(f"[red]Queue item not found: {queue_id}[/red]")
|
|
2177
|
-
raise typer.Exit(1)
|
|
2178
|
-
|
|
2179
|
-
# Display status
|
|
2180
|
-
console.print(f"\n[bold]Queue Item: {item.id}[/bold]")
|
|
2181
|
-
console.print(f"Operation: {item.operation}")
|
|
2182
|
-
console.print(f"Adapter: {item.adapter}")
|
|
2183
|
-
|
|
2184
|
-
# Status with color
|
|
2185
|
-
if item.status == QueueStatus.COMPLETED:
|
|
2186
|
-
console.print(f"Status: [green]{item.status}[/green]")
|
|
2187
|
-
elif item.status == QueueStatus.FAILED:
|
|
2188
|
-
console.print(f"Status: [red]{item.status}[/red]")
|
|
2189
|
-
elif item.status == QueueStatus.PROCESSING:
|
|
2190
|
-
console.print(f"Status: [yellow]{item.status}[/yellow]")
|
|
2191
|
-
else:
|
|
2192
|
-
console.print(f"Status: {item.status}")
|
|
2193
|
-
|
|
2194
|
-
# Timestamps
|
|
2195
|
-
console.print(f"Created: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
2196
|
-
if item.processed_at:
|
|
2197
|
-
console.print(f"Processed: {item.processed_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
2198
|
-
|
|
2199
|
-
# Error or result
|
|
2200
|
-
if item.error_message:
|
|
2201
|
-
console.print(f"\n[red]Error:[/red] {item.error_message}")
|
|
2202
|
-
elif item.result:
|
|
2203
|
-
console.print("\n[green]Result:[/green]")
|
|
2204
|
-
for key, value in item.result.items():
|
|
2205
|
-
console.print(f" {key}: {value}")
|
|
2206
|
-
|
|
2207
|
-
if item.retry_count > 0:
|
|
2208
|
-
console.print(f"\nRetry Count: {item.retry_count}")
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
@mcp_app.command(name="serve")
|
|
2212
|
-
def mcp_serve(
|
|
2213
|
-
adapter: AdapterType | None = typer.Option(
|
|
2214
|
-
None, "--adapter", "-a", help="Override default adapter type"
|
|
2215
|
-
),
|
|
2216
|
-
base_path: str | None = typer.Option(
|
|
2217
|
-
None, "--base-path", help="Base path for AITrackdown adapter"
|
|
2218
|
-
),
|
|
2219
|
-
):
|
|
2220
|
-
"""Start MCP server for JSON-RPC communication over stdio.
|
|
2221
|
-
|
|
2222
|
-
This command is used by Claude Code/Desktop when connecting to the MCP server.
|
|
2223
|
-
You typically don't need to run this manually - use 'mcp-ticketer install add' to configure.
|
|
2224
|
-
|
|
2225
|
-
Configuration Resolution:
|
|
2226
|
-
- When MCP server starts, it uses the current working directory (cwd)
|
|
2227
|
-
- The cwd is set by Claude Code/Desktop from the 'cwd' field in .mcp/config.json
|
|
2228
|
-
- Configuration is loaded with this priority:
|
|
2229
|
-
1. Project-specific: .mcp-ticketer/config.json in cwd
|
|
2230
|
-
2. Global: ~/.mcp-ticketer/config.json
|
|
2231
|
-
3. Default: aitrackdown adapter with .aitrackdown base path
|
|
2232
|
-
"""
|
|
2233
|
-
from ..mcp.server.server_sdk import configure_adapter
|
|
2234
|
-
from ..mcp.server.server_sdk import main as sdk_main
|
|
2235
|
-
|
|
2236
|
-
# Load configuration (respects project-specific config in cwd)
|
|
2237
|
-
config = load_config()
|
|
2238
|
-
|
|
2239
|
-
# Determine adapter type with priority: CLI arg > .env files > config > default
|
|
2240
|
-
if adapter:
|
|
2241
|
-
# Priority 1: Command line argument
|
|
2242
|
-
adapter_type = adapter.value
|
|
2243
|
-
# Get base config from config file
|
|
2244
|
-
adapters_config = config.get("adapters", {})
|
|
2245
|
-
adapter_config = adapters_config.get(adapter_type, {})
|
|
2246
|
-
else:
|
|
2247
|
-
# Priority 2: .env files
|
|
2248
|
-
from ..mcp.server.main import _load_env_configuration
|
|
2249
|
-
|
|
2250
|
-
env_config = _load_env_configuration()
|
|
2251
|
-
if env_config:
|
|
2252
|
-
adapter_type = env_config["adapter_type"]
|
|
2253
|
-
adapter_config = env_config["adapter_config"]
|
|
2254
|
-
else:
|
|
2255
|
-
# Priority 3: Configuration file
|
|
2256
|
-
adapter_type = config.get("default_adapter", "aitrackdown")
|
|
2257
|
-
adapters_config = config.get("adapters", {})
|
|
2258
|
-
adapter_config = adapters_config.get(adapter_type, {})
|
|
2259
|
-
|
|
2260
|
-
# Override with command line options if provided (highest priority)
|
|
2261
|
-
if base_path and adapter_type == "aitrackdown":
|
|
2262
|
-
adapter_config["base_path"] = base_path
|
|
2263
|
-
|
|
2264
|
-
# Fallback to legacy config format
|
|
2265
|
-
if not adapter_config and "config" in config:
|
|
2266
|
-
adapter_config = config["config"]
|
|
2267
|
-
|
|
2268
|
-
# MCP server uses stdio for JSON-RPC, so we can't print to stdout
|
|
2269
|
-
# Only print to stderr to avoid interfering with the protocol
|
|
2270
|
-
import sys
|
|
2271
|
-
|
|
2272
|
-
if sys.stderr.isatty():
|
|
2273
|
-
# Only print if stderr is a terminal (not redirected)
|
|
2274
|
-
console.file = sys.stderr
|
|
2275
|
-
console.print(
|
|
2276
|
-
f"[green]Starting MCP SDK server[/green] with {adapter_type} adapter"
|
|
2277
|
-
)
|
|
2278
|
-
console.print(
|
|
2279
|
-
"[dim]Server running on stdio. Send JSON-RPC requests via stdin.[/dim]"
|
|
2280
|
-
)
|
|
2281
|
-
|
|
2282
|
-
# Configure adapter and run SDK server
|
|
2283
|
-
try:
|
|
2284
|
-
configure_adapter(adapter_type, adapter_config)
|
|
2285
|
-
sdk_main()
|
|
2286
|
-
except KeyboardInterrupt:
|
|
2287
|
-
# Send this to stderr
|
|
2288
|
-
if sys.stderr.isatty():
|
|
2289
|
-
console.print("\n[yellow]Server stopped by user[/yellow]")
|
|
2290
|
-
sys.exit(0)
|
|
2291
|
-
except Exception as e:
|
|
2292
|
-
# Log error to stderr
|
|
2293
|
-
sys.stderr.write(f"MCP server error: {e}\n")
|
|
2294
|
-
sys.exit(1)
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
@mcp_app.command(name="claude")
|
|
2298
|
-
def mcp_claude(
|
|
2299
|
-
global_config: bool = typer.Option(
|
|
2300
|
-
False,
|
|
2301
|
-
"--global",
|
|
2302
|
-
"-g",
|
|
2303
|
-
help="Configure Claude Desktop instead of project-level",
|
|
2304
|
-
),
|
|
2305
|
-
force: bool = typer.Option(
|
|
2306
|
-
False, "--force", "-f", help="Overwrite existing configuration"
|
|
2307
|
-
),
|
|
2308
|
-
):
|
|
2309
|
-
"""Configure Claude Code to use mcp-ticketer MCP server.
|
|
2310
|
-
|
|
2311
|
-
Reads configuration from .mcp-ticketer/config.json and updates
|
|
2312
|
-
Claude Code's MCP settings accordingly.
|
|
2313
|
-
|
|
2314
|
-
By default, configures project-level (.mcp/config.json).
|
|
2315
|
-
Use --global to configure Claude Desktop instead.
|
|
2316
|
-
|
|
2317
|
-
Examples:
|
|
2318
|
-
# Configure for current project (default)
|
|
2319
|
-
mcp-ticketer mcp claude
|
|
2320
|
-
|
|
2321
|
-
# Configure Claude Desktop globally
|
|
2322
|
-
mcp-ticketer mcp claude --global
|
|
2323
|
-
|
|
2324
|
-
# Force overwrite existing configuration
|
|
2325
|
-
mcp-ticketer mcp claude --force
|
|
2326
|
-
|
|
2327
|
-
"""
|
|
2328
|
-
from ..cli.mcp_configure import configure_claude_mcp
|
|
2329
|
-
|
|
2330
|
-
try:
|
|
2331
|
-
configure_claude_mcp(global_config=global_config, force=force)
|
|
2332
|
-
except Exception as e:
|
|
2333
|
-
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
2334
|
-
raise typer.Exit(1)
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
@mcp_app.command(name="gemini")
|
|
2338
|
-
def mcp_gemini(
|
|
2339
|
-
scope: str = typer.Option(
|
|
2340
|
-
"project",
|
|
2341
|
-
"--scope",
|
|
2342
|
-
"-s",
|
|
2343
|
-
help="Configuration scope: 'project' (default) or 'user'",
|
|
2344
|
-
),
|
|
2345
|
-
force: bool = typer.Option(
|
|
2346
|
-
False, "--force", "-f", help="Overwrite existing configuration"
|
|
2347
|
-
),
|
|
2348
|
-
):
|
|
2349
|
-
"""Configure Gemini CLI to use mcp-ticketer MCP server.
|
|
2350
|
-
|
|
2351
|
-
Reads configuration from .mcp-ticketer/config.json and creates
|
|
2352
|
-
Gemini CLI settings file with mcp-ticketer configuration.
|
|
2353
|
-
|
|
2354
|
-
By default, configures project-level (.gemini/settings.json).
|
|
2355
|
-
Use --scope user to configure user-level (~/.gemini/settings.json).
|
|
2356
|
-
|
|
2357
|
-
Examples:
|
|
2358
|
-
# Configure for current project (default)
|
|
2359
|
-
mcp-ticketer mcp gemini
|
|
2360
|
-
|
|
2361
|
-
# Configure at user level
|
|
2362
|
-
mcp-ticketer mcp gemini --scope user
|
|
2363
|
-
|
|
2364
|
-
# Force overwrite existing configuration
|
|
2365
|
-
mcp-ticketer mcp gemini --force
|
|
2366
|
-
|
|
2367
|
-
"""
|
|
2368
|
-
from ..cli.gemini_configure import configure_gemini_mcp
|
|
2369
|
-
|
|
2370
|
-
# Validate scope parameter
|
|
2371
|
-
if scope not in ["project", "user"]:
|
|
2372
|
-
console.print(
|
|
2373
|
-
f"[red]✗ Invalid scope:[/red] '{scope}'. Must be 'project' or 'user'"
|
|
2374
|
-
)
|
|
2375
|
-
raise typer.Exit(1)
|
|
2376
|
-
|
|
2377
|
-
try:
|
|
2378
|
-
configure_gemini_mcp(scope=scope, force=force) # type: ignore
|
|
2379
|
-
except Exception as e:
|
|
2380
|
-
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
2381
|
-
raise typer.Exit(1)
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
@mcp_app.command(name="codex")
|
|
2385
|
-
def mcp_codex(
|
|
2386
|
-
force: bool = typer.Option(
|
|
2387
|
-
False, "--force", "-f", help="Overwrite existing configuration"
|
|
2388
|
-
),
|
|
2389
|
-
):
|
|
2390
|
-
"""Configure Codex CLI to use mcp-ticketer MCP server.
|
|
2391
|
-
|
|
2392
|
-
Reads configuration from .mcp-ticketer/config.json and creates
|
|
2393
|
-
Codex CLI config.toml with mcp-ticketer configuration.
|
|
2394
|
-
|
|
2395
|
-
IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
2396
|
-
There is no project-level configuration support. After configuration,
|
|
2397
|
-
you must restart Codex CLI for changes to take effect.
|
|
2398
|
-
|
|
2399
|
-
Examples:
|
|
2400
|
-
# Configure Codex CLI globally
|
|
2401
|
-
mcp-ticketer mcp codex
|
|
2402
|
-
|
|
2403
|
-
# Force overwrite existing configuration
|
|
2404
|
-
mcp-ticketer mcp codex --force
|
|
2405
|
-
|
|
2406
|
-
"""
|
|
2407
|
-
from ..cli.codex_configure import configure_codex_mcp
|
|
2408
|
-
|
|
2409
|
-
try:
|
|
2410
|
-
configure_codex_mcp(force=force)
|
|
2411
|
-
except Exception as e:
|
|
2412
|
-
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
2413
|
-
raise typer.Exit(1)
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
@mcp_app.command(name="auggie")
|
|
2417
|
-
def mcp_auggie(
|
|
2418
|
-
force: bool = typer.Option(
|
|
2419
|
-
False, "--force", "-f", help="Overwrite existing configuration"
|
|
2420
|
-
),
|
|
2421
|
-
):
|
|
2422
|
-
"""Configure Auggie CLI to use mcp-ticketer MCP server.
|
|
2423
|
-
|
|
2424
|
-
Reads configuration from .mcp-ticketer/config.json and creates
|
|
2425
|
-
Auggie CLI settings.json with mcp-ticketer configuration.
|
|
2426
|
-
|
|
2427
|
-
IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
|
|
2428
|
-
There is no project-level configuration support. After configuration,
|
|
2429
|
-
you must restart Auggie CLI for changes to take effect.
|
|
2430
|
-
|
|
2431
|
-
Examples:
|
|
2432
|
-
# Configure Auggie CLI globally
|
|
2433
|
-
mcp-ticketer mcp auggie
|
|
2434
|
-
|
|
2435
|
-
# Force overwrite existing configuration
|
|
2436
|
-
mcp-ticketer mcp auggie --force
|
|
2437
|
-
|
|
2438
|
-
"""
|
|
2439
|
-
from ..cli.auggie_configure import configure_auggie_mcp
|
|
2440
|
-
|
|
2441
|
-
try:
|
|
2442
|
-
configure_auggie_mcp(force=force)
|
|
2443
|
-
except Exception as e:
|
|
2444
|
-
console.print(f"[red]✗ Configuration failed:[/red] {e}")
|
|
2445
|
-
raise typer.Exit(1)
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
@mcp_app.command(name="status")
|
|
2449
|
-
def mcp_status():
|
|
2450
|
-
"""Check MCP server status.
|
|
2451
|
-
|
|
2452
|
-
Shows whether the MCP server is configured and running for various platforms.
|
|
2453
|
-
|
|
2454
|
-
Examples:
|
|
2455
|
-
mcp-ticketer mcp status
|
|
2456
|
-
|
|
2457
|
-
"""
|
|
2458
|
-
import json
|
|
2459
|
-
from pathlib import Path
|
|
2460
|
-
|
|
2461
|
-
console.print("[bold]MCP Server Status[/bold]\n")
|
|
2462
|
-
|
|
2463
|
-
# Check project-level configuration
|
|
2464
|
-
project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
2465
|
-
if project_config.exists():
|
|
2466
|
-
console.print(f"[green]✓[/green] Project config found: {project_config}")
|
|
2467
|
-
try:
|
|
2468
|
-
with open(project_config) as f:
|
|
2469
|
-
config = json.load(f)
|
|
2470
|
-
adapter = config.get("default_adapter", "aitrackdown")
|
|
2471
|
-
console.print(f" Default adapter: [cyan]{adapter}[/cyan]")
|
|
2472
|
-
except Exception as e:
|
|
2473
|
-
console.print(f" [yellow]Warning: Could not read config: {e}[/yellow]")
|
|
2474
|
-
else:
|
|
2475
|
-
console.print("[yellow]○[/yellow] No project config found")
|
|
2476
|
-
|
|
2477
|
-
# Check Claude Code configuration
|
|
2478
|
-
claude_code_config = Path.cwd() / ".mcp" / "config.json"
|
|
2479
|
-
if claude_code_config.exists():
|
|
2480
|
-
console.print(
|
|
2481
|
-
f"\n[green]✓[/green] Claude Code configured: {claude_code_config}"
|
|
2482
|
-
)
|
|
2483
|
-
else:
|
|
2484
|
-
console.print("\n[yellow]○[/yellow] Claude Code not configured")
|
|
2485
|
-
|
|
2486
|
-
# Check Claude Desktop configuration
|
|
2487
|
-
claude_desktop_config = (
|
|
2488
|
-
Path.home()
|
|
2489
|
-
/ "Library"
|
|
2490
|
-
/ "Application Support"
|
|
2491
|
-
/ "Claude"
|
|
2492
|
-
/ "claude_desktop_config.json"
|
|
2493
|
-
)
|
|
2494
|
-
if claude_desktop_config.exists():
|
|
2495
|
-
try:
|
|
2496
|
-
with open(claude_desktop_config) as f:
|
|
2497
|
-
config = json.load(f)
|
|
2498
|
-
if "mcpServers" in config and "mcp-ticketer" in config["mcpServers"]:
|
|
2499
|
-
console.print(
|
|
2500
|
-
f"[green]✓[/green] Claude Desktop configured: {claude_desktop_config}"
|
|
2501
|
-
)
|
|
2502
|
-
else:
|
|
2503
|
-
console.print(
|
|
2504
|
-
"[yellow]○[/yellow] Claude Desktop config exists but mcp-ticketer not found"
|
|
2505
|
-
)
|
|
2506
|
-
except Exception:
|
|
2507
|
-
console.print(
|
|
2508
|
-
"[yellow]○[/yellow] Claude Desktop config exists but could not be read"
|
|
2509
|
-
)
|
|
2510
|
-
else:
|
|
2511
|
-
console.print("[yellow]○[/yellow] Claude Desktop not configured")
|
|
2512
|
-
|
|
2513
|
-
# Check Gemini configuration
|
|
2514
|
-
gemini_project_config = Path.cwd() / ".gemini" / "settings.json"
|
|
2515
|
-
gemini_user_config = Path.home() / ".gemini" / "settings.json"
|
|
2516
|
-
if gemini_project_config.exists():
|
|
2517
|
-
console.print(
|
|
2518
|
-
f"\n[green]✓[/green] Gemini (project) configured: {gemini_project_config}"
|
|
2519
|
-
)
|
|
2520
|
-
elif gemini_user_config.exists():
|
|
2521
|
-
console.print(
|
|
2522
|
-
f"\n[green]✓[/green] Gemini (user) configured: {gemini_user_config}"
|
|
2523
|
-
)
|
|
2524
|
-
else:
|
|
2525
|
-
console.print("\n[yellow]○[/yellow] Gemini not configured")
|
|
2526
|
-
|
|
2527
|
-
# Check Codex configuration
|
|
2528
|
-
codex_config = Path.home() / ".codex" / "config.toml"
|
|
2529
|
-
if codex_config.exists():
|
|
2530
|
-
console.print(f"[green]✓[/green] Codex configured: {codex_config}")
|
|
2531
|
-
else:
|
|
2532
|
-
console.print("[yellow]○[/yellow] Codex not configured")
|
|
2533
|
-
|
|
2534
|
-
# Check Auggie configuration
|
|
2535
|
-
auggie_config = Path.home() / ".augment" / "settings.json"
|
|
2536
|
-
if auggie_config.exists():
|
|
2537
|
-
console.print(f"[green]✓[/green] Auggie configured: {auggie_config}")
|
|
2538
|
-
else:
|
|
2539
|
-
console.print("[yellow]○[/yellow] Auggie not configured")
|
|
2540
|
-
|
|
2541
|
-
console.print(
|
|
2542
|
-
"\n[dim]Run 'mcp-ticketer install <platform>' to configure a platform[/dim]"
|
|
2543
|
-
)
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
@mcp_app.command(name="stop")
|
|
2547
|
-
def mcp_stop():
|
|
2548
|
-
"""Stop MCP server (placeholder - MCP runs on-demand via stdio).
|
|
2549
|
-
|
|
2550
|
-
Note: The MCP server runs on-demand when AI clients connect via stdio.
|
|
2551
|
-
It doesn't run as a persistent background service, so there's nothing to stop.
|
|
2552
|
-
This command is provided for consistency but has no effect.
|
|
2553
|
-
|
|
2554
|
-
Examples:
|
|
2555
|
-
mcp-ticketer mcp stop
|
|
2556
|
-
|
|
2557
|
-
"""
|
|
2558
|
-
console.print(
|
|
2559
|
-
"[yellow]ℹ[/yellow] MCP server runs on-demand via stdio (not as a background service)"
|
|
2560
|
-
)
|
|
2561
|
-
console.print("There is no persistent server process to stop.")
|
|
2562
|
-
console.print(
|
|
2563
|
-
"\n[dim]The server starts automatically when AI clients connect and stops when they disconnect.[/dim]"
|
|
2564
|
-
)
|
|
618
|
+
raise typer.Exit(result) from None
|
|
2565
619
|
|
|
2566
620
|
|
|
2567
621
|
# Add command groups to main app (must be after all subcommands are defined)
|
|
2568
622
|
app.add_typer(mcp_app, name="mcp")
|
|
2569
623
|
|
|
2570
624
|
|
|
2571
|
-
def main():
|
|
2572
|
-
"""
|
|
625
|
+
def main() -> None:
|
|
626
|
+
"""Execute the main CLI application entry point."""
|
|
2573
627
|
app()
|
|
2574
628
|
|
|
2575
629
|
|