mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.22__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 (42) hide show
  1. mcp_ticketer/__init__.py +7 -7
  2. mcp_ticketer/__version__.py +4 -2
  3. mcp_ticketer/adapters/__init__.py +4 -4
  4. mcp_ticketer/adapters/aitrackdown.py +54 -38
  5. mcp_ticketer/adapters/github.py +175 -109
  6. mcp_ticketer/adapters/hybrid.py +90 -45
  7. mcp_ticketer/adapters/jira.py +139 -130
  8. mcp_ticketer/adapters/linear.py +374 -225
  9. mcp_ticketer/cache/__init__.py +1 -1
  10. mcp_ticketer/cache/memory.py +14 -15
  11. mcp_ticketer/cli/__init__.py +1 -1
  12. mcp_ticketer/cli/configure.py +69 -93
  13. mcp_ticketer/cli/discover.py +43 -35
  14. mcp_ticketer/cli/main.py +250 -293
  15. mcp_ticketer/cli/mcp_configure.py +39 -15
  16. mcp_ticketer/cli/migrate_config.py +10 -12
  17. mcp_ticketer/cli/queue_commands.py +21 -58
  18. mcp_ticketer/cli/utils.py +115 -60
  19. mcp_ticketer/core/__init__.py +2 -2
  20. mcp_ticketer/core/adapter.py +36 -30
  21. mcp_ticketer/core/config.py +113 -77
  22. mcp_ticketer/core/env_discovery.py +51 -19
  23. mcp_ticketer/core/http_client.py +46 -29
  24. mcp_ticketer/core/mappers.py +79 -35
  25. mcp_ticketer/core/models.py +29 -15
  26. mcp_ticketer/core/project_config.py +131 -66
  27. mcp_ticketer/core/registry.py +12 -12
  28. mcp_ticketer/mcp/__init__.py +1 -1
  29. mcp_ticketer/mcp/server.py +183 -129
  30. mcp_ticketer/queue/__init__.py +2 -2
  31. mcp_ticketer/queue/__main__.py +1 -1
  32. mcp_ticketer/queue/manager.py +29 -25
  33. mcp_ticketer/queue/queue.py +144 -82
  34. mcp_ticketer/queue/run_worker.py +2 -3
  35. mcp_ticketer/queue/worker.py +48 -33
  36. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/METADATA +1 -1
  37. mcp_ticketer-0.1.22.dist-info/RECORD +42 -0
  38. mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
  39. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/WHEEL +0 -0
  40. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/entry_points.txt +0 -0
  41. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/licenses/LICENSE +0 -0
  42. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/top_level.txt +0 -0
@@ -20,9 +20,11 @@ def find_mcp_ticketer_binary() -> str:
20
20
 
21
21
  Raises:
22
22
  FileNotFoundError: If binary not found
23
+
23
24
  """
24
25
  # Check if running from development environment
25
26
  import mcp_ticketer
27
+
26
28
  package_path = Path(mcp_ticketer.__file__).parent.parent.parent
27
29
 
28
30
  # Check for virtual environment bin
@@ -34,7 +36,13 @@ def find_mcp_ticketer_binary() -> str:
34
36
  # System installation
35
37
  Path.home() / ".local" / "bin" / "mcp-ticketer",
36
38
  # pipx installation
37
- Path.home() / ".local" / "pipx" / "venvs" / "mcp-ticketer" / "bin" / "mcp-ticketer",
39
+ Path.home()
40
+ / ".local"
41
+ / "pipx"
42
+ / "venvs"
43
+ / "mcp-ticketer"
44
+ / "bin"
45
+ / "mcp-ticketer",
38
46
  ]
39
47
 
40
48
  # Check PATH
@@ -62,6 +70,7 @@ def load_project_config() -> dict:
62
70
  Raises:
63
71
  FileNotFoundError: If config not found
64
72
  ValueError: If config is invalid
73
+
65
74
  """
66
75
  # Check for project-specific config first
67
76
  project_config_path = Path.cwd() / ".mcp-ticketer" / "config.json"
@@ -77,7 +86,7 @@ def load_project_config() -> dict:
77
86
  "Run 'mcp-ticketer init' to create configuration."
78
87
  )
79
88
 
80
- with open(project_config_path, "r") as f:
89
+ with open(project_config_path) as f:
81
90
  config = json.load(f)
82
91
 
83
92
  # Validate config
@@ -95,15 +104,28 @@ def find_claude_mcp_config(global_config: bool = False) -> Path:
95
104
 
96
105
  Returns:
97
106
  Path to MCP configuration file
107
+
98
108
  """
99
109
  if global_config:
100
110
  # Claude Desktop configuration
101
111
  if sys.platform == "darwin": # macOS
102
- config_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
112
+ config_path = (
113
+ Path.home()
114
+ / "Library"
115
+ / "Application Support"
116
+ / "Claude"
117
+ / "claude_desktop_config.json"
118
+ )
103
119
  elif sys.platform == "win32": # Windows
104
- config_path = Path(os.environ.get("APPDATA", "")) / "Claude" / "claude_desktop_config.json"
120
+ config_path = (
121
+ Path(os.environ.get("APPDATA", ""))
122
+ / "Claude"
123
+ / "claude_desktop_config.json"
124
+ )
105
125
  else: # Linux
106
- config_path = Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
126
+ config_path = (
127
+ Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
128
+ )
107
129
  else:
108
130
  # Project-level configuration
109
131
  config_path = Path.cwd() / ".mcp" / "config.json"
@@ -119,9 +141,10 @@ def load_claude_mcp_config(config_path: Path) -> dict:
119
141
 
120
142
  Returns:
121
143
  MCP configuration dict
144
+
122
145
  """
123
146
  if config_path.exists():
124
- with open(config_path, "r") as f:
147
+ with open(config_path) as f:
125
148
  return json.load(f)
126
149
 
127
150
  # Return empty structure
@@ -134,6 +157,7 @@ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
134
157
  Args:
135
158
  config_path: Path to MCP config file
136
159
  config: Configuration to save
160
+
137
161
  """
138
162
  # Ensure directory exists
139
163
  config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -144,9 +168,7 @@ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
144
168
 
145
169
 
146
170
  def create_mcp_server_config(
147
- binary_path: str,
148
- project_config: dict,
149
- cwd: Optional[str] = None
171
+ binary_path: str, project_config: dict, cwd: Optional[str] = None
150
172
  ) -> dict:
151
173
  """Create MCP server configuration for mcp-ticketer.
152
174
 
@@ -157,6 +179,7 @@ def create_mcp_server_config(
157
179
 
158
180
  Returns:
159
181
  MCP server configuration dict
182
+
160
183
  """
161
184
  config = {
162
185
  "command": binary_path,
@@ -201,6 +224,7 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
201
224
  Raises:
202
225
  FileNotFoundError: If binary or project config not found
203
226
  ValueError: If configuration is invalid
227
+
204
228
  """
205
229
  # Step 1: Find mcp-ticketer binary
206
230
  console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
@@ -243,9 +267,7 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
243
267
  # Step 6: Create mcp-ticketer server config
244
268
  cwd = str(Path.cwd()) if not global_config else None
245
269
  server_config = create_mcp_server_config(
246
- binary_path=binary_path,
247
- project_config=project_config,
248
- cwd=cwd
270
+ binary_path=binary_path, project_config=project_config, cwd=cwd
249
271
  )
250
272
 
251
273
  # Step 7: Update MCP configuration
@@ -257,18 +279,20 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
257
279
  # Step 8: Save configuration
258
280
  try:
259
281
  save_claude_mcp_config(mcp_config_path, mcp_config)
260
- console.print(f"\n[green]✓ Successfully configured mcp-ticketer[/green]")
282
+ console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
261
283
  console.print(f"[dim]Configuration saved to: {mcp_config_path}[/dim]")
262
284
 
263
285
  # Print configuration details
264
286
  console.print("\n[bold]Configuration Details:[/bold]")
265
- console.print(f" Server name: mcp-ticketer")
287
+ console.print(" Server name: mcp-ticketer")
266
288
  console.print(f" Adapter: {adapter}")
267
289
  console.print(f" Binary: {binary_path}")
268
290
  if cwd:
269
291
  console.print(f" Working directory: {cwd}")
270
292
  if "env" in server_config:
271
- console.print(f" Environment variables: {list(server_config['env'].keys())}")
293
+ console.print(
294
+ f" Environment variables: {list(server_config['env'].keys())}"
295
+ )
272
296
 
273
297
  # Next steps
274
298
  console.print("\n[bold cyan]Next Steps:[/bold cyan]")
@@ -2,18 +2,16 @@
2
2
 
3
3
  import json
4
4
  import shutil
5
- from pathlib import Path
6
- from typing import Dict, Any
7
5
  from datetime import datetime
6
+ from typing import Any, Dict
8
7
 
9
8
  from rich.console import Console
10
9
  from rich.prompt import Confirm
11
10
 
12
11
  from ..core.project_config import (
12
+ AdapterConfig,
13
13
  ConfigResolver,
14
14
  TicketerConfig,
15
- AdapterConfig,
16
- AdapterType
17
15
  )
18
16
 
19
17
  console = Console()
@@ -24,6 +22,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
24
22
 
25
23
  Args:
26
24
  dry_run: If True, show what would be done without making changes
25
+
27
26
  """
28
27
  resolver = ConfigResolver()
29
28
 
@@ -34,7 +33,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
34
33
 
35
34
  # Load old config
36
35
  try:
37
- with open(resolver.GLOBAL_CONFIG_PATH, 'r') as f:
36
+ with open(resolver.GLOBAL_CONFIG_PATH) as f:
38
37
  old_config = json.load(f)
39
38
  except Exception as e:
40
39
  console.print(f"[red]Failed to load config: {e}[/red]")
@@ -76,8 +75,8 @@ def migrate_config_command(dry_run: bool = False) -> None:
76
75
  return
77
76
 
78
77
  # Backup old config
79
- backup_path = resolver.GLOBAL_CONFIG_PATH.with_suffix('.json.bak')
80
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
78
+ backup_path = resolver.GLOBAL_CONFIG_PATH.with_suffix(".json.bak")
79
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
81
80
  backup_path = resolver.GLOBAL_CONFIG_PATH.parent / f"config.{timestamp}.bak"
82
81
 
83
82
  try:
@@ -90,7 +89,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
90
89
  # Save new config
91
90
  try:
92
91
  resolver.save_global_config(new_config)
93
- console.print(f"[green]✓[/green] Migration complete!")
92
+ console.print("[green]✓[/green] Migration complete!")
94
93
  console.print(f"[dim]New config saved to: {resolver.GLOBAL_CONFIG_PATH}[/dim]")
95
94
  except Exception as e:
96
95
  console.print(f"[red]Failed to save new config: {e}[/red]")
@@ -132,6 +131,7 @@ def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
132
131
 
133
132
  Returns:
134
133
  New TicketerConfig object
134
+
135
135
  """
136
136
  adapters = {}
137
137
  default_adapter = "aitrackdown"
@@ -164,10 +164,7 @@ def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
164
164
  default_adapter = old_config.get("default_adapter", "aitrackdown")
165
165
 
166
166
  # Create new config
167
- new_config = TicketerConfig(
168
- default_adapter=default_adapter,
169
- adapters=adapters
170
- )
167
+ new_config = TicketerConfig(default_adapter=default_adapter, adapters=adapters)
171
168
 
172
169
  return new_config
173
170
 
@@ -180,6 +177,7 @@ def validate_migrated_config(config: TicketerConfig) -> bool:
180
177
 
181
178
  Returns:
182
179
  True if valid, False otherwise
180
+
183
181
  """
184
182
  from ..core.project_config import ConfigValidator
185
183
 
@@ -1,34 +1,21 @@
1
1
  """Queue-related CLI commands."""
2
2
 
3
+ from datetime import datetime
4
+
3
5
  import typer
4
6
  from rich.console import Console
5
7
  from rich.table import Table
6
- from rich import print as rprint
7
- from datetime import datetime
8
8
 
9
- from ..queue import Queue, QueueStatus, WorkerManager, Worker
9
+ from ..queue import Queue, QueueStatus, Worker, WorkerManager
10
10
 
11
- app = typer.Typer(
12
- name="queue",
13
- help="Queue management commands"
14
- )
11
+ app = typer.Typer(name="queue", help="Queue management commands")
15
12
  console = Console()
16
13
 
17
14
 
18
15
  @app.command("list")
19
16
  def list_queue(
20
- status: QueueStatus = typer.Option(
21
- None,
22
- "--status",
23
- "-s",
24
- help="Filter by status"
25
- ),
26
- limit: int = typer.Option(
27
- 25,
28
- "--limit",
29
- "-l",
30
- help="Maximum items to show"
31
- )
17
+ status: QueueStatus = typer.Option(None, "--status", "-s", help="Filter by status"),
18
+ limit: int = typer.Option(25, "--limit", "-l", help="Maximum items to show"),
32
19
  ):
33
20
  """List queue items."""
34
21
  queue = Queue()
@@ -67,7 +54,7 @@ def list_queue(
67
54
  item.adapter,
68
55
  status_str,
69
56
  created_str,
70
- str(item.retry_count)
57
+ str(item.retry_count),
71
58
  )
72
59
 
73
60
  console.print(table)
@@ -82,9 +69,7 @@ def list_queue(
82
69
 
83
70
 
84
71
  @app.command("retry")
85
- def retry_item(
86
- queue_id: str = typer.Argument(..., help="Queue ID to retry")
87
- ):
72
+ def retry_item(queue_id: str = typer.Argument(..., help="Queue ID to retry")):
88
73
  """Retry a failed queue item."""
89
74
  queue = Queue()
90
75
  item = queue.get_item(queue_id)
@@ -94,7 +79,9 @@ def retry_item(
94
79
  raise typer.Exit(1)
95
80
 
96
81
  if item.status != QueueStatus.FAILED:
97
- console.print(f"[yellow]Item {queue_id} is not failed (status: {item.status})[/yellow]")
82
+ console.print(
83
+ f"[yellow]Item {queue_id} is not failed (status: {item.status})[/yellow]"
84
+ )
98
85
  raise typer.Exit(1)
99
86
 
100
87
  # Reset to pending
@@ -110,23 +97,12 @@ def retry_item(
110
97
  @app.command("clear")
111
98
  def clear_queue(
112
99
  status: QueueStatus = typer.Option(
113
- None,
114
- "--status",
115
- "-s",
116
- help="Clear only items with this status"
100
+ None, "--status", "-s", help="Clear only items with this status"
117
101
  ),
118
102
  days: int = typer.Option(
119
- 7,
120
- "--days",
121
- "-d",
122
- help="Clear items older than this many days"
103
+ 7, "--days", "-d", help="Clear items older than this many days"
123
104
  ),
124
- confirm: bool = typer.Option(
125
- False,
126
- "--yes",
127
- "-y",
128
- help="Skip confirmation"
129
- )
105
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
130
106
  ):
131
107
  """Clear old queue items."""
132
108
  queue = Queue()
@@ -142,14 +118,11 @@ def clear_queue(
142
118
  raise typer.Exit(0)
143
119
 
144
120
  queue.cleanup_old(days=days)
145
- console.print(f"[green]✓[/green] Cleared old queue items")
121
+ console.print("[green]✓[/green] Cleared old queue items")
146
122
 
147
123
 
148
124
  # Worker commands
149
- worker_app = typer.Typer(
150
- name="worker",
151
- help="Worker management commands"
152
- )
125
+ worker_app = typer.Typer(name="worker", help="Worker management commands")
153
126
 
154
127
 
155
128
  @worker_app.command("start")
@@ -209,7 +182,7 @@ def worker_status():
209
182
  status = manager.get_status()
210
183
 
211
184
  if status["running"]:
212
- console.print(f"[green]● Worker is running[/green]")
185
+ console.print("[green]● Worker is running[/green]")
213
186
  console.print(f" PID: {status.get('pid')}")
214
187
 
215
188
  if "cpu_percent" in status:
@@ -237,18 +210,8 @@ def worker_status():
237
210
 
238
211
  @worker_app.command("logs")
239
212
  def worker_logs(
240
- lines: int = typer.Option(
241
- 50,
242
- "--lines",
243
- "-n",
244
- help="Number of lines to show"
245
- ),
246
- follow: bool = typer.Option(
247
- False,
248
- "--follow",
249
- "-f",
250
- help="Follow log output"
251
- )
213
+ lines: int = typer.Option(50, "--lines", "-n", help="Number of lines to show"),
214
+ follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
252
215
  ):
253
216
  """View worker logs."""
254
217
  import time
@@ -264,7 +227,7 @@ def worker_logs(
264
227
  # Follow mode - like tail -f
265
228
  console.print("[dim]Following worker logs (Ctrl+C to stop)...[/dim]\n")
266
229
  try:
267
- with open(log_file, "r") as f:
230
+ with open(log_file) as f:
268
231
  # Go to end of file
269
232
  f.seek(0, 2)
270
233
  while True:
@@ -282,4 +245,4 @@ def worker_logs(
282
245
 
283
246
 
284
247
  # Add worker subcommand to queue app
285
- app.add_typer(worker_app, name="worker")
248
+ app.add_typer(worker_app, name="worker")