mcp-ticketer 0.3.7__py3-none-any.whl → 0.4.0__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.

@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.3.7"
3
+ __version__ = "0.4.0"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
mcp_ticketer/cli/main.py CHANGED
@@ -24,9 +24,10 @@ from ..queue.ticket_registry import TicketRegistry
24
24
  from .configure import configure_wizard, set_adapter_config, show_current_config
25
25
  from .diagnostics import run_diagnostics
26
26
  from .discover import app as discover_app
27
- from .linear_commands import app as linear_app
28
27
  from .migrate_config import migrate_config_command
28
+ from .platform_commands import app as platform_app
29
29
  from .queue_commands import app as queue_app
30
+ from .ticket_commands import app as ticket_app
30
31
 
31
32
  # Load environment variables from .env files
32
33
  # Priority: .env.local (highest) > .env (base)
@@ -1142,9 +1143,14 @@ def migrate_config(
1142
1143
  migrate_config_command(dry_run=dry_run)
1143
1144
 
1144
1145
 
1145
- @app.command("status")
1146
- def status_command():
1147
- """Show queue and worker status."""
1146
+ @app.command("queue-status", deprecated=True, hidden=True)
1147
+ def old_queue_status_command():
1148
+ """Show queue and worker status.
1149
+
1150
+ DEPRECATED: Use 'mcp-ticketer queue status' instead.
1151
+ """
1152
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer queue status' instead.[/yellow]\n")
1153
+
1148
1154
  queue = Queue()
1149
1155
  manager = WorkerManager()
1150
1156
 
@@ -1169,12 +1175,12 @@ def status_command():
1169
1175
  console.print("\n[red]○ Worker is not running[/red]")
1170
1176
  if pending > 0:
1171
1177
  console.print(
1172
- "[yellow]Note: There are pending items. Start worker with 'mcp-ticketer worker start'[/yellow]"
1178
+ "[yellow]Note: There are pending items. Start worker with 'mcp-ticketer queue worker start'[/yellow]"
1173
1179
  )
1174
1180
 
1175
1181
 
1176
- @app.command()
1177
- def health(
1182
+ @app.command("queue-health", deprecated=True, hidden=True)
1183
+ def old_queue_health_command(
1178
1184
  auto_repair: bool = typer.Option(
1179
1185
  False, "--auto-repair", help="Attempt automatic repair of issues"
1180
1186
  ),
@@ -1182,7 +1188,11 @@ def health(
1182
1188
  False, "--verbose", "-v", help="Show detailed health information"
1183
1189
  ),
1184
1190
  ) -> None:
1185
- """Check queue system health and detect issues immediately."""
1191
+ """Check queue system health and detect issues immediately.
1192
+
1193
+ DEPRECATED: Use 'mcp-ticketer queue health' instead.
1194
+ """
1195
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer queue health' instead.[/yellow]\n")
1186
1196
  health_monitor = QueueHealthMonitor()
1187
1197
  health = health_monitor.check_health()
1188
1198
 
@@ -1251,7 +1261,7 @@ def health(
1251
1261
  raise typer.Exit(2)
1252
1262
 
1253
1263
 
1254
- @app.command()
1264
+ @app.command(deprecated=True, hidden=True)
1255
1265
  def create(
1256
1266
  title: str = typer.Argument(..., help="Ticket title"),
1257
1267
  description: Optional[str] = typer.Option(
@@ -1280,7 +1290,12 @@ def create(
1280
1290
  None, "--adapter", help="Override default adapter"
1281
1291
  ),
1282
1292
  ) -> None:
1283
- """Create a new ticket with comprehensive health checks."""
1293
+ """Create a new ticket with comprehensive health checks.
1294
+
1295
+ DEPRECATED: Use 'mcp-ticketer ticket create' instead.
1296
+ """
1297
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket create' instead.[/yellow]\n")
1298
+
1284
1299
  # IMMEDIATE HEALTH CHECK - Critical for reliability
1285
1300
  health_monitor = QueueHealthMonitor()
1286
1301
  health = health_monitor.check_health()
@@ -1476,7 +1491,7 @@ def create(
1476
1491
  )
1477
1492
 
1478
1493
 
1479
- @app.command("list")
1494
+ @app.command("list", deprecated=True, hidden=True)
1480
1495
  def list_tickets(
1481
1496
  state: Optional[TicketState] = typer.Option(
1482
1497
  None, "--state", "-s", help="Filter by state"
@@ -1489,7 +1504,11 @@ def list_tickets(
1489
1504
  None, "--adapter", help="Override default adapter"
1490
1505
  ),
1491
1506
  ) -> None:
1492
- """List tickets with optional filters."""
1507
+ """List tickets with optional filters.
1508
+
1509
+ DEPRECATED: Use 'mcp-ticketer ticket list' instead.
1510
+ """
1511
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket list' instead.[/yellow]\n")
1493
1512
 
1494
1513
  async def _list():
1495
1514
  adapter_instance = get_adapter(
@@ -1531,7 +1550,7 @@ def list_tickets(
1531
1550
  console.print(table)
1532
1551
 
1533
1552
 
1534
- @app.command()
1553
+ @app.command(deprecated=True, hidden=True)
1535
1554
  def show(
1536
1555
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1537
1556
  comments: bool = typer.Option(False, "--comments", "-c", help="Show comments"),
@@ -1539,7 +1558,11 @@ def show(
1539
1558
  None, "--adapter", help="Override default adapter"
1540
1559
  ),
1541
1560
  ) -> None:
1542
- """Show detailed ticket information."""
1561
+ """Show detailed ticket information.
1562
+
1563
+ DEPRECATED: Use 'mcp-ticketer ticket show' instead.
1564
+ """
1565
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket show' instead.[/yellow]\n")
1543
1566
 
1544
1567
  async def _show():
1545
1568
  adapter_instance = get_adapter(
@@ -1581,7 +1604,7 @@ def show(
1581
1604
  console.print(comment.content)
1582
1605
 
1583
1606
 
1584
- @app.command()
1607
+ @app.command(deprecated=True, hidden=True)
1585
1608
  def comment(
1586
1609
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1587
1610
  content: str = typer.Argument(..., help="Comment content"),
@@ -1589,7 +1612,11 @@ def comment(
1589
1612
  None, "--adapter", help="Override default adapter"
1590
1613
  ),
1591
1614
  ) -> None:
1592
- """Add a comment to a ticket."""
1615
+ """Add a comment to a ticket.
1616
+
1617
+ DEPRECATED: Use 'mcp-ticketer ticket comment' instead.
1618
+ """
1619
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket comment' instead.[/yellow]\n")
1593
1620
 
1594
1621
  async def _comment():
1595
1622
  adapter_instance = get_adapter(
@@ -1617,7 +1644,7 @@ def comment(
1617
1644
  raise typer.Exit(1)
1618
1645
 
1619
1646
 
1620
- @app.command()
1647
+ @app.command(deprecated=True, hidden=True)
1621
1648
  def update(
1622
1649
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1623
1650
  title: Optional[str] = typer.Option(None, "--title", help="New title"),
@@ -1634,7 +1661,11 @@ def update(
1634
1661
  None, "--adapter", help="Override default adapter"
1635
1662
  ),
1636
1663
  ) -> None:
1637
- """Update ticket fields."""
1664
+ """Update ticket fields.
1665
+
1666
+ DEPRECATED: Use 'mcp-ticketer ticket update' instead.
1667
+ """
1668
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket update' instead.[/yellow]\n")
1638
1669
  updates = {}
1639
1670
  if title:
1640
1671
  updates["title"] = title
@@ -1681,7 +1712,7 @@ def update(
1681
1712
  console.print("[dim]Worker started to process request[/dim]")
1682
1713
 
1683
1714
 
1684
- @app.command()
1715
+ @app.command(deprecated=True, hidden=True)
1685
1716
  def transition(
1686
1717
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1687
1718
  state_positional: Optional[TicketState] = typer.Argument(
@@ -1696,15 +1727,19 @@ def transition(
1696
1727
  ) -> None:
1697
1728
  """Change ticket state with validation.
1698
1729
 
1730
+ DEPRECATED: Use 'mcp-ticketer ticket transition' instead.
1731
+
1699
1732
  Examples:
1700
1733
  # Recommended syntax with flag:
1701
- mcp-ticketer transition BTA-215 --state done
1702
- mcp-ticketer transition BTA-215 -s in_progress
1734
+ mcp-ticketer ticket transition BTA-215 --state done
1735
+ mcp-ticketer ticket transition BTA-215 -s in_progress
1703
1736
 
1704
1737
  # Legacy positional syntax (still supported):
1705
- mcp-ticketer transition BTA-215 done
1738
+ mcp-ticketer ticket transition BTA-215 done
1706
1739
 
1707
1740
  """
1741
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket transition' instead.[/yellow]\n")
1742
+
1708
1743
  # Determine which state to use (prefer flag over positional)
1709
1744
  target_state = state if state is not None else state_positional
1710
1745
 
@@ -1747,7 +1782,7 @@ def transition(
1747
1782
  console.print("[dim]Worker started to process request[/dim]")
1748
1783
 
1749
1784
 
1750
- @app.command()
1785
+ @app.command(deprecated=True, hidden=True)
1751
1786
  def search(
1752
1787
  query: Optional[str] = typer.Argument(None, help="Search query"),
1753
1788
  state: Optional[TicketState] = typer.Option(None, "--state", "-s"),
@@ -1758,7 +1793,11 @@ def search(
1758
1793
  None, "--adapter", help="Override default adapter"
1759
1794
  ),
1760
1795
  ) -> None:
1761
- """Search tickets with advanced query."""
1796
+ """Search tickets with advanced query.
1797
+
1798
+ DEPRECATED: Use 'mcp-ticketer ticket search' instead.
1799
+ """
1800
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket search' instead.[/yellow]\n")
1762
1801
 
1763
1802
  async def _search():
1764
1803
  adapter_instance = get_adapter(
@@ -1790,6 +1829,12 @@ def search(
1790
1829
  console.print()
1791
1830
 
1792
1831
 
1832
+ # Add ticket command group to main app
1833
+ app.add_typer(ticket_app, name="ticket")
1834
+
1835
+ # Add platform command group to main app
1836
+ app.add_typer(platform_app, name="platform")
1837
+
1793
1838
  # Add queue command to main app
1794
1839
  app.add_typer(queue_app, name="queue")
1795
1840
 
@@ -1798,8 +1843,8 @@ app.add_typer(discover_app, name="discover")
1798
1843
 
1799
1844
 
1800
1845
  # Add diagnostics command
1801
- @app.command()
1802
- def diagnose(
1846
+ @app.command("diagnose")
1847
+ def diagnose_command(
1803
1848
  output_file: Optional[str] = typer.Option(
1804
1849
  None, "--output", "-o", help="Save full report to file"
1805
1850
  ),
@@ -1810,7 +1855,7 @@ def diagnose(
1810
1855
  False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
1811
1856
  ),
1812
1857
  ) -> None:
1813
- """Run comprehensive system diagnostics and health check."""
1858
+ """Run comprehensive system diagnostics and health check (alias: doctor)."""
1814
1859
  if simple:
1815
1860
  from .simple_health import simple_diagnose
1816
1861
 
@@ -1845,9 +1890,36 @@ def diagnose(
1845
1890
  raise typer.Exit(1)
1846
1891
 
1847
1892
 
1848
- @app.command()
1849
- def health() -> None:
1850
- """Quick health check - shows system status summary."""
1893
+ @app.command("doctor")
1894
+ def doctor_alias(
1895
+ output_file: Optional[str] = typer.Option(
1896
+ None, "--output", "-o", help="Save full report to file"
1897
+ ),
1898
+ json_output: bool = typer.Option(
1899
+ False, "--json", help="Output report in JSON format"
1900
+ ),
1901
+ simple: bool = typer.Option(
1902
+ False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
1903
+ ),
1904
+ ) -> None:
1905
+ """Run comprehensive system diagnostics and health check (alias for diagnose)."""
1906
+ # Call the diagnose_command function with the same parameters
1907
+ diagnose_command(output_file=output_file, json_output=json_output, simple=simple)
1908
+
1909
+
1910
+ @app.command("status")
1911
+ def status_command() -> None:
1912
+ """Quick health check - shows system status summary (alias: health)."""
1913
+ from .simple_health import simple_health_check
1914
+
1915
+ result = simple_health_check()
1916
+ if result != 0:
1917
+ raise typer.Exit(result)
1918
+
1919
+
1920
+ @app.command("health")
1921
+ def health_alias() -> None:
1922
+ """Quick health check - shows system status summary (alias for status)."""
1851
1923
  from .simple_health import simple_health_check
1852
1924
 
1853
1925
  result = simple_health_check()
@@ -1863,9 +1935,13 @@ mcp_app = typer.Typer(
1863
1935
  )
1864
1936
 
1865
1937
 
1866
- @app.command()
1938
+ @app.command(deprecated=True, hidden=True)
1867
1939
  def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
1868
- """Check status of a queued operation."""
1940
+ """Check status of a queued operation.
1941
+
1942
+ DEPRECATED: Use 'mcp-ticketer ticket check' instead.
1943
+ """
1944
+ console.print("[yellow]⚠️ This command is deprecated. Use 'mcp-ticketer ticket check' instead.[/yellow]\n")
1869
1945
  queue = Queue()
1870
1946
  item = queue.get_item(queue_id)
1871
1947
 
@@ -2141,7 +2217,6 @@ def mcp_auggie(
2141
2217
 
2142
2218
 
2143
2219
  # Add command groups to main app (must be after all subcommands are defined)
2144
- app.add_typer(linear_app, name="linear")
2145
2220
  app.add_typer(mcp_app, name="mcp")
2146
2221
 
2147
2222
 
@@ -0,0 +1,133 @@
1
+ """Platform-specific command groups."""
2
+
3
+ import typer
4
+
5
+ # Import platform-specific command modules
6
+ from .linear_commands import app as linear_app
7
+
8
+ # Create main platform command group
9
+ app = typer.Typer(
10
+ name="platform",
11
+ help="Platform-specific commands (Linear, JIRA, GitHub, AITrackdown)",
12
+ )
13
+
14
+ # Register Linear commands
15
+ app.add_typer(linear_app, name="linear")
16
+
17
+ # Create placeholder apps for other platforms
18
+
19
+ # JIRA platform commands (placeholder)
20
+ jira_app = typer.Typer(
21
+ name="jira",
22
+ help="JIRA-specific workspace and project management",
23
+ )
24
+
25
+
26
+ @jira_app.command("projects")
27
+ def jira_list_projects():
28
+ """List JIRA projects (placeholder - not yet implemented)."""
29
+ from rich.console import Console
30
+
31
+ console = Console()
32
+ console.print(
33
+ "[yellow]JIRA platform commands are not yet implemented.[/yellow]"
34
+ )
35
+ console.print(
36
+ "Use the generic ticket commands for JIRA operations:\n"
37
+ " mcp-ticketer ticket create 'My ticket'\n"
38
+ " mcp-ticketer ticket list"
39
+ )
40
+
41
+
42
+ @jira_app.command("configure")
43
+ def jira_configure():
44
+ """Configure JIRA adapter (placeholder - not yet implemented)."""
45
+ from rich.console import Console
46
+
47
+ console = Console()
48
+ console.print(
49
+ "[yellow]JIRA platform commands are not yet implemented.[/yellow]"
50
+ )
51
+ console.print(
52
+ "Use 'mcp-ticketer init --adapter jira' to configure JIRA adapter."
53
+ )
54
+
55
+
56
+ # GitHub platform commands (placeholder)
57
+ github_app = typer.Typer(
58
+ name="github",
59
+ help="GitHub-specific repository and issue management",
60
+ )
61
+
62
+
63
+ @github_app.command("repos")
64
+ def github_list_repos():
65
+ """List GitHub repositories (placeholder - not yet implemented)."""
66
+ from rich.console import Console
67
+
68
+ console = Console()
69
+ console.print(
70
+ "[yellow]GitHub platform commands are not yet implemented.[/yellow]"
71
+ )
72
+ console.print(
73
+ "Use the generic ticket commands for GitHub operations:\n"
74
+ " mcp-ticketer ticket create 'My issue'\n"
75
+ " mcp-ticketer ticket list"
76
+ )
77
+
78
+
79
+ @github_app.command("configure")
80
+ def github_configure():
81
+ """Configure GitHub adapter (placeholder - not yet implemented)."""
82
+ from rich.console import Console
83
+
84
+ console = Console()
85
+ console.print(
86
+ "[yellow]GitHub platform commands are not yet implemented.[/yellow]"
87
+ )
88
+ console.print(
89
+ "Use 'mcp-ticketer init --adapter github' to configure GitHub adapter."
90
+ )
91
+
92
+
93
+ # AITrackdown platform commands (placeholder)
94
+ aitrackdown_app = typer.Typer(
95
+ name="aitrackdown",
96
+ help="AITrackdown-specific local file management",
97
+ )
98
+
99
+
100
+ @aitrackdown_app.command("info")
101
+ def aitrackdown_info():
102
+ """Show AITrackdown storage information (placeholder - not yet implemented)."""
103
+ from rich.console import Console
104
+
105
+ console = Console()
106
+ console.print(
107
+ "[yellow]AITrackdown platform commands are not yet implemented.[/yellow]"
108
+ )
109
+ console.print(
110
+ "Use the generic ticket commands for AITrackdown operations:\n"
111
+ " mcp-ticketer ticket create 'My ticket'\n"
112
+ " mcp-ticketer ticket list"
113
+ )
114
+
115
+
116
+ @aitrackdown_app.command("configure")
117
+ def aitrackdown_configure():
118
+ """Configure AITrackdown adapter (placeholder - not yet implemented)."""
119
+ from rich.console import Console
120
+
121
+ console = Console()
122
+ console.print(
123
+ "[yellow]AITrackdown platform commands are not yet implemented.[/yellow]"
124
+ )
125
+ console.print(
126
+ "Use 'mcp-ticketer init --adapter aitrackdown' to configure AITrackdown adapter."
127
+ )
128
+
129
+
130
+ # Register all platform command groups
131
+ app.add_typer(jira_app, name="jira")
132
+ app.add_typer(github_app, name="github")
133
+ app.add_typer(aitrackdown_app, name="aitrackdown")
@@ -0,0 +1,765 @@
1
+ """Ticket management commands."""
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ from ..core import AdapterRegistry, Priority, TicketState
15
+ from ..core.models import Comment, SearchQuery
16
+ from ..queue import Queue, QueueStatus, WorkerManager
17
+ from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
18
+ from ..queue.ticket_registry import TicketRegistry
19
+
20
+
21
+ # Moved from main.py to avoid circular import
22
+ class AdapterType(str, Enum):
23
+ """Available adapter types."""
24
+
25
+ AITRACKDOWN = "aitrackdown"
26
+ LINEAR = "linear"
27
+ JIRA = "jira"
28
+ GITHUB = "github"
29
+
30
+
31
+ app = typer.Typer(
32
+ name="ticket",
33
+ help="Ticket management operations (create, list, update, search, etc.)",
34
+ )
35
+ console = Console()
36
+
37
+
38
+ # Configuration functions (moved from main.py to avoid circular import)
39
+ def load_config(project_dir: Optional[Path] = None) -> dict:
40
+ """Load configuration from project-local config file."""
41
+ import logging
42
+
43
+ logger = logging.getLogger(__name__)
44
+ base_dir = project_dir or Path.cwd()
45
+ project_config = base_dir / ".mcp-ticketer" / "config.json"
46
+
47
+ if project_config.exists():
48
+ try:
49
+ with open(project_config) as f:
50
+ config = json.load(f)
51
+ logger.info(f"Loaded configuration from: {project_config}")
52
+ return config
53
+ except (OSError, json.JSONDecodeError) as e:
54
+ logger.warning(f"Could not load project config: {e}, using defaults")
55
+ console.print(f"[yellow]Warning: Could not load project config: {e}[/yellow]")
56
+
57
+ logger.info("No project-local config found, defaulting to aitrackdown adapter")
58
+ return {"adapter": "aitrackdown", "config": {"base_path": ".aitrackdown"}}
59
+
60
+
61
+ def save_config(config: dict) -> None:
62
+ """Save configuration to project-local config file."""
63
+ import logging
64
+
65
+ logger = logging.getLogger(__name__)
66
+ project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
67
+ project_config.parent.mkdir(parents=True, exist_ok=True)
68
+ with open(project_config, "w") as f:
69
+ json.dump(config, f, indent=2)
70
+ logger.info(f"Saved configuration to: {project_config}")
71
+
72
+
73
+ def get_adapter(override_adapter: Optional[str] = None, override_config: Optional[dict] = None):
74
+ """Get configured adapter instance."""
75
+ config = load_config()
76
+
77
+ if override_adapter:
78
+ adapter_type = override_adapter
79
+ adapters_config = config.get("adapters", {})
80
+ adapter_config = adapters_config.get(adapter_type, {})
81
+ if override_config:
82
+ adapter_config.update(override_config)
83
+ else:
84
+ adapter_type = config.get("default_adapter", "aitrackdown")
85
+ adapters_config = config.get("adapters", {})
86
+ adapter_config = adapters_config.get(adapter_type, {})
87
+
88
+ if not adapter_config and "config" in config:
89
+ adapter_config = config["config"]
90
+
91
+ # Add environment variables for authentication
92
+ if adapter_type == "linear":
93
+ if not adapter_config.get("api_key"):
94
+ adapter_config["api_key"] = os.getenv("LINEAR_API_KEY")
95
+ elif adapter_type == "github":
96
+ if not adapter_config.get("api_key") and not adapter_config.get("token"):
97
+ adapter_config["api_key"] = os.getenv("GITHUB_TOKEN")
98
+ elif adapter_type == "jira":
99
+ if not adapter_config.get("api_token"):
100
+ adapter_config["api_token"] = os.getenv("JIRA_ACCESS_TOKEN")
101
+ if not adapter_config.get("email"):
102
+ adapter_config["email"] = os.getenv("JIRA_ACCESS_USER")
103
+
104
+ return AdapterRegistry.get_adapter(adapter_type, adapter_config)
105
+
106
+
107
+ def _discover_from_env_files() -> Optional[str]:
108
+ """Discover adapter configuration from .env or .env.local files.
109
+
110
+ Returns:
111
+ Adapter name if discovered, None otherwise
112
+
113
+ """
114
+ import logging
115
+ from pathlib import Path
116
+
117
+ logger = logging.getLogger(__name__)
118
+
119
+ # Check .env.local first, then .env
120
+ env_files = [".env.local", ".env"]
121
+
122
+ for env_file in env_files:
123
+ env_path = Path.cwd() / env_file
124
+ if env_path.exists():
125
+ try:
126
+ # Simple .env parsing (key=value format)
127
+ env_vars = {}
128
+ with open(env_path) as f:
129
+ for line in f:
130
+ line = line.strip()
131
+ if line and not line.startswith("#") and "=" in line:
132
+ key, value = line.split("=", 1)
133
+ env_vars[key.strip()] = value.strip().strip("\"'")
134
+
135
+ # Check for adapter-specific variables
136
+ if env_vars.get("LINEAR_API_KEY"):
137
+ logger.info(f"Discovered Linear configuration in {env_file}")
138
+ return "linear"
139
+ elif env_vars.get("GITHUB_TOKEN"):
140
+ logger.info(f"Discovered GitHub configuration in {env_file}")
141
+ return "github"
142
+ elif env_vars.get("JIRA_SERVER"):
143
+ logger.info(f"Discovered JIRA configuration in {env_file}")
144
+ return "jira"
145
+
146
+ except Exception as e:
147
+ logger.warning(f"Could not read {env_file}: {e}")
148
+
149
+ return None
150
+
151
+
152
+ def _save_adapter_to_config(adapter_name: str) -> None:
153
+ """Save adapter configuration to config file.
154
+
155
+ Args:
156
+ adapter_name: Name of the adapter to save as default
157
+
158
+ """
159
+ import logging
160
+
161
+ from .main import save_config
162
+
163
+ logger = logging.getLogger(__name__)
164
+
165
+ try:
166
+ config = load_config()
167
+ config["default_adapter"] = adapter_name
168
+
169
+ # Ensure adapters section exists
170
+ if "adapters" not in config:
171
+ config["adapters"] = {}
172
+
173
+ # Add basic adapter config if not exists
174
+ if adapter_name not in config["adapters"]:
175
+ if adapter_name == "aitrackdown":
176
+ config["adapters"][adapter_name] = {"base_path": ".aitrackdown"}
177
+ else:
178
+ config["adapters"][adapter_name] = {"type": adapter_name}
179
+
180
+ save_config(config)
181
+ logger.info(f"Saved {adapter_name} as default adapter")
182
+
183
+ except Exception as e:
184
+ logger.warning(f"Could not save adapter configuration: {e}")
185
+
186
+
187
+ @app.command()
188
+ def create(
189
+ title: str = typer.Argument(..., help="Ticket title"),
190
+ description: Optional[str] = typer.Option(
191
+ None, "--description", "-d", help="Ticket description"
192
+ ),
193
+ priority: Priority = typer.Option(
194
+ Priority.MEDIUM, "--priority", "-p", help="Priority level"
195
+ ),
196
+ tags: Optional[list[str]] = typer.Option(
197
+ None, "--tag", "-t", help="Tags (can be specified multiple times)"
198
+ ),
199
+ assignee: Optional[str] = typer.Option(
200
+ None, "--assignee", "-a", help="Assignee username"
201
+ ),
202
+ project: Optional[str] = typer.Option(
203
+ None,
204
+ "--project",
205
+ help="Parent project/epic ID (synonym for --epic)",
206
+ ),
207
+ epic: Optional[str] = typer.Option(
208
+ None,
209
+ "--epic",
210
+ help="Parent epic/project ID (synonym for --project)",
211
+ ),
212
+ adapter: Optional[AdapterType] = typer.Option(
213
+ None, "--adapter", help="Override default adapter"
214
+ ),
215
+ ) -> None:
216
+ """Create a new ticket with comprehensive health checks."""
217
+ # IMMEDIATE HEALTH CHECK - Critical for reliability
218
+ health_monitor = QueueHealthMonitor()
219
+ health = health_monitor.check_health()
220
+
221
+ # Display health status
222
+ if health["status"] == HealthStatus.CRITICAL:
223
+ console.print("[red]🚨 CRITICAL: Queue system has serious issues![/red]")
224
+ for alert in health["alerts"]:
225
+ if alert["level"] == "critical":
226
+ console.print(f"[red] • {alert['message']}[/red]")
227
+
228
+ # Attempt auto-repair
229
+ console.print("[yellow]Attempting automatic repair...[/yellow]")
230
+ repair_result = health_monitor.auto_repair()
231
+
232
+ if repair_result["actions_taken"]:
233
+ for action in repair_result["actions_taken"]:
234
+ console.print(f"[yellow] ✓ {action}[/yellow]")
235
+
236
+ # Re-check health after repair
237
+ health = health_monitor.check_health()
238
+ if health["status"] == HealthStatus.CRITICAL:
239
+ console.print(
240
+ "[red]❌ Auto-repair failed. Manual intervention required.[/red]"
241
+ )
242
+ console.print(
243
+ "[red]Cannot safely create ticket. Please check system status.[/red]"
244
+ )
245
+ raise typer.Exit(1)
246
+ else:
247
+ console.print(
248
+ "[green]✓ Auto-repair successful. Proceeding with ticket creation.[/green]"
249
+ )
250
+ else:
251
+ console.print(
252
+ "[red]❌ No repair actions available. Manual intervention required.[/red]"
253
+ )
254
+ raise typer.Exit(1)
255
+
256
+ elif health["status"] == HealthStatus.WARNING:
257
+ console.print("[yellow]⚠️ Warning: Queue system has minor issues[/yellow]")
258
+ for alert in health["alerts"]:
259
+ if alert["level"] == "warning":
260
+ console.print(f"[yellow] • {alert['message']}[/yellow]")
261
+ console.print("[yellow]Proceeding with ticket creation...[/yellow]")
262
+
263
+ # Get the adapter name with priority: 1) argument, 2) config, 3) .env files, 4) default
264
+ if adapter:
265
+ # Priority 1: Command-line argument - save to config for future use
266
+ adapter_name = adapter.value
267
+ _save_adapter_to_config(adapter_name)
268
+ else:
269
+ # Priority 2: Check existing config
270
+ config = load_config()
271
+ adapter_name = config.get("default_adapter")
272
+
273
+ if not adapter_name or adapter_name == "aitrackdown":
274
+ # Priority 3: Check .env files and save if found
275
+ env_adapter = _discover_from_env_files()
276
+ if env_adapter:
277
+ adapter_name = env_adapter
278
+ _save_adapter_to_config(adapter_name)
279
+ else:
280
+ # Priority 4: Default
281
+ adapter_name = "aitrackdown"
282
+
283
+ # Resolve project/epic synonym - prefer whichever is provided
284
+ parent_epic_id = project or epic
285
+
286
+ # Create task data
287
+ # Import Priority for type checking
288
+ from ..core.models import Priority as PriorityEnum
289
+
290
+ task_data = {
291
+ "title": title,
292
+ "description": description,
293
+ "priority": priority.value if isinstance(priority, PriorityEnum) else priority,
294
+ "tags": tags or [],
295
+ "assignee": assignee,
296
+ "parent_epic": parent_epic_id,
297
+ }
298
+
299
+ # WORKAROUND: Use direct operation for Linear adapter to bypass worker subprocess issue
300
+ if adapter_name == "linear":
301
+ console.print(
302
+ "[yellow]⚠️[/yellow] Using direct operation for Linear adapter (bypassing queue)"
303
+ )
304
+ try:
305
+ # Load configuration and create adapter directly
306
+ config = load_config()
307
+ adapter_config = config.get("adapters", {}).get(adapter_name, {})
308
+
309
+ # Import and create adapter
310
+ from ..core.registry import AdapterRegistry
311
+
312
+ adapter_instance = AdapterRegistry.get_adapter(adapter_name, adapter_config)
313
+
314
+ # Create task directly
315
+ from ..core.models import Priority, Task
316
+
317
+ task = Task(
318
+ title=task_data["title"],
319
+ description=task_data.get("description"),
320
+ priority=(
321
+ Priority(task_data["priority"])
322
+ if task_data.get("priority")
323
+ else Priority.MEDIUM
324
+ ),
325
+ tags=task_data.get("tags", []),
326
+ assignee=task_data.get("assignee"),
327
+ parent_epic=task_data.get("parent_epic"),
328
+ )
329
+
330
+ # Create ticket synchronously
331
+ import asyncio
332
+
333
+ result = asyncio.run(adapter_instance.create(task))
334
+
335
+ console.print(f"[green]✓[/green] Ticket created successfully: {result.id}")
336
+ console.print(f" Title: {result.title}")
337
+ console.print(f" Priority: {result.priority}")
338
+ console.print(f" State: {result.state}")
339
+ # Get URL from metadata if available
340
+ if (
341
+ result.metadata
342
+ and "linear" in result.metadata
343
+ and "url" in result.metadata["linear"]
344
+ ):
345
+ console.print(f" URL: {result.metadata['linear']['url']}")
346
+
347
+ return result.id
348
+
349
+ except Exception as e:
350
+ console.print(f"[red]❌[/red] Failed to create ticket: {e}")
351
+ raise
352
+
353
+ # Use queue for other adapters
354
+ queue = Queue()
355
+ queue_id = queue.add(
356
+ ticket_data=task_data,
357
+ adapter=adapter_name,
358
+ operation="create",
359
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
360
+ )
361
+
362
+ # Register in ticket registry for tracking
363
+ registry = TicketRegistry()
364
+ registry.register_ticket_operation(
365
+ queue_id, adapter_name, "create", title, task_data
366
+ )
367
+
368
+ console.print(f"[green]✓[/green] Queued ticket creation: {queue_id}")
369
+ console.print(f" Title: {title}")
370
+ console.print(f" Priority: {priority}")
371
+ console.print(f" Adapter: {adapter_name}")
372
+ console.print("[dim]Use 'mcp-ticketer ticket check {queue_id}' to check progress[/dim]")
373
+
374
+ # Start worker if needed with immediate feedback
375
+ manager = WorkerManager()
376
+ worker_started = manager.start_if_needed()
377
+
378
+ if worker_started:
379
+ console.print("[dim]Worker started to process request[/dim]")
380
+
381
+ # Give immediate feedback on processing
382
+ import time
383
+
384
+ time.sleep(1) # Brief pause to let worker start
385
+
386
+ # Check if item is being processed
387
+ item = queue.get_item(queue_id)
388
+ if item and item.status == QueueStatus.PROCESSING:
389
+ console.print("[green]✓ Item is being processed by worker[/green]")
390
+ elif item and item.status == QueueStatus.PENDING:
391
+ console.print("[yellow]⏳ Item is queued for processing[/yellow]")
392
+ else:
393
+ console.print(
394
+ "[red]⚠️ Item status unclear - check with 'mcp-ticketer ticket check {queue_id}'[/red]"
395
+ )
396
+ else:
397
+ # Worker didn't start - this is a problem
398
+ pending_count = queue.get_pending_count()
399
+ if pending_count > 1: # More than just this item
400
+ console.print(
401
+ f"[red]❌ Worker failed to start with {pending_count} pending items![/red]"
402
+ )
403
+ console.print(
404
+ "[red]This is a critical issue. Try 'mcp-ticketer queue worker start' manually.[/red]"
405
+ )
406
+ else:
407
+ console.print(
408
+ "[yellow]Worker not started (no other pending items)[/yellow]"
409
+ )
410
+
411
+
412
+ @app.command("list")
413
+ def list_tickets(
414
+ state: Optional[TicketState] = typer.Option(
415
+ None, "--state", "-s", help="Filter by state"
416
+ ),
417
+ priority: Optional[Priority] = typer.Option(
418
+ None, "--priority", "-p", help="Filter by priority"
419
+ ),
420
+ limit: int = typer.Option(10, "--limit", "-l", help="Maximum number of tickets"),
421
+ adapter: Optional[AdapterType] = typer.Option(
422
+ None, "--adapter", help="Override default adapter"
423
+ ),
424
+ ) -> None:
425
+ """List tickets with optional filters."""
426
+
427
+ async def _list():
428
+ adapter_instance = get_adapter(
429
+ override_adapter=adapter.value if adapter else None
430
+ )
431
+ filters = {}
432
+ if state:
433
+ filters["state"] = state
434
+ if priority:
435
+ filters["priority"] = priority
436
+ return await adapter_instance.list(limit=limit, filters=filters)
437
+
438
+ tickets = asyncio.run(_list())
439
+
440
+ if not tickets:
441
+ console.print("[yellow]No tickets found[/yellow]")
442
+ return
443
+
444
+ # Create table
445
+ table = Table(title="Tickets")
446
+ table.add_column("ID", style="cyan", no_wrap=True)
447
+ table.add_column("Title", style="white")
448
+ table.add_column("State", style="green")
449
+ table.add_column("Priority", style="yellow")
450
+ table.add_column("Assignee", style="blue")
451
+
452
+ for ticket in tickets:
453
+ # Handle assignee field - Epic doesn't have assignee, Task does
454
+ assignee = getattr(ticket, "assignee", None) or "-"
455
+
456
+ table.add_row(
457
+ ticket.id or "N/A",
458
+ ticket.title,
459
+ ticket.state,
460
+ ticket.priority,
461
+ assignee,
462
+ )
463
+
464
+ console.print(table)
465
+
466
+
467
+ @app.command()
468
+ def show(
469
+ ticket_id: str = typer.Argument(..., help="Ticket ID"),
470
+ comments: bool = typer.Option(False, "--comments", "-c", help="Show comments"),
471
+ adapter: Optional[AdapterType] = typer.Option(
472
+ None, "--adapter", help="Override default adapter"
473
+ ),
474
+ ) -> None:
475
+ """Show detailed ticket information."""
476
+
477
+ async def _show():
478
+ adapter_instance = get_adapter(
479
+ override_adapter=adapter.value if adapter else None
480
+ )
481
+ ticket = await adapter_instance.read(ticket_id)
482
+ ticket_comments = None
483
+ if comments and ticket:
484
+ ticket_comments = await adapter_instance.get_comments(ticket_id)
485
+ return ticket, ticket_comments
486
+
487
+ ticket, ticket_comments = asyncio.run(_show())
488
+
489
+ if not ticket:
490
+ console.print(f"[red]✗[/red] Ticket not found: {ticket_id}")
491
+ raise typer.Exit(1)
492
+
493
+ # Display ticket details
494
+ console.print(f"\n[bold]Ticket: {ticket.id}[/bold]")
495
+ console.print(f"Title: {ticket.title}")
496
+ console.print(f"State: [green]{ticket.state}[/green]")
497
+ console.print(f"Priority: [yellow]{ticket.priority}[/yellow]")
498
+
499
+ if ticket.description:
500
+ console.print("\n[dim]Description:[/dim]")
501
+ console.print(ticket.description)
502
+
503
+ if ticket.tags:
504
+ console.print(f"\nTags: {', '.join(ticket.tags)}")
505
+
506
+ if ticket.assignee:
507
+ console.print(f"Assignee: {ticket.assignee}")
508
+
509
+ # Display comments if requested
510
+ if ticket_comments:
511
+ console.print(f"\n[bold]Comments ({len(ticket_comments)}):[/bold]")
512
+ for comment in ticket_comments:
513
+ console.print(f"\n[dim]{comment.created_at} - {comment.author}:[/dim]")
514
+ console.print(comment.content)
515
+
516
+
517
+ @app.command()
518
+ def comment(
519
+ ticket_id: str = typer.Argument(..., help="Ticket ID"),
520
+ content: str = typer.Argument(..., help="Comment content"),
521
+ adapter: Optional[AdapterType] = typer.Option(
522
+ None, "--adapter", help="Override default adapter"
523
+ ),
524
+ ) -> None:
525
+ """Add a comment to a ticket."""
526
+
527
+ async def _comment():
528
+ adapter_instance = get_adapter(
529
+ override_adapter=adapter.value if adapter else None
530
+ )
531
+
532
+ # Create comment
533
+ comment_obj = Comment(
534
+ ticket_id=ticket_id,
535
+ content=content,
536
+ author="cli-user", # Could be made configurable
537
+ )
538
+
539
+ result = await adapter_instance.add_comment(comment_obj)
540
+ return result
541
+
542
+ try:
543
+ result = asyncio.run(_comment())
544
+ console.print("[green]✓[/green] Comment added successfully")
545
+ if result.id:
546
+ console.print(f"Comment ID: {result.id}")
547
+ console.print(f"Content: {content}")
548
+ except Exception as e:
549
+ console.print(f"[red]✗[/red] Failed to add comment: {e}")
550
+ raise typer.Exit(1)
551
+
552
+
553
+ @app.command()
554
+ def update(
555
+ ticket_id: str = typer.Argument(..., help="Ticket ID"),
556
+ title: Optional[str] = typer.Option(None, "--title", help="New title"),
557
+ description: Optional[str] = typer.Option(
558
+ None, "--description", "-d", help="New description"
559
+ ),
560
+ priority: Optional[Priority] = typer.Option(
561
+ None, "--priority", "-p", help="New priority"
562
+ ),
563
+ assignee: Optional[str] = typer.Option(
564
+ None, "--assignee", "-a", help="New assignee"
565
+ ),
566
+ adapter: Optional[AdapterType] = typer.Option(
567
+ None, "--adapter", help="Override default adapter"
568
+ ),
569
+ ) -> None:
570
+ """Update ticket fields."""
571
+ updates = {}
572
+ if title:
573
+ updates["title"] = title
574
+ if description:
575
+ updates["description"] = description
576
+ if priority:
577
+ updates["priority"] = (
578
+ priority.value if isinstance(priority, Priority) else priority
579
+ )
580
+ if assignee:
581
+ updates["assignee"] = assignee
582
+
583
+ if not updates:
584
+ console.print("[yellow]No updates specified[/yellow]")
585
+ raise typer.Exit(1)
586
+
587
+ # Get the adapter name
588
+ config = load_config()
589
+ adapter_name = (
590
+ adapter.value if adapter else config.get("default_adapter", "aitrackdown")
591
+ )
592
+
593
+ # Add ticket_id to updates
594
+ updates["ticket_id"] = ticket_id
595
+
596
+ # Add to queue with explicit project directory
597
+ queue = Queue()
598
+ queue_id = queue.add(
599
+ ticket_data=updates,
600
+ adapter=adapter_name,
601
+ operation="update",
602
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
603
+ )
604
+
605
+ console.print(f"[green]✓[/green] Queued ticket update: {queue_id}")
606
+ for key, value in updates.items():
607
+ if key != "ticket_id":
608
+ console.print(f" {key}: {value}")
609
+ console.print("[dim]Use 'mcp-ticketer ticket check {queue_id}' to check progress[/dim]")
610
+
611
+ # Start worker if needed
612
+ manager = WorkerManager()
613
+ if manager.start_if_needed():
614
+ console.print("[dim]Worker started to process request[/dim]")
615
+
616
+
617
+ @app.command()
618
+ def transition(
619
+ ticket_id: str = typer.Argument(..., help="Ticket ID"),
620
+ state_positional: Optional[TicketState] = typer.Argument(
621
+ None, help="Target state (positional - deprecated, use --state instead)"
622
+ ),
623
+ state: Optional[TicketState] = typer.Option(
624
+ None, "--state", "-s", help="Target state (recommended)"
625
+ ),
626
+ adapter: Optional[AdapterType] = typer.Option(
627
+ None, "--adapter", help="Override default adapter"
628
+ ),
629
+ ) -> None:
630
+ """Change ticket state with validation.
631
+
632
+ Examples:
633
+ # Recommended syntax with flag:
634
+ mcp-ticketer ticket transition BTA-215 --state done
635
+ mcp-ticketer ticket transition BTA-215 -s in_progress
636
+
637
+ # Legacy positional syntax (still supported):
638
+ mcp-ticketer ticket transition BTA-215 done
639
+
640
+ """
641
+ # Determine which state to use (prefer flag over positional)
642
+ target_state = state if state is not None else state_positional
643
+
644
+ if target_state is None:
645
+ console.print("[red]Error: State is required[/red]")
646
+ console.print(
647
+ "Use either:\n"
648
+ " - Flag syntax (recommended): mcp-ticketer ticket transition TICKET-ID --state STATE\n"
649
+ " - Positional syntax: mcp-ticketer ticket transition TICKET-ID STATE"
650
+ )
651
+ raise typer.Exit(1)
652
+
653
+ # Get the adapter name
654
+ config = load_config()
655
+ adapter_name = (
656
+ adapter.value if adapter else config.get("default_adapter", "aitrackdown")
657
+ )
658
+
659
+ # Add to queue with explicit project directory
660
+ queue = Queue()
661
+ queue_id = queue.add(
662
+ ticket_data={
663
+ "ticket_id": ticket_id,
664
+ "state": (
665
+ target_state.value if hasattr(target_state, "value") else target_state
666
+ ),
667
+ },
668
+ adapter=adapter_name,
669
+ operation="transition",
670
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
671
+ )
672
+
673
+ console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
674
+ console.print(f" Ticket: {ticket_id} → {target_state}")
675
+ console.print("[dim]Use 'mcp-ticketer ticket check {queue_id}' to check progress[/dim]")
676
+
677
+ # Start worker if needed
678
+ manager = WorkerManager()
679
+ if manager.start_if_needed():
680
+ console.print("[dim]Worker started to process request[/dim]")
681
+
682
+
683
+ @app.command()
684
+ def search(
685
+ query: Optional[str] = typer.Argument(None, help="Search query"),
686
+ state: Optional[TicketState] = typer.Option(None, "--state", "-s"),
687
+ priority: Optional[Priority] = typer.Option(None, "--priority", "-p"),
688
+ assignee: Optional[str] = typer.Option(None, "--assignee", "-a"),
689
+ limit: int = typer.Option(10, "--limit", "-l"),
690
+ adapter: Optional[AdapterType] = typer.Option(
691
+ None, "--adapter", help="Override default adapter"
692
+ ),
693
+ ) -> None:
694
+ """Search tickets with advanced query."""
695
+
696
+ async def _search():
697
+ adapter_instance = get_adapter(
698
+ override_adapter=adapter.value if adapter else None
699
+ )
700
+ search_query = SearchQuery(
701
+ query=query,
702
+ state=state,
703
+ priority=priority,
704
+ assignee=assignee,
705
+ limit=limit,
706
+ )
707
+ return await adapter_instance.search(search_query)
708
+
709
+ tickets = asyncio.run(_search())
710
+
711
+ if not tickets:
712
+ console.print("[yellow]No tickets found matching query[/yellow]")
713
+ return
714
+
715
+ # Display results
716
+ console.print(f"\n[bold]Found {len(tickets)} ticket(s)[/bold]\n")
717
+
718
+ for ticket in tickets:
719
+ console.print(f"[cyan]{ticket.id}[/cyan]: {ticket.title}")
720
+ console.print(f" State: {ticket.state} | Priority: {ticket.priority}")
721
+ if ticket.assignee:
722
+ console.print(f" Assignee: {ticket.assignee}")
723
+ console.print()
724
+
725
+
726
+ @app.command()
727
+ def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
728
+ """Check status of a queued operation."""
729
+ queue = Queue()
730
+ item = queue.get_item(queue_id)
731
+
732
+ if not item:
733
+ console.print(f"[red]Queue item not found: {queue_id}[/red]")
734
+ raise typer.Exit(1)
735
+
736
+ # Display status
737
+ console.print(f"\n[bold]Queue Item: {item.id}[/bold]")
738
+ console.print(f"Operation: {item.operation}")
739
+ console.print(f"Adapter: {item.adapter}")
740
+
741
+ # Status with color
742
+ if item.status == QueueStatus.COMPLETED:
743
+ console.print(f"Status: [green]{item.status}[/green]")
744
+ elif item.status == QueueStatus.FAILED:
745
+ console.print(f"Status: [red]{item.status}[/red]")
746
+ elif item.status == QueueStatus.PROCESSING:
747
+ console.print(f"Status: [yellow]{item.status}[/yellow]")
748
+ else:
749
+ console.print(f"Status: {item.status}")
750
+
751
+ # Timestamps
752
+ console.print(f"Created: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
753
+ if item.processed_at:
754
+ console.print(f"Processed: {item.processed_at.strftime('%Y-%m-%d %H:%M:%S')}")
755
+
756
+ # Error or result
757
+ if item.error_message:
758
+ console.print(f"\n[red]Error:[/red] {item.error_message}")
759
+ elif item.result:
760
+ console.print("\n[green]Result:[/green]")
761
+ for key, value in item.result.items():
762
+ console.print(f" {key}: {value}")
763
+
764
+ if item.retry_count > 0:
765
+ console.print(f"\nRetry Count: {item.retry_count}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.3.7
3
+ Version: 0.4.0
4
4
  Summary: Universal ticket management interface for AI agents with MCP support
5
5
  Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
6
6
  Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
@@ -1,5 +1,5 @@
1
1
  mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
2
- mcp_ticketer/__version__.py,sha256=s7BXKaEC9wzrKYxOdqIb1UHlYxMSgLWk-8vzfr8WEIc,1117
2
+ mcp_ticketer/__version__.py,sha256=CR9K0foaKbPjgF5Yl6n14vC3Mv6RP3UcC71ofaagTzE,1117
3
3
  mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
5
5
  mcp_ticketer/adapters/aitrackdown.py,sha256=Ecw2SQAGVQs5yMH6m2pj61LxCJsuy-g2bvF8uwTpLUE,22588
@@ -24,11 +24,13 @@ mcp_ticketer/cli/diagnostics.py,sha256=jHF68ydW3RNVGumBnHUjUmq6YOjQD2UDkx0O7M__x
24
24
  mcp_ticketer/cli/discover.py,sha256=AF_qlQc1Oo0UkWayoF5pmRChS5J3fJjH6f2YZzd_k8w,13188
25
25
  mcp_ticketer/cli/gemini_configure.py,sha256=ZNSA1lBW-itVToza-JxW95Po7daVXKiZAh7lp6pmXMU,9343
26
26
  mcp_ticketer/cli/linear_commands.py,sha256=_8f8ze_1MbiUweU6RFHpldgfHLirysIdPjHr2_S0YhI,17319
27
- mcp_ticketer/cli/main.py,sha256=BFo7QYU0tTOHWSfTmegzDqflSYFytCE8Jw6QGB7TwaY,74470
27
+ mcp_ticketer/cli/main.py,sha256=veWHcq44L58SxegU3iU--r1jz9LM1OWv4fUG47K3BGw,77798
28
28
  mcp_ticketer/cli/mcp_configure.py,sha256=RzV50UjXgOmvMp-9S0zS39psuvjffVByaMrqrUaAGAM,9594
29
29
  mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
30
+ mcp_ticketer/cli/platform_commands.py,sha256=koLWUFW-q1oRxJMVI_V35cDZ8OOH3F3bIdD8RRTTRpM,3694
30
31
  mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19OtHiZI,7884
31
32
  mcp_ticketer/cli/simple_health.py,sha256=GlOLRRFoifCna995NoHuKpb3xmFkLi2b3Ke1hyeDvq4,7950
33
+ mcp_ticketer/cli/ticket_commands.py,sha256=AD9D-EidPA3vk_-MpY3B4QGKhezCQqbKCn8vRsg57Rg,26637
32
34
  mcp_ticketer/cli/utils.py,sha256=IOycMmhwtCDpL3RN_wVEZkYMg9FHyDr4mg5M9Bxzfbo,23041
33
35
  mcp_ticketer/core/__init__.py,sha256=eXovsaJymQRP2AwOBuOy6mFtI3I68D7gGenZ5V-IMqo,349
34
36
  mcp_ticketer/core/adapter.py,sha256=q64LxOInIno7EIbmuxItf8KEsd-g9grCs__Z4uwZHto,10273
@@ -54,9 +56,9 @@ mcp_ticketer/queue/queue.py,sha256=PIB_8gOE4rCb5_tBNKw9qD6YhSgH3Ei3IzVrUSY3F_o,1
54
56
  mcp_ticketer/queue/run_worker.py,sha256=WhoeamL8LKZ66TM8W1PkMPwjF2w_EDFMP-mevs6C1TM,1019
55
57
  mcp_ticketer/queue/ticket_registry.py,sha256=FE6W_D8NA-66cJQ6VqghChF3JasYW845JVfEZdiqLbA,15449
56
58
  mcp_ticketer/queue/worker.py,sha256=AF6W1bdxWnHiJd6-iBWqTHkZ4lFflsS65CAtgFPR0FA,20983
57
- mcp_ticketer-0.3.7.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
58
- mcp_ticketer-0.3.7.dist-info/METADATA,sha256=ivW854H301BPY_YzUtzYhhhQ15sQKF_u-owFjOq6e24,13219
59
- mcp_ticketer-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
- mcp_ticketer-0.3.7.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
61
- mcp_ticketer-0.3.7.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
62
- mcp_ticketer-0.3.7.dist-info/RECORD,,
59
+ mcp_ticketer-0.4.0.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
60
+ mcp_ticketer-0.4.0.dist-info/METADATA,sha256=kU9H9axMFLLYInrAxYQPW-4rlfBEd9USR_kOHdlfWQY,13219
61
+ mcp_ticketer-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
62
+ mcp_ticketer-0.4.0.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
63
+ mcp_ticketer-0.4.0.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
64
+ mcp_ticketer-0.4.0.dist-info/RECORD,,