mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.23__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 +66 -49
  5. mcp_ticketer/adapters/github.py +192 -125
  6. mcp_ticketer/adapters/hybrid.py +99 -53
  7. mcp_ticketer/adapters/jira.py +161 -151
  8. mcp_ticketer/adapters/linear.py +396 -246
  9. mcp_ticketer/cache/__init__.py +1 -1
  10. mcp_ticketer/cache/memory.py +15 -16
  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 +283 -298
  15. mcp_ticketer/cli/mcp_configure.py +39 -15
  16. mcp_ticketer/cli/migrate_config.py +11 -13
  17. mcp_ticketer/cli/queue_commands.py +21 -58
  18. mcp_ticketer/cli/utils.py +121 -66
  19. mcp_ticketer/core/__init__.py +2 -2
  20. mcp_ticketer/core/adapter.py +46 -39
  21. mcp_ticketer/core/config.py +128 -92
  22. mcp_ticketer/core/env_discovery.py +69 -37
  23. mcp_ticketer/core/http_client.py +57 -40
  24. mcp_ticketer/core/mappers.py +98 -54
  25. mcp_ticketer/core/models.py +38 -24
  26. mcp_ticketer/core/project_config.py +145 -80
  27. mcp_ticketer/core/registry.py +16 -16
  28. mcp_ticketer/mcp/__init__.py +1 -1
  29. mcp_ticketer/mcp/server.py +199 -145
  30. mcp_ticketer/queue/__init__.py +2 -2
  31. mcp_ticketer/queue/__main__.py +1 -1
  32. mcp_ticketer/queue/manager.py +30 -26
  33. mcp_ticketer/queue/queue.py +147 -85
  34. mcp_ticketer/queue/run_worker.py +2 -3
  35. mcp_ticketer/queue/worker.py +55 -40
  36. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/METADATA +1 -1
  37. mcp_ticketer-0.1.23.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.23.dist-info}/WHEEL +0 -0
  40. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/entry_points.txt +0 -0
  41. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/licenses/LICENSE +0 -0
  42. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/top_level.txt +0 -0
@@ -2,4 +2,4 @@
2
2
 
3
3
  from .memory import MemoryCache, cache_decorator
4
4
 
5
- __all__ = ["MemoryCache", "cache_decorator"]
5
+ __all__ = ["MemoryCache", "cache_decorator"]
@@ -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, Optional, Callable, Dict, Tuple
8
+ from typing import Any, Callable, Optional
9
9
 
10
10
 
11
11
  class CacheEntry:
@@ -17,6 +17,7 @@ class CacheEntry:
17
17
  Args:
18
18
  value: Cached value
19
19
  ttl: Time to live in seconds
20
+
20
21
  """
21
22
  self.value = value
22
23
  self.expires_at = time.time() + ttl if ttl > 0 else float("inf")
@@ -34,8 +35,9 @@ class MemoryCache:
34
35
 
35
36
  Args:
36
37
  default_ttl: Default TTL in seconds (5 minutes)
38
+
37
39
  """
38
- self._cache: Dict[str, CacheEntry] = {}
40
+ self._cache: dict[str, CacheEntry] = {}
39
41
  self._default_ttl = default_ttl
40
42
  self._lock = asyncio.Lock()
41
43
 
@@ -47,6 +49,7 @@ class MemoryCache:
47
49
 
48
50
  Returns:
49
51
  Cached value or None if not found/expired
52
+
50
53
  """
51
54
  async with self._lock:
52
55
  entry = self._cache.get(key)
@@ -57,18 +60,14 @@ class MemoryCache:
57
60
  del self._cache[key]
58
61
  return None
59
62
 
60
- async def set(
61
- self,
62
- key: str,
63
- value: Any,
64
- ttl: Optional[float] = None
65
- ) -> None:
63
+ async def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
66
64
  """Set value in cache.
67
65
 
68
66
  Args:
69
67
  key: Cache key
70
68
  value: Value to cache
71
69
  ttl: Optional TTL override
70
+
72
71
  """
73
72
  async with self._lock:
74
73
  ttl = ttl if ttl is not None else self._default_ttl
@@ -82,6 +81,7 @@ class MemoryCache:
82
81
 
83
82
  Returns:
84
83
  True if key was deleted
84
+
85
85
  """
86
86
  async with self._lock:
87
87
  if key in self._cache:
@@ -99,11 +99,11 @@ class MemoryCache:
99
99
 
100
100
  Returns:
101
101
  Number of entries removed
102
+
102
103
  """
103
104
  async with self._lock:
104
105
  expired_keys = [
105
- key for key, entry in self._cache.items()
106
- if entry.is_expired()
106
+ key for key, entry in self._cache.items() if entry.is_expired()
107
107
  ]
108
108
  for key in expired_keys:
109
109
  del self._cache[key]
@@ -123,12 +123,10 @@ class MemoryCache:
123
123
 
124
124
  Returns:
125
125
  Hash-based cache key
126
+
126
127
  """
127
128
  # Create string representation of arguments
128
- key_data = {
129
- "args": args,
130
- "kwargs": sorted(kwargs.items())
131
- }
129
+ key_data = {"args": args, "kwargs": sorted(kwargs.items())}
132
130
  key_str = json.dumps(key_data, sort_keys=True, default=str)
133
131
 
134
132
  # Generate hash
@@ -138,7 +136,7 @@ class MemoryCache:
138
136
  def cache_decorator(
139
137
  ttl: Optional[float] = None,
140
138
  key_prefix: str = "",
141
- cache_instance: Optional[MemoryCache] = None
139
+ cache_instance: Optional[MemoryCache] = None,
142
140
  ) -> Callable:
143
141
  """Decorator for caching async function results.
144
142
 
@@ -149,6 +147,7 @@ def cache_decorator(
149
147
 
150
148
  Returns:
151
149
  Decorated function
150
+
152
151
  """
153
152
  # Use shared cache instance or create new
154
153
  cache = cache_instance or MemoryCache()
@@ -190,4 +189,4 @@ _global_cache = MemoryCache()
190
189
 
191
190
  def get_global_cache() -> MemoryCache:
192
191
  """Get global cache instance."""
193
- return _global_cache
192
+ return _global_cache
@@ -2,4 +2,4 @@
2
2
 
3
3
  from .main import app
4
4
 
5
- __all__ = ["app"]
5
+ __all__ = ["app"]
@@ -1,25 +1,22 @@
1
1
  """Interactive configuration wizard for MCP Ticketer."""
2
2
 
3
3
  import os
4
- import sys
5
- from typing import Optional, Dict, Any
6
- from pathlib import Path
4
+ from typing import Optional
7
5
 
8
6
  import typer
9
7
  from rich.console import Console
10
- from rich.prompt import Prompt, Confirm
11
8
  from rich.panel import Panel
9
+ from rich.prompt import Confirm, Prompt
12
10
  from rich.table import Table
13
11
 
14
12
  from ..core.project_config import (
15
- ConfigResolver,
16
- TicketerConfig,
17
13
  AdapterConfig,
18
- ProjectConfig,
19
- HybridConfig,
20
14
  AdapterType,
15
+ ConfigResolver,
16
+ ConfigValidator,
17
+ HybridConfig,
21
18
  SyncStrategy,
22
- ConfigValidator
19
+ TicketerConfig,
23
20
  )
24
21
 
25
22
  console = Console()
@@ -27,22 +24,20 @@ console = Console()
27
24
 
28
25
  def configure_wizard() -> None:
29
26
  """Run interactive configuration wizard."""
30
- console.print(Panel.fit(
31
- "[bold cyan]MCP-Ticketer Configuration Wizard[/bold cyan]\n"
32
- "Configure your ticketing system integration",
33
- border_style="cyan"
34
- ))
27
+ console.print(
28
+ Panel.fit(
29
+ "[bold cyan]MCP-Ticketer Configuration Wizard[/bold cyan]\n"
30
+ "Configure your ticketing system integration",
31
+ border_style="cyan",
32
+ )
33
+ )
35
34
 
36
35
  # Step 1: Choose integration mode
37
36
  console.print("\n[bold]Step 1: Integration Mode[/bold]")
38
37
  console.print("1. Single Adapter (recommended for most projects)")
39
38
  console.print("2. Hybrid Mode (sync across multiple platforms)")
40
39
 
41
- mode = Prompt.ask(
42
- "Select mode",
43
- choices=["1", "2"],
44
- default="1"
45
- )
40
+ mode = Prompt.ask("Select mode", choices=["1", "2"], default="1")
46
41
 
47
42
  if mode == "1":
48
43
  config = _configure_single_adapter()
@@ -54,18 +49,16 @@ def configure_wizard() -> None:
54
49
  console.print("1. Global (all projects): ~/.mcp-ticketer/config.json")
55
50
  console.print("2. Project-specific: .mcp-ticketer/config.json in project root")
56
51
 
57
- scope = Prompt.ask(
58
- "Save configuration as",
59
- choices=["1", "2"],
60
- default="2"
61
- )
52
+ scope = Prompt.ask("Save configuration as", choices=["1", "2"], default="2")
62
53
 
63
54
  resolver = ConfigResolver()
64
55
 
65
56
  if scope == "1":
66
57
  # Save global
67
58
  resolver.save_global_config(config)
68
- console.print(f"\n[green]✓[/green] Configuration saved globally to {resolver.GLOBAL_CONFIG_PATH}")
59
+ console.print(
60
+ f"\n[green]✓[/green] Configuration saved globally to {resolver.GLOBAL_CONFIG_PATH}"
61
+ )
69
62
  else:
70
63
  # Save project-specific
71
64
  resolver.save_project_config(config)
@@ -74,9 +67,11 @@ def configure_wizard() -> None:
74
67
 
75
68
  # Show usage instructions
76
69
  console.print("\n[bold]Usage:[/bold]")
77
- console.print(" CLI: [cyan]mcp-ticketer create \"Task title\"[/cyan]")
70
+ console.print(' CLI: [cyan]mcp-ticketer create "Task title"[/cyan]')
78
71
  console.print(" MCP: Configure Claude Desktop to use this adapter")
79
- console.print("\nRun [cyan]mcp-ticketer configure --show[/cyan] to view your configuration")
72
+ console.print(
73
+ "\nRun [cyan]mcp-ticketer configure --show[/cyan] to view your configuration"
74
+ )
80
75
 
81
76
 
82
77
  def _configure_single_adapter() -> TicketerConfig:
@@ -88,9 +83,7 @@ def _configure_single_adapter() -> TicketerConfig:
88
83
  console.print("4. Internal/AITrackdown (File-based, no API)")
89
84
 
90
85
  adapter_choice = Prompt.ask(
91
- "Select system",
92
- choices=["1", "2", "3", "4"],
93
- default="1"
86
+ "Select system", choices=["1", "2", "3", "4"], default="1"
94
87
  )
95
88
 
96
89
  adapter_type_map = {
@@ -115,7 +108,7 @@ def _configure_single_adapter() -> TicketerConfig:
115
108
  # Create config
116
109
  config = TicketerConfig(
117
110
  default_adapter=adapter_type.value,
118
- adapters={adapter_type.value: adapter_config}
111
+ adapters={adapter_type.value: adapter_config},
119
112
  )
120
113
 
121
114
  return config
@@ -128,34 +121,22 @@ def _configure_linear() -> AdapterConfig:
128
121
  # API Key
129
122
  api_key = os.getenv("LINEAR_API_KEY") or ""
130
123
  if api_key:
131
- console.print(f"[dim]Found LINEAR_API_KEY in environment[/dim]")
124
+ console.print("[dim]Found LINEAR_API_KEY in environment[/dim]")
132
125
  use_env = Confirm.ask("Use this API key?", default=True)
133
126
  if not use_env:
134
127
  api_key = ""
135
128
 
136
129
  if not api_key:
137
- api_key = Prompt.ask(
138
- "Linear API Key",
139
- password=True
140
- )
130
+ api_key = Prompt.ask("Linear API Key", password=True)
141
131
 
142
132
  # Team ID
143
- team_id = Prompt.ask(
144
- "Team ID (optional, e.g., team-abc)",
145
- default=""
146
- )
133
+ team_id = Prompt.ask("Team ID (optional, e.g., team-abc)", default="")
147
134
 
148
135
  # Team Key
149
- team_key = Prompt.ask(
150
- "Team Key (optional, e.g., ENG)",
151
- default=""
152
- )
136
+ team_key = Prompt.ask("Team Key (optional, e.g., ENG)", default="")
153
137
 
154
138
  # Project ID
155
- project_id = Prompt.ask(
156
- "Project ID (optional)",
157
- default=""
158
- )
139
+ project_id = Prompt.ask("Project ID (optional)", default="")
159
140
 
160
141
  config_dict = {
161
142
  "adapter": AdapterType.LINEAR.value,
@@ -185,9 +166,7 @@ def _configure_jira() -> AdapterConfig:
185
166
  # Server URL
186
167
  server = os.getenv("JIRA_SERVER") or ""
187
168
  if not server:
188
- server = Prompt.ask(
189
- "JIRA Server URL (e.g., https://company.atlassian.net)"
190
- )
169
+ server = Prompt.ask("JIRA Server URL (e.g., https://company.atlassian.net)")
191
170
 
192
171
  # Email
193
172
  email = os.getenv("JIRA_EMAIL") or ""
@@ -197,21 +176,17 @@ def _configure_jira() -> AdapterConfig:
197
176
  # API Token
198
177
  api_token = os.getenv("JIRA_API_TOKEN") or ""
199
178
  if not api_token:
200
- console.print("[dim]Generate token at: https://id.atlassian.com/manage/api-tokens[/dim]")
201
- api_token = Prompt.ask(
202
- "JIRA API Token",
203
- password=True
179
+ console.print(
180
+ "[dim]Generate token at: https://id.atlassian.com/manage/api-tokens[/dim]"
204
181
  )
182
+ api_token = Prompt.ask("JIRA API Token", password=True)
205
183
 
206
184
  # Project Key
207
- project_key = Prompt.ask(
208
- "Default Project Key (optional, e.g., PROJ)",
209
- default=""
210
- )
185
+ project_key = Prompt.ask("Default Project Key (optional, e.g., PROJ)", default="")
211
186
 
212
187
  config_dict = {
213
188
  "adapter": AdapterType.JIRA.value,
214
- "server": server.rstrip('/'),
189
+ "server": server.rstrip("/"),
215
190
  "email": email,
216
191
  "api_token": api_token,
217
192
  }
@@ -235,18 +210,19 @@ def _configure_github() -> AdapterConfig:
235
210
  # Token
236
211
  token = os.getenv("GITHUB_TOKEN") or ""
237
212
  if token:
238
- console.print(f"[dim]Found GITHUB_TOKEN in environment[/dim]")
213
+ console.print("[dim]Found GITHUB_TOKEN in environment[/dim]")
239
214
  use_env = Confirm.ask("Use this token?", default=True)
240
215
  if not use_env:
241
216
  token = ""
242
217
 
243
218
  if not token:
244
- console.print("[dim]Create token at: https://github.com/settings/tokens/new[/dim]")
245
- console.print("[dim]Required scopes: repo (or public_repo for public repos)[/dim]")
246
- token = Prompt.ask(
247
- "GitHub Personal Access Token",
248
- password=True
219
+ console.print(
220
+ "[dim]Create token at: https://github.com/settings/tokens/new[/dim]"
249
221
  )
222
+ console.print(
223
+ "[dim]Required scopes: repo (or public_repo for public repos)[/dim]"
224
+ )
225
+ token = Prompt.ask("GitHub Personal Access Token", password=True)
250
226
 
251
227
  # Repository Owner
252
228
  owner = os.getenv("GITHUB_OWNER") or ""
@@ -279,10 +255,7 @@ def _configure_aitrackdown() -> AdapterConfig:
279
255
  """Configure AITrackdown adapter."""
280
256
  console.print("\n[bold]Configure AITrackdown (File-based):[/bold]")
281
257
 
282
- base_path = Prompt.ask(
283
- "Base path for ticket storage",
284
- default=".aitrackdown"
285
- )
258
+ base_path = Prompt.ask("Base path for ticket storage", default=".aitrackdown")
286
259
 
287
260
  config_dict = {
288
261
  "adapter": AdapterType.AITRACKDOWN.value,
@@ -305,8 +278,7 @@ def _configure_hybrid_mode() -> TicketerConfig:
305
278
  console.print("4. AITrackdown")
306
279
 
307
280
  selections = Prompt.ask(
308
- "Select adapters (e.g., 1,3 for Linear and GitHub)",
309
- default="1,3"
281
+ "Select adapters (e.g., 1,3 for Linear and GitHub)", default="1,3"
310
282
  )
311
283
 
312
284
  adapter_choices = [s.strip() for s in selections.split(",")]
@@ -318,7 +290,9 @@ def _configure_hybrid_mode() -> TicketerConfig:
318
290
  "4": AdapterType.AITRACKDOWN,
319
291
  }
320
292
 
321
- selected_adapters = [adapter_type_map[c] for c in adapter_choices if c in adapter_type_map]
293
+ selected_adapters = [
294
+ adapter_type_map[c] for c in adapter_choices if c in adapter_type_map
295
+ ]
322
296
 
323
297
  if len(selected_adapters) < 2:
324
298
  console.print("[red]Hybrid mode requires at least 2 adapters[/red]")
@@ -345,11 +319,13 @@ def _configure_hybrid_mode() -> TicketerConfig:
345
319
  for idx, adapter_type in enumerate(selected_adapters, 1):
346
320
  console.print(f"{idx}. {adapter_type.value}")
347
321
 
348
- primary_idx = int(Prompt.ask(
349
- "Primary adapter",
350
- choices=[str(i) for i in range(1, len(selected_adapters) + 1)],
351
- default="1"
352
- ))
322
+ primary_idx = int(
323
+ Prompt.ask(
324
+ "Primary adapter",
325
+ choices=[str(i) for i in range(1, len(selected_adapters) + 1)],
326
+ default="1",
327
+ )
328
+ )
353
329
 
354
330
  primary_adapter = selected_adapters[primary_idx - 1].value
355
331
 
@@ -359,11 +335,7 @@ def _configure_hybrid_mode() -> TicketerConfig:
359
335
  console.print("2. Bidirectional (two-way sync)")
360
336
  console.print("3. Mirror (clone tickets across all)")
361
337
 
362
- strategy_choice = Prompt.ask(
363
- "Sync strategy",
364
- choices=["1", "2", "3"],
365
- default="1"
366
- )
338
+ strategy_choice = Prompt.ask("Sync strategy", choices=["1", "2", "3"], default="1")
367
339
 
368
340
  strategy_map = {
369
341
  "1": SyncStrategy.PRIMARY_SOURCE,
@@ -378,14 +350,12 @@ def _configure_hybrid_mode() -> TicketerConfig:
378
350
  enabled=True,
379
351
  adapters=[a.value for a in selected_adapters],
380
352
  primary_adapter=primary_adapter,
381
- sync_strategy=sync_strategy
353
+ sync_strategy=sync_strategy,
382
354
  )
383
355
 
384
356
  # Create full config
385
357
  config = TicketerConfig(
386
- default_adapter=primary_adapter,
387
- adapters=adapters,
388
- hybrid_mode=hybrid_config
358
+ default_adapter=primary_adapter, adapters=adapters, hybrid_mode=hybrid_config
389
359
  )
390
360
 
391
361
  return config
@@ -440,9 +410,15 @@ def show_current_config() -> None:
440
410
 
441
411
  if project_config.hybrid_mode and project_config.hybrid_mode.enabled:
442
412
  console.print("\n[bold]Hybrid Mode:[/bold] Enabled")
443
- console.print(f" Adapters: {', '.join(project_config.hybrid_mode.adapters)}")
444
- console.print(f" Primary: {project_config.hybrid_mode.primary_adapter}")
445
- console.print(f" Strategy: {project_config.hybrid_mode.sync_strategy.value}")
413
+ console.print(
414
+ f" Adapters: {', '.join(project_config.hybrid_mode.adapters)}"
415
+ )
416
+ console.print(
417
+ f" Primary: {project_config.hybrid_mode.primary_adapter}"
418
+ )
419
+ console.print(
420
+ f" Strategy: {project_config.hybrid_mode.sync_strategy.value}"
421
+ )
446
422
  else:
447
423
  console.print("[yellow]No project-specific configuration found[/yellow]")
448
424
 
@@ -469,7 +445,7 @@ def set_adapter_config(
469
445
  project_id: Optional[str] = None,
470
446
  team_id: Optional[str] = None,
471
447
  global_scope: bool = False,
472
- **kwargs
448
+ **kwargs,
473
449
  ) -> None:
474
450
  """Set specific adapter configuration values.
475
451
 
@@ -480,6 +456,7 @@ def set_adapter_config(
480
456
  team_id: Team ID (Linear)
481
457
  global_scope: Save to global config instead of project
482
458
  **kwargs: Additional adapter-specific options
459
+
483
460
  """
484
461
  resolver = ConfigResolver()
485
462
 
@@ -511,8 +488,7 @@ def set_adapter_config(
511
488
  # Get or create adapter config
512
489
  if target_adapter not in config.adapters:
513
490
  config.adapters[target_adapter] = AdapterConfig(
514
- adapter=target_adapter,
515
- **updates
491
+ adapter=target_adapter, **updates
516
492
  )
517
493
  else:
518
494
  # Update existing
@@ -1,21 +1,17 @@
1
1
  """CLI command for auto-discovering configuration from .env files."""
2
2
 
3
- import json
4
3
  from pathlib import Path
5
4
  from typing import Optional
6
5
 
7
6
  import typer
8
7
  from rich.console import Console
9
- from rich.table import Table
10
- from rich.panel import Panel
11
- from rich import print as rprint
12
8
 
13
- from ..core.env_discovery import EnvDiscovery, DiscoveredAdapter
9
+ from ..core.env_discovery import DiscoveredAdapter, EnvDiscovery
14
10
  from ..core.project_config import (
15
- ConfigResolver,
16
- TicketerConfig,
17
11
  AdapterConfig,
12
+ ConfigResolver,
18
13
  ConfigValidator,
14
+ TicketerConfig,
19
15
  )
20
16
 
21
17
  console = Console()
@@ -31,6 +27,7 @@ def _mask_sensitive(value: str, key: str) -> str:
31
27
 
32
28
  Returns:
33
29
  Masked or original value
30
+
34
31
  """
35
32
  sensitive_keys = ["token", "key", "password", "secret", "api_token"]
36
33
 
@@ -52,12 +49,15 @@ def _mask_sensitive(value: str, key: str) -> str:
52
49
  return value
53
50
 
54
51
 
55
- def _display_discovered_adapter(adapter: DiscoveredAdapter, discovery: EnvDiscovery) -> None:
52
+ def _display_discovered_adapter(
53
+ adapter: DiscoveredAdapter, discovery: EnvDiscovery
54
+ ) -> None:
56
55
  """Display information about a discovered adapter.
57
56
 
58
57
  Args:
59
58
  adapter: Discovered adapter to display
60
59
  discovery: EnvDiscovery instance for validation
60
+
61
61
  """
62
62
  # Header
63
63
  completeness = "✅ Complete" if adapter.is_complete() else "⚠️ Incomplete"
@@ -80,7 +80,9 @@ def _display_discovered_adapter(adapter: DiscoveredAdapter, discovery: EnvDiscov
80
80
 
81
81
  # Missing fields
82
82
  if adapter.missing_fields:
83
- console.print(f" [yellow]Missing:[/yellow] {', '.join(adapter.missing_fields)}")
83
+ console.print(
84
+ f" [yellow]Missing:[/yellow] {', '.join(adapter.missing_fields)}"
85
+ )
84
86
 
85
87
  # Validation warnings
86
88
  warnings = discovery.validate_discovered_config(adapter)
@@ -95,7 +97,7 @@ def show(
95
97
  None,
96
98
  "--path",
97
99
  "-p",
98
- help="Project path to scan (defaults to current directory)"
100
+ help="Project path to scan (defaults to current directory)",
99
101
  ),
100
102
  ) -> None:
101
103
  """Show discovered configuration without saving."""
@@ -119,7 +121,9 @@ def show(
119
121
  # Show discovered adapters
120
122
  if result.adapters:
121
123
  console.print("\n[bold]Detected adapter configurations:[/bold]")
122
- for adapter in sorted(result.adapters, key=lambda a: a.confidence, reverse=True):
124
+ for adapter in sorted(
125
+ result.adapters, key=lambda a: a.confidence, reverse=True
126
+ ):
123
127
  _display_discovered_adapter(adapter, discovery)
124
128
 
125
129
  # Show recommended adapter
@@ -131,7 +135,9 @@ def show(
131
135
  )
132
136
  else:
133
137
  console.print("\n[yellow]No adapter configurations detected[/yellow]")
134
- console.print("[dim]Make sure your .env file contains adapter credentials[/dim]")
138
+ console.print(
139
+ "[dim]Make sure your .env file contains adapter credentials[/dim]"
140
+ )
135
141
 
136
142
  # Show warnings
137
143
  if result.warnings:
@@ -143,27 +149,19 @@ def show(
143
149
  @app.command()
144
150
  def save(
145
151
  adapter: Optional[str] = typer.Option(
146
- None,
147
- "--adapter",
148
- "-a",
149
- help="Which adapter to save (defaults to recommended)"
152
+ None, "--adapter", "-a", help="Which adapter to save (defaults to recommended)"
150
153
  ),
151
154
  global_config: bool = typer.Option(
152
- False,
153
- "--global",
154
- "-g",
155
- help="Save to global config instead of project config"
155
+ False, "--global", "-g", help="Save to global config instead of project config"
156
156
  ),
157
157
  dry_run: bool = typer.Option(
158
- False,
159
- "--dry-run",
160
- help="Show what would be saved without saving"
158
+ False, "--dry-run", help="Show what would be saved without saving"
161
159
  ),
162
160
  project_path: Optional[Path] = typer.Option(
163
161
  None,
164
162
  "--path",
165
163
  "-p",
166
- help="Project path to scan (defaults to current directory)"
164
+ help="Project path to scan (defaults to current directory)",
167
165
  ),
168
166
  ) -> None:
169
167
  """Discover configuration and save to config file.
@@ -181,7 +179,9 @@ def save(
181
179
 
182
180
  if not result.adapters:
183
181
  console.print("[red]No adapter configurations detected[/red]")
184
- console.print("[dim]Make sure your .env file contains adapter credentials[/dim]")
182
+ console.print(
183
+ "[dim]Make sure your .env file contains adapter credentials[/dim]"
184
+ )
185
185
  raise typer.Exit(1)
186
186
 
187
187
  # Determine which adapter to save
@@ -189,7 +189,9 @@ def save(
189
189
  discovered_adapter = result.get_adapter_by_type(adapter)
190
190
  if not discovered_adapter:
191
191
  console.print(f"[red]No configuration found for adapter: {adapter}[/red]")
192
- console.print(f"[dim]Available: {', '.join(a.adapter_type for a in result.adapters)}[/dim]")
192
+ console.print(
193
+ f"[dim]Available: {', '.join(a.adapter_type for a in result.adapters)}[/dim]"
194
+ )
193
195
  raise typer.Exit(1)
194
196
  else:
195
197
  # Use recommended adapter
@@ -207,13 +209,14 @@ def save(
207
209
 
208
210
  # Validate configuration
209
211
  is_valid, error_msg = ConfigValidator.validate(
210
- discovered_adapter.adapter_type,
211
- discovered_adapter.config
212
+ discovered_adapter.adapter_type, discovered_adapter.config
212
213
  )
213
214
 
214
215
  if not is_valid:
215
216
  console.print(f"\n[red]Configuration validation failed:[/red] {error_msg}")
216
- console.print("[dim]Fix the configuration in your .env file and try again[/dim]")
217
+ console.print(
218
+ "[dim]Fix the configuration in your .env file and try again[/dim]"
219
+ )
217
220
  raise typer.Exit(1)
218
221
 
219
222
  if dry_run:
@@ -247,7 +250,9 @@ def save(
247
250
  config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
248
251
 
249
252
  console.print(f"\n[green]✅ Configuration saved to:[/green] {config_location}")
250
- console.print(f"[green]✅ Default adapter set to:[/green] {discovered_adapter.adapter_type}")
253
+ console.print(
254
+ f"[green]✅ Default adapter set to:[/green] {discovered_adapter.adapter_type}"
255
+ )
251
256
 
252
257
  except Exception as e:
253
258
  console.print(f"\n[red]Failed to save configuration:[/red] {e}")
@@ -260,7 +265,7 @@ def interactive(
260
265
  None,
261
266
  "--path",
262
267
  "-p",
263
- help="Project path to scan (defaults to current directory)"
268
+ help="Project path to scan (defaults to current directory)",
264
269
  ),
265
270
  ) -> None:
266
271
  """Interactive mode for discovering and saving configuration."""
@@ -284,7 +289,9 @@ def interactive(
284
289
  # Show discovered adapters
285
290
  if not result.adapters:
286
291
  console.print("\n[red]No adapter configurations detected[/red]")
287
- console.print("[dim]Make sure your .env file contains adapter credentials[/dim]")
292
+ console.print(
293
+ "[dim]Make sure your .env file contains adapter credentials[/dim]"
294
+ )
288
295
  raise typer.Exit(1)
289
296
 
290
297
  console.print("\n[bold]Detected adapter configurations:[/bold]")
@@ -344,7 +351,9 @@ def interactive(
344
351
  raise typer.Exit(1)
345
352
  else: # choice == 4
346
353
  adapters_to_save = result.adapters
347
- default_adapter = primary.adapter_type if primary else result.adapters[0].adapter_type
354
+ default_adapter = (
355
+ primary.adapter_type if primary else result.adapters[0].adapter_type
356
+ )
348
357
 
349
358
  # Determine save location
350
359
  save_global = choice == 2
@@ -364,8 +373,7 @@ def interactive(
364
373
  for discovered_adapter in adapters_to_save:
365
374
  # Validate
366
375
  is_valid, error_msg = ConfigValidator.validate(
367
- discovered_adapter.adapter_type,
368
- discovered_adapter.config
376
+ discovered_adapter.adapter_type, discovered_adapter.config
369
377
  )
370
378
 
371
379
  if not is_valid: