mcp-ticketer 0.1.22__py3-none-any.whl → 0.1.24__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 (33) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +15 -14
  4. mcp_ticketer/adapters/github.py +21 -20
  5. mcp_ticketer/adapters/hybrid.py +13 -12
  6. mcp_ticketer/adapters/jira.py +32 -27
  7. mcp_ticketer/adapters/linear.py +29 -26
  8. mcp_ticketer/cache/memory.py +2 -2
  9. mcp_ticketer/cli/auggie_configure.py +237 -0
  10. mcp_ticketer/cli/codex_configure.py +257 -0
  11. mcp_ticketer/cli/gemini_configure.py +261 -0
  12. mcp_ticketer/cli/main.py +171 -10
  13. mcp_ticketer/cli/migrate_config.py +3 -7
  14. mcp_ticketer/cli/utils.py +8 -8
  15. mcp_ticketer/core/adapter.py +12 -11
  16. mcp_ticketer/core/config.py +17 -17
  17. mcp_ticketer/core/env_discovery.py +24 -24
  18. mcp_ticketer/core/http_client.py +13 -13
  19. mcp_ticketer/core/mappers.py +25 -25
  20. mcp_ticketer/core/models.py +10 -10
  21. mcp_ticketer/core/project_config.py +25 -22
  22. mcp_ticketer/core/registry.py +7 -7
  23. mcp_ticketer/mcp/server.py +18 -18
  24. mcp_ticketer/queue/manager.py +2 -2
  25. mcp_ticketer/queue/queue.py +7 -7
  26. mcp_ticketer/queue/worker.py +8 -8
  27. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/METADATA +58 -8
  28. mcp_ticketer-0.1.24.dist-info/RECORD +45 -0
  29. mcp_ticketer-0.1.22.dist-info/RECORD +0 -42
  30. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/WHEEL +0 -0
  31. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/entry_points.txt +0 -0
  32. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/licenses/LICENSE +0 -0
  33. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,11 @@
1
1
  """Linear adapter implementation using native GraphQL API with full feature support."""
2
2
 
3
3
  import asyncio
4
+ import builtins
4
5
  import os
5
6
  from datetime import date, datetime
6
7
  from enum import Enum
7
- from typing import Any, Dict, List, Optional, Union
8
+ from typing import Any, Optional, Union
8
9
 
9
10
  from gql import Client, gql
10
11
  from gql.transport.exceptions import TransportQueryError
@@ -278,7 +279,7 @@ ISSUE_LIST_FRAGMENTS = (
278
279
  class LinearAdapter(BaseAdapter[Task]):
279
280
  """Adapter for Linear issue tracking system using native GraphQL API."""
280
281
 
281
- def __init__(self, config: Dict[str, Any]):
282
+ def __init__(self, config: dict[str, Any]):
282
283
  """Initialize Linear adapter.
283
284
 
284
285
  Args:
@@ -315,9 +316,9 @@ class LinearAdapter(BaseAdapter[Task]):
315
316
 
316
317
  # Caches for frequently used data
317
318
  self._team_id: Optional[str] = None
318
- self._workflow_states: Optional[Dict[str, Dict[str, Any]]] = None
319
- self._labels: Optional[Dict[str, str]] = None # name -> id
320
- self._users: Optional[Dict[str, str]] = None # email -> id
319
+ self._workflow_states: Optional[dict[str, dict[str, Any]]] = None
320
+ self._labels: Optional[dict[str, str]] = None # name -> id
321
+ self._users: Optional[dict[str, str]] = None # email -> id
321
322
 
322
323
  # Initialize state mapping
323
324
  self._state_mapping = self._get_state_mapping()
@@ -435,7 +436,7 @@ class LinearAdapter(BaseAdapter[Task]):
435
436
 
436
437
  async def _fetch_workflow_states_data(
437
438
  self, team_id: str
438
- ) -> Dict[str, Dict[str, Any]]:
439
+ ) -> dict[str, dict[str, Any]]:
439
440
  """Fetch workflow states data."""
440
441
  query = gql(
441
442
  """
@@ -467,7 +468,7 @@ class LinearAdapter(BaseAdapter[Task]):
467
468
 
468
469
  return workflow_states
469
470
 
470
- async def _fetch_labels_data(self, team_id: str) -> Dict[str, str]:
471
+ async def _fetch_labels_data(self, team_id: str) -> dict[str, str]:
471
472
  """Fetch labels data."""
472
473
  query = gql(
473
474
  """
@@ -498,7 +499,7 @@ class LinearAdapter(BaseAdapter[Task]):
498
499
  await self._ensure_initialized()
499
500
  return self._team_id
500
501
 
501
- async def _get_workflow_states(self) -> Dict[str, Dict[str, Any]]:
502
+ async def _get_workflow_states(self) -> dict[str, dict[str, Any]]:
502
503
  """Get cached workflow states from Linear."""
503
504
  await self._ensure_initialized()
504
505
  return self._workflow_states
@@ -619,7 +620,7 @@ class LinearAdapter(BaseAdapter[Task]):
619
620
  )
620
621
  return True, ""
621
622
 
622
- def _get_state_mapping(self) -> Dict[TicketState, str]:
623
+ def _get_state_mapping(self) -> dict[TicketState, str]:
623
624
  """Get mapping from universal states to Linear state types.
624
625
 
625
626
  Required by BaseAdapter abstract method.
@@ -643,7 +644,7 @@ class LinearAdapter(BaseAdapter[Task]):
643
644
  return self._state_mapping.get(state, LinearStateType.BACKLOG)
644
645
 
645
646
  def _map_linear_state(
646
- self, state_data: Dict[str, Any], labels: List[str]
647
+ self, state_data: dict[str, Any], labels: list[str]
647
648
  ) -> TicketState:
648
649
  """Map Linear state and labels to universal state."""
649
650
  state_type = state_data.get("type", "").lower()
@@ -669,7 +670,7 @@ class LinearAdapter(BaseAdapter[Task]):
669
670
  }
670
671
  return state_mapping.get(state_type, TicketState.OPEN)
671
672
 
672
- def _task_from_linear_issue(self, issue: Dict[str, Any]) -> Task:
673
+ def _task_from_linear_issue(self, issue: dict[str, Any]) -> Task:
673
674
  """Convert Linear issue to universal Task."""
674
675
  # Extract labels
675
676
  tags = []
@@ -786,7 +787,7 @@ class LinearAdapter(BaseAdapter[Task]):
786
787
  metadata=metadata,
787
788
  )
788
789
 
789
- def _epic_from_linear_project(self, project: Dict[str, Any]) -> Epic:
790
+ def _epic_from_linear_project(self, project: dict[str, Any]) -> Epic:
790
791
  """Convert Linear project to universal Epic."""
791
792
  # Map project state to ticket state
792
793
  project_state = project.get("state", "planned").lower()
@@ -1005,7 +1006,7 @@ class LinearAdapter(BaseAdapter[Task]):
1005
1006
 
1006
1007
  return None
1007
1008
 
1008
- async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[Task]:
1009
+ async def update(self, ticket_id: str, updates: dict[str, Any]) -> Optional[Task]:
1009
1010
  """Update a Linear issue with comprehensive field support."""
1010
1011
  # Validate credentials before attempting operation
1011
1012
  is_valid, error_message = self.validate_credentials()
@@ -1162,8 +1163,8 @@ class LinearAdapter(BaseAdapter[Task]):
1162
1163
  return result.get("issueArchive", {}).get("success", False)
1163
1164
 
1164
1165
  async def list(
1165
- self, limit: int = 10, offset: int = 0, filters: Optional[Dict[str, Any]] = None
1166
- ) -> List[Task]:
1166
+ self, limit: int = 10, offset: int = 0, filters: Optional[dict[str, Any]] = None
1167
+ ) -> list[Task]:
1167
1168
  """List Linear issues with comprehensive filtering."""
1168
1169
  team_id = await self._ensure_team_id()
1169
1170
 
@@ -1271,7 +1272,7 @@ class LinearAdapter(BaseAdapter[Task]):
1271
1272
 
1272
1273
  return tasks
1273
1274
 
1274
- async def search(self, query: SearchQuery) -> List[Task]:
1275
+ async def search(self, query: SearchQuery) -> builtins.list[Task]:
1275
1276
  """Search Linear issues with advanced filtering and text search."""
1276
1277
  team_id = await self._ensure_team_id()
1277
1278
 
@@ -1445,7 +1446,7 @@ class LinearAdapter(BaseAdapter[Task]):
1445
1446
 
1446
1447
  async def get_comments(
1447
1448
  self, ticket_id: str, limit: int = 10, offset: int = 0
1448
- ) -> List[Comment]:
1449
+ ) -> builtins.list[Comment]:
1449
1450
  """Get comments for a Linear issue with pagination."""
1450
1451
  query = gql(
1451
1452
  USER_FRAGMENT
@@ -1546,7 +1547,9 @@ class LinearAdapter(BaseAdapter[Task]):
1546
1547
 
1547
1548
  return result["projectCreate"]["project"]["id"]
1548
1549
 
1549
- async def get_cycles(self, active_only: bool = True) -> List[Dict[str, Any]]:
1550
+ async def get_cycles(
1551
+ self, active_only: bool = True
1552
+ ) -> builtins.list[dict[str, Any]]:
1550
1553
  """Get Linear cycles (sprints) for the team."""
1551
1554
  team_id = await self._ensure_team_id()
1552
1555
 
@@ -1638,7 +1641,7 @@ class LinearAdapter(BaseAdapter[Task]):
1638
1641
  ticket_id: str,
1639
1642
  pr_url: str,
1640
1643
  pr_number: Optional[int] = None,
1641
- ) -> Dict[str, Any]:
1644
+ ) -> dict[str, Any]:
1642
1645
  """Link a Linear issue to a GitHub pull request.
1643
1646
 
1644
1647
  Args:
@@ -1741,8 +1744,8 @@ class LinearAdapter(BaseAdapter[Task]):
1741
1744
  async def create_pull_request_for_issue(
1742
1745
  self,
1743
1746
  ticket_id: str,
1744
- github_config: Dict[str, Any],
1745
- ) -> Dict[str, Any]:
1747
+ github_config: dict[str, Any],
1748
+ ) -> dict[str, Any]:
1746
1749
  """Create a GitHub PR for a Linear issue using GitHub integration.
1747
1750
 
1748
1751
  This requires GitHub integration to be configured in Linear.
@@ -1837,7 +1840,7 @@ class LinearAdapter(BaseAdapter[Task]):
1837
1840
  else:
1838
1841
  raise ValueError(f"Failed to update issue {ticket_id} with branch name")
1839
1842
 
1840
- async def _search_by_identifier(self, identifier: str) -> Optional[Dict[str, Any]]:
1843
+ async def _search_by_identifier(self, identifier: str) -> Optional[dict[str, Any]]:
1841
1844
  """Search for an issue by its identifier."""
1842
1845
  search_query = gql(
1843
1846
  """
@@ -1857,7 +1860,7 @@ class LinearAdapter(BaseAdapter[Task]):
1857
1860
  search_query, variable_values={"identifier": identifier}
1858
1861
  )
1859
1862
  return result.get("issue")
1860
- except:
1863
+ except Exception:
1861
1864
  return None
1862
1865
 
1863
1866
  # Epic/Issue/Task Hierarchy Methods (Linear: Project = Epic, Issue = Issue, Sub-issue = Task)
@@ -1950,7 +1953,7 @@ class LinearAdapter(BaseAdapter[Task]):
1950
1953
 
1951
1954
  return None
1952
1955
 
1953
- async def list_epics(self, **kwargs) -> List[Epic]:
1956
+ async def list_epics(self, **kwargs) -> builtins.list[Epic]:
1954
1957
  """List all Linear Projects (Epics).
1955
1958
 
1956
1959
  Args:
@@ -2037,7 +2040,7 @@ class LinearAdapter(BaseAdapter[Task]):
2037
2040
  # The existing create method handles project association via parent_epic field
2038
2041
  return await self.create(task)
2039
2042
 
2040
- async def list_issues_by_epic(self, epic_id: str) -> List[Task]:
2043
+ async def list_issues_by_epic(self, epic_id: str) -> builtins.list[Task]:
2041
2044
  """List all issues in a Linear project (epic).
2042
2045
 
2043
2046
  Args:
@@ -2197,7 +2200,7 @@ class LinearAdapter(BaseAdapter[Task]):
2197
2200
  created_issue = result["issueCreate"]["issue"]
2198
2201
  return self._task_from_linear_issue(created_issue)
2199
2202
 
2200
- async def list_tasks_by_issue(self, issue_id: str) -> List[Task]:
2203
+ async def list_tasks_by_issue(self, issue_id: str) -> builtins.list[Task]:
2201
2204
  """List all tasks (sub-issues) under an issue.
2202
2205
 
2203
2206
  Args:
@@ -5,7 +5,7 @@ import hashlib
5
5
  import json
6
6
  import time
7
7
  from functools import wraps
8
- from typing import Any, Callable, Dict, Optional
8
+ from typing import Any, Callable, Optional
9
9
 
10
10
 
11
11
  class CacheEntry:
@@ -37,7 +37,7 @@ class MemoryCache:
37
37
  default_ttl: Default TTL in seconds (5 minutes)
38
38
 
39
39
  """
40
- self._cache: Dict[str, CacheEntry] = {}
40
+ self._cache: dict[str, CacheEntry] = {}
41
41
  self._default_ttl = default_ttl
42
42
  self._lock = asyncio.Lock()
43
43
 
@@ -0,0 +1,237 @@
1
+ """Auggie CLI configuration for mcp-ticketer integration.
2
+
3
+ IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
4
+ There is no project-level configuration support.
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from rich.console import Console
12
+
13
+ from .mcp_configure import find_mcp_ticketer_binary, load_project_config
14
+
15
+ console = Console()
16
+
17
+
18
+ def find_auggie_config() -> Path:
19
+ """Find or create Auggie CLI configuration file.
20
+
21
+ Auggie CLI only supports global user-level configuration.
22
+
23
+ Returns:
24
+ Path to Auggie settings file at ~/.augment/settings.json
25
+
26
+ """
27
+ # Global user-level configuration (ONLY option for Auggie)
28
+ config_path = Path.home() / ".augment" / "settings.json"
29
+ return config_path
30
+
31
+
32
+ def load_auggie_config(config_path: Path) -> dict[str, Any]:
33
+ """Load existing Auggie configuration or return empty structure.
34
+
35
+ Args:
36
+ config_path: Path to Auggie settings file
37
+
38
+ Returns:
39
+ Auggie configuration dict
40
+
41
+ """
42
+ if config_path.exists():
43
+ try:
44
+ with open(config_path) as f:
45
+ config: dict[str, Any] = json.load(f)
46
+ return config
47
+ except json.JSONDecodeError as e:
48
+ console.print(
49
+ f"[yellow]⚠ Warning: Could not parse existing config: {e}[/yellow]"
50
+ )
51
+ console.print("[yellow]Creating new configuration...[/yellow]")
52
+
53
+ # Return empty structure with mcpServers section
54
+ return {"mcpServers": {}}
55
+
56
+
57
+ def save_auggie_config(config_path: Path, config: dict[str, Any]) -> None:
58
+ """Save Auggie configuration to file.
59
+
60
+ Args:
61
+ config_path: Path to Auggie settings file
62
+ config: Configuration to save
63
+
64
+ """
65
+ # Ensure directory exists
66
+ config_path.parent.mkdir(parents=True, exist_ok=True)
67
+
68
+ # Write with 2-space indentation (JSON standard)
69
+ with open(config_path, "w") as f:
70
+ json.dump(config, f, indent=2)
71
+
72
+
73
+ def create_auggie_server_config(
74
+ binary_path: str, project_config: dict[str, Any]
75
+ ) -> dict[str, Any]:
76
+ """Create Auggie MCP server configuration for mcp-ticketer.
77
+
78
+ Args:
79
+ binary_path: Path to mcp-ticketer binary
80
+ project_config: Project configuration from .mcp-ticketer/config.json
81
+
82
+ Returns:
83
+ Auggie MCP server configuration dict
84
+
85
+ """
86
+ # Get adapter configuration
87
+ adapter = project_config.get("default_adapter", "aitrackdown")
88
+ adapters_config = project_config.get("adapters", {})
89
+ adapter_config = adapters_config.get(adapter, {})
90
+
91
+ # Build environment variables
92
+ env_vars = {}
93
+
94
+ # Add adapter type
95
+ env_vars["MCP_TICKETER_ADAPTER"] = adapter
96
+
97
+ # Add adapter-specific environment variables
98
+ if adapter == "aitrackdown":
99
+ # Set base path for local adapter
100
+ base_path = adapter_config.get("base_path", ".aitrackdown")
101
+ # Use absolute path to home directory for global config
102
+ # Since Auggie is global, we can't rely on project-specific paths
103
+ env_vars["MCP_TICKETER_BASE_PATH"] = str(
104
+ Path.home() / ".mcp-ticketer" / base_path
105
+ )
106
+
107
+ elif adapter == "linear":
108
+ if "api_key" in adapter_config:
109
+ env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
110
+ if "team_id" in adapter_config:
111
+ env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
112
+
113
+ elif adapter == "github":
114
+ if "token" in adapter_config:
115
+ env_vars["GITHUB_TOKEN"] = adapter_config["token"]
116
+ if "owner" in adapter_config:
117
+ env_vars["GITHUB_OWNER"] = adapter_config["owner"]
118
+ if "repo" in adapter_config:
119
+ env_vars["GITHUB_REPO"] = adapter_config["repo"]
120
+
121
+ elif adapter == "jira":
122
+ if "api_token" in adapter_config:
123
+ env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
124
+ if "email" in adapter_config:
125
+ env_vars["JIRA_EMAIL"] = adapter_config["email"]
126
+ if "server" in adapter_config:
127
+ env_vars["JIRA_SERVER"] = adapter_config["server"]
128
+ if "project_key" in adapter_config:
129
+ env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
130
+
131
+ # Create server configuration (simpler than Gemini - no timeout/trust)
132
+ config = {
133
+ "command": binary_path,
134
+ "args": ["serve"],
135
+ "env": env_vars,
136
+ }
137
+
138
+ return config
139
+
140
+
141
+ def configure_auggie_mcp(force: bool = False) -> None:
142
+ """Configure Auggie CLI to use mcp-ticketer.
143
+
144
+ IMPORTANT: Auggie CLI ONLY supports global configuration.
145
+ This will configure ~/.augment/settings.json for all projects.
146
+
147
+ Args:
148
+ force: Overwrite existing configuration
149
+
150
+ Raises:
151
+ FileNotFoundError: If binary or project config not found
152
+ ValueError: If configuration is invalid
153
+
154
+ """
155
+ # Step 1: Find mcp-ticketer binary
156
+ console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
157
+ try:
158
+ binary_path = find_mcp_ticketer_binary()
159
+ console.print(f"[green]✓[/green] Found: {binary_path}")
160
+ except FileNotFoundError as e:
161
+ console.print(f"[red]✗[/red] {e}")
162
+ raise
163
+
164
+ # Step 2: Load project configuration
165
+ console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
166
+ try:
167
+ project_config = load_project_config()
168
+ adapter = project_config.get("default_adapter", "aitrackdown")
169
+ console.print(f"[green]✓[/green] Adapter: {adapter}")
170
+ except (FileNotFoundError, ValueError) as e:
171
+ console.print(f"[red]✗[/red] {e}")
172
+ raise
173
+
174
+ # Step 3: Find Auggie config location
175
+ console.print("\n[cyan]🔧 Configuring global Auggie CLI...[/cyan]")
176
+ console.print(
177
+ "[yellow]⚠ NOTE: Auggie only supports global configuration (affects all projects)[/yellow]"
178
+ )
179
+
180
+ auggie_config_path = find_auggie_config()
181
+ console.print(f"[dim]Config location: {auggie_config_path}[/dim]")
182
+
183
+ # Step 4: Load existing Auggie configuration
184
+ auggie_config = load_auggie_config(auggie_config_path)
185
+
186
+ # Step 5: Check if mcp-ticketer already configured
187
+ if "mcp-ticketer" in auggie_config.get("mcpServers", {}):
188
+ if not force:
189
+ console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
190
+ console.print("[dim]Use --force to overwrite existing configuration[/dim]")
191
+ return
192
+ else:
193
+ console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
194
+
195
+ # Step 6: Create mcp-ticketer server config
196
+ server_config = create_auggie_server_config(
197
+ binary_path=binary_path, project_config=project_config
198
+ )
199
+
200
+ # Step 7: Update Auggie configuration
201
+ if "mcpServers" not in auggie_config:
202
+ auggie_config["mcpServers"] = {}
203
+
204
+ auggie_config["mcpServers"]["mcp-ticketer"] = server_config
205
+
206
+ # Step 8: Save configuration
207
+ try:
208
+ save_auggie_config(auggie_config_path, auggie_config)
209
+ console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
210
+ console.print(f"[dim]Configuration saved to: {auggie_config_path}[/dim]")
211
+
212
+ # Print configuration details
213
+ console.print("\n[bold]Configuration Details:[/bold]")
214
+ console.print(" Server name: mcp-ticketer")
215
+ console.print(f" Adapter: {adapter}")
216
+ console.print(f" Binary: {binary_path}")
217
+ console.print(" Scope: Global (affects all projects)")
218
+ if "env" in server_config:
219
+ console.print(
220
+ f" Environment variables: {list(server_config['env'].keys())}"
221
+ )
222
+
223
+ # Next steps
224
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
225
+ console.print("1. Restart Auggie CLI for changes to take effect")
226
+ console.print("2. Run 'auggie' command in any directory")
227
+ console.print("3. mcp-ticketer tools will be available via MCP")
228
+ console.print(
229
+ "\n[yellow]⚠ Warning: This is a global configuration affecting all projects[/yellow]"
230
+ )
231
+ console.print(
232
+ "[dim]If you need project-specific configuration, use Claude or Gemini instead[/dim]"
233
+ )
234
+
235
+ except Exception as e:
236
+ console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
237
+ raise