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.

Files changed (84) hide show
  1. mcp_ticketer/__version__.py +3 -3
  2. mcp_ticketer/adapters/__init__.py +2 -0
  3. mcp_ticketer/adapters/aitrackdown.py +263 -14
  4. mcp_ticketer/adapters/asana/__init__.py +15 -0
  5. mcp_ticketer/adapters/asana/adapter.py +1308 -0
  6. mcp_ticketer/adapters/asana/client.py +292 -0
  7. mcp_ticketer/adapters/asana/mappers.py +334 -0
  8. mcp_ticketer/adapters/asana/types.py +146 -0
  9. mcp_ticketer/adapters/github.py +326 -109
  10. mcp_ticketer/adapters/hybrid.py +11 -11
  11. mcp_ticketer/adapters/jira.py +271 -25
  12. mcp_ticketer/adapters/linear/adapter.py +693 -39
  13. mcp_ticketer/adapters/linear/client.py +61 -9
  14. mcp_ticketer/adapters/linear/mappers.py +9 -3
  15. mcp_ticketer/adapters/linear/queries.py +9 -7
  16. mcp_ticketer/cache/memory.py +9 -8
  17. mcp_ticketer/cli/adapter_diagnostics.py +1 -1
  18. mcp_ticketer/cli/auggie_configure.py +104 -15
  19. mcp_ticketer/cli/codex_configure.py +188 -32
  20. mcp_ticketer/cli/configure.py +37 -48
  21. mcp_ticketer/cli/diagnostics.py +20 -18
  22. mcp_ticketer/cli/discover.py +292 -26
  23. mcp_ticketer/cli/gemini_configure.py +107 -26
  24. mcp_ticketer/cli/instruction_commands.py +429 -0
  25. mcp_ticketer/cli/linear_commands.py +105 -22
  26. mcp_ticketer/cli/main.py +1830 -435
  27. mcp_ticketer/cli/mcp_configure.py +296 -89
  28. mcp_ticketer/cli/migrate_config.py +12 -8
  29. mcp_ticketer/cli/platform_commands.py +123 -0
  30. mcp_ticketer/cli/platform_detection.py +412 -0
  31. mcp_ticketer/cli/python_detection.py +126 -0
  32. mcp_ticketer/cli/queue_commands.py +15 -15
  33. mcp_ticketer/cli/simple_health.py +1 -1
  34. mcp_ticketer/cli/ticket_commands.py +773 -0
  35. mcp_ticketer/cli/update_checker.py +313 -0
  36. mcp_ticketer/cli/utils.py +67 -62
  37. mcp_ticketer/core/__init__.py +14 -1
  38. mcp_ticketer/core/adapter.py +84 -15
  39. mcp_ticketer/core/config.py +44 -39
  40. mcp_ticketer/core/env_discovery.py +42 -12
  41. mcp_ticketer/core/env_loader.py +15 -14
  42. mcp_ticketer/core/exceptions.py +3 -3
  43. mcp_ticketer/core/http_client.py +26 -26
  44. mcp_ticketer/core/instructions.py +405 -0
  45. mcp_ticketer/core/mappers.py +11 -11
  46. mcp_ticketer/core/models.py +50 -20
  47. mcp_ticketer/core/onepassword_secrets.py +379 -0
  48. mcp_ticketer/core/project_config.py +57 -35
  49. mcp_ticketer/core/registry.py +3 -3
  50. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  51. mcp_ticketer/mcp/__init__.py +29 -1
  52. mcp_ticketer/mcp/__main__.py +60 -0
  53. mcp_ticketer/mcp/server/__init__.py +25 -0
  54. mcp_ticketer/mcp/server/__main__.py +60 -0
  55. mcp_ticketer/mcp/{dto.py → server/dto.py} +32 -32
  56. mcp_ticketer/mcp/{server.py → server/main.py} +127 -74
  57. mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +2 -2
  58. mcp_ticketer/mcp/server/server_sdk.py +93 -0
  59. mcp_ticketer/mcp/server/tools/__init__.py +47 -0
  60. mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
  61. mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
  62. mcp_ticketer/mcp/server/tools/comment_tools.py +90 -0
  63. mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
  64. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +532 -0
  65. mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
  66. mcp_ticketer/mcp/server/tools/pr_tools.py +154 -0
  67. mcp_ticketer/mcp/server/tools/search_tools.py +206 -0
  68. mcp_ticketer/mcp/server/tools/ticket_tools.py +430 -0
  69. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
  70. mcp_ticketer/queue/__init__.py +1 -0
  71. mcp_ticketer/queue/health_monitor.py +5 -4
  72. mcp_ticketer/queue/manager.py +15 -51
  73. mcp_ticketer/queue/queue.py +19 -19
  74. mcp_ticketer/queue/run_worker.py +1 -1
  75. mcp_ticketer/queue/ticket_registry.py +14 -14
  76. mcp_ticketer/queue/worker.py +16 -14
  77. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +168 -32
  78. mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
  79. mcp_ticketer-0.3.5.dist-info/RECORD +0 -62
  80. /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
  81. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
  82. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
  83. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
  84. {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, Optional
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 find_mcp_ticketer_binary, load_project_config
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
- binary_path: str, project_config: dict, cwd: Optional[str] = None
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
- binary_path: Path to mcp-ticketer binary
82
+ python_path: Path to Python executable in mcp-ticketer venv
87
83
  project_config: Project configuration from .mcp-ticketer/config.json
88
- cwd: Working directory for server (optional, not used for global config)
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 if running from development environment
103
- if cwd:
104
- env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
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 cwd:
114
- # Use absolute path if cwd is provided
115
- env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(cwd) / base_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": binary_path,
147
- "args": ["serve"],
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 binary or project config not found
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 mcp-ticketer binary
171
- console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
309
+ # Step 1: Find Python executable
310
+ console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
172
311
  try:
173
- binary_path = find_mcp_ticketer_binary()
174
- console.print(f"[green]✓[/green] Found: {binary_path}")
175
- except FileNotFoundError as e:
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
- cwd = str(Path.cwd())
357
+ project_path = str(Path.cwd())
215
358
  server_config = create_codex_server_config(
216
- binary_path=binary_path, project_config=project_config, cwd=cwd
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" Binary: {binary_path}")
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" Working directory: {cwd}")
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)")
@@ -1,7 +1,7 @@
1
1
  """Interactive configuration wizard for MCP Ticketer."""
2
2
 
3
3
  import os
4
- from typing import Optional
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("1. Global (all projects): ~/.mcp-ticketer/config.json")
50
- console.print("2. Project-specific: .mcp-ticketer/config.json in project root")
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="2")
54
+ scope = Prompt.ask("Save configuration as", choices=["1", "2"], default="1")
53
55
 
54
56
  resolver = ConfigResolver()
55
57
 
56
- if scope == "1":
57
- # Save global
58
- resolver.save_global_config(config)
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
- f"\n[green]✓[/green] Configuration saved globally to {resolver.GLOBAL_CONFIG_PATH}"
64
+ "[yellow]Note: Global config is deprecated for security. Saving to project config instead.[/yellow]"
61
65
  )
62
- else:
63
- # Save project-specific
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
- # Global config
375
- if resolver.GLOBAL_CONFIG_PATH.exists():
376
- console.print(f"[cyan]Global:[/cyan] {resolver.GLOBAL_CONFIG_PATH}")
377
- console.print(f" Default adapter: {global_config.default_adapter}")
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: Optional[str] = None,
444
- api_key: Optional[str] = None,
445
- project_id: Optional[str] = None,
446
- team_id: Optional[str] = None,
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
- resolver.save_global_config(config)
504
- console.print(f"[dim]Saved to {resolver.GLOBAL_CONFIG_PATH}[/dim]")
505
- else:
506
- resolver.save_project_config(config)
507
- config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
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]")
@@ -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, Optional
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(self, log_file: Path, log_analysis: dict[str, Any]):
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: Optional[str] = None,
800
+ output_file: str | None = None,
799
801
  json_output: bool = False,
800
802
  ) -> None:
801
803
  """Run comprehensive system diagnostics."""