mcp-ticketer 0.3.5__py3-none-any.whl → 0.12.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.
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +263 -14
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1308 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +334 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +326 -109
- mcp_ticketer/adapters/hybrid.py +11 -11
- mcp_ticketer/adapters/jira.py +271 -25
- mcp_ticketer/adapters/linear/adapter.py +693 -39
- mcp_ticketer/adapters/linear/client.py +61 -9
- mcp_ticketer/adapters/linear/mappers.py +9 -3
- mcp_ticketer/adapters/linear/queries.py +9 -7
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +1 -1
- mcp_ticketer/cli/auggie_configure.py +104 -15
- mcp_ticketer/cli/codex_configure.py +188 -32
- mcp_ticketer/cli/configure.py +37 -48
- mcp_ticketer/cli/diagnostics.py +20 -18
- mcp_ticketer/cli/discover.py +292 -26
- mcp_ticketer/cli/gemini_configure.py +107 -26
- mcp_ticketer/cli/instruction_commands.py +429 -0
- mcp_ticketer/cli/linear_commands.py +105 -22
- mcp_ticketer/cli/main.py +1830 -435
- mcp_ticketer/cli/mcp_configure.py +296 -89
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +412 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/simple_health.py +1 -1
- mcp_ticketer/cli/ticket_commands.py +773 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +67 -62
- mcp_ticketer/core/__init__.py +14 -1
- mcp_ticketer/core/adapter.py +84 -15
- mcp_ticketer/core/config.py +44 -39
- mcp_ticketer/core/env_discovery.py +42 -12
- mcp_ticketer/core/env_loader.py +15 -14
- mcp_ticketer/core/exceptions.py +3 -3
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/mappers.py +11 -11
- mcp_ticketer/core/models.py +50 -20
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/project_config.py +57 -35
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/{dto.py → server/dto.py} +32 -32
- mcp_ticketer/mcp/{server.py → server/main.py} +127 -74
- mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +2 -2
- mcp_ticketer/mcp/server/server_sdk.py +93 -0
- mcp_ticketer/mcp/server/tools/__init__.py +47 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +90 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +532 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +154 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +206 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +430 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +5 -4
- mcp_ticketer/queue/manager.py +15 -51
- mcp_ticketer/queue/queue.py +19 -19
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +14 -14
- mcp_ticketer/queue/worker.py +16 -14
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +168 -32
- mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
- mcp_ticketer-0.3.5.dist-info/RECORD +0 -62
- /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
|
@@ -4,19 +4,15 @@ Codex CLI only supports global configuration at ~/.codex/config.toml.
|
|
|
4
4
|
Unlike Claude Code and Gemini CLI, there is no project-level configuration support.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import sys
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
if sys.version_info >= (3, 11):
|
|
12
|
-
import tomllib
|
|
13
|
-
else:
|
|
14
|
-
import tomli as tomllib
|
|
8
|
+
from typing import Any
|
|
15
9
|
|
|
16
10
|
import tomli_w
|
|
11
|
+
import tomllib
|
|
17
12
|
from rich.console import Console
|
|
18
13
|
|
|
19
|
-
from .mcp_configure import
|
|
14
|
+
from .mcp_configure import load_project_config
|
|
15
|
+
from .python_detection import get_mcp_ticketer_python
|
|
20
16
|
|
|
21
17
|
console = Console()
|
|
22
18
|
|
|
@@ -78,19 +74,21 @@ def save_codex_config(config_path: Path, config: dict[str, Any]) -> None:
|
|
|
78
74
|
|
|
79
75
|
|
|
80
76
|
def create_codex_server_config(
|
|
81
|
-
|
|
77
|
+
python_path: str, project_config: dict, project_path: str | None = None
|
|
82
78
|
) -> dict[str, Any]:
|
|
83
79
|
"""Create Codex MCP server configuration for mcp-ticketer.
|
|
84
80
|
|
|
85
81
|
Args:
|
|
86
|
-
|
|
82
|
+
python_path: Path to Python executable in mcp-ticketer venv
|
|
87
83
|
project_config: Project configuration from .mcp-ticketer/config.json
|
|
88
|
-
|
|
84
|
+
project_path: Project directory path (optional, not used for global config)
|
|
89
85
|
|
|
90
86
|
Returns:
|
|
91
87
|
Codex MCP server configuration dict
|
|
92
88
|
|
|
93
89
|
"""
|
|
90
|
+
# Use Python module invocation pattern (works regardless of where package is installed)
|
|
91
|
+
|
|
94
92
|
# Get adapter configuration
|
|
95
93
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
96
94
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -99,9 +97,9 @@ def create_codex_server_config(
|
|
|
99
97
|
# Build environment variables
|
|
100
98
|
env_vars: dict[str, str] = {}
|
|
101
99
|
|
|
102
|
-
# Add PYTHONPATH
|
|
103
|
-
if
|
|
104
|
-
env_vars["PYTHONPATH"] =
|
|
100
|
+
# Add PYTHONPATH for project context
|
|
101
|
+
if project_path:
|
|
102
|
+
env_vars["PYTHONPATH"] = project_path
|
|
105
103
|
|
|
106
104
|
# Add adapter type
|
|
107
105
|
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
@@ -110,9 +108,9 @@ def create_codex_server_config(
|
|
|
110
108
|
if adapter == "aitrackdown":
|
|
111
109
|
# Set base path for local adapter
|
|
112
110
|
base_path = adapter_config.get("base_path", ".aitrackdown")
|
|
113
|
-
if
|
|
114
|
-
# Use absolute path if
|
|
115
|
-
env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(
|
|
111
|
+
if project_path:
|
|
112
|
+
# Use absolute path if project_path is provided
|
|
113
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(project_path) / base_path)
|
|
116
114
|
else:
|
|
117
115
|
env_vars["MCP_TICKETER_BASE_PATH"] = base_path
|
|
118
116
|
|
|
@@ -140,17 +138,158 @@ def create_codex_server_config(
|
|
|
140
138
|
if "project_key" in adapter_config:
|
|
141
139
|
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
142
140
|
|
|
141
|
+
# Use Python module invocation pattern
|
|
142
|
+
args = ["-m", "mcp_ticketer.mcp.server"]
|
|
143
|
+
if project_path:
|
|
144
|
+
args.append(project_path)
|
|
145
|
+
|
|
143
146
|
# Create server configuration with Codex-specific structure
|
|
144
|
-
# NOTE: Codex uses nested dict structure for env vars
|
|
145
147
|
config: dict[str, Any] = {
|
|
146
|
-
"command":
|
|
147
|
-
"args":
|
|
148
|
+
"command": python_path,
|
|
149
|
+
"args": args,
|
|
148
150
|
"env": env_vars,
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
return config
|
|
152
154
|
|
|
153
155
|
|
|
156
|
+
def _test_configuration(adapter: str, project_config: dict) -> bool:
|
|
157
|
+
"""Test the configuration by validating adapter credentials.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
adapter: Adapter type (linear, github, jira, aitrackdown)
|
|
161
|
+
project_config: Project configuration dict
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if validation passed, False otherwise
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
from ..core import AdapterRegistry
|
|
169
|
+
|
|
170
|
+
# Get adapter configuration
|
|
171
|
+
adapters_config = project_config.get("adapters", {})
|
|
172
|
+
adapter_config = adapters_config.get(adapter, {})
|
|
173
|
+
|
|
174
|
+
# Test adapter instantiation
|
|
175
|
+
console.print(f" Testing {adapter} adapter...")
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
adapter_instance = AdapterRegistry.get_adapter(adapter, adapter_config)
|
|
179
|
+
console.print(" [green]✓[/green] Adapter instantiated successfully")
|
|
180
|
+
|
|
181
|
+
# Test credentials if validation method exists
|
|
182
|
+
if hasattr(adapter_instance, "validate_credentials"):
|
|
183
|
+
console.print(f" Validating {adapter} credentials...")
|
|
184
|
+
is_valid, error_msg = adapter_instance.validate_credentials()
|
|
185
|
+
|
|
186
|
+
if is_valid:
|
|
187
|
+
console.print(" [green]✓[/green] Credentials are valid")
|
|
188
|
+
return True
|
|
189
|
+
else:
|
|
190
|
+
console.print(
|
|
191
|
+
f" [red]✗[/red] Credential validation failed: {error_msg}"
|
|
192
|
+
)
|
|
193
|
+
return False
|
|
194
|
+
else:
|
|
195
|
+
# No validation method, assume valid
|
|
196
|
+
console.print(
|
|
197
|
+
f" [yellow]○[/yellow] No credential validation available for {adapter}"
|
|
198
|
+
)
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
console.print(f" [red]✗[/red] Adapter instantiation failed: {e}")
|
|
203
|
+
|
|
204
|
+
# Provide helpful error messages based on adapter type
|
|
205
|
+
if adapter == "linear":
|
|
206
|
+
console.print(
|
|
207
|
+
"\n [yellow]Linear requires:[/yellow] LINEAR_API_KEY and LINEAR_TEAM_ID"
|
|
208
|
+
)
|
|
209
|
+
elif adapter == "github":
|
|
210
|
+
console.print(
|
|
211
|
+
"\n [yellow]GitHub requires:[/yellow] GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO"
|
|
212
|
+
)
|
|
213
|
+
elif adapter == "jira":
|
|
214
|
+
console.print(
|
|
215
|
+
"\n [yellow]JIRA requires:[/yellow] JIRA_SERVER, JIRA_EMAIL, JIRA_API_TOKEN"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
console.print(f" [red]✗[/red] Configuration test error: {e}")
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def remove_codex_mcp(dry_run: bool = False) -> None:
|
|
226
|
+
"""Remove mcp-ticketer from Codex CLI configuration.
|
|
227
|
+
|
|
228
|
+
IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
|
|
229
|
+
This will remove mcp-ticketer from the global configuration.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
dry_run: Show what would be removed without making changes
|
|
233
|
+
|
|
234
|
+
"""
|
|
235
|
+
# Step 1: Find Codex config location (always global)
|
|
236
|
+
console.print("[cyan]🔍 Removing Codex CLI global configuration...[/cyan]")
|
|
237
|
+
console.print(
|
|
238
|
+
"[yellow]⚠ Note: Codex CLI only supports global configuration[/yellow]"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
codex_config_path = find_codex_config()
|
|
242
|
+
console.print(f"[dim]Config location: {codex_config_path}[/dim]")
|
|
243
|
+
|
|
244
|
+
# Step 2: Check if config file exists
|
|
245
|
+
if not codex_config_path.exists():
|
|
246
|
+
console.print(
|
|
247
|
+
f"[yellow]⚠ No configuration found at {codex_config_path}[/yellow]"
|
|
248
|
+
)
|
|
249
|
+
console.print("[dim]mcp-ticketer is not configured for Codex CLI[/dim]")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
# Step 3: Load existing Codex configuration
|
|
253
|
+
codex_config = load_codex_config(codex_config_path)
|
|
254
|
+
|
|
255
|
+
# Step 4: Check if mcp-ticketer is configured
|
|
256
|
+
# NOTE: Use underscore mcp_servers, not camelCase
|
|
257
|
+
mcp_servers = codex_config.get("mcp_servers", {})
|
|
258
|
+
if "mcp-ticketer" not in mcp_servers:
|
|
259
|
+
console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
|
|
260
|
+
console.print(f"[dim]No mcp-ticketer entry found in {codex_config_path}[/dim]")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
# Step 5: Show what would be removed (dry run or actual removal)
|
|
264
|
+
if dry_run:
|
|
265
|
+
console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
|
|
266
|
+
console.print(" Server name: mcp-ticketer")
|
|
267
|
+
console.print(f" From: {codex_config_path}")
|
|
268
|
+
console.print(" Scope: Global (all sessions)")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
# Step 6: Remove mcp-ticketer from configuration
|
|
272
|
+
del codex_config["mcp_servers"]["mcp-ticketer"]
|
|
273
|
+
|
|
274
|
+
# Step 7: Save updated configuration
|
|
275
|
+
try:
|
|
276
|
+
save_codex_config(codex_config_path, codex_config)
|
|
277
|
+
console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
|
|
278
|
+
console.print(f"[dim]Configuration updated: {codex_config_path}[/dim]")
|
|
279
|
+
|
|
280
|
+
# Next steps
|
|
281
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
282
|
+
console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
|
|
283
|
+
console.print("2. mcp-ticketer will no longer be available via MCP")
|
|
284
|
+
console.print(
|
|
285
|
+
"\n[yellow]⚠ Note: This removes global configuration affecting all Codex sessions[/yellow]"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
|
|
290
|
+
raise
|
|
291
|
+
|
|
292
|
+
|
|
154
293
|
def configure_codex_mcp(force: bool = False) -> None:
|
|
155
294
|
"""Configure Codex CLI to use mcp-ticketer.
|
|
156
295
|
|
|
@@ -163,18 +302,22 @@ def configure_codex_mcp(force: bool = False) -> None:
|
|
|
163
302
|
force: Overwrite existing configuration
|
|
164
303
|
|
|
165
304
|
Raises:
|
|
166
|
-
FileNotFoundError: If
|
|
305
|
+
FileNotFoundError: If Python executable or project config not found
|
|
167
306
|
ValueError: If configuration is invalid
|
|
168
307
|
|
|
169
308
|
"""
|
|
170
|
-
# Step 1: Find
|
|
171
|
-
console.print("[cyan]🔍 Finding mcp-ticketer
|
|
309
|
+
# Step 1: Find Python executable
|
|
310
|
+
console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
|
|
172
311
|
try:
|
|
173
|
-
|
|
174
|
-
console.print(f"[green]✓[/green] Found: {
|
|
175
|
-
except
|
|
176
|
-
console.print(f"[red]✗[/red] {e}")
|
|
177
|
-
raise
|
|
312
|
+
python_path = get_mcp_ticketer_python()
|
|
313
|
+
console.print(f"[green]✓[/green] Found: {python_path}")
|
|
314
|
+
except Exception as e:
|
|
315
|
+
console.print(f"[red]✗[/red] Could not find Python executable: {e}")
|
|
316
|
+
raise FileNotFoundError(
|
|
317
|
+
"Could not find mcp-ticketer Python executable. "
|
|
318
|
+
"Please ensure mcp-ticketer is installed.\n"
|
|
319
|
+
"Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
|
|
320
|
+
) from e
|
|
178
321
|
|
|
179
322
|
# Step 2: Load project configuration
|
|
180
323
|
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
@@ -211,9 +354,11 @@ def configure_codex_mcp(force: bool = False) -> None:
|
|
|
211
354
|
|
|
212
355
|
# Step 6: Create mcp-ticketer server config
|
|
213
356
|
# For global config, include current working directory for context
|
|
214
|
-
|
|
357
|
+
project_path = str(Path.cwd())
|
|
215
358
|
server_config = create_codex_server_config(
|
|
216
|
-
|
|
359
|
+
python_path=python_path,
|
|
360
|
+
project_config=project_config,
|
|
361
|
+
project_path=project_path,
|
|
217
362
|
)
|
|
218
363
|
|
|
219
364
|
# Step 7: Update Codex configuration
|
|
@@ -232,14 +377,25 @@ def configure_codex_mcp(force: bool = False) -> None:
|
|
|
232
377
|
console.print("\n[bold]Configuration Details:[/bold]")
|
|
233
378
|
console.print(" Server name: mcp-ticketer")
|
|
234
379
|
console.print(f" Adapter: {adapter}")
|
|
235
|
-
console.print(f"
|
|
380
|
+
console.print(f" Python: {python_path}")
|
|
381
|
+
console.print(" Command: python -m mcp_ticketer.mcp.server")
|
|
236
382
|
console.print(" Scope: global (Codex only supports global config)")
|
|
237
|
-
console.print(f"
|
|
383
|
+
console.print(f" Project path: {project_path}")
|
|
238
384
|
if "env" in server_config:
|
|
239
385
|
console.print(
|
|
240
386
|
f" Environment variables: {list(server_config['env'].keys())}"
|
|
241
387
|
)
|
|
242
388
|
|
|
389
|
+
# Step 9: Test configuration
|
|
390
|
+
console.print("\n[cyan]🧪 Testing configuration...[/cyan]")
|
|
391
|
+
test_success = _test_configuration(adapter, project_config)
|
|
392
|
+
|
|
393
|
+
if not test_success:
|
|
394
|
+
console.print(
|
|
395
|
+
"[yellow]⚠ Configuration saved but validation failed. "
|
|
396
|
+
"Please check your credentials and settings.[/yellow]"
|
|
397
|
+
)
|
|
398
|
+
|
|
243
399
|
# Next steps
|
|
244
400
|
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
245
401
|
console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
|
mcp_ticketer/cli/configure.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Interactive configuration wizard for MCP Ticketer."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
from rich.console import Console
|
|
@@ -46,24 +46,25 @@ def configure_wizard() -> None:
|
|
|
46
46
|
|
|
47
47
|
# Step 2: Choose where to save
|
|
48
48
|
console.print("\n[bold]Step 2: Configuration Scope[/bold]")
|
|
49
|
-
console.print(
|
|
50
|
-
|
|
49
|
+
console.print(
|
|
50
|
+
"1. Project-specific (recommended): .mcp-ticketer/config.json in project root"
|
|
51
|
+
)
|
|
52
|
+
console.print("2. Legacy global (deprecated): saves to project config for security")
|
|
51
53
|
|
|
52
|
-
scope = Prompt.ask("Save configuration as", choices=["1", "2"], default="
|
|
54
|
+
scope = Prompt.ask("Save configuration as", choices=["1", "2"], default="1")
|
|
53
55
|
|
|
54
56
|
resolver = ConfigResolver()
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
# Always save to project config (global config removed for security)
|
|
59
|
+
resolver.save_project_config(config)
|
|
60
|
+
config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
61
|
+
|
|
62
|
+
if scope == "2":
|
|
59
63
|
console.print(
|
|
60
|
-
|
|
64
|
+
"[yellow]Note: Global config is deprecated for security. Saving to project config instead.[/yellow]"
|
|
61
65
|
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
resolver.save_project_config(config)
|
|
65
|
-
config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
66
|
-
console.print(f"\n[green]✓[/green] Configuration saved to {config_path}")
|
|
66
|
+
|
|
67
|
+
console.print(f"\n[green]✓[/green] Configuration saved to {config_path}")
|
|
67
68
|
|
|
68
69
|
# Show usage instructions
|
|
69
70
|
console.print("\n[bold]Usage:[/bold]")
|
|
@@ -154,7 +155,7 @@ def _configure_linear() -> AdapterConfig:
|
|
|
154
155
|
is_valid, error = ConfigValidator.validate_linear_config(config_dict)
|
|
155
156
|
if not is_valid:
|
|
156
157
|
console.print(f"[red]Configuration error: {error}[/red]")
|
|
157
|
-
raise typer.Exit(1)
|
|
158
|
+
raise typer.Exit(1) from None
|
|
158
159
|
|
|
159
160
|
return AdapterConfig.from_dict(config_dict)
|
|
160
161
|
|
|
@@ -198,7 +199,7 @@ def _configure_jira() -> AdapterConfig:
|
|
|
198
199
|
is_valid, error = ConfigValidator.validate_jira_config(config_dict)
|
|
199
200
|
if not is_valid:
|
|
200
201
|
console.print(f"[red]Configuration error: {error}[/red]")
|
|
201
|
-
raise typer.Exit(1)
|
|
202
|
+
raise typer.Exit(1) from None
|
|
202
203
|
|
|
203
204
|
return AdapterConfig.from_dict(config_dict)
|
|
204
205
|
|
|
@@ -246,7 +247,7 @@ def _configure_github() -> AdapterConfig:
|
|
|
246
247
|
is_valid, error = ConfigValidator.validate_github_config(config_dict)
|
|
247
248
|
if not is_valid:
|
|
248
249
|
console.print(f"[red]Configuration error: {error}[/red]")
|
|
249
|
-
raise typer.Exit(1)
|
|
250
|
+
raise typer.Exit(1) from None
|
|
250
251
|
|
|
251
252
|
return AdapterConfig.from_dict(config_dict)
|
|
252
253
|
|
|
@@ -296,7 +297,7 @@ def _configure_hybrid_mode() -> TicketerConfig:
|
|
|
296
297
|
|
|
297
298
|
if len(selected_adapters) < 2:
|
|
298
299
|
console.print("[red]Hybrid mode requires at least 2 adapters[/red]")
|
|
299
|
-
raise typer.Exit(1)
|
|
300
|
+
raise typer.Exit(1) from None
|
|
300
301
|
|
|
301
302
|
# Configure each adapter
|
|
302
303
|
adapters = {}
|
|
@@ -366,31 +367,17 @@ def show_current_config() -> None:
|
|
|
366
367
|
resolver = ConfigResolver()
|
|
367
368
|
|
|
368
369
|
# Try to load configs
|
|
369
|
-
global_config = resolver.load_global_config()
|
|
370
370
|
project_config = resolver.load_project_config()
|
|
371
371
|
|
|
372
372
|
console.print("[bold]Current Configuration:[/bold]\n")
|
|
373
373
|
|
|
374
|
-
#
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if global_config.adapters:
|
|
380
|
-
table = Table(title="Global Adapters")
|
|
381
|
-
table.add_column("Adapter", style="cyan")
|
|
382
|
-
table.add_column("Configured", style="green")
|
|
383
|
-
|
|
384
|
-
for name, config in global_config.adapters.items():
|
|
385
|
-
configured = "✓" if config.enabled else "✗"
|
|
386
|
-
table.add_row(name, configured)
|
|
387
|
-
|
|
388
|
-
console.print(table)
|
|
389
|
-
else:
|
|
390
|
-
console.print("[yellow]No global configuration found[/yellow]")
|
|
374
|
+
# Note about global config deprecation
|
|
375
|
+
console.print(
|
|
376
|
+
"[dim]Note: Global config has been deprecated for security reasons.[/dim]"
|
|
377
|
+
)
|
|
378
|
+
console.print("[dim]All configuration is now project-specific only.[/dim]\n")
|
|
391
379
|
|
|
392
380
|
# Project config
|
|
393
|
-
console.print()
|
|
394
381
|
project_config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
395
382
|
if project_config_path.exists():
|
|
396
383
|
console.print(f"[cyan]Project:[/cyan] {project_config_path}")
|
|
@@ -440,12 +427,12 @@ def show_current_config() -> None:
|
|
|
440
427
|
|
|
441
428
|
|
|
442
429
|
def set_adapter_config(
|
|
443
|
-
adapter:
|
|
444
|
-
api_key:
|
|
445
|
-
project_id:
|
|
446
|
-
team_id:
|
|
430
|
+
adapter: str | None = None,
|
|
431
|
+
api_key: str | None = None,
|
|
432
|
+
project_id: str | None = None,
|
|
433
|
+
team_id: str | None = None,
|
|
447
434
|
global_scope: bool = False,
|
|
448
|
-
**kwargs,
|
|
435
|
+
**kwargs: Any,
|
|
449
436
|
) -> None:
|
|
450
437
|
"""Set specific adapter configuration values.
|
|
451
438
|
|
|
@@ -498,11 +485,13 @@ def set_adapter_config(
|
|
|
498
485
|
|
|
499
486
|
console.print(f"[green]✓[/green] Updated {target_adapter} configuration")
|
|
500
487
|
|
|
501
|
-
# Save config
|
|
488
|
+
# Save config (always to project config for security)
|
|
489
|
+
resolver.save_project_config(config)
|
|
490
|
+
config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
491
|
+
|
|
502
492
|
if global_scope:
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
console.print(f"[dim]Saved to {config_path}[/dim]")
|
|
493
|
+
console.print(
|
|
494
|
+
"[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
console.print(f"[dim]Saved to {config_path}[/dim]")
|
mcp_ticketer/cli/diagnostics.py
CHANGED
|
@@ -5,14 +5,14 @@ import logging
|
|
|
5
5
|
import sys
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
from rich.console import Console
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def get_config():
|
|
15
|
+
def get_config() -> Any:
|
|
16
16
|
"""Get configuration using the real configuration system."""
|
|
17
17
|
from ..core.config import ConfigurationManager
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ def get_config():
|
|
|
20
20
|
return config_manager.load_config()
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def safe_import_registry():
|
|
23
|
+
def safe_import_registry() -> type:
|
|
24
24
|
"""Safely import adapter registry with fallback."""
|
|
25
25
|
try:
|
|
26
26
|
from ..core.registry import AdapterRegistry
|
|
@@ -30,13 +30,13 @@ def safe_import_registry():
|
|
|
30
30
|
|
|
31
31
|
class MockRegistry:
|
|
32
32
|
@staticmethod
|
|
33
|
-
def get_adapter(adapter_type):
|
|
33
|
+
def get_adapter(adapter_type: str) -> None:
|
|
34
34
|
raise ImportError(f"Adapter {adapter_type} not available")
|
|
35
35
|
|
|
36
36
|
return MockRegistry
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def safe_import_queue_manager():
|
|
39
|
+
def safe_import_queue_manager() -> type:
|
|
40
40
|
"""Safely import worker manager with fallback."""
|
|
41
41
|
try:
|
|
42
42
|
from ..queue.manager import WorkerManager as RealWorkerManager
|
|
@@ -55,16 +55,16 @@ def safe_import_queue_manager():
|
|
|
55
55
|
pass
|
|
56
56
|
|
|
57
57
|
class MockWorkerManager:
|
|
58
|
-
def get_status(self):
|
|
58
|
+
def get_status(self) -> dict[str, Any]:
|
|
59
59
|
return {"running": False, "pid": None, "status": "fallback_mode"}
|
|
60
60
|
|
|
61
|
-
def get_worker_status(self):
|
|
61
|
+
def get_worker_status(self) -> dict[str, Any]:
|
|
62
62
|
return {"running": False, "pid": None, "status": "fallback_mode"}
|
|
63
63
|
|
|
64
|
-
def get_queue_stats(self):
|
|
64
|
+
def get_queue_stats(self) -> dict[str, Any]:
|
|
65
65
|
return {"total": 0, "failed": 0, "pending": 0, "completed": 0}
|
|
66
66
|
|
|
67
|
-
def health_check(self):
|
|
67
|
+
def health_check(self) -> dict[str, Any]:
|
|
68
68
|
return {
|
|
69
69
|
"status": "degraded",
|
|
70
70
|
"score": 50,
|
|
@@ -85,11 +85,11 @@ logger = logging.getLogger(__name__)
|
|
|
85
85
|
class SystemDiagnostics:
|
|
86
86
|
"""Comprehensive system diagnostics and health reporting."""
|
|
87
87
|
|
|
88
|
-
def __init__(self):
|
|
88
|
+
def __init__(self) -> None:
|
|
89
89
|
# Initialize lists first
|
|
90
|
-
self.issues = []
|
|
91
|
-
self.warnings = []
|
|
92
|
-
self.successes = []
|
|
90
|
+
self.issues: list[str] = []
|
|
91
|
+
self.warnings: list[str] = []
|
|
92
|
+
self.successes: list[str] = []
|
|
93
93
|
|
|
94
94
|
try:
|
|
95
95
|
self.config = get_config()
|
|
@@ -611,7 +611,7 @@ class SystemDiagnostics:
|
|
|
611
611
|
|
|
612
612
|
async def _analyze_log_directory(
|
|
613
613
|
self, log_path: Path, log_analysis: dict[str, Any]
|
|
614
|
-
):
|
|
614
|
+
) -> None:
|
|
615
615
|
"""Analyze logs in a specific directory."""
|
|
616
616
|
try:
|
|
617
617
|
for log_file in log_path.glob("*.log"):
|
|
@@ -623,7 +623,9 @@ class SystemDiagnostics:
|
|
|
623
623
|
except Exception as e:
|
|
624
624
|
self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
|
|
625
625
|
|
|
626
|
-
async def _parse_log_file(
|
|
626
|
+
async def _parse_log_file(
|
|
627
|
+
self, log_file: Path, log_analysis: dict[str, Any]
|
|
628
|
+
) -> None:
|
|
627
629
|
"""Parse individual log file for issues."""
|
|
628
630
|
try:
|
|
629
631
|
with open(log_file) as f:
|
|
@@ -672,7 +674,7 @@ class SystemDiagnostics:
|
|
|
672
674
|
|
|
673
675
|
def _generate_recommendations(self) -> list[str]:
|
|
674
676
|
"""Generate actionable recommendations based on diagnosis."""
|
|
675
|
-
recommendations = []
|
|
677
|
+
recommendations: list[str] = []
|
|
676
678
|
|
|
677
679
|
if self.issues:
|
|
678
680
|
recommendations.append(
|
|
@@ -705,7 +707,7 @@ class SystemDiagnostics:
|
|
|
705
707
|
|
|
706
708
|
return recommendations
|
|
707
709
|
|
|
708
|
-
def _display_diagnosis_summary(self, report: dict[str, Any]):
|
|
710
|
+
def _display_diagnosis_summary(self, report: dict[str, Any]) -> None:
|
|
709
711
|
"""Display a comprehensive diagnosis summary."""
|
|
710
712
|
console.print("\n" + "=" * 60)
|
|
711
713
|
console.print("📋 [bold green]DIAGNOSIS SUMMARY[/bold green]")
|
|
@@ -795,7 +797,7 @@ class SystemDiagnostics:
|
|
|
795
797
|
|
|
796
798
|
|
|
797
799
|
async def run_diagnostics(
|
|
798
|
-
output_file:
|
|
800
|
+
output_file: str | None = None,
|
|
799
801
|
json_output: bool = False,
|
|
800
802
|
) -> None:
|
|
801
803
|
"""Run comprehensive system diagnostics."""
|