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.
- mcp_ticketer/__init__.py +7 -7
- mcp_ticketer/__version__.py +4 -2
- mcp_ticketer/adapters/__init__.py +4 -4
- mcp_ticketer/adapters/aitrackdown.py +54 -38
- mcp_ticketer/adapters/github.py +175 -109
- mcp_ticketer/adapters/hybrid.py +90 -45
- mcp_ticketer/adapters/jira.py +139 -130
- mcp_ticketer/adapters/linear.py +374 -225
- mcp_ticketer/cache/__init__.py +1 -1
- mcp_ticketer/cache/memory.py +14 -15
- mcp_ticketer/cli/__init__.py +1 -1
- mcp_ticketer/cli/configure.py +69 -93
- mcp_ticketer/cli/discover.py +43 -35
- mcp_ticketer/cli/main.py +250 -293
- mcp_ticketer/cli/mcp_configure.py +39 -15
- mcp_ticketer/cli/migrate_config.py +10 -12
- mcp_ticketer/cli/queue_commands.py +21 -58
- mcp_ticketer/cli/utils.py +115 -60
- mcp_ticketer/core/__init__.py +2 -2
- mcp_ticketer/core/adapter.py +36 -30
- mcp_ticketer/core/config.py +113 -77
- mcp_ticketer/core/env_discovery.py +51 -19
- mcp_ticketer/core/http_client.py +46 -29
- mcp_ticketer/core/mappers.py +79 -35
- mcp_ticketer/core/models.py +29 -15
- mcp_ticketer/core/project_config.py +131 -66
- mcp_ticketer/core/registry.py +12 -12
- mcp_ticketer/mcp/__init__.py +1 -1
- mcp_ticketer/mcp/server.py +183 -129
- mcp_ticketer/queue/__init__.py +2 -2
- mcp_ticketer/queue/__main__.py +1 -1
- mcp_ticketer/queue/manager.py +29 -25
- mcp_ticketer/queue/queue.py +144 -82
- mcp_ticketer/queue/run_worker.py +2 -3
- mcp_ticketer/queue/worker.py +48 -33
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/METADATA +1 -1
- mcp_ticketer-0.1.22.dist-info/RECORD +42 -0
- mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.22.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py
CHANGED
|
@@ -3,25 +3,23 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Optional, List
|
|
8
6
|
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
|
+
from dotenv import load_dotenv
|
|
11
12
|
from rich.console import Console
|
|
12
13
|
from rich.table import Table
|
|
13
|
-
from rich import print as rprint
|
|
14
|
-
from dotenv import load_dotenv
|
|
15
14
|
|
|
16
|
-
from ..
|
|
15
|
+
from ..__version__ import __version__
|
|
16
|
+
from ..core import AdapterRegistry, Priority, TicketState
|
|
17
17
|
from ..core.models import SearchQuery
|
|
18
|
-
from ..adapters import AITrackdownAdapter
|
|
19
18
|
from ..queue import Queue, QueueStatus, WorkerManager
|
|
20
|
-
from .
|
|
21
|
-
from ..__version__ import __version__
|
|
22
|
-
from .configure import configure_wizard, show_current_config, set_adapter_config
|
|
23
|
-
from .migrate_config import migrate_config_command
|
|
19
|
+
from .configure import configure_wizard, set_adapter_config, show_current_config
|
|
24
20
|
from .discover import app as discover_app
|
|
21
|
+
from .migrate_config import migrate_config_command
|
|
22
|
+
from .queue_commands import app as queue_app
|
|
25
23
|
|
|
26
24
|
# Load environment variables from .env files
|
|
27
25
|
# Priority: .env.local (highest) > .env (base)
|
|
@@ -58,21 +56,21 @@ def main_callback(
|
|
|
58
56
|
"-v",
|
|
59
57
|
callback=version_callback,
|
|
60
58
|
is_eager=True,
|
|
61
|
-
help="Show version and exit"
|
|
59
|
+
help="Show version and exit",
|
|
62
60
|
),
|
|
63
61
|
):
|
|
64
|
-
"""
|
|
65
|
-
MCP Ticketer - Universal ticket management interface.
|
|
62
|
+
"""MCP Ticketer - Universal ticket management interface.
|
|
66
63
|
"""
|
|
67
64
|
pass
|
|
68
65
|
|
|
69
66
|
|
|
70
|
-
# Configuration file management
|
|
71
|
-
CONFIG_FILE = Path.
|
|
67
|
+
# Configuration file management - PROJECT-LOCAL ONLY
|
|
68
|
+
CONFIG_FILE = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
72
69
|
|
|
73
70
|
|
|
74
71
|
class AdapterType(str, Enum):
|
|
75
72
|
"""Available adapter types."""
|
|
73
|
+
|
|
76
74
|
AITRACKDOWN = "aitrackdown"
|
|
77
75
|
LINEAR = "linear"
|
|
78
76
|
JIRA = "jira"
|
|
@@ -80,48 +78,82 @@ class AdapterType(str, Enum):
|
|
|
80
78
|
|
|
81
79
|
|
|
82
80
|
def load_config(project_dir: Optional[Path] = None) -> dict:
|
|
83
|
-
"""Load configuration from file.
|
|
81
|
+
"""Load configuration from project-local config file ONLY.
|
|
82
|
+
|
|
83
|
+
SECURITY: This method ONLY reads from the current project directory
|
|
84
|
+
to prevent configuration leakage across projects. It will NEVER read
|
|
85
|
+
from user home directory or system-wide locations.
|
|
84
86
|
|
|
85
87
|
Args:
|
|
86
88
|
project_dir: Optional project directory to load config from
|
|
87
89
|
|
|
88
90
|
Resolution order:
|
|
89
91
|
1. Project-specific config (.mcp-ticketer/config.json in project_dir or cwd)
|
|
90
|
-
2.
|
|
92
|
+
2. Default to aitrackdown adapter
|
|
91
93
|
|
|
92
94
|
Returns:
|
|
93
|
-
Configuration dictionary
|
|
95
|
+
Configuration dictionary with adapter and config keys.
|
|
96
|
+
Defaults to aitrackdown if no local config exists.
|
|
97
|
+
|
|
94
98
|
"""
|
|
99
|
+
import logging
|
|
100
|
+
|
|
101
|
+
logger = logging.getLogger(__name__)
|
|
102
|
+
|
|
95
103
|
# Use provided project_dir or current working directory
|
|
96
104
|
base_dir = project_dir or Path.cwd()
|
|
97
105
|
|
|
98
|
-
#
|
|
106
|
+
# ONLY check project-specific config in project directory
|
|
99
107
|
project_config = base_dir / ".mcp-ticketer" / "config.json"
|
|
100
108
|
if project_config.exists():
|
|
109
|
+
# Validate that config file is actually in project directory
|
|
101
110
|
try:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
console.print(f"[yellow]Warning: Could not load global config: {e}[/yellow]")
|
|
111
|
+
if not project_config.resolve().is_relative_to(base_dir.resolve()):
|
|
112
|
+
logger.error(
|
|
113
|
+
f"Security violation: Config file {project_config} "
|
|
114
|
+
"is not within project directory"
|
|
115
|
+
)
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"Security violation: Config file {project_config} "
|
|
118
|
+
"is not within project directory"
|
|
119
|
+
)
|
|
120
|
+
except (ValueError, RuntimeError):
|
|
121
|
+
# is_relative_to may raise ValueError in some cases
|
|
122
|
+
pass
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
try:
|
|
125
|
+
with open(project_config) as f:
|
|
126
|
+
config = json.load(f)
|
|
127
|
+
logger.info(
|
|
128
|
+
f"Loaded configuration from project-local: {project_config}"
|
|
129
|
+
)
|
|
130
|
+
return config
|
|
131
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
132
|
+
logger.warning(f"Could not load project config: {e}, using defaults")
|
|
133
|
+
console.print(
|
|
134
|
+
f"[yellow]Warning: Could not load project config: {e}[/yellow]"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Default to aitrackdown with local base path
|
|
138
|
+
logger.info("No project-local config found, defaulting to aitrackdown adapter")
|
|
117
139
|
return {"adapter": "aitrackdown", "config": {"base_path": ".aitrackdown"}}
|
|
118
140
|
|
|
119
141
|
|
|
120
142
|
def save_config(config: dict) -> None:
|
|
121
|
-
"""Save configuration to file.
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
"""Save configuration to project-local config file ONLY.
|
|
144
|
+
|
|
145
|
+
SECURITY: This method ONLY saves to the current project directory
|
|
146
|
+
to prevent configuration leakage across projects.
|
|
147
|
+
"""
|
|
148
|
+
import logging
|
|
149
|
+
|
|
150
|
+
logger = logging.getLogger(__name__)
|
|
151
|
+
|
|
152
|
+
project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
153
|
+
project_config.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
with open(project_config, "w") as f:
|
|
124
155
|
json.dump(config, f, indent=2)
|
|
156
|
+
logger.info(f"Saved configuration to project-local: {project_config}")
|
|
125
157
|
|
|
126
158
|
|
|
127
159
|
def merge_config(updates: dict) -> dict:
|
|
@@ -132,6 +164,7 @@ def merge_config(updates: dict) -> dict:
|
|
|
132
164
|
|
|
133
165
|
Returns:
|
|
134
166
|
Updated configuration
|
|
167
|
+
|
|
135
168
|
"""
|
|
136
169
|
config = load_config()
|
|
137
170
|
|
|
@@ -151,12 +184,15 @@ def merge_config(updates: dict) -> dict:
|
|
|
151
184
|
return config
|
|
152
185
|
|
|
153
186
|
|
|
154
|
-
def get_adapter(
|
|
187
|
+
def get_adapter(
|
|
188
|
+
override_adapter: Optional[str] = None, override_config: Optional[dict] = None
|
|
189
|
+
):
|
|
155
190
|
"""Get configured adapter instance.
|
|
156
191
|
|
|
157
192
|
Args:
|
|
158
193
|
override_adapter: Override the default adapter type
|
|
159
194
|
override_config: Override configuration for the adapter
|
|
195
|
+
|
|
160
196
|
"""
|
|
161
197
|
config = load_config()
|
|
162
198
|
|
|
@@ -182,6 +218,7 @@ def get_adapter(override_adapter: Optional[str] = None, override_config: Optiona
|
|
|
182
218
|
|
|
183
219
|
# Add environment variables for authentication
|
|
184
220
|
import os
|
|
221
|
+
|
|
185
222
|
if adapter_type == "linear":
|
|
186
223
|
if not adapter_config.get("api_key"):
|
|
187
224
|
adapter_config["api_key"] = os.getenv("LINEAR_API_KEY")
|
|
@@ -203,64 +240,48 @@ def init(
|
|
|
203
240
|
None,
|
|
204
241
|
"--adapter",
|
|
205
242
|
"-a",
|
|
206
|
-
help="Adapter type to use (auto-detected from .env if not specified)"
|
|
243
|
+
help="Adapter type to use (auto-detected from .env if not specified)",
|
|
207
244
|
),
|
|
208
245
|
project_path: Optional[str] = typer.Option(
|
|
209
|
-
None,
|
|
210
|
-
"--path",
|
|
211
|
-
help="Project path (default: current directory)"
|
|
246
|
+
None, "--path", help="Project path (default: current directory)"
|
|
212
247
|
),
|
|
213
248
|
global_config: bool = typer.Option(
|
|
214
249
|
False,
|
|
215
250
|
"--global",
|
|
216
251
|
"-g",
|
|
217
|
-
help="Save to global config instead of project-specific"
|
|
252
|
+
help="Save to global config instead of project-specific",
|
|
218
253
|
),
|
|
219
254
|
base_path: Optional[str] = typer.Option(
|
|
220
255
|
None,
|
|
221
256
|
"--base-path",
|
|
222
257
|
"-p",
|
|
223
|
-
help="Base path for ticket storage (AITrackdown only)"
|
|
258
|
+
help="Base path for ticket storage (AITrackdown only)",
|
|
224
259
|
),
|
|
225
260
|
api_key: Optional[str] = typer.Option(
|
|
226
|
-
None,
|
|
227
|
-
"--api-key",
|
|
228
|
-
help="API key for Linear or API token for JIRA"
|
|
261
|
+
None, "--api-key", help="API key for Linear or API token for JIRA"
|
|
229
262
|
),
|
|
230
263
|
team_id: Optional[str] = typer.Option(
|
|
231
|
-
None,
|
|
232
|
-
"--team-id",
|
|
233
|
-
help="Linear team ID (required for Linear adapter)"
|
|
264
|
+
None, "--team-id", help="Linear team ID (required for Linear adapter)"
|
|
234
265
|
),
|
|
235
266
|
jira_server: Optional[str] = typer.Option(
|
|
236
267
|
None,
|
|
237
268
|
"--jira-server",
|
|
238
|
-
help="JIRA server URL (e.g., https://company.atlassian.net)"
|
|
269
|
+
help="JIRA server URL (e.g., https://company.atlassian.net)",
|
|
239
270
|
),
|
|
240
271
|
jira_email: Optional[str] = typer.Option(
|
|
241
|
-
None,
|
|
242
|
-
"--jira-email",
|
|
243
|
-
help="JIRA user email for authentication"
|
|
272
|
+
None, "--jira-email", help="JIRA user email for authentication"
|
|
244
273
|
),
|
|
245
274
|
jira_project: Optional[str] = typer.Option(
|
|
246
|
-
None,
|
|
247
|
-
"--jira-project",
|
|
248
|
-
help="Default JIRA project key"
|
|
275
|
+
None, "--jira-project", help="Default JIRA project key"
|
|
249
276
|
),
|
|
250
277
|
github_owner: Optional[str] = typer.Option(
|
|
251
|
-
None,
|
|
252
|
-
"--github-owner",
|
|
253
|
-
help="GitHub repository owner"
|
|
278
|
+
None, "--github-owner", help="GitHub repository owner"
|
|
254
279
|
),
|
|
255
280
|
github_repo: Optional[str] = typer.Option(
|
|
256
|
-
None,
|
|
257
|
-
"--github-repo",
|
|
258
|
-
help="GitHub repository name"
|
|
281
|
+
None, "--github-repo", help="GitHub repository name"
|
|
259
282
|
),
|
|
260
283
|
github_token: Optional[str] = typer.Option(
|
|
261
|
-
None,
|
|
262
|
-
"--github-token",
|
|
263
|
-
help="GitHub Personal Access Token"
|
|
284
|
+
None, "--github-token", help="GitHub Personal Access Token"
|
|
264
285
|
),
|
|
265
286
|
) -> None:
|
|
266
287
|
"""Initialize mcp-ticketer for the current project.
|
|
@@ -280,10 +301,12 @@ def init(
|
|
|
280
301
|
|
|
281
302
|
# Save globally (not recommended)
|
|
282
303
|
mcp-ticketer init --global
|
|
304
|
+
|
|
283
305
|
"""
|
|
284
306
|
from pathlib import Path
|
|
285
|
-
|
|
307
|
+
|
|
286
308
|
from ..core.env_discovery import discover_config
|
|
309
|
+
from ..core.project_config import ConfigResolver
|
|
287
310
|
|
|
288
311
|
# Determine project path
|
|
289
312
|
proj_path = Path(project_path) if project_path else Path.cwd()
|
|
@@ -295,7 +318,7 @@ def init(
|
|
|
295
318
|
if config_path.exists():
|
|
296
319
|
if not typer.confirm(
|
|
297
320
|
f"Configuration already exists at {config_path}. Overwrite?",
|
|
298
|
-
default=False
|
|
321
|
+
default=False,
|
|
299
322
|
):
|
|
300
323
|
console.print("[yellow]Initialization cancelled.[/yellow]")
|
|
301
324
|
raise typer.Exit(0)
|
|
@@ -305,30 +328,37 @@ def init(
|
|
|
305
328
|
adapter_type = adapter
|
|
306
329
|
|
|
307
330
|
if not adapter_type:
|
|
308
|
-
console.print(
|
|
331
|
+
console.print(
|
|
332
|
+
"[cyan]🔍 Auto-discovering configuration from .env files...[/cyan]"
|
|
333
|
+
)
|
|
309
334
|
discovered = discover_config(proj_path)
|
|
310
335
|
|
|
311
336
|
if discovered and discovered.adapters:
|
|
312
337
|
primary = discovered.get_primary_adapter()
|
|
313
338
|
if primary:
|
|
314
339
|
adapter_type = primary.adapter_type
|
|
315
|
-
console.print(
|
|
340
|
+
console.print(
|
|
341
|
+
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
342
|
+
)
|
|
316
343
|
|
|
317
344
|
# Show what was discovered
|
|
318
|
-
console.print(
|
|
345
|
+
console.print(
|
|
346
|
+
f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
|
|
347
|
+
)
|
|
319
348
|
console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
|
|
320
349
|
else:
|
|
321
350
|
adapter_type = "aitrackdown" # Fallback
|
|
322
|
-
console.print(
|
|
351
|
+
console.print(
|
|
352
|
+
"[yellow]⚠ No credentials found, defaulting to aitrackdown[/yellow]"
|
|
353
|
+
)
|
|
323
354
|
else:
|
|
324
355
|
adapter_type = "aitrackdown" # Fallback
|
|
325
|
-
console.print(
|
|
356
|
+
console.print(
|
|
357
|
+
"[yellow]⚠ No .env files found, defaulting to aitrackdown[/yellow]"
|
|
358
|
+
)
|
|
326
359
|
|
|
327
360
|
# 2. Create configuration based on adapter type
|
|
328
|
-
config = {
|
|
329
|
-
"default_adapter": adapter_type,
|
|
330
|
-
"adapters": {}
|
|
331
|
-
}
|
|
361
|
+
config = {"default_adapter": adapter_type, "adapters": {}}
|
|
332
362
|
|
|
333
363
|
# 3. If discovered and matches adapter_type, use discovered config
|
|
334
364
|
if discovered and adapter_type != "aitrackdown":
|
|
@@ -355,7 +385,9 @@ def init(
|
|
|
355
385
|
linear_config["api_key"] = linear_api_key
|
|
356
386
|
elif not discovered:
|
|
357
387
|
console.print("[yellow]Warning:[/yellow] No Linear API key provided.")
|
|
358
|
-
console.print(
|
|
388
|
+
console.print(
|
|
389
|
+
"Set LINEAR_API_KEY environment variable or use --api-key option"
|
|
390
|
+
)
|
|
359
391
|
|
|
360
392
|
if linear_config:
|
|
361
393
|
config["adapters"]["linear"] = linear_config
|
|
@@ -370,7 +402,9 @@ def init(
|
|
|
370
402
|
|
|
371
403
|
if not server:
|
|
372
404
|
console.print("[red]Error:[/red] JIRA server URL is required")
|
|
373
|
-
console.print(
|
|
405
|
+
console.print(
|
|
406
|
+
"Use --jira-server or set JIRA_SERVER environment variable"
|
|
407
|
+
)
|
|
374
408
|
raise typer.Exit(1)
|
|
375
409
|
|
|
376
410
|
if not email:
|
|
@@ -380,15 +414,15 @@ def init(
|
|
|
380
414
|
|
|
381
415
|
if not token:
|
|
382
416
|
console.print("[red]Error:[/red] JIRA API token is required")
|
|
383
|
-
console.print(
|
|
384
|
-
|
|
417
|
+
console.print(
|
|
418
|
+
"Use --api-key or set JIRA_API_TOKEN environment variable"
|
|
419
|
+
)
|
|
420
|
+
console.print(
|
|
421
|
+
"[dim]Generate token at: https://id.atlassian.com/manage/api-tokens[/dim]"
|
|
422
|
+
)
|
|
385
423
|
raise typer.Exit(1)
|
|
386
424
|
|
|
387
|
-
jira_config = {
|
|
388
|
-
"server": server,
|
|
389
|
-
"email": email,
|
|
390
|
-
"api_token": token
|
|
391
|
-
}
|
|
425
|
+
jira_config = {"server": server, "email": email, "api_token": token}
|
|
392
426
|
|
|
393
427
|
if project:
|
|
394
428
|
jira_config["project_key"] = project
|
|
@@ -404,25 +438,37 @@ def init(
|
|
|
404
438
|
|
|
405
439
|
if not owner:
|
|
406
440
|
console.print("[red]Error:[/red] GitHub repository owner is required")
|
|
407
|
-
console.print(
|
|
441
|
+
console.print(
|
|
442
|
+
"Use --github-owner or set GITHUB_OWNER environment variable"
|
|
443
|
+
)
|
|
408
444
|
raise typer.Exit(1)
|
|
409
445
|
|
|
410
446
|
if not repo:
|
|
411
447
|
console.print("[red]Error:[/red] GitHub repository name is required")
|
|
412
|
-
console.print(
|
|
448
|
+
console.print(
|
|
449
|
+
"Use --github-repo or set GITHUB_REPO environment variable"
|
|
450
|
+
)
|
|
413
451
|
raise typer.Exit(1)
|
|
414
452
|
|
|
415
453
|
if not token:
|
|
416
|
-
console.print(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
console.print(
|
|
454
|
+
console.print(
|
|
455
|
+
"[red]Error:[/red] GitHub Personal Access Token is required"
|
|
456
|
+
)
|
|
457
|
+
console.print(
|
|
458
|
+
"Use --github-token or set GITHUB_TOKEN environment variable"
|
|
459
|
+
)
|
|
460
|
+
console.print(
|
|
461
|
+
"[dim]Create token at: https://github.com/settings/tokens/new[/dim]"
|
|
462
|
+
)
|
|
463
|
+
console.print(
|
|
464
|
+
"[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]"
|
|
465
|
+
)
|
|
420
466
|
raise typer.Exit(1)
|
|
421
467
|
|
|
422
468
|
config["adapters"]["github"] = {
|
|
423
469
|
"owner": owner,
|
|
424
470
|
"repo": repo,
|
|
425
|
-
"token": token
|
|
471
|
+
"token": token,
|
|
426
472
|
}
|
|
427
473
|
|
|
428
474
|
# 5. Save to appropriate location
|
|
@@ -432,7 +478,7 @@ def init(
|
|
|
432
478
|
config_file_path = resolver.GLOBAL_CONFIG_PATH
|
|
433
479
|
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
434
480
|
|
|
435
|
-
with open(config_file_path,
|
|
481
|
+
with open(config_file_path, "w") as f:
|
|
436
482
|
json.dump(config, f, indent=2)
|
|
437
483
|
|
|
438
484
|
console.print(f"[green]✓ Initialized with {adapter_type} adapter[/green]")
|
|
@@ -442,7 +488,7 @@ def init(
|
|
|
442
488
|
config_file_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
443
489
|
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
444
490
|
|
|
445
|
-
with open(config_file_path,
|
|
491
|
+
with open(config_file_path, "w") as f:
|
|
446
492
|
json.dump(config, f, indent=2)
|
|
447
493
|
|
|
448
494
|
console.print(f"[green]✓ Initialized with {adapter_type} adapter[/green]")
|
|
@@ -453,12 +499,12 @@ def init(
|
|
|
453
499
|
if gitignore_path.exists():
|
|
454
500
|
gitignore_content = gitignore_path.read_text()
|
|
455
501
|
if ".mcp-ticketer" not in gitignore_content:
|
|
456
|
-
with open(gitignore_path,
|
|
502
|
+
with open(gitignore_path, "a") as f:
|
|
457
503
|
f.write("\n# MCP Ticketer\n.mcp-ticketer/\n")
|
|
458
504
|
console.print("[dim]✓ Added .mcp-ticketer/ to .gitignore[/dim]")
|
|
459
505
|
else:
|
|
460
506
|
# Create .gitignore if it doesn't exist
|
|
461
|
-
with open(gitignore_path,
|
|
507
|
+
with open(gitignore_path, "w") as f:
|
|
462
508
|
f.write("# MCP Ticketer\n.mcp-ticketer/\n")
|
|
463
509
|
console.print("[dim]✓ Created .gitignore with .mcp-ticketer/[/dim]")
|
|
464
510
|
|
|
@@ -469,64 +515,48 @@ def install(
|
|
|
469
515
|
None,
|
|
470
516
|
"--adapter",
|
|
471
517
|
"-a",
|
|
472
|
-
help="Adapter type to use (auto-detected from .env if not specified)"
|
|
518
|
+
help="Adapter type to use (auto-detected from .env if not specified)",
|
|
473
519
|
),
|
|
474
520
|
project_path: Optional[str] = typer.Option(
|
|
475
|
-
None,
|
|
476
|
-
"--path",
|
|
477
|
-
help="Project path (default: current directory)"
|
|
521
|
+
None, "--path", help="Project path (default: current directory)"
|
|
478
522
|
),
|
|
479
523
|
global_config: bool = typer.Option(
|
|
480
524
|
False,
|
|
481
525
|
"--global",
|
|
482
526
|
"-g",
|
|
483
|
-
help="Save to global config instead of project-specific"
|
|
527
|
+
help="Save to global config instead of project-specific",
|
|
484
528
|
),
|
|
485
529
|
base_path: Optional[str] = typer.Option(
|
|
486
530
|
None,
|
|
487
531
|
"--base-path",
|
|
488
532
|
"-p",
|
|
489
|
-
help="Base path for ticket storage (AITrackdown only)"
|
|
533
|
+
help="Base path for ticket storage (AITrackdown only)",
|
|
490
534
|
),
|
|
491
535
|
api_key: Optional[str] = typer.Option(
|
|
492
|
-
None,
|
|
493
|
-
"--api-key",
|
|
494
|
-
help="API key for Linear or API token for JIRA"
|
|
536
|
+
None, "--api-key", help="API key for Linear or API token for JIRA"
|
|
495
537
|
),
|
|
496
538
|
team_id: Optional[str] = typer.Option(
|
|
497
|
-
None,
|
|
498
|
-
"--team-id",
|
|
499
|
-
help="Linear team ID (required for Linear adapter)"
|
|
539
|
+
None, "--team-id", help="Linear team ID (required for Linear adapter)"
|
|
500
540
|
),
|
|
501
541
|
jira_server: Optional[str] = typer.Option(
|
|
502
542
|
None,
|
|
503
543
|
"--jira-server",
|
|
504
|
-
help="JIRA server URL (e.g., https://company.atlassian.net)"
|
|
544
|
+
help="JIRA server URL (e.g., https://company.atlassian.net)",
|
|
505
545
|
),
|
|
506
546
|
jira_email: Optional[str] = typer.Option(
|
|
507
|
-
None,
|
|
508
|
-
"--jira-email",
|
|
509
|
-
help="JIRA user email for authentication"
|
|
547
|
+
None, "--jira-email", help="JIRA user email for authentication"
|
|
510
548
|
),
|
|
511
549
|
jira_project: Optional[str] = typer.Option(
|
|
512
|
-
None,
|
|
513
|
-
"--jira-project",
|
|
514
|
-
help="Default JIRA project key"
|
|
550
|
+
None, "--jira-project", help="Default JIRA project key"
|
|
515
551
|
),
|
|
516
552
|
github_owner: Optional[str] = typer.Option(
|
|
517
|
-
None,
|
|
518
|
-
"--github-owner",
|
|
519
|
-
help="GitHub repository owner"
|
|
553
|
+
None, "--github-owner", help="GitHub repository owner"
|
|
520
554
|
),
|
|
521
555
|
github_repo: Optional[str] = typer.Option(
|
|
522
|
-
None,
|
|
523
|
-
"--github-repo",
|
|
524
|
-
help="GitHub repository name"
|
|
556
|
+
None, "--github-repo", help="GitHub repository name"
|
|
525
557
|
),
|
|
526
558
|
github_token: Optional[str] = typer.Option(
|
|
527
|
-
None,
|
|
528
|
-
"--github-token",
|
|
529
|
-
help="GitHub Personal Access Token"
|
|
559
|
+
None, "--github-token", help="GitHub Personal Access Token"
|
|
530
560
|
),
|
|
531
561
|
) -> None:
|
|
532
562
|
"""Initialize mcp-ticketer for the current project (alias for init).
|
|
@@ -547,6 +577,7 @@ def install(
|
|
|
547
577
|
|
|
548
578
|
# Save globally (not recommended)
|
|
549
579
|
mcp-ticketer install --global
|
|
580
|
+
|
|
550
581
|
"""
|
|
551
582
|
# Call init with all parameters
|
|
552
583
|
init(
|
|
@@ -568,45 +599,20 @@ def install(
|
|
|
568
599
|
@app.command("set")
|
|
569
600
|
def set_config(
|
|
570
601
|
adapter: Optional[AdapterType] = typer.Option(
|
|
571
|
-
None,
|
|
572
|
-
"--adapter",
|
|
573
|
-
"-a",
|
|
574
|
-
help="Set default adapter"
|
|
602
|
+
None, "--adapter", "-a", help="Set default adapter"
|
|
575
603
|
),
|
|
576
604
|
team_key: Optional[str] = typer.Option(
|
|
577
|
-
None,
|
|
578
|
-
"--team-key",
|
|
579
|
-
help="Linear team key (e.g., BTA)"
|
|
580
|
-
),
|
|
581
|
-
team_id: Optional[str] = typer.Option(
|
|
582
|
-
None,
|
|
583
|
-
"--team-id",
|
|
584
|
-
help="Linear team ID"
|
|
605
|
+
None, "--team-key", help="Linear team key (e.g., BTA)"
|
|
585
606
|
),
|
|
607
|
+
team_id: Optional[str] = typer.Option(None, "--team-id", help="Linear team ID"),
|
|
586
608
|
owner: Optional[str] = typer.Option(
|
|
587
|
-
None,
|
|
588
|
-
"--owner",
|
|
589
|
-
help="GitHub repository owner"
|
|
590
|
-
),
|
|
591
|
-
repo: Optional[str] = typer.Option(
|
|
592
|
-
None,
|
|
593
|
-
"--repo",
|
|
594
|
-
help="GitHub repository name"
|
|
595
|
-
),
|
|
596
|
-
server: Optional[str] = typer.Option(
|
|
597
|
-
None,
|
|
598
|
-
"--server",
|
|
599
|
-
help="JIRA server URL"
|
|
600
|
-
),
|
|
601
|
-
project: Optional[str] = typer.Option(
|
|
602
|
-
None,
|
|
603
|
-
"--project",
|
|
604
|
-
help="JIRA project key"
|
|
609
|
+
None, "--owner", help="GitHub repository owner"
|
|
605
610
|
),
|
|
611
|
+
repo: Optional[str] = typer.Option(None, "--repo", help="GitHub repository name"),
|
|
612
|
+
server: Optional[str] = typer.Option(None, "--server", help="JIRA server URL"),
|
|
613
|
+
project: Optional[str] = typer.Option(None, "--project", help="JIRA project key"),
|
|
606
614
|
base_path: Optional[str] = typer.Option(
|
|
607
|
-
None,
|
|
608
|
-
"--base-path",
|
|
609
|
-
help="AITrackdown base path"
|
|
615
|
+
None, "--base-path", help="AITrackdown base path"
|
|
610
616
|
),
|
|
611
617
|
) -> None:
|
|
612
618
|
"""Set default adapter and adapter-specific configuration.
|
|
@@ -617,7 +623,9 @@ def set_config(
|
|
|
617
623
|
# Show current configuration
|
|
618
624
|
config = load_config()
|
|
619
625
|
console.print("[bold]Current Configuration:[/bold]")
|
|
620
|
-
console.print(
|
|
626
|
+
console.print(
|
|
627
|
+
f"Default adapter: [cyan]{config.get('default_adapter', 'aitrackdown')}[/cyan]"
|
|
628
|
+
)
|
|
621
629
|
|
|
622
630
|
adapters_config = config.get("adapters", {})
|
|
623
631
|
if adapters_config:
|
|
@@ -626,7 +634,11 @@ def set_config(
|
|
|
626
634
|
console.print(f"\n[cyan]{adapter_name}:[/cyan]")
|
|
627
635
|
for key, value in adapter_config.items():
|
|
628
636
|
# Don't display sensitive values like tokens
|
|
629
|
-
if
|
|
637
|
+
if (
|
|
638
|
+
"token" in key.lower()
|
|
639
|
+
or "key" in key.lower()
|
|
640
|
+
and "team" not in key.lower()
|
|
641
|
+
):
|
|
630
642
|
value = "***" if value else "not set"
|
|
631
643
|
console.print(f" {key}: {value}")
|
|
632
644
|
return
|
|
@@ -649,7 +661,7 @@ def set_config(
|
|
|
649
661
|
if team_id:
|
|
650
662
|
linear_config["team_id"] = team_id
|
|
651
663
|
adapter_configs["linear"] = linear_config
|
|
652
|
-
console.print(
|
|
664
|
+
console.print("[green]✓[/green] Linear settings updated")
|
|
653
665
|
|
|
654
666
|
# GitHub configuration
|
|
655
667
|
if owner or repo:
|
|
@@ -659,7 +671,7 @@ def set_config(
|
|
|
659
671
|
if repo:
|
|
660
672
|
github_config["repo"] = repo
|
|
661
673
|
adapter_configs["github"] = github_config
|
|
662
|
-
console.print(
|
|
674
|
+
console.print("[green]✓[/green] GitHub settings updated")
|
|
663
675
|
|
|
664
676
|
# JIRA configuration
|
|
665
677
|
if server or project:
|
|
@@ -669,12 +681,12 @@ def set_config(
|
|
|
669
681
|
if project:
|
|
670
682
|
jira_config["project_key"] = project
|
|
671
683
|
adapter_configs["jira"] = jira_config
|
|
672
|
-
console.print(
|
|
684
|
+
console.print("[green]✓[/green] JIRA settings updated")
|
|
673
685
|
|
|
674
686
|
# AITrackdown configuration
|
|
675
687
|
if base_path:
|
|
676
688
|
adapter_configs["aitrackdown"] = {"base_path": base_path}
|
|
677
|
-
console.print(
|
|
689
|
+
console.print("[green]✓[/green] AITrackdown settings updated")
|
|
678
690
|
|
|
679
691
|
if adapter_configs:
|
|
680
692
|
updates["adapters"] = adapter_configs
|
|
@@ -688,36 +700,22 @@ def set_config(
|
|
|
688
700
|
|
|
689
701
|
@app.command("configure")
|
|
690
702
|
def configure_command(
|
|
691
|
-
show: bool = typer.Option(
|
|
692
|
-
False,
|
|
693
|
-
"--show",
|
|
694
|
-
help="Show current configuration"
|
|
695
|
-
),
|
|
703
|
+
show: bool = typer.Option(False, "--show", help="Show current configuration"),
|
|
696
704
|
adapter: Optional[str] = typer.Option(
|
|
697
|
-
None,
|
|
698
|
-
"--adapter",
|
|
699
|
-
help="Set default adapter type"
|
|
700
|
-
),
|
|
701
|
-
api_key: Optional[str] = typer.Option(
|
|
702
|
-
None,
|
|
703
|
-
"--api-key",
|
|
704
|
-
help="Set API key/token"
|
|
705
|
+
None, "--adapter", help="Set default adapter type"
|
|
705
706
|
),
|
|
707
|
+
api_key: Optional[str] = typer.Option(None, "--api-key", help="Set API key/token"),
|
|
706
708
|
project_id: Optional[str] = typer.Option(
|
|
707
|
-
None,
|
|
708
|
-
"--project-id",
|
|
709
|
-
help="Set project ID"
|
|
709
|
+
None, "--project-id", help="Set project ID"
|
|
710
710
|
),
|
|
711
711
|
team_id: Optional[str] = typer.Option(
|
|
712
|
-
None,
|
|
713
|
-
"--team-id",
|
|
714
|
-
help="Set team ID (Linear)"
|
|
712
|
+
None, "--team-id", help="Set team ID (Linear)"
|
|
715
713
|
),
|
|
716
714
|
global_scope: bool = typer.Option(
|
|
717
715
|
False,
|
|
718
716
|
"--global",
|
|
719
717
|
"-g",
|
|
720
|
-
help="Save to global config instead of project-specific"
|
|
718
|
+
help="Save to global config instead of project-specific",
|
|
721
719
|
),
|
|
722
720
|
) -> None:
|
|
723
721
|
"""Configure MCP Ticketer integration.
|
|
@@ -738,7 +736,7 @@ def configure_command(
|
|
|
738
736
|
api_key=api_key,
|
|
739
737
|
project_id=project_id,
|
|
740
738
|
team_id=team_id,
|
|
741
|
-
global_scope=global_scope
|
|
739
|
+
global_scope=global_scope,
|
|
742
740
|
)
|
|
743
741
|
return
|
|
744
742
|
|
|
@@ -749,9 +747,7 @@ def configure_command(
|
|
|
749
747
|
@app.command("migrate-config")
|
|
750
748
|
def migrate_config(
|
|
751
749
|
dry_run: bool = typer.Option(
|
|
752
|
-
False,
|
|
753
|
-
"--dry-run",
|
|
754
|
-
help="Show what would be done without making changes"
|
|
750
|
+
False, "--dry-run", help="Show what would be done without making changes"
|
|
755
751
|
),
|
|
756
752
|
) -> None:
|
|
757
753
|
"""Migrate configuration from old format to new format.
|
|
@@ -785,50 +781,42 @@ def status_command():
|
|
|
785
781
|
# Show worker status
|
|
786
782
|
worker_status = manager.get_status()
|
|
787
783
|
if worker_status["running"]:
|
|
788
|
-
console.print(
|
|
784
|
+
console.print(
|
|
785
|
+
f"\n[green]● Worker is running[/green] (PID: {worker_status.get('pid')})"
|
|
786
|
+
)
|
|
789
787
|
else:
|
|
790
788
|
console.print("\n[red]○ Worker is not running[/red]")
|
|
791
789
|
if pending > 0:
|
|
792
|
-
console.print(
|
|
790
|
+
console.print(
|
|
791
|
+
"[yellow]Note: There are pending items. Start worker with 'mcp-ticketer worker start'[/yellow]"
|
|
792
|
+
)
|
|
793
793
|
|
|
794
794
|
|
|
795
795
|
@app.command()
|
|
796
796
|
def create(
|
|
797
797
|
title: str = typer.Argument(..., help="Ticket title"),
|
|
798
798
|
description: Optional[str] = typer.Option(
|
|
799
|
-
None,
|
|
800
|
-
"--description",
|
|
801
|
-
"-d",
|
|
802
|
-
help="Ticket description"
|
|
799
|
+
None, "--description", "-d", help="Ticket description"
|
|
803
800
|
),
|
|
804
801
|
priority: Priority = typer.Option(
|
|
805
|
-
Priority.MEDIUM,
|
|
806
|
-
"--priority",
|
|
807
|
-
"-p",
|
|
808
|
-
help="Priority level"
|
|
802
|
+
Priority.MEDIUM, "--priority", "-p", help="Priority level"
|
|
809
803
|
),
|
|
810
804
|
tags: Optional[List[str]] = typer.Option(
|
|
811
|
-
None,
|
|
812
|
-
"--tag",
|
|
813
|
-
"-t",
|
|
814
|
-
help="Tags (can be specified multiple times)"
|
|
805
|
+
None, "--tag", "-t", help="Tags (can be specified multiple times)"
|
|
815
806
|
),
|
|
816
807
|
assignee: Optional[str] = typer.Option(
|
|
817
|
-
None,
|
|
818
|
-
"--assignee",
|
|
819
|
-
"-a",
|
|
820
|
-
help="Assignee username"
|
|
808
|
+
None, "--assignee", "-a", help="Assignee username"
|
|
821
809
|
),
|
|
822
810
|
adapter: Optional[AdapterType] = typer.Option(
|
|
823
|
-
None,
|
|
824
|
-
"--adapter",
|
|
825
|
-
help="Override default adapter"
|
|
811
|
+
None, "--adapter", help="Override default adapter"
|
|
826
812
|
),
|
|
827
813
|
) -> None:
|
|
828
814
|
"""Create a new ticket."""
|
|
829
815
|
# Get the adapter name
|
|
830
816
|
config = load_config()
|
|
831
|
-
adapter_name =
|
|
817
|
+
adapter_name = (
|
|
818
|
+
adapter.value if adapter else config.get("default_adapter", "aitrackdown")
|
|
819
|
+
)
|
|
832
820
|
|
|
833
821
|
# Create task data
|
|
834
822
|
task_data = {
|
|
@@ -842,9 +830,7 @@ def create(
|
|
|
842
830
|
# Add to queue
|
|
843
831
|
queue = Queue()
|
|
844
832
|
queue_id = queue.add(
|
|
845
|
-
ticket_data=task_data,
|
|
846
|
-
adapter=adapter_name,
|
|
847
|
-
operation="create"
|
|
833
|
+
ticket_data=task_data, adapter=adapter_name, operation="create"
|
|
848
834
|
)
|
|
849
835
|
|
|
850
836
|
console.print(f"[green]✓[/green] Queued ticket creation: {queue_id}")
|
|
@@ -861,32 +847,22 @@ def create(
|
|
|
861
847
|
@app.command("list")
|
|
862
848
|
def list_tickets(
|
|
863
849
|
state: Optional[TicketState] = typer.Option(
|
|
864
|
-
None,
|
|
865
|
-
"--state",
|
|
866
|
-
"-s",
|
|
867
|
-
help="Filter by state"
|
|
850
|
+
None, "--state", "-s", help="Filter by state"
|
|
868
851
|
),
|
|
869
852
|
priority: Optional[Priority] = typer.Option(
|
|
870
|
-
None,
|
|
871
|
-
"--priority",
|
|
872
|
-
"-p",
|
|
873
|
-
help="Filter by priority"
|
|
874
|
-
),
|
|
875
|
-
limit: int = typer.Option(
|
|
876
|
-
10,
|
|
877
|
-
"--limit",
|
|
878
|
-
"-l",
|
|
879
|
-
help="Maximum number of tickets"
|
|
853
|
+
None, "--priority", "-p", help="Filter by priority"
|
|
880
854
|
),
|
|
855
|
+
limit: int = typer.Option(10, "--limit", "-l", help="Maximum number of tickets"),
|
|
881
856
|
adapter: Optional[AdapterType] = typer.Option(
|
|
882
|
-
None,
|
|
883
|
-
"--adapter",
|
|
884
|
-
help="Override default adapter"
|
|
857
|
+
None, "--adapter", help="Override default adapter"
|
|
885
858
|
),
|
|
886
859
|
) -> None:
|
|
887
860
|
"""List tickets with optional filters."""
|
|
861
|
+
|
|
888
862
|
async def _list():
|
|
889
|
-
adapter_instance = get_adapter(
|
|
863
|
+
adapter_instance = get_adapter(
|
|
864
|
+
override_adapter=adapter.value if adapter else None
|
|
865
|
+
)
|
|
890
866
|
filters = {}
|
|
891
867
|
if state:
|
|
892
868
|
filters["state"] = state
|
|
@@ -923,21 +899,17 @@ def list_tickets(
|
|
|
923
899
|
@app.command()
|
|
924
900
|
def show(
|
|
925
901
|
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
926
|
-
comments: bool = typer.Option(
|
|
927
|
-
False,
|
|
928
|
-
"--comments",
|
|
929
|
-
"-c",
|
|
930
|
-
help="Show comments"
|
|
931
|
-
),
|
|
902
|
+
comments: bool = typer.Option(False, "--comments", "-c", help="Show comments"),
|
|
932
903
|
adapter: Optional[AdapterType] = typer.Option(
|
|
933
|
-
None,
|
|
934
|
-
"--adapter",
|
|
935
|
-
help="Override default adapter"
|
|
904
|
+
None, "--adapter", help="Override default adapter"
|
|
936
905
|
),
|
|
937
906
|
) -> None:
|
|
938
907
|
"""Show detailed ticket information."""
|
|
908
|
+
|
|
939
909
|
async def _show():
|
|
940
|
-
adapter_instance = get_adapter(
|
|
910
|
+
adapter_instance = get_adapter(
|
|
911
|
+
override_adapter=adapter.value if adapter else None
|
|
912
|
+
)
|
|
941
913
|
ticket = await adapter_instance.read(ticket_id)
|
|
942
914
|
ticket_comments = None
|
|
943
915
|
if comments and ticket:
|
|
@@ -957,7 +929,7 @@ def show(
|
|
|
957
929
|
console.print(f"Priority: [yellow]{ticket.priority}[/yellow]")
|
|
958
930
|
|
|
959
931
|
if ticket.description:
|
|
960
|
-
console.print(
|
|
932
|
+
console.print("\n[dim]Description:[/dim]")
|
|
961
933
|
console.print(ticket.description)
|
|
962
934
|
|
|
963
935
|
if ticket.tags:
|
|
@@ -979,27 +951,16 @@ def update(
|
|
|
979
951
|
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
980
952
|
title: Optional[str] = typer.Option(None, "--title", help="New title"),
|
|
981
953
|
description: Optional[str] = typer.Option(
|
|
982
|
-
None,
|
|
983
|
-
"--description",
|
|
984
|
-
"-d",
|
|
985
|
-
help="New description"
|
|
954
|
+
None, "--description", "-d", help="New description"
|
|
986
955
|
),
|
|
987
956
|
priority: Optional[Priority] = typer.Option(
|
|
988
|
-
None,
|
|
989
|
-
"--priority",
|
|
990
|
-
"-p",
|
|
991
|
-
help="New priority"
|
|
957
|
+
None, "--priority", "-p", help="New priority"
|
|
992
958
|
),
|
|
993
959
|
assignee: Optional[str] = typer.Option(
|
|
994
|
-
None,
|
|
995
|
-
"--assignee",
|
|
996
|
-
"-a",
|
|
997
|
-
help="New assignee"
|
|
960
|
+
None, "--assignee", "-a", help="New assignee"
|
|
998
961
|
),
|
|
999
962
|
adapter: Optional[AdapterType] = typer.Option(
|
|
1000
|
-
None,
|
|
1001
|
-
"--adapter",
|
|
1002
|
-
help="Override default adapter"
|
|
963
|
+
None, "--adapter", help="Override default adapter"
|
|
1003
964
|
),
|
|
1004
965
|
) -> None:
|
|
1005
966
|
"""Update ticket fields."""
|
|
@@ -1009,7 +970,9 @@ def update(
|
|
|
1009
970
|
if description:
|
|
1010
971
|
updates["description"] = description
|
|
1011
972
|
if priority:
|
|
1012
|
-
updates["priority"] =
|
|
973
|
+
updates["priority"] = (
|
|
974
|
+
priority.value if isinstance(priority, Priority) else priority
|
|
975
|
+
)
|
|
1013
976
|
if assignee:
|
|
1014
977
|
updates["assignee"] = assignee
|
|
1015
978
|
|
|
@@ -1019,18 +982,16 @@ def update(
|
|
|
1019
982
|
|
|
1020
983
|
# Get the adapter name
|
|
1021
984
|
config = load_config()
|
|
1022
|
-
adapter_name =
|
|
985
|
+
adapter_name = (
|
|
986
|
+
adapter.value if adapter else config.get("default_adapter", "aitrackdown")
|
|
987
|
+
)
|
|
1023
988
|
|
|
1024
989
|
# Add ticket_id to updates
|
|
1025
990
|
updates["ticket_id"] = ticket_id
|
|
1026
991
|
|
|
1027
992
|
# Add to queue
|
|
1028
993
|
queue = Queue()
|
|
1029
|
-
queue_id = queue.add(
|
|
1030
|
-
ticket_data=updates,
|
|
1031
|
-
adapter=adapter_name,
|
|
1032
|
-
operation="update"
|
|
1033
|
-
)
|
|
994
|
+
queue_id = queue.add(ticket_data=updates, adapter=adapter_name, operation="update")
|
|
1034
995
|
|
|
1035
996
|
console.print(f"[green]✓[/green] Queued ticket update: {queue_id}")
|
|
1036
997
|
for key, value in updates.items():
|
|
@@ -1049,25 +1010,25 @@ def transition(
|
|
|
1049
1010
|
ticket_id: str = typer.Argument(..., help="Ticket ID"),
|
|
1050
1011
|
state: TicketState = typer.Argument(..., help="Target state"),
|
|
1051
1012
|
adapter: Optional[AdapterType] = typer.Option(
|
|
1052
|
-
None,
|
|
1053
|
-
"--adapter",
|
|
1054
|
-
help="Override default adapter"
|
|
1013
|
+
None, "--adapter", help="Override default adapter"
|
|
1055
1014
|
),
|
|
1056
1015
|
) -> None:
|
|
1057
1016
|
"""Change ticket state with validation."""
|
|
1058
1017
|
# Get the adapter name
|
|
1059
1018
|
config = load_config()
|
|
1060
|
-
adapter_name =
|
|
1019
|
+
adapter_name = (
|
|
1020
|
+
adapter.value if adapter else config.get("default_adapter", "aitrackdown")
|
|
1021
|
+
)
|
|
1061
1022
|
|
|
1062
1023
|
# Add to queue
|
|
1063
1024
|
queue = Queue()
|
|
1064
1025
|
queue_id = queue.add(
|
|
1065
1026
|
ticket_data={
|
|
1066
1027
|
"ticket_id": ticket_id,
|
|
1067
|
-
"state": state.value if hasattr(state,
|
|
1028
|
+
"state": state.value if hasattr(state, "value") else state,
|
|
1068
1029
|
},
|
|
1069
1030
|
adapter=adapter_name,
|
|
1070
|
-
operation="transition"
|
|
1031
|
+
operation="transition",
|
|
1071
1032
|
)
|
|
1072
1033
|
|
|
1073
1034
|
console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
|
|
@@ -1088,14 +1049,15 @@ def search(
|
|
|
1088
1049
|
assignee: Optional[str] = typer.Option(None, "--assignee", "-a"),
|
|
1089
1050
|
limit: int = typer.Option(10, "--limit", "-l"),
|
|
1090
1051
|
adapter: Optional[AdapterType] = typer.Option(
|
|
1091
|
-
None,
|
|
1092
|
-
"--adapter",
|
|
1093
|
-
help="Override default adapter"
|
|
1052
|
+
None, "--adapter", help="Override default adapter"
|
|
1094
1053
|
),
|
|
1095
1054
|
) -> None:
|
|
1096
1055
|
"""Search tickets with advanced query."""
|
|
1056
|
+
|
|
1097
1057
|
async def _search():
|
|
1098
|
-
adapter_instance = get_adapter(
|
|
1058
|
+
adapter_instance = get_adapter(
|
|
1059
|
+
override_adapter=adapter.value if adapter else None
|
|
1060
|
+
)
|
|
1099
1061
|
search_query = SearchQuery(
|
|
1100
1062
|
query=query,
|
|
1101
1063
|
state=state,
|
|
@@ -1130,9 +1092,7 @@ app.add_typer(discover_app, name="discover")
|
|
|
1130
1092
|
|
|
1131
1093
|
|
|
1132
1094
|
@app.command()
|
|
1133
|
-
def check(
|
|
1134
|
-
queue_id: str = typer.Argument(..., help="Queue ID to check")
|
|
1135
|
-
):
|
|
1095
|
+
def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
|
|
1136
1096
|
"""Check status of a queued operation."""
|
|
1137
1097
|
queue = Queue()
|
|
1138
1098
|
item = queue.get_item(queue_id)
|
|
@@ -1165,7 +1125,7 @@ def check(
|
|
|
1165
1125
|
if item.error_message:
|
|
1166
1126
|
console.print(f"\n[red]Error:[/red] {item.error_message}")
|
|
1167
1127
|
elif item.result:
|
|
1168
|
-
console.print(
|
|
1128
|
+
console.print("\n[green]Result:[/green]")
|
|
1169
1129
|
for key, value in item.result.items():
|
|
1170
1130
|
console.print(f" {key}: {value}")
|
|
1171
1131
|
|
|
@@ -1176,15 +1136,10 @@ def check(
|
|
|
1176
1136
|
@app.command()
|
|
1177
1137
|
def serve(
|
|
1178
1138
|
adapter: Optional[AdapterType] = typer.Option(
|
|
1179
|
-
None,
|
|
1180
|
-
"--adapter",
|
|
1181
|
-
"-a",
|
|
1182
|
-
help="Override default adapter type"
|
|
1139
|
+
None, "--adapter", "-a", help="Override default adapter type"
|
|
1183
1140
|
),
|
|
1184
1141
|
base_path: Optional[str] = typer.Option(
|
|
1185
|
-
None,
|
|
1186
|
-
"--base-path",
|
|
1187
|
-
help="Base path for AITrackdown adapter"
|
|
1142
|
+
None, "--base-path", help="Base path for AITrackdown adapter"
|
|
1188
1143
|
),
|
|
1189
1144
|
):
|
|
1190
1145
|
"""Start MCP server for JSON-RPC communication over stdio.
|
|
@@ -1206,7 +1161,9 @@ def serve(
|
|
|
1206
1161
|
config = load_config()
|
|
1207
1162
|
|
|
1208
1163
|
# Determine adapter type
|
|
1209
|
-
adapter_type =
|
|
1164
|
+
adapter_type = (
|
|
1165
|
+
adapter.value if adapter else config.get("default_adapter", "aitrackdown")
|
|
1166
|
+
)
|
|
1210
1167
|
|
|
1211
1168
|
# Get adapter configuration
|
|
1212
1169
|
adapters_config = config.get("adapters", {})
|
|
@@ -1223,11 +1180,14 @@ def serve(
|
|
|
1223
1180
|
# MCP server uses stdio for JSON-RPC, so we can't print to stdout
|
|
1224
1181
|
# Only print to stderr to avoid interfering with the protocol
|
|
1225
1182
|
import sys
|
|
1183
|
+
|
|
1226
1184
|
if sys.stderr.isatty():
|
|
1227
1185
|
# Only print if stderr is a terminal (not redirected)
|
|
1228
1186
|
console.file = sys.stderr
|
|
1229
1187
|
console.print(f"[green]Starting MCP server[/green] with {adapter_type} adapter")
|
|
1230
|
-
console.print(
|
|
1188
|
+
console.print(
|
|
1189
|
+
"[dim]Server running on stdio. Send JSON-RPC requests via stdin.[/dim]"
|
|
1190
|
+
)
|
|
1231
1191
|
|
|
1232
1192
|
# Create and run server
|
|
1233
1193
|
try:
|
|
@@ -1237,7 +1197,7 @@ def serve(
|
|
|
1237
1197
|
# Also send this to stderr
|
|
1238
1198
|
if sys.stderr.isatty():
|
|
1239
1199
|
console.print("\n[yellow]Server stopped by user[/yellow]")
|
|
1240
|
-
if
|
|
1200
|
+
if "server" in locals():
|
|
1241
1201
|
asyncio.run(server.stop())
|
|
1242
1202
|
except Exception as e:
|
|
1243
1203
|
# Log error to stderr
|
|
@@ -1251,13 +1211,10 @@ def mcp(
|
|
|
1251
1211
|
False,
|
|
1252
1212
|
"--global",
|
|
1253
1213
|
"-g",
|
|
1254
|
-
help="Configure Claude Desktop instead of project-level"
|
|
1214
|
+
help="Configure Claude Desktop instead of project-level",
|
|
1255
1215
|
),
|
|
1256
1216
|
force: bool = typer.Option(
|
|
1257
|
-
False,
|
|
1258
|
-
"--force",
|
|
1259
|
-
"-f",
|
|
1260
|
-
help="Overwrite existing configuration"
|
|
1217
|
+
False, "--force", "-f", help="Overwrite existing configuration"
|
|
1261
1218
|
),
|
|
1262
1219
|
):
|
|
1263
1220
|
"""Configure Claude Code to use mcp-ticketer MCP server.
|
|
@@ -1283,4 +1240,4 @@ def main():
|
|
|
1283
1240
|
|
|
1284
1241
|
|
|
1285
1242
|
if __name__ == "__main__":
|
|
1286
|
-
main()
|
|
1243
|
+
main()
|