mcp-ticketer 0.1.22__py3-none-any.whl → 0.1.24__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 (33) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +15 -14
  4. mcp_ticketer/adapters/github.py +21 -20
  5. mcp_ticketer/adapters/hybrid.py +13 -12
  6. mcp_ticketer/adapters/jira.py +32 -27
  7. mcp_ticketer/adapters/linear.py +29 -26
  8. mcp_ticketer/cache/memory.py +2 -2
  9. mcp_ticketer/cli/auggie_configure.py +237 -0
  10. mcp_ticketer/cli/codex_configure.py +257 -0
  11. mcp_ticketer/cli/gemini_configure.py +261 -0
  12. mcp_ticketer/cli/main.py +171 -10
  13. mcp_ticketer/cli/migrate_config.py +3 -7
  14. mcp_ticketer/cli/utils.py +8 -8
  15. mcp_ticketer/core/adapter.py +12 -11
  16. mcp_ticketer/core/config.py +17 -17
  17. mcp_ticketer/core/env_discovery.py +24 -24
  18. mcp_ticketer/core/http_client.py +13 -13
  19. mcp_ticketer/core/mappers.py +25 -25
  20. mcp_ticketer/core/models.py +10 -10
  21. mcp_ticketer/core/project_config.py +25 -22
  22. mcp_ticketer/core/registry.py +7 -7
  23. mcp_ticketer/mcp/server.py +18 -18
  24. mcp_ticketer/queue/manager.py +2 -2
  25. mcp_ticketer/queue/queue.py +7 -7
  26. mcp_ticketer/queue/worker.py +8 -8
  27. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/METADATA +58 -8
  28. mcp_ticketer-0.1.24.dist-info/RECORD +45 -0
  29. mcp_ticketer-0.1.22.dist-info/RECORD +0 -42
  30. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/WHEEL +0 -0
  31. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/entry_points.txt +0 -0
  32. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/licenses/LICENSE +0 -0
  33. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,257 @@
1
+ """Codex CLI configuration for mcp-ticketer integration.
2
+
3
+ Codex CLI only supports global configuration at ~/.codex/config.toml.
4
+ Unlike Claude Code and Gemini CLI, there is no project-level configuration support.
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any, Dict, Optional
10
+
11
+ if sys.version_info >= (3, 11):
12
+ import tomllib
13
+ else:
14
+ import tomli as tomllib
15
+
16
+ import tomli_w
17
+ from rich.console import Console
18
+
19
+ from .mcp_configure import find_mcp_ticketer_binary, load_project_config
20
+
21
+ console = Console()
22
+
23
+
24
+ def find_codex_config() -> Path:
25
+ """Find Codex CLI configuration file location.
26
+
27
+ Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
28
+ No project-level or user-scoped configuration is available.
29
+
30
+ Returns:
31
+ Path to Codex global config file at ~/.codex/config.toml
32
+
33
+ """
34
+ # Codex only supports global config (no project-level support)
35
+ config_path = Path.home() / ".codex" / "config.toml"
36
+ return config_path
37
+
38
+
39
+ def load_codex_config(config_path: Path) -> Dict[str, Any]:
40
+ """Load existing Codex configuration or return empty structure.
41
+
42
+ Args:
43
+ config_path: Path to Codex config.toml file
44
+
45
+ Returns:
46
+ Codex configuration dict with mcp_servers section
47
+
48
+ """
49
+ if config_path.exists():
50
+ try:
51
+ with open(config_path, "rb") as f:
52
+ return tomllib.load(f)
53
+ except Exception as e:
54
+ console.print(
55
+ f"[yellow]⚠ Warning: Could not parse existing config: {e}[/yellow]"
56
+ )
57
+ console.print("[yellow]Creating new configuration...[/yellow]")
58
+
59
+ # Return empty structure with mcp_servers section
60
+ # NOTE: Use underscore mcp_servers, not camelCase mcpServers
61
+ return {"mcp_servers": {}}
62
+
63
+
64
+ def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
65
+ """Save Codex configuration to TOML file.
66
+
67
+ Args:
68
+ config_path: Path to Codex config.toml file
69
+ config: Configuration to save
70
+
71
+ """
72
+ # Ensure directory exists
73
+ config_path.parent.mkdir(parents=True, exist_ok=True)
74
+
75
+ # Write TOML with proper formatting
76
+ with open(config_path, "wb") as f:
77
+ tomli_w.dump(config, f)
78
+
79
+
80
+ def create_codex_server_config(
81
+ binary_path: str, project_config: dict, cwd: Optional[str] = None
82
+ ) -> Dict[str, Any]:
83
+ """Create Codex MCP server configuration for mcp-ticketer.
84
+
85
+ Args:
86
+ binary_path: Path to mcp-ticketer binary
87
+ project_config: Project configuration from .mcp-ticketer/config.json
88
+ cwd: Working directory for server (optional, not used for global config)
89
+
90
+ Returns:
91
+ Codex MCP server configuration dict
92
+
93
+ """
94
+ # Get adapter configuration
95
+ adapter = project_config.get("default_adapter", "aitrackdown")
96
+ adapters_config = project_config.get("adapters", {})
97
+ adapter_config = adapters_config.get(adapter, {})
98
+
99
+ # Build environment variables
100
+ env_vars: Dict[str, str] = {}
101
+
102
+ # Add PYTHONPATH if running from development environment
103
+ if cwd:
104
+ env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
105
+
106
+ # Add adapter type
107
+ env_vars["MCP_TICKETER_ADAPTER"] = adapter
108
+
109
+ # Add adapter-specific environment variables
110
+ if adapter == "aitrackdown":
111
+ # Set base path for local adapter
112
+ 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)
116
+ else:
117
+ env_vars["MCP_TICKETER_BASE_PATH"] = base_path
118
+
119
+ elif adapter == "linear":
120
+ if "api_key" in adapter_config:
121
+ env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
122
+ if "team_id" in adapter_config:
123
+ env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
124
+
125
+ elif adapter == "github":
126
+ if "token" in adapter_config:
127
+ env_vars["GITHUB_TOKEN"] = adapter_config["token"]
128
+ if "owner" in adapter_config:
129
+ env_vars["GITHUB_OWNER"] = adapter_config["owner"]
130
+ if "repo" in adapter_config:
131
+ env_vars["GITHUB_REPO"] = adapter_config["repo"]
132
+
133
+ elif adapter == "jira":
134
+ if "api_token" in adapter_config:
135
+ env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
136
+ if "email" in adapter_config:
137
+ env_vars["JIRA_EMAIL"] = adapter_config["email"]
138
+ if "server" in adapter_config:
139
+ env_vars["JIRA_SERVER"] = adapter_config["server"]
140
+ if "project_key" in adapter_config:
141
+ env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
142
+
143
+ # Create server configuration with Codex-specific structure
144
+ # NOTE: Codex uses nested dict structure for env vars
145
+ config: Dict[str, Any] = {
146
+ "command": binary_path,
147
+ "args": ["serve"],
148
+ "env": env_vars,
149
+ }
150
+
151
+ return config
152
+
153
+
154
+ def configure_codex_mcp(force: bool = False) -> None:
155
+ """Configure Codex CLI to use mcp-ticketer.
156
+
157
+ IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
158
+ There is no project-level or user-scoped configuration available.
159
+
160
+ After configuration, you must restart Codex CLI for changes to take effect.
161
+
162
+ Args:
163
+ force: Overwrite existing configuration
164
+
165
+ Raises:
166
+ FileNotFoundError: If binary or project config not found
167
+ ValueError: If configuration is invalid
168
+
169
+ """
170
+ # Step 1: Find mcp-ticketer binary
171
+ console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
172
+ 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
178
+
179
+ # Step 2: Load project configuration
180
+ console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
181
+ try:
182
+ project_config = load_project_config()
183
+ adapter = project_config.get("default_adapter", "aitrackdown")
184
+ console.print(f"[green]✓[/green] Adapter: {adapter}")
185
+ except (FileNotFoundError, ValueError) as e:
186
+ console.print(f"[red]✗[/red] {e}")
187
+ raise
188
+
189
+ # Step 3: Find Codex config location (always global)
190
+ console.print("\n[cyan]🔧 Configuring Codex CLI (global-only)...[/cyan]")
191
+ console.print(
192
+ "[yellow]⚠ Note: Codex CLI only supports global configuration[/yellow]"
193
+ )
194
+
195
+ codex_config_path = find_codex_config()
196
+ console.print(f"[dim]Config location: {codex_config_path}[/dim]")
197
+
198
+ # Step 4: Load existing Codex configuration
199
+ codex_config = load_codex_config(codex_config_path)
200
+
201
+ # Step 5: Check if mcp-ticketer already configured
202
+ # NOTE: Use underscore mcp_servers, not camelCase
203
+ mcp_servers = codex_config.get("mcp_servers", {})
204
+ if "mcp-ticketer" in mcp_servers:
205
+ if not force:
206
+ console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
207
+ console.print("[dim]Use --force to overwrite existing configuration[/dim]")
208
+ return
209
+ else:
210
+ console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
211
+
212
+ # Step 6: Create mcp-ticketer server config
213
+ # For global config, include current working directory for context
214
+ cwd = str(Path.cwd())
215
+ server_config = create_codex_server_config(
216
+ binary_path=binary_path, project_config=project_config, cwd=cwd
217
+ )
218
+
219
+ # Step 7: Update Codex configuration
220
+ if "mcp_servers" not in codex_config:
221
+ codex_config["mcp_servers"] = {}
222
+
223
+ codex_config["mcp_servers"]["mcp-ticketer"] = server_config
224
+
225
+ # Step 8: Save configuration
226
+ try:
227
+ save_codex_config(codex_config_path, codex_config)
228
+ console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
229
+ console.print(f"[dim]Configuration saved to: {codex_config_path}[/dim]")
230
+
231
+ # Print configuration details
232
+ console.print("\n[bold]Configuration Details:[/bold]")
233
+ console.print(" Server name: mcp-ticketer")
234
+ console.print(f" Adapter: {adapter}")
235
+ console.print(f" Binary: {binary_path}")
236
+ console.print(" Scope: global (Codex only supports global config)")
237
+ console.print(f" Working directory: {cwd}")
238
+ if "env" in server_config:
239
+ console.print(
240
+ f" Environment variables: {list(server_config['env'].keys())}"
241
+ )
242
+
243
+ # Next steps
244
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
245
+ console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
246
+ console.print("2. Run 'codex' command from any directory")
247
+ console.print("3. mcp-ticketer tools will be available via MCP")
248
+ console.print(
249
+ "\n[yellow]⚠ Warning: This is a global configuration that affects all Codex sessions[/yellow]"
250
+ )
251
+ console.print(
252
+ "[yellow] The configuration includes paths from your current project directory[/yellow]"
253
+ )
254
+
255
+ except Exception as e:
256
+ console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
257
+ raise
@@ -0,0 +1,261 @@
1
+ """Gemini CLI configuration for mcp-ticketer integration."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Literal, Optional
6
+
7
+ from rich.console import Console
8
+
9
+ from .mcp_configure import find_mcp_ticketer_binary, load_project_config
10
+
11
+ console = Console()
12
+
13
+
14
+ def find_gemini_config(scope: Literal["project", "user"] = "project") -> Path:
15
+ """Find or create Gemini CLI configuration file.
16
+
17
+ Args:
18
+ scope: Configuration scope - "project" for .gemini/settings.json
19
+ or "user" for ~/.gemini/settings.json
20
+
21
+ Returns:
22
+ Path to Gemini settings file
23
+
24
+ """
25
+ if scope == "user":
26
+ # User-level configuration
27
+ config_path = Path.home() / ".gemini" / "settings.json"
28
+ else:
29
+ # Project-level configuration
30
+ config_path = Path.cwd() / ".gemini" / "settings.json"
31
+
32
+ return config_path
33
+
34
+
35
+ def load_gemini_config(config_path: Path) -> dict:
36
+ """Load existing Gemini configuration or return empty structure.
37
+
38
+ Args:
39
+ config_path: Path to Gemini settings file
40
+
41
+ Returns:
42
+ Gemini configuration dict
43
+
44
+ """
45
+ if config_path.exists():
46
+ try:
47
+ with open(config_path) as f:
48
+ return json.load(f)
49
+ except json.JSONDecodeError as e:
50
+ console.print(
51
+ f"[yellow]⚠ Warning: Could not parse existing config: {e}[/yellow]"
52
+ )
53
+ console.print("[yellow]Creating new configuration...[/yellow]")
54
+
55
+ # Return empty structure with mcpServers section
56
+ return {"mcpServers": {}}
57
+
58
+
59
+ def save_gemini_config(config_path: Path, config: dict) -> None:
60
+ """Save Gemini configuration to file.
61
+
62
+ Args:
63
+ config_path: Path to Gemini settings file
64
+ config: Configuration to save
65
+
66
+ """
67
+ # Ensure directory exists
68
+ config_path.parent.mkdir(parents=True, exist_ok=True)
69
+
70
+ # Write with 2-space indentation (Gemini CLI standard)
71
+ with open(config_path, "w") as f:
72
+ json.dump(config, f, indent=2)
73
+
74
+
75
+ def create_gemini_server_config(
76
+ binary_path: str, project_config: dict, cwd: Optional[str] = None
77
+ ) -> dict:
78
+ """Create Gemini MCP server configuration for mcp-ticketer.
79
+
80
+ Args:
81
+ binary_path: Path to mcp-ticketer binary
82
+ project_config: Project configuration from .mcp-ticketer/config.json
83
+ cwd: Working directory for server (optional)
84
+
85
+ Returns:
86
+ Gemini MCP server configuration dict
87
+
88
+ """
89
+ # Get adapter configuration
90
+ adapter = project_config.get("default_adapter", "aitrackdown")
91
+ adapters_config = project_config.get("adapters", {})
92
+ adapter_config = adapters_config.get(adapter, {})
93
+
94
+ # Build environment variables
95
+ env_vars = {}
96
+
97
+ # Add PYTHONPATH if running from development environment
98
+ if cwd:
99
+ env_vars["PYTHONPATH"] = str(Path(cwd) / "src")
100
+
101
+ # Add adapter type
102
+ env_vars["MCP_TICKETER_ADAPTER"] = adapter
103
+
104
+ # Add adapter-specific environment variables
105
+ if adapter == "aitrackdown":
106
+ # Set base path for local adapter
107
+ base_path = adapter_config.get("base_path", ".aitrackdown")
108
+ if cwd:
109
+ # Use absolute path if cwd is provided
110
+ env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(cwd) / base_path)
111
+ else:
112
+ env_vars["MCP_TICKETER_BASE_PATH"] = base_path
113
+
114
+ elif adapter == "linear":
115
+ if "api_key" in adapter_config:
116
+ env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
117
+ if "team_id" in adapter_config:
118
+ env_vars["LINEAR_TEAM_ID"] = adapter_config["team_id"]
119
+
120
+ elif adapter == "github":
121
+ if "token" in adapter_config:
122
+ env_vars["GITHUB_TOKEN"] = adapter_config["token"]
123
+ if "owner" in adapter_config:
124
+ env_vars["GITHUB_OWNER"] = adapter_config["owner"]
125
+ if "repo" in adapter_config:
126
+ env_vars["GITHUB_REPO"] = adapter_config["repo"]
127
+
128
+ elif adapter == "jira":
129
+ if "api_token" in adapter_config:
130
+ env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
131
+ if "email" in adapter_config:
132
+ env_vars["JIRA_EMAIL"] = adapter_config["email"]
133
+ if "server" in adapter_config:
134
+ env_vars["JIRA_SERVER"] = adapter_config["server"]
135
+ if "project_key" in adapter_config:
136
+ env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
137
+
138
+ # Create server configuration with Gemini-specific options
139
+ config = {
140
+ "command": binary_path,
141
+ "args": ["serve"],
142
+ "env": env_vars,
143
+ "timeout": 15000, # 15 seconds timeout
144
+ "trust": False, # Don't trust by default (security)
145
+ }
146
+
147
+ return config
148
+
149
+
150
+ def configure_gemini_mcp(
151
+ scope: Literal["project", "user"] = "project", force: bool = False
152
+ ) -> None:
153
+ """Configure Gemini CLI to use mcp-ticketer.
154
+
155
+ Args:
156
+ scope: Configuration scope - "project" or "user"
157
+ force: Overwrite existing configuration
158
+
159
+ Raises:
160
+ FileNotFoundError: If binary or project config not found
161
+ ValueError: If configuration is invalid
162
+
163
+ """
164
+ # Step 1: Find mcp-ticketer binary
165
+ console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
166
+ try:
167
+ binary_path = find_mcp_ticketer_binary()
168
+ console.print(f"[green]✓[/green] Found: {binary_path}")
169
+ except FileNotFoundError as e:
170
+ console.print(f"[red]✗[/red] {e}")
171
+ raise
172
+
173
+ # Step 2: Load project configuration
174
+ console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
175
+ try:
176
+ project_config = load_project_config()
177
+ adapter = project_config.get("default_adapter", "aitrackdown")
178
+ console.print(f"[green]✓[/green] Adapter: {adapter}")
179
+ except (FileNotFoundError, ValueError) as e:
180
+ console.print(f"[red]✗[/red] {e}")
181
+ raise
182
+
183
+ # Step 3: Find Gemini config location
184
+ config_type = "user-level" if scope == "user" else "project-level"
185
+ console.print(f"\n[cyan]🔧 Configuring {config_type} Gemini CLI...[/cyan]")
186
+
187
+ gemini_config_path = find_gemini_config(scope)
188
+ console.print(f"[dim]Config location: {gemini_config_path}[/dim]")
189
+
190
+ # Step 4: Load existing Gemini configuration
191
+ gemini_config = load_gemini_config(gemini_config_path)
192
+
193
+ # Step 5: Check if mcp-ticketer already configured
194
+ if "mcp-ticketer" in gemini_config.get("mcpServers", {}):
195
+ if not force:
196
+ console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
197
+ console.print("[dim]Use --force to overwrite existing configuration[/dim]")
198
+ return
199
+ else:
200
+ console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
201
+
202
+ # Step 6: Create mcp-ticketer server config
203
+ cwd = str(Path.cwd()) if scope == "project" else None
204
+ server_config = create_gemini_server_config(
205
+ binary_path=binary_path, project_config=project_config, cwd=cwd
206
+ )
207
+
208
+ # Step 7: Update Gemini configuration
209
+ if "mcpServers" not in gemini_config:
210
+ gemini_config["mcpServers"] = {}
211
+
212
+ gemini_config["mcpServers"]["mcp-ticketer"] = server_config
213
+
214
+ # Step 8: Save configuration
215
+ try:
216
+ save_gemini_config(gemini_config_path, gemini_config)
217
+ console.print("\n[green]✓ Successfully configured mcp-ticketer[/green]")
218
+ console.print(f"[dim]Configuration saved to: {gemini_config_path}[/dim]")
219
+
220
+ # Print configuration details
221
+ console.print("\n[bold]Configuration Details:[/bold]")
222
+ console.print(" Server name: mcp-ticketer")
223
+ console.print(f" Adapter: {adapter}")
224
+ console.print(f" Binary: {binary_path}")
225
+ console.print(f" Timeout: {server_config['timeout']}ms")
226
+ console.print(f" Trust: {server_config['trust']}")
227
+ if cwd:
228
+ console.print(f" Working directory: {cwd}")
229
+ if "env" in server_config:
230
+ console.print(
231
+ f" Environment variables: {list(server_config['env'].keys())}"
232
+ )
233
+
234
+ # Next steps
235
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
236
+ if scope == "user":
237
+ console.print("1. Gemini CLI will use this configuration globally")
238
+ console.print("2. Run 'gemini' command in any directory")
239
+ else:
240
+ console.print("1. Run 'gemini' command in this project directory")
241
+ console.print("2. Gemini CLI will detect project-level configuration")
242
+ console.print("3. mcp-ticketer tools will be available via MCP")
243
+
244
+ # Add .gemini to .gitignore for project-level config
245
+ if scope == "project":
246
+ gitignore_path = Path.cwd() / ".gitignore"
247
+ if gitignore_path.exists():
248
+ gitignore_content = gitignore_path.read_text()
249
+ if ".gemini" not in gitignore_content:
250
+ with open(gitignore_path, "a") as f:
251
+ f.write("\n# Gemini CLI\n.gemini/\n")
252
+ console.print("\n[dim]✓ Added .gemini/ to .gitignore[/dim]")
253
+ else:
254
+ # Create .gitignore if it doesn't exist
255
+ with open(gitignore_path, "w") as f:
256
+ f.write("# Gemini CLI\n.gemini/\n")
257
+ console.print("\n[dim]✓ Created .gitignore with .gemini/[/dim]")
258
+
259
+ except Exception as e:
260
+ console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
261
+ raise