mcp-ticketer 0.4.2__py3-none-any.whl → 0.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcp-ticketer might be problematic. Click here for more details.

Files changed (54) hide show
  1. mcp_ticketer/__init__.py +3 -12
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +243 -11
  4. mcp_ticketer/adapters/github.py +15 -14
  5. mcp_ticketer/adapters/hybrid.py +11 -11
  6. mcp_ticketer/adapters/jira.py +22 -25
  7. mcp_ticketer/adapters/linear/adapter.py +9 -21
  8. mcp_ticketer/adapters/linear/client.py +2 -1
  9. mcp_ticketer/adapters/linear/mappers.py +2 -1
  10. mcp_ticketer/cache/memory.py +6 -5
  11. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  12. mcp_ticketer/cli/codex_configure.py +2 -2
  13. mcp_ticketer/cli/configure.py +7 -14
  14. mcp_ticketer/cli/diagnostics.py +2 -2
  15. mcp_ticketer/cli/discover.py +6 -11
  16. mcp_ticketer/cli/gemini_configure.py +2 -2
  17. mcp_ticketer/cli/linear_commands.py +6 -7
  18. mcp_ticketer/cli/main.py +218 -242
  19. mcp_ticketer/cli/mcp_configure.py +1 -2
  20. mcp_ticketer/cli/ticket_commands.py +27 -30
  21. mcp_ticketer/cli/utils.py +23 -22
  22. mcp_ticketer/core/__init__.py +3 -1
  23. mcp_ticketer/core/adapter.py +82 -13
  24. mcp_ticketer/core/config.py +27 -29
  25. mcp_ticketer/core/env_discovery.py +10 -10
  26. mcp_ticketer/core/env_loader.py +8 -8
  27. mcp_ticketer/core/http_client.py +16 -16
  28. mcp_ticketer/core/mappers.py +10 -10
  29. mcp_ticketer/core/models.py +50 -20
  30. mcp_ticketer/core/project_config.py +40 -34
  31. mcp_ticketer/core/registry.py +2 -2
  32. mcp_ticketer/mcp/dto.py +32 -32
  33. mcp_ticketer/mcp/response_builder.py +2 -2
  34. mcp_ticketer/mcp/server.py +17 -37
  35. mcp_ticketer/mcp/server_sdk.py +2 -2
  36. mcp_ticketer/mcp/tools/__init__.py +7 -9
  37. mcp_ticketer/mcp/tools/attachment_tools.py +3 -4
  38. mcp_ticketer/mcp/tools/comment_tools.py +2 -2
  39. mcp_ticketer/mcp/tools/hierarchy_tools.py +8 -8
  40. mcp_ticketer/mcp/tools/pr_tools.py +2 -2
  41. mcp_ticketer/mcp/tools/search_tools.py +6 -6
  42. mcp_ticketer/mcp/tools/ticket_tools.py +12 -12
  43. mcp_ticketer/queue/health_monitor.py +4 -4
  44. mcp_ticketer/queue/manager.py +2 -2
  45. mcp_ticketer/queue/queue.py +16 -16
  46. mcp_ticketer/queue/ticket_registry.py +7 -7
  47. mcp_ticketer/queue/worker.py +2 -2
  48. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/METADATA +61 -2
  49. mcp_ticketer-0.4.3.dist-info/RECORD +73 -0
  50. mcp_ticketer-0.4.2.dist-info/RECORD +0 -73
  51. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/WHEEL +0 -0
  52. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/entry_points.txt +0 -0
  53. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/licenses/LICENSE +0 -0
  54. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py CHANGED
@@ -5,7 +5,6 @@ import json
5
5
  import os
6
6
  from enum import Enum
7
7
  from pathlib import Path
8
- from typing import Optional
9
8
 
10
9
  import typer
11
10
  from dotenv import load_dotenv
@@ -21,7 +20,8 @@ from ..core.models import Comment, SearchQuery
21
20
  from ..queue import Queue, QueueStatus, WorkerManager
22
21
  from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
23
22
  from ..queue.ticket_registry import TicketRegistry
24
- from .configure import configure_wizard, set_adapter_config, show_current_config
23
+ from .configure import (configure_wizard, set_adapter_config,
24
+ show_current_config)
25
25
  from .diagnostics import run_diagnostics
26
26
  from .discover import app as discover_app
27
27
  from .migrate_config import migrate_config_command
@@ -84,7 +84,7 @@ class AdapterType(str, Enum):
84
84
  GITHUB = "github"
85
85
 
86
86
 
87
- def load_config(project_dir: Optional[Path] = None) -> dict:
87
+ def load_config(project_dir: Path | None = None) -> dict:
88
88
  """Load configuration from project-local config file ONLY.
89
89
 
90
90
  SECURITY: This method ONLY reads from the current project directory
@@ -146,7 +146,7 @@ def load_config(project_dir: Optional[Path] = None) -> dict:
146
146
  return {"adapter": "aitrackdown", "config": {"base_path": ".aitrackdown"}}
147
147
 
148
148
 
149
- def _discover_from_env_files() -> Optional[str]:
149
+ def _discover_from_env_files() -> str | None:
150
150
  """Discover adapter configuration from .env or .env.local files.
151
151
 
152
152
  Returns:
@@ -270,7 +270,7 @@ def merge_config(updates: dict) -> dict:
270
270
 
271
271
 
272
272
  def get_adapter(
273
- override_adapter: Optional[str] = None, override_config: Optional[dict] = None
273
+ override_adapter: str | None = None, override_config: dict | None = None
274
274
  ):
275
275
  """Get configured adapter instance.
276
276
 
@@ -387,13 +387,13 @@ def _prompt_for_adapter_selection(console: Console) -> str:
387
387
 
388
388
  @app.command()
389
389
  def setup(
390
- adapter: Optional[str] = typer.Option(
390
+ adapter: str | None = typer.Option(
391
391
  None,
392
392
  "--adapter",
393
393
  "-a",
394
394
  help="Adapter type to use (interactive prompt if not specified)",
395
395
  ),
396
- project_path: Optional[str] = typer.Option(
396
+ project_path: str | None = typer.Option(
397
397
  None, "--path", help="Project path (default: current directory)"
398
398
  ),
399
399
  global_config: bool = typer.Option(
@@ -402,36 +402,36 @@ def setup(
402
402
  "-g",
403
403
  help="Save to global config instead of project-specific",
404
404
  ),
405
- base_path: Optional[str] = typer.Option(
405
+ base_path: str | None = typer.Option(
406
406
  None,
407
407
  "--base-path",
408
408
  "-p",
409
409
  help="Base path for ticket storage (AITrackdown only)",
410
410
  ),
411
- api_key: Optional[str] = typer.Option(
411
+ api_key: str | None = typer.Option(
412
412
  None, "--api-key", help="API key for Linear or API token for JIRA"
413
413
  ),
414
- team_id: Optional[str] = typer.Option(
414
+ team_id: str | None = typer.Option(
415
415
  None, "--team-id", help="Linear team ID (required for Linear adapter)"
416
416
  ),
417
- jira_server: Optional[str] = typer.Option(
417
+ jira_server: str | None = typer.Option(
418
418
  None,
419
419
  "--jira-server",
420
420
  help="JIRA server URL (e.g., https://company.atlassian.net)",
421
421
  ),
422
- jira_email: Optional[str] = typer.Option(
422
+ jira_email: str | None = typer.Option(
423
423
  None, "--jira-email", help="JIRA user email for authentication"
424
424
  ),
425
- jira_project: Optional[str] = typer.Option(
425
+ jira_project: str | None = typer.Option(
426
426
  None, "--jira-project", help="Default JIRA project key"
427
427
  ),
428
- github_owner: Optional[str] = typer.Option(
428
+ github_owner: str | None = typer.Option(
429
429
  None, "--github-owner", help="GitHub repository owner"
430
430
  ),
431
- github_repo: Optional[str] = typer.Option(
431
+ github_repo: str | None = typer.Option(
432
432
  None, "--github-repo", help="GitHub repository name"
433
433
  ),
434
- github_token: Optional[str] = typer.Option(
434
+ github_token: str | None = typer.Option(
435
435
  None, "--github-token", help="GitHub Personal Access Token"
436
436
  ),
437
437
  ) -> None:
@@ -471,13 +471,13 @@ def setup(
471
471
 
472
472
  @app.command()
473
473
  def init(
474
- adapter: Optional[str] = typer.Option(
474
+ adapter: str | None = typer.Option(
475
475
  None,
476
476
  "--adapter",
477
477
  "-a",
478
478
  help="Adapter type to use (interactive prompt if not specified)",
479
479
  ),
480
- project_path: Optional[str] = typer.Option(
480
+ project_path: str | None = typer.Option(
481
481
  None, "--path", help="Project path (default: current directory)"
482
482
  ),
483
483
  global_config: bool = typer.Option(
@@ -486,40 +486,40 @@ def init(
486
486
  "-g",
487
487
  help="Save to global config instead of project-specific",
488
488
  ),
489
- base_path: Optional[str] = typer.Option(
489
+ base_path: str | None = typer.Option(
490
490
  None,
491
491
  "--base-path",
492
492
  "-p",
493
493
  help="Base path for ticket storage (AITrackdown only)",
494
494
  ),
495
- api_key: Optional[str] = typer.Option(
495
+ api_key: str | None = typer.Option(
496
496
  None, "--api-key", help="API key for Linear or API token for JIRA"
497
497
  ),
498
- team_id: Optional[str] = typer.Option(
498
+ team_id: str | None = typer.Option(
499
499
  None, "--team-id", help="Linear team ID (required for Linear adapter)"
500
500
  ),
501
- jira_server: Optional[str] = typer.Option(
501
+ jira_server: str | None = typer.Option(
502
502
  None,
503
503
  "--jira-server",
504
504
  help="JIRA server URL (e.g., https://company.atlassian.net)",
505
505
  ),
506
- jira_email: Optional[str] = typer.Option(
506
+ jira_email: str | None = typer.Option(
507
507
  None, "--jira-email", help="JIRA user email for authentication"
508
508
  ),
509
- jira_project: Optional[str] = typer.Option(
509
+ jira_project: str | None = typer.Option(
510
510
  None, "--jira-project", help="Default JIRA project key"
511
511
  ),
512
- github_owner: Optional[str] = typer.Option(
512
+ github_owner: str | None = typer.Option(
513
513
  None, "--github-owner", help="GitHub repository owner"
514
514
  ),
515
- github_repo: Optional[str] = typer.Option(
515
+ github_repo: str | None = typer.Option(
516
516
  None, "--github-repo", help="GitHub repository name"
517
517
  ),
518
- github_token: Optional[str] = typer.Option(
518
+ github_token: str | None = typer.Option(
519
519
  None, "--github-token", help="GitHub Personal Access Token"
520
520
  ),
521
521
  ) -> None:
522
- """Initialize mcp-ticketer for the current project.
522
+ """Initialize mcp-ticketer for the current project (synonymous with 'install' and 'setup').
523
523
 
524
524
  This command sets up MCP Ticketer configuration with interactive prompts
525
525
  to guide you through the process. It auto-detects adapter configuration
@@ -528,11 +528,13 @@ def init(
528
528
  Creates .mcp-ticketer/config.json in the current directory with
529
529
  auto-detected or specified adapter configuration.
530
530
 
531
- Note: 'setup' and 'install' are synonyms for this command.
531
+ Note: 'init', 'install', and 'setup' are all synonyms - use whichever feels natural.
532
532
 
533
533
  Examples:
534
- # Interactive setup (same as 'setup' and 'install')
534
+ # Interactive setup (all three commands are identical)
535
535
  mcp-ticketer init
536
+ mcp-ticketer install
537
+ mcp-ticketer setup
536
538
 
537
539
  # Force specific adapter
538
540
  mcp-ticketer init --adapter linear
@@ -669,20 +671,29 @@ def init(
669
671
  if linear_api_key:
670
672
  linear_config["api_key"] = linear_api_key
671
673
 
672
- # Team ID
674
+ # Team ID or Team Key
675
+ # Try environment variables first
676
+ linear_team_key = os.getenv("LINEAR_TEAM_KEY")
673
677
  linear_team_id = team_id or os.getenv("LINEAR_TEAM_ID")
674
- if not linear_team_id and not discovered:
675
- console.print("\nYou need your Linear team ID.")
676
- console.print("[dim]Find it in Linear settings or team URL[/dim]\n")
677
678
 
678
- linear_team_id = typer.prompt("Enter your Linear team ID")
679
+ if not linear_team_key and not linear_team_id and not discovered:
680
+ console.print("\n[bold]Linear Team Configuration[/bold]")
681
+ console.print("Enter your team key (e.g., 'ENG', 'DESIGN', 'PRODUCT')")
682
+ console.print("[dim]Find it in: Linear Settings → Teams → Your Team → Key field[/dim]\n")
683
+
684
+ linear_team_key = typer.prompt("Team key")
679
685
 
686
+ # Save whichever was provided
687
+ if linear_team_key:
688
+ linear_config["team_key"] = linear_team_key
680
689
  if linear_team_id:
681
690
  linear_config["team_id"] = linear_team_id
682
691
 
683
- if not linear_config.get("api_key") or not linear_config.get("team_id"):
692
+ if not linear_config.get("api_key") or (
693
+ not linear_config.get("team_id") and not linear_config.get("team_key")
694
+ ):
684
695
  console.print(
685
- "[red]Error:[/red] Linear requires both API key and team ID"
696
+ "[red]Error:[/red] Linear requires both API key and team ID/key"
686
697
  )
687
698
  console.print(
688
699
  "Run 'mcp-ticketer init --adapter linear' with proper credentials"
@@ -891,98 +902,20 @@ def _show_next_steps(
891
902
  console.print("[dim]Run 'mcp-ticketer --help' for more commands[/dim]")
892
903
 
893
904
 
894
- # Keep the old install command as deprecated alias to init
895
- @app.command(deprecated=True, hidden=True)
896
- def install(
897
- adapter: Optional[str] = typer.Option(
898
- None,
899
- "--adapter",
900
- "-a",
901
- help="Adapter type to use (auto-detected from .env if not specified)",
902
- ),
903
- project_path: Optional[str] = typer.Option(
904
- None, "--path", help="Project path (default: current directory)"
905
- ),
906
- global_config: bool = typer.Option(
907
- False,
908
- "--global",
909
- "-g",
910
- help="Save to global config instead of project-specific",
911
- ),
912
- base_path: Optional[str] = typer.Option(
913
- None,
914
- "--base-path",
915
- "-p",
916
- help="Base path for ticket storage (AITrackdown only)",
917
- ),
918
- api_key: Optional[str] = typer.Option(
919
- None, "--api-key", help="API key for Linear or API token for JIRA"
920
- ),
921
- team_id: Optional[str] = typer.Option(
922
- None, "--team-id", help="Linear team ID (required for Linear adapter)"
923
- ),
924
- jira_server: Optional[str] = typer.Option(
925
- None,
926
- "--jira-server",
927
- help="JIRA server URL (e.g., https://company.atlassian.net)",
928
- ),
929
- jira_email: Optional[str] = typer.Option(
930
- None, "--jira-email", help="JIRA user email for authentication"
931
- ),
932
- jira_project: Optional[str] = typer.Option(
933
- None, "--jira-project", help="Default JIRA project key"
934
- ),
935
- github_owner: Optional[str] = typer.Option(
936
- None, "--github-owner", help="GitHub repository owner"
937
- ),
938
- github_repo: Optional[str] = typer.Option(
939
- None, "--github-repo", help="GitHub repository name"
940
- ),
941
- github_token: Optional[str] = typer.Option(
942
- None, "--github-token", help="GitHub Personal Access Token"
943
- ),
944
- ) -> None:
945
- """DEPRECATED: Use 'mcp-ticketer init' instead.
946
-
947
- This command is deprecated. Use 'mcp-ticketer init' for project initialization.
948
-
949
- """
950
- console.print(
951
- "[yellow]⚠️ 'install' is deprecated. Use 'mcp-ticketer init' instead.[/yellow]\n"
952
- )
953
- # Call init with all parameters
954
- init(
955
- adapter=adapter,
956
- project_path=project_path,
957
- global_config=global_config,
958
- base_path=base_path,
959
- api_key=api_key,
960
- team_id=team_id,
961
- jira_server=jira_server,
962
- jira_email=jira_email,
963
- jira_project=jira_project,
964
- github_owner=github_owner,
965
- github_repo=github_repo,
966
- github_token=github_token,
967
- )
968
-
969
-
970
905
  @app.command("set")
971
906
  def set_config(
972
- adapter: Optional[AdapterType] = typer.Option(
907
+ adapter: AdapterType | None = typer.Option(
973
908
  None, "--adapter", "-a", help="Set default adapter"
974
909
  ),
975
- team_key: Optional[str] = typer.Option(
910
+ team_key: str | None = typer.Option(
976
911
  None, "--team-key", help="Linear team key (e.g., BTA)"
977
912
  ),
978
- team_id: Optional[str] = typer.Option(None, "--team-id", help="Linear team ID"),
979
- owner: Optional[str] = typer.Option(
980
- None, "--owner", help="GitHub repository owner"
981
- ),
982
- repo: Optional[str] = typer.Option(None, "--repo", help="GitHub repository name"),
983
- server: Optional[str] = typer.Option(None, "--server", help="JIRA server URL"),
984
- project: Optional[str] = typer.Option(None, "--project", help="JIRA project key"),
985
- base_path: Optional[str] = typer.Option(
913
+ team_id: str | None = typer.Option(None, "--team-id", help="Linear team ID"),
914
+ owner: str | None = typer.Option(None, "--owner", help="GitHub repository owner"),
915
+ repo: str | None = typer.Option(None, "--repo", help="GitHub repository name"),
916
+ server: str | None = typer.Option(None, "--server", help="JIRA server URL"),
917
+ project: str | None = typer.Option(None, "--project", help="JIRA project key"),
918
+ base_path: str | None = typer.Option(
986
919
  None, "--base-path", help="AITrackdown base path"
987
920
  ),
988
921
  ) -> None:
@@ -1072,16 +1005,12 @@ def set_config(
1072
1005
  @app.command("configure")
1073
1006
  def configure_command(
1074
1007
  show: bool = typer.Option(False, "--show", help="Show current configuration"),
1075
- adapter: Optional[str] = typer.Option(
1008
+ adapter: str | None = typer.Option(
1076
1009
  None, "--adapter", help="Set default adapter type"
1077
1010
  ),
1078
- api_key: Optional[str] = typer.Option(None, "--api-key", help="Set API key/token"),
1079
- project_id: Optional[str] = typer.Option(
1080
- None, "--project-id", help="Set project ID"
1081
- ),
1082
- team_id: Optional[str] = typer.Option(
1083
- None, "--team-id", help="Set team ID (Linear)"
1084
- ),
1011
+ api_key: str | None = typer.Option(None, "--api-key", help="Set API key/token"),
1012
+ project_id: str | None = typer.Option(None, "--project-id", help="Set project ID"),
1013
+ team_id: str | None = typer.Option(None, "--team-id", help="Set team ID (Linear)"),
1085
1014
  global_scope: bool = typer.Option(
1086
1015
  False,
1087
1016
  "--global",
@@ -1257,29 +1186,29 @@ def old_queue_health_command(
1257
1186
  @app.command(deprecated=True, hidden=True)
1258
1187
  def create(
1259
1188
  title: str = typer.Argument(..., help="Ticket title"),
1260
- description: Optional[str] = typer.Option(
1189
+ description: str | None = typer.Option(
1261
1190
  None, "--description", "-d", help="Ticket description"
1262
1191
  ),
1263
1192
  priority: Priority = typer.Option(
1264
1193
  Priority.MEDIUM, "--priority", "-p", help="Priority level"
1265
1194
  ),
1266
- tags: Optional[list[str]] = typer.Option(
1195
+ tags: list[str] | None = typer.Option(
1267
1196
  None, "--tag", "-t", help="Tags (can be specified multiple times)"
1268
1197
  ),
1269
- assignee: Optional[str] = typer.Option(
1198
+ assignee: str | None = typer.Option(
1270
1199
  None, "--assignee", "-a", help="Assignee username"
1271
1200
  ),
1272
- project: Optional[str] = typer.Option(
1201
+ project: str | None = typer.Option(
1273
1202
  None,
1274
1203
  "--project",
1275
1204
  help="Parent project/epic ID (synonym for --epic)",
1276
1205
  ),
1277
- epic: Optional[str] = typer.Option(
1206
+ epic: str | None = typer.Option(
1278
1207
  None,
1279
1208
  "--epic",
1280
1209
  help="Parent epic/project ID (synonym for --project)",
1281
1210
  ),
1282
- adapter: Optional[AdapterType] = typer.Option(
1211
+ adapter: AdapterType | None = typer.Option(
1283
1212
  None, "--adapter", help="Override default adapter"
1284
1213
  ),
1285
1214
  ) -> None:
@@ -1488,14 +1417,14 @@ def create(
1488
1417
 
1489
1418
  @app.command("list", deprecated=True, hidden=True)
1490
1419
  def list_tickets(
1491
- state: Optional[TicketState] = typer.Option(
1420
+ state: TicketState | None = typer.Option(
1492
1421
  None, "--state", "-s", help="Filter by state"
1493
1422
  ),
1494
- priority: Optional[Priority] = typer.Option(
1423
+ priority: Priority | None = typer.Option(
1495
1424
  None, "--priority", "-p", help="Filter by priority"
1496
1425
  ),
1497
1426
  limit: int = typer.Option(10, "--limit", "-l", help="Maximum number of tickets"),
1498
- adapter: Optional[AdapterType] = typer.Option(
1427
+ adapter: AdapterType | None = typer.Option(
1499
1428
  None, "--adapter", help="Override default adapter"
1500
1429
  ),
1501
1430
  ) -> None:
@@ -1551,7 +1480,7 @@ def list_tickets(
1551
1480
  def show(
1552
1481
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1553
1482
  comments: bool = typer.Option(False, "--comments", "-c", help="Show comments"),
1554
- adapter: Optional[AdapterType] = typer.Option(
1483
+ adapter: AdapterType | None = typer.Option(
1555
1484
  None, "--adapter", help="Override default adapter"
1556
1485
  ),
1557
1486
  ) -> None:
@@ -1607,7 +1536,7 @@ def show(
1607
1536
  def comment(
1608
1537
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1609
1538
  content: str = typer.Argument(..., help="Comment content"),
1610
- adapter: Optional[AdapterType] = typer.Option(
1539
+ adapter: AdapterType | None = typer.Option(
1611
1540
  None, "--adapter", help="Override default adapter"
1612
1541
  ),
1613
1542
  ) -> None:
@@ -1648,17 +1577,15 @@ def comment(
1648
1577
  @app.command(deprecated=True, hidden=True)
1649
1578
  def update(
1650
1579
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1651
- title: Optional[str] = typer.Option(None, "--title", help="New title"),
1652
- description: Optional[str] = typer.Option(
1580
+ title: str | None = typer.Option(None, "--title", help="New title"),
1581
+ description: str | None = typer.Option(
1653
1582
  None, "--description", "-d", help="New description"
1654
1583
  ),
1655
- priority: Optional[Priority] = typer.Option(
1584
+ priority: Priority | None = typer.Option(
1656
1585
  None, "--priority", "-p", help="New priority"
1657
1586
  ),
1658
- assignee: Optional[str] = typer.Option(
1659
- None, "--assignee", "-a", help="New assignee"
1660
- ),
1661
- adapter: Optional[AdapterType] = typer.Option(
1587
+ assignee: str | None = typer.Option(None, "--assignee", "-a", help="New assignee"),
1588
+ adapter: AdapterType | None = typer.Option(
1662
1589
  None, "--adapter", help="Override default adapter"
1663
1590
  ),
1664
1591
  ) -> None:
@@ -1718,13 +1645,13 @@ def update(
1718
1645
  @app.command(deprecated=True, hidden=True)
1719
1646
  def transition(
1720
1647
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1721
- state_positional: Optional[TicketState] = typer.Argument(
1648
+ state_positional: TicketState | None = typer.Argument(
1722
1649
  None, help="Target state (positional - deprecated, use --state instead)"
1723
1650
  ),
1724
- state: Optional[TicketState] = typer.Option(
1651
+ state: TicketState | None = typer.Option(
1725
1652
  None, "--state", "-s", help="Target state (recommended)"
1726
1653
  ),
1727
- adapter: Optional[AdapterType] = typer.Option(
1654
+ adapter: AdapterType | None = typer.Option(
1728
1655
  None, "--adapter", help="Override default adapter"
1729
1656
  ),
1730
1657
  ) -> None:
@@ -1789,12 +1716,12 @@ def transition(
1789
1716
 
1790
1717
  @app.command(deprecated=True, hidden=True)
1791
1718
  def search(
1792
- query: Optional[str] = typer.Argument(None, help="Search query"),
1793
- state: Optional[TicketState] = typer.Option(None, "--state", "-s"),
1794
- priority: Optional[Priority] = typer.Option(None, "--priority", "-p"),
1795
- assignee: Optional[str] = typer.Option(None, "--assignee", "-a"),
1719
+ query: str | None = typer.Argument(None, help="Search query"),
1720
+ state: TicketState | None = typer.Option(None, "--state", "-s"),
1721
+ priority: Priority | None = typer.Option(None, "--priority", "-p"),
1722
+ assignee: str | None = typer.Option(None, "--assignee", "-a"),
1796
1723
  limit: int = typer.Option(10, "--limit", "-l"),
1797
- adapter: Optional[AdapterType] = typer.Option(
1724
+ adapter: AdapterType | None = typer.Option(
1798
1725
  None, "--adapter", help="Override default adapter"
1799
1726
  ),
1800
1727
  ) -> None:
@@ -1852,7 +1779,7 @@ app.add_typer(discover_app, name="discover")
1852
1779
  # Add diagnostics command
1853
1780
  @app.command("diagnose")
1854
1781
  def diagnose_command(
1855
- output_file: Optional[str] = typer.Option(
1782
+ output_file: str | None = typer.Option(
1856
1783
  None, "--output", "-o", help="Save full report to file"
1857
1784
  ),
1858
1785
  json_output: bool = typer.Option(
@@ -1899,7 +1826,7 @@ def diagnose_command(
1899
1826
 
1900
1827
  @app.command("doctor")
1901
1828
  def doctor_alias(
1902
- output_file: Optional[str] = typer.Option(
1829
+ output_file: str | None = typer.Option(
1903
1830
  None, "--output", "-o", help="Save full report to file"
1904
1831
  ),
1905
1832
  json_output: bool = typer.Option(
@@ -1944,111 +1871,160 @@ mcp_app = typer.Typer(
1944
1871
 
1945
1872
  @app.command()
1946
1873
  def install(
1947
- platform: Optional[str] = typer.Argument(
1874
+ adapter: str | None = typer.Option(
1948
1875
  None,
1949
- help="Platform to install (claude-code, claude-desktop, auggie, gemini, codex)",
1876
+ "--adapter",
1877
+ "-a",
1878
+ help="Adapter type to use (interactive prompt if not specified)",
1879
+ ),
1880
+ project_path: str | None = typer.Option(
1881
+ None, "--path", help="Project path (default: current directory)"
1882
+ ),
1883
+ global_config: bool = typer.Option(
1884
+ False,
1885
+ "--global",
1886
+ "-g",
1887
+ help="Save to global config instead of project-specific",
1888
+ ),
1889
+ base_path: str | None = typer.Option(
1890
+ None,
1891
+ "--base-path",
1892
+ "-p",
1893
+ help="Base path for ticket storage (AITrackdown only)",
1894
+ ),
1895
+ api_key: str | None = typer.Option(
1896
+ None, "--api-key", help="API key for Linear or API token for JIRA"
1897
+ ),
1898
+ team_id: str | None = typer.Option(
1899
+ None, "--team-id", help="Linear team ID (required for Linear adapter)"
1900
+ ),
1901
+ jira_server: str | None = typer.Option(
1902
+ None,
1903
+ "--jira-server",
1904
+ help="JIRA server URL (e.g., https://company.atlassian.net)",
1905
+ ),
1906
+ jira_email: str | None = typer.Option(
1907
+ None, "--jira-email", help="JIRA user email for authentication"
1908
+ ),
1909
+ jira_project: str | None = typer.Option(
1910
+ None, "--jira-project", help="Default JIRA project key"
1911
+ ),
1912
+ github_owner: str | None = typer.Option(
1913
+ None, "--github-owner", help="GitHub repository owner"
1914
+ ),
1915
+ github_repo: str | None = typer.Option(
1916
+ None, "--github-repo", help="GitHub repository name"
1917
+ ),
1918
+ github_token: str | None = typer.Option(
1919
+ None, "--github-token", help="GitHub Personal Access Token"
1920
+ ),
1921
+ platform: str | None = typer.Option(
1922
+ None,
1923
+ "--platform",
1924
+ help="Platform to configure MCP for (claude-code, claude-desktop, auggie, gemini, codex)",
1950
1925
  ),
1951
1926
  dry_run: bool = typer.Option(
1952
- False, "--dry-run", help="Show what would be done without making changes"
1927
+ False,
1928
+ "--dry-run",
1929
+ help="Show what would be done without making changes (for platform installation)",
1953
1930
  ),
1954
1931
  ) -> None:
1955
- """Install mcp-ticketer for AI platforms.
1932
+ """Install and initialize mcp-ticketer (synonymous with 'init' and 'setup').
1956
1933
 
1957
- Without arguments, shows installation status and available platforms.
1958
- With a platform argument, installs MCP configuration for that platform.
1934
+ Without arguments, runs interactive setup wizard to configure your ticket adapter.
1935
+ With --platform, installs MCP configuration for AI platforms.
1959
1936
 
1960
- Each platform gets the right configuration automatically:
1961
- - claude-code: Project-level MCP server
1962
- - claude-desktop: Global MCP server
1963
- - auggie: Project-level MCP server
1964
- - gemini: Project-level MCP server
1965
- - codex: Project-level MCP server
1937
+ This command serves two purposes:
1938
+ 1. Adapter initialization (same as 'init' and 'setup')
1939
+ 2. Platform MCP configuration (when using --platform flag)
1966
1940
 
1967
1941
  Examples:
1968
- # Show status and available platforms
1942
+ # Interactive setup (same as 'init' and 'setup')
1969
1943
  mcp-ticketer install
1970
1944
 
1971
- # Install for Claude Code (project-level)
1972
- mcp-ticketer install claude-code
1973
-
1974
- # Install for Claude Desktop (global)
1975
- mcp-ticketer install claude-desktop
1945
+ # Setup with specific adapter
1946
+ mcp-ticketer install --adapter linear
1976
1947
 
1977
- # Install for Auggie
1978
- mcp-ticketer install auggie
1948
+ # Install MCP for Claude Code
1949
+ mcp-ticketer install --platform claude-code
1979
1950
 
1980
- # Dry run to preview changes
1981
- mcp-ticketer install claude-code --dry-run
1951
+ # Install MCP for Claude Desktop
1952
+ mcp-ticketer install --platform claude-desktop
1982
1953
 
1983
1954
  """
1984
- # If no platform specified, show help message
1985
- if platform is None:
1986
- console.print("[green]✓[/green] mcp-ticketer CLI is already installed.\n")
1987
- console.print(
1988
- "[bold]To configure MCP for a specific platform, use:[/bold]\n"
1989
- " mcp-ticketer install <platform>\n"
1990
- )
1991
- console.print("[bold]Available platforms:[/bold]")
1992
- console.print(" • claude-code - Claude Code (project-level)")
1993
- console.print(" • claude-desktop - Claude Desktop (global)")
1994
- console.print(" • auggie - Auggie (project-level)")
1995
- console.print(" gemini - Gemini CLI (project-level)")
1996
- console.print(" codex - Codex (project-level)")
1997
- return
1998
-
1999
- # Import configuration functions
2000
- from .auggie_configure import configure_auggie_mcp
2001
- from .codex_configure import configure_codex_mcp
2002
- from .gemini_configure import configure_gemini_mcp
2003
- from .mcp_configure import configure_claude_mcp
1955
+ # If platform flag is provided, handle MCP platform installation
1956
+ if platform is not None:
1957
+ # Import configuration functions
1958
+ from .auggie_configure import configure_auggie_mcp
1959
+ from .codex_configure import configure_codex_mcp
1960
+ from .gemini_configure import configure_gemini_mcp
1961
+ from .mcp_configure import configure_claude_mcp
1962
+
1963
+ # Map platform names to configuration functions
1964
+ platform_mapping = {
1965
+ "claude-code": {
1966
+ "func": lambda: configure_claude_mcp(global_config=False, force=True),
1967
+ "name": "Claude Code",
1968
+ },
1969
+ "claude-desktop": {
1970
+ "func": lambda: configure_claude_mcp(global_config=True, force=True),
1971
+ "name": "Claude Desktop",
1972
+ },
1973
+ "auggie": {
1974
+ "func": lambda: configure_auggie_mcp(force=True),
1975
+ "name": "Auggie",
1976
+ },
1977
+ "gemini": {
1978
+ "func": lambda: configure_gemini_mcp(scope="project", force=True),
1979
+ "name": "Gemini CLI",
1980
+ },
1981
+ "codex": {
1982
+ "func": lambda: configure_codex_mcp(force=True),
1983
+ "name": "Codex",
1984
+ },
1985
+ }
2004
1986
 
2005
- # Map platform names to configuration functions
2006
- platform_mapping = {
2007
- "claude-code": {
2008
- "func": lambda: configure_claude_mcp(global_config=False, force=True),
2009
- "name": "Claude Code",
2010
- },
2011
- "claude-desktop": {
2012
- "func": lambda: configure_claude_mcp(global_config=True, force=True),
2013
- "name": "Claude Desktop",
2014
- },
2015
- "auggie": {
2016
- "func": lambda: configure_auggie_mcp(force=True),
2017
- "name": "Auggie",
2018
- },
2019
- "gemini": {
2020
- "func": lambda: configure_gemini_mcp(scope="project", force=True),
2021
- "name": "Gemini CLI",
2022
- },
2023
- "codex": {
2024
- "func": lambda: configure_codex_mcp(force=True),
2025
- "name": "Codex",
2026
- },
2027
- }
1987
+ if platform not in platform_mapping:
1988
+ console.print(f"[red]Unknown platform: {platform}[/red]")
1989
+ console.print("\n[bold]Available platforms:[/bold]")
1990
+ for p in platform_mapping.keys():
1991
+ console.print(f" {p}")
1992
+ raise typer.Exit(1)
2028
1993
 
2029
- if platform not in platform_mapping:
2030
- console.print(f"[red]Unknown platform: {platform}[/red]")
2031
- console.print("\n[bold]Available platforms:[/bold]")
2032
- for p in platform_mapping.keys():
2033
- console.print(f" • {p}")
2034
- raise typer.Exit(1)
1994
+ config = platform_mapping[platform]
2035
1995
 
2036
- config = platform_mapping[platform]
1996
+ if dry_run:
1997
+ console.print(f"[cyan]DRY RUN - Would install for {config['name']}[/cyan]")
1998
+ return
2037
1999
 
2038
- if dry_run:
2039
- console.print(f"[cyan]DRY RUN - Would install for {config['name']}[/cyan]")
2000
+ try:
2001
+ config["func"]()
2002
+ except Exception as e:
2003
+ console.print(f"[red]Installation failed: {e}[/red]")
2004
+ raise typer.Exit(1)
2040
2005
  return
2041
2006
 
2042
- try:
2043
- config["func"]()
2044
- except Exception as e:
2045
- console.print(f"[red]Installation failed: {e}[/red]")
2046
- raise typer.Exit(1)
2007
+ # Otherwise, delegate to init for adapter initialization
2008
+ # This makes 'install' and 'init' synonymous when called without --platform
2009
+ init(
2010
+ adapter=adapter,
2011
+ project_path=project_path,
2012
+ global_config=global_config,
2013
+ base_path=base_path,
2014
+ api_key=api_key,
2015
+ team_id=team_id,
2016
+ jira_server=jira_server,
2017
+ jira_email=jira_email,
2018
+ jira_project=jira_project,
2019
+ github_owner=github_owner,
2020
+ github_repo=github_repo,
2021
+ github_token=github_token,
2022
+ )
2047
2023
 
2048
2024
 
2049
2025
  @app.command()
2050
2026
  def remove(
2051
- platform: Optional[str] = typer.Argument(
2027
+ platform: str | None = typer.Argument(
2052
2028
  None,
2053
2029
  help="Platform to remove (claude-code, claude-desktop, auggie, gemini, codex)",
2054
2030
  ),
@@ -2135,7 +2111,7 @@ def remove(
2135
2111
 
2136
2112
  @app.command()
2137
2113
  def uninstall(
2138
- platform: Optional[str] = typer.Argument(
2114
+ platform: str | None = typer.Argument(
2139
2115
  None,
2140
2116
  help="Platform to uninstall (claude-code, claude-desktop, auggie, gemini, codex)",
2141
2117
  ),
@@ -2218,10 +2194,10 @@ def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
2218
2194
 
2219
2195
  @mcp_app.command(name="serve")
2220
2196
  def mcp_serve(
2221
- adapter: Optional[AdapterType] = typer.Option(
2197
+ adapter: AdapterType | None = typer.Option(
2222
2198
  None, "--adapter", "-a", help="Override default adapter type"
2223
2199
  ),
2224
- base_path: Optional[str] = typer.Option(
2200
+ base_path: str | None = typer.Option(
2225
2201
  None, "--base-path", help="Base path for AITrackdown adapter"
2226
2202
  ),
2227
2203
  ):